feat: menus tableur a icones, fermeture au focus et polish mobile #179
This commit is contained in:
+147
-61
@@ -19,7 +19,7 @@ import { openAssistant } from './ai-fab.js';
|
||||
import { parseRef, columnName, findTd, sheetOfRef, firstCellOfRange } from './xlsx/refs.js';
|
||||
import { buildCommandBar } from './xlsx/command-bar.js';
|
||||
import { renderDashboardLoading, renderDashboardHtml } from './xlsx/dashboard.js';
|
||||
import { openContextMenu, closeContextMenu } from './xlsx/context-menu.js';
|
||||
import { openContextMenu, closeContextMenu, trackDismissable, enableArrowNav } from './xlsx/context-menu.js';
|
||||
|
||||
// ── Multi-format export ────────────────────────────────────────────────────
|
||||
// Downloads a file export (HTML / MD bundle / ePub) via the authenticated
|
||||
@@ -2284,25 +2284,42 @@ export function renderXlsxViewer(area, data) {
|
||||
// Structure menu: built on demand, positioned under the button.
|
||||
// (.csv / read-only workbooks have no such button — the menu block above
|
||||
// is skipped for them.)
|
||||
// #179 — tracked dismissal of the structure menu (outside, focus loss…).
|
||||
let dismissStructureMenu = null;
|
||||
const structureBtn = area.querySelector("#xlsx-structure-btn");
|
||||
if (structureBtn) structureBtn.addEventListener("click", (e) => {
|
||||
const old = area.querySelector(".xlsx-structure-menu");
|
||||
if (old) { old.remove(); return; }
|
||||
if (dismissStructureMenu) { dismissStructureMenu(); dismissStructureMenu = null; return; }
|
||||
const idx = visibleSheetIndex();
|
||||
const sheetName = sheets[idx]?.name || "";
|
||||
const activeRef = cellName(activeTd && activeTd.closest(".xlsx-panel") === visiblePanel() ? activeTd : null);
|
||||
const parsed = parseRef(activeRef) || { row: 1, col: 1 };
|
||||
const menu = document.createElement("div");
|
||||
menu.className = "xlsx-structure-menu";
|
||||
const item = (label, fn) => {
|
||||
menu.setAttribute("role", "menu");
|
||||
// #179 — representative icon per entry; destructive ones read as danger.
|
||||
const item = (iconName, label, fn, danger = false) => {
|
||||
const b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "btn-action xlsx-structure-item";
|
||||
b.textContent = label;
|
||||
b.addEventListener("click", () => { menu.remove(); fn(); });
|
||||
b.className = `btn-action xlsx-structure-item${danger ? " xlsx-context-danger" : ""}`;
|
||||
b.setAttribute("role", "menuitem");
|
||||
if (iconName) {
|
||||
const ic = document.createElement("i");
|
||||
ic.setAttribute("data-lucide", iconName);
|
||||
ic.className = "xlsx-menu-icon";
|
||||
ic.setAttribute("aria-hidden", "true");
|
||||
b.appendChild(ic);
|
||||
}
|
||||
const lab = document.createElement("span");
|
||||
lab.className = "xlsx-menu-label";
|
||||
lab.textContent = label;
|
||||
b.appendChild(lab);
|
||||
b.addEventListener("click", () => {
|
||||
if (dismissStructureMenu) { dismissStructureMenu(); dismissStructureMenu = null; }
|
||||
fn();
|
||||
});
|
||||
menu.appendChild(b);
|
||||
};
|
||||
item(t("xlsx.sheet_add"), async () => {
|
||||
item("file-plus", t("xlsx.sheet_add"), async () => {
|
||||
const name = await showPrompt({
|
||||
title: t("xlsx.sheet_add"),
|
||||
message: t("xlsx.structure_prompt_add"),
|
||||
@@ -2311,7 +2328,7 @@ export function renderXlsxViewer(area, data) {
|
||||
const actions = [{ op: "sheet_add", name }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
item(t("xlsx.sheet_rename"), async () => {
|
||||
item("pencil", t("xlsx.sheet_rename"), async () => {
|
||||
const to = await showPrompt({
|
||||
title: t("xlsx.sheet_rename"),
|
||||
message: t("xlsx.structure_prompt_rename"),
|
||||
@@ -2321,7 +2338,7 @@ export function renderXlsxViewer(area, data) {
|
||||
const actions = [{ op: "sheet_rename", from: sheetName, to }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
item(t("xlsx.sheet_duplicate"), async () => {
|
||||
item("copy", t("xlsx.sheet_duplicate"), async () => {
|
||||
const as = await showPrompt({
|
||||
title: t("xlsx.sheet_duplicate"),
|
||||
message: t("xlsx.structure_prompt_add"),
|
||||
@@ -2331,7 +2348,7 @@ export function renderXlsxViewer(area, data) {
|
||||
const actions = [{ op: "sheet_duplicate", name: sheetName, as }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
item(t("xlsx.sheet_delete"), async () => {
|
||||
item("trash-2", t("xlsx.sheet_delete"), async () => {
|
||||
if (sheets.length <= 1) { showToast(t("xlsx.last_sheet"), "info"); return; }
|
||||
const okDelete = await showConfirm({
|
||||
title: t("xlsx.sheet_delete"),
|
||||
@@ -2342,14 +2359,14 @@ export function renderXlsxViewer(area, data) {
|
||||
if (!okDelete) return;
|
||||
const actions = [{ op: "sheet_delete", name: sheetName }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
}, true);
|
||||
if (parsed) {
|
||||
menu.appendChild(Object.assign(document.createElement("div"), { className: "xlsx-structure-sep" }));
|
||||
item(t("xlsx.row_insert"), async () => {
|
||||
item("rows", t("xlsx.row_insert"), async () => {
|
||||
const actions = [{ op: "row_insert", sheet: sheetName, at: parsed.row }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
item(t("xlsx.row_delete"), async () => {
|
||||
item("trash-2", t("xlsx.row_delete"), async () => {
|
||||
const okRow = await showConfirm({
|
||||
title: t("xlsx.row_delete"),
|
||||
message: t("xlsx.structure_confirm_row", { n: parsed.row }),
|
||||
@@ -2359,12 +2376,12 @@ export function renderXlsxViewer(area, data) {
|
||||
if (!okRow) return;
|
||||
const actions = [{ op: "row_delete", sheet: sheetName, at: parsed.row }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
item(t("xlsx.col_insert"), async () => {
|
||||
}, true);
|
||||
item("columns", t("xlsx.col_insert"), async () => {
|
||||
const actions = [{ op: "col_insert", sheet: sheetName, at: parsed.col }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
item(t("xlsx.col_delete"), async () => {
|
||||
item("trash-2", t("xlsx.col_delete"), async () => {
|
||||
const okCol = await showConfirm({
|
||||
title: t("xlsx.col_delete"),
|
||||
message: t("xlsx.structure_confirm_col", { n: columnName(parsed.col) }),
|
||||
@@ -2374,9 +2391,21 @@ export function renderXlsxViewer(area, data) {
|
||||
if (!okCol) return;
|
||||
const actions = [{ op: "col_delete", sheet: sheetName, at: parsed.col }];
|
||||
try { await putStructure(actions); } catch (err) { structureError(err, actions); }
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
enableArrowNav(menu, ".xlsx-structure-item");
|
||||
e.target.closest(".xlsx-toolbar").appendChild(menu);
|
||||
safeCreateIcons();
|
||||
structureBtn.setAttribute("aria-expanded", "true");
|
||||
// #179 — close on outside click, focus loss, Escape, scroll, resize.
|
||||
dismissStructureMenu = trackDismissable(menu, {
|
||||
toggle: structureBtn,
|
||||
onClose: () => {
|
||||
dismissStructureMenu = null;
|
||||
structureBtn.setAttribute("aria-expanded", "false");
|
||||
menu.remove();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// ── #156-A11 — sortie : CSV / Markdown / HTML / impression ────────────
|
||||
@@ -2491,14 +2520,34 @@ export function renderXlsxViewer(area, data) {
|
||||
}
|
||||
};
|
||||
|
||||
// #179 — the export menu used to stay open until its button was clicked
|
||||
// again; it now closes on outside click, focus loss, Escape, scroll, resize.
|
||||
let dismissExportMenu = null;
|
||||
const exportMenu = area.querySelector("#xlsx-export-menu");
|
||||
if (exportMenu) {
|
||||
area.querySelector("#xlsx-export-btn").addEventListener("click", () => {
|
||||
exportMenu.style.display = exportMenu.style.display === "block" ? "none" : "block";
|
||||
const exportBtn = area.querySelector("#xlsx-export-btn");
|
||||
if (exportMenu && exportBtn) {
|
||||
const hideExportMenu = () => {
|
||||
if (dismissExportMenu) { dismissExportMenu(); dismissExportMenu = null; }
|
||||
exportMenu.style.display = "none";
|
||||
exportBtn.setAttribute("aria-expanded", "false");
|
||||
};
|
||||
exportBtn.addEventListener("click", () => {
|
||||
if (exportMenu.style.display === "block") { hideExportMenu(); return; }
|
||||
exportMenu.style.display = "block";
|
||||
exportBtn.setAttribute("aria-expanded", "true");
|
||||
dismissExportMenu = trackDismissable(exportMenu, {
|
||||
toggle: exportBtn,
|
||||
onClose: () => {
|
||||
dismissExportMenu = null;
|
||||
exportMenu.style.display = "none";
|
||||
exportBtn.setAttribute("aria-expanded", "false");
|
||||
},
|
||||
});
|
||||
});
|
||||
enableArrowNav(exportMenu, "[data-xlsx-export]");
|
||||
exportMenu.querySelectorAll("[data-xlsx-export]").forEach((item) => {
|
||||
item.addEventListener("click", () => {
|
||||
exportMenu.style.display = "none";
|
||||
hideExportMenu();
|
||||
if (item.dataset.xlsxExport === "print") printSheet();
|
||||
else exportDocument(item.dataset.xlsxExport);
|
||||
});
|
||||
@@ -2575,19 +2624,36 @@ export function renderXlsxViewer(area, data) {
|
||||
return Boolean(td && td.style.textDecoration === "underline");
|
||||
};
|
||||
|
||||
// #179 — tracked dismissal of the format menu (outside, focus loss…).
|
||||
let dismissFormatMenu = null;
|
||||
const formatBtn = area.querySelector("#xlsx-format-btn");
|
||||
if (formatBtn) formatBtn.addEventListener("click", () => {
|
||||
const old = area.querySelector(".xlsx-format-menu");
|
||||
if (old) { old.remove(); return; }
|
||||
if (dismissFormatMenu) { dismissFormatMenu(); dismissFormatMenu = null; return; }
|
||||
const sheetName = sheets[visibleSheetIndex()]?.name || "";
|
||||
const menu = document.createElement("div");
|
||||
menu.className = "xlsx-structure-menu xlsx-format-menu";
|
||||
const item = (label, fn) => {
|
||||
menu.setAttribute("role", "menu");
|
||||
// #179 — representative icon per entry.
|
||||
const item = (iconName, label, fn) => {
|
||||
const b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "btn-action xlsx-structure-item";
|
||||
b.textContent = label;
|
||||
b.addEventListener("click", () => { menu.remove(); fn(); });
|
||||
b.setAttribute("role", "menuitem");
|
||||
if (iconName) {
|
||||
const ic = document.createElement("i");
|
||||
ic.setAttribute("data-lucide", iconName);
|
||||
ic.className = "xlsx-menu-icon";
|
||||
ic.setAttribute("aria-hidden", "true");
|
||||
b.appendChild(ic);
|
||||
}
|
||||
const lab = document.createElement("span");
|
||||
lab.className = "xlsx-menu-label";
|
||||
lab.textContent = label;
|
||||
b.appendChild(lab);
|
||||
b.addEventListener("click", () => {
|
||||
if (dismissFormatMenu) { dismissFormatMenu(); dismissFormatMenu = null; }
|
||||
fn();
|
||||
});
|
||||
menu.appendChild(b);
|
||||
};
|
||||
const sep = () => menu.appendChild(
|
||||
@@ -2598,22 +2664,29 @@ export function renderXlsxViewer(area, data) {
|
||||
]);
|
||||
const numFormat = (fmt) => cellStyle({ number_format: fmt });
|
||||
|
||||
item(t("xlsx.format_bold"), cellStyle({ bold: !hasBold() }));
|
||||
item(t("xlsx.format_italic"), cellStyle({ italic: !hasItalic() }));
|
||||
item(t("xlsx.format_underline"), cellStyle({ underline: !hasUnderline() }));
|
||||
item(t("xlsx.format_clear"), cellStyle({
|
||||
item("bold", t("xlsx.format_bold"), cellStyle({ bold: !hasBold() }));
|
||||
item("italic", t("xlsx.format_italic"), cellStyle({ italic: !hasItalic() }));
|
||||
item("underline", t("xlsx.format_underline"), cellStyle({ underline: !hasUnderline() }));
|
||||
item("remove-formatting", t("xlsx.format_clear"), cellStyle({
|
||||
bold: false, italic: false, underline: false,
|
||||
font_color: "", fill_color: "", align: "left", number_format: "General",
|
||||
}));
|
||||
sep();
|
||||
item(t("xlsx.format_align_left"), cellStyle({ align: "left" }));
|
||||
item(t("xlsx.format_align_center"), cellStyle({ align: "center" }));
|
||||
item(t("xlsx.format_align_right"), cellStyle({ align: "right" }));
|
||||
item("align-left", t("xlsx.format_align_left"), cellStyle({ align: "left" }));
|
||||
item("align-center", t("xlsx.format_align_center"), cellStyle({ align: "center" }));
|
||||
item("align-right", t("xlsx.format_align_right"), cellStyle({ align: "right" }));
|
||||
sep();
|
||||
// Colours come from the native picker; applied once when it closes.
|
||||
const colour = (label, apply) => {
|
||||
const colour = (iconName, label, apply) => {
|
||||
const row = document.createElement("label");
|
||||
row.className = "xlsx-format-colour";
|
||||
if (iconName) {
|
||||
const ic = document.createElement("i");
|
||||
ic.setAttribute("data-lucide", iconName);
|
||||
ic.className = "xlsx-menu-icon";
|
||||
ic.setAttribute("aria-hidden", "true");
|
||||
row.appendChild(ic);
|
||||
}
|
||||
const span = document.createElement("span");
|
||||
span.textContent = label;
|
||||
const input = document.createElement("input");
|
||||
@@ -2623,21 +2696,21 @@ export function renderXlsxViewer(area, data) {
|
||||
row.appendChild(input);
|
||||
menu.appendChild(row);
|
||||
};
|
||||
colour(t("xlsx.format_font_color"), (e) => runStyle([
|
||||
colour("palette", t("xlsx.format_font_color"), (e) => runStyle([
|
||||
{ op: "cell", sheet: sheetName, range: selectionRange(), style: { font_color: e.target.value } },
|
||||
]));
|
||||
colour(t("xlsx.format_fill_color"), (e) => runStyle([
|
||||
colour("paint-bucket", t("xlsx.format_fill_color"), (e) => runStyle([
|
||||
{ op: "cell", sheet: sheetName, range: selectionRange(), style: { fill_color: e.target.value } },
|
||||
]));
|
||||
sep();
|
||||
item(t("xlsx.format_num_general"), numFormat("General"));
|
||||
item(t("xlsx.format_num_number"), numFormat("0.00"));
|
||||
item(t("xlsx.format_num_percent"), numFormat("0.0%"));
|
||||
item(t("xlsx.format_num_currency"), numFormat('#,##0.00 "€"'));
|
||||
item(t("xlsx.format_num_date"), numFormat("DD/MM/YYYY"));
|
||||
item(t("xlsx.format_num_text"), numFormat("@"));
|
||||
item("type", t("xlsx.format_num_general"), numFormat("General"));
|
||||
item("hash", t("xlsx.format_num_number"), numFormat("0.00"));
|
||||
item("percent", t("xlsx.format_num_percent"), numFormat("0.0%"));
|
||||
item("euro", t("xlsx.format_num_currency"), numFormat('#,##0.00 "€"'));
|
||||
item("calendar", t("xlsx.format_num_date"), numFormat("DD/MM/YYYY"));
|
||||
item("pilcrow", t("xlsx.format_num_text"), numFormat("@"));
|
||||
sep();
|
||||
item(t("xlsx.format_merge"), () => {
|
||||
item("combine", t("xlsx.format_merge"), () => {
|
||||
const b = selectionBounds();
|
||||
if (!b || !selection || selection.panel !== visiblePanel() || (b.r1 === b.r2 && b.c1 === b.c2)) {
|
||||
showToast(t("xlsx.format_merge_needs_range"), "info");
|
||||
@@ -2645,16 +2718,16 @@ export function renderXlsxViewer(area, data) {
|
||||
}
|
||||
runStyle([{ op: "merge", sheet: sheetName, range: rangeLabel() }]);
|
||||
});
|
||||
item(t("xlsx.format_unmerge"), () => {
|
||||
item("ungroup", t("xlsx.format_unmerge"), () => {
|
||||
runStyle([{ op: "unmerge", sheet: sheetName, range: selectionRange() }]);
|
||||
});
|
||||
sep();
|
||||
item(t("xlsx.format_freeze"), () => {
|
||||
item("snowflake", t("xlsx.format_freeze"), () => {
|
||||
runStyle([{ op: "freeze", sheet: sheetName, cell: selectionRange().split(":")[0] }]);
|
||||
});
|
||||
item(t("xlsx.format_unfreeze"), () => runStyle([{ op: "freeze", sheet: sheetName, cell: "" }]));
|
||||
item("sun", t("xlsx.format_unfreeze"), () => runStyle([{ op: "freeze", sheet: sheetName, cell: "" }]));
|
||||
sep();
|
||||
item(t("xlsx.format_col_width"), async () => {
|
||||
item("move-horizontal", t("xlsx.format_col_width"), async () => {
|
||||
const b = selectionBounds();
|
||||
const cols = [];
|
||||
if (b && selection && selection.panel === visiblePanel()) {
|
||||
@@ -2672,7 +2745,7 @@ export function renderXlsxViewer(area, data) {
|
||||
if (!answer || !Number.isFinite(width) || width < 0 || width > 255) return;
|
||||
runStyle(cols.map((col) => ({ op: "col_width", sheet: sheetName, col, width })));
|
||||
});
|
||||
item(t("xlsx.format_row_height"), async () => {
|
||||
item("move-vertical", t("xlsx.format_row_height"), async () => {
|
||||
const b = selectionBounds();
|
||||
const rows = [];
|
||||
if (b && selection && selection.panel === visiblePanel()) {
|
||||
@@ -2690,7 +2763,19 @@ export function renderXlsxViewer(area, data) {
|
||||
if (!answer || !Number.isFinite(height) || height < 0 || height > 409) return;
|
||||
runStyle(rows.map((row) => ({ op: "row_height", sheet: sheetName, row, height })));
|
||||
});
|
||||
enableArrowNav(menu, ".xlsx-structure-item");
|
||||
formatBtn.closest(".xlsx-toolbar").appendChild(menu);
|
||||
safeCreateIcons();
|
||||
formatBtn.setAttribute("aria-expanded", "true");
|
||||
// #179 — close on outside click, focus loss, Escape, scroll, resize.
|
||||
dismissFormatMenu = trackDismissable(menu, {
|
||||
toggle: formatBtn,
|
||||
onClose: () => {
|
||||
dismissFormatMenu = null;
|
||||
formatBtn.setAttribute("aria-expanded", "false");
|
||||
menu.remove();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// ══ #155 — Excel-like selection & context menu ═════════════════════════
|
||||
@@ -3228,27 +3313,28 @@ export function renderXlsxViewer(area, data) {
|
||||
const colCount = b.c2 - b.c1 + 1;
|
||||
const items = [];
|
||||
// #156-A5 — clipboard entries first, in the spreadsheet order.
|
||||
items.push({ label: t("xlsx.cut"), disabled: !editable, onClick: () => cutSelection() });
|
||||
items.push({ label: t("xlsx.copy"), onClick: () => copySelection() });
|
||||
items.push({ label: t("xlsx.paste"), disabled: !editable, onClick: () => pasteFromClipboard() });
|
||||
// #179 — every entry carries a representative Lucide icon.
|
||||
items.push({ icon: "scissors", label: t("xlsx.cut"), disabled: !editable, onClick: () => cutSelection() });
|
||||
items.push({ icon: "copy", label: t("xlsx.copy"), onClick: () => copySelection() });
|
||||
items.push({ icon: "clipboard-paste", label: t("xlsx.paste"), disabled: !editable, onClick: () => pasteFromClipboard() });
|
||||
items.push({ separator: true });
|
||||
if (editable) {
|
||||
items.push({ label: t("xlsx.insert_row_above"), onClick: () => structureAction({ op: "row_insert", sheet: sheetName, at: b.r1, count: rowCount }) });
|
||||
items.push({ label: t("xlsx.insert_row_below"), onClick: () => structureAction({ op: "row_insert", sheet: sheetName, at: b.r2 + 1, count: rowCount }) });
|
||||
items.push({ label: t("xlsx.insert_col_left"), onClick: () => structureAction({ op: "col_insert", sheet: sheetName, at: b.c1, count: colCount }) });
|
||||
items.push({ label: t("xlsx.insert_col_right"), onClick: () => structureAction({ op: "col_insert", sheet: sheetName, at: b.c2 + 1, count: colCount }) });
|
||||
items.push({ icon: "rows", label: t("xlsx.insert_row_above"), onClick: () => structureAction({ op: "row_insert", sheet: sheetName, at: b.r1, count: rowCount }) });
|
||||
items.push({ icon: "rows", label: t("xlsx.insert_row_below"), onClick: () => structureAction({ op: "row_insert", sheet: sheetName, at: b.r2 + 1, count: rowCount }) });
|
||||
items.push({ icon: "columns", label: t("xlsx.insert_col_left"), onClick: () => structureAction({ op: "col_insert", sheet: sheetName, at: b.c1, count: colCount }) });
|
||||
items.push({ icon: "columns", label: t("xlsx.insert_col_right"), onClick: () => structureAction({ op: "col_insert", sheet: sheetName, at: b.c2 + 1, count: colCount }) });
|
||||
items.push({ separator: true });
|
||||
items.push({ label: t("xlsx.delete_rows", { n: rowCount }), danger: true, onClick: () => structureAction({ op: "row_delete", sheet: sheetName, at: b.r1, count: rowCount }, t("xlsx.confirm_delete_rows", { n: rowCount })) });
|
||||
items.push({ label: t("xlsx.delete_cols", { n: colCount }), danger: true, onClick: () => structureAction({ op: "col_delete", sheet: sheetName, at: b.c1, count: colCount }, t("xlsx.confirm_delete_cols", { n: colCount })) });
|
||||
items.push({ icon: "trash-2", label: t("xlsx.delete_rows", { n: rowCount }), danger: true, onClick: () => structureAction({ op: "row_delete", sheet: sheetName, at: b.r1, count: rowCount }, t("xlsx.confirm_delete_rows", { n: rowCount })) });
|
||||
items.push({ icon: "trash-2", label: t("xlsx.delete_cols", { n: colCount }), danger: true, onClick: () => structureAction({ op: "col_delete", sheet: sheetName, at: b.c1, count: colCount }, t("xlsx.confirm_delete_cols", { n: colCount })) });
|
||||
items.push({ separator: true });
|
||||
}
|
||||
if (!isCsv) {
|
||||
items.push({ label: t("xlsx.sort_asc"), onClick: () => applySort(panel, b.c1, "asc") });
|
||||
items.push({ label: t("xlsx.sort_desc"), onClick: () => applySort(panel, b.c1, "desc") });
|
||||
items.push({ icon: "arrow-up", label: t("xlsx.sort_asc"), onClick: () => applySort(panel, b.c1, "asc") });
|
||||
items.push({ icon: "arrow-down", label: t("xlsx.sort_desc"), onClick: () => applySort(panel, b.c1, "desc") });
|
||||
}
|
||||
if (editable) {
|
||||
items.push({ separator: true });
|
||||
items.push({ label: t("xlsx.clear_contents"), onClick: () => clearContents(panel, b) });
|
||||
items.push({ icon: "eraser", label: t("xlsx.clear_contents"), onClick: () => clearContents(panel, b) });
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user