@svgrid/enterprise 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SvFormulaBar.svelte +606 -0
- package/dist/SvFormulaBar.svelte.d.ts +62 -0
- package/dist/SvPivotDesigner.svelte +65 -14
- package/dist/SvRibbonIcon.svelte +46 -0
- package/dist/SvRibbonIcon.svelte.d.ts +17 -0
- package/dist/SvSchemaChart.svelte +4 -2
- package/dist/SvSheet.svelte +4114 -0
- package/dist/SvSheet.svelte.d.ts +142 -0
- package/dist/SvSheetComment.svelte +98 -0
- package/dist/SvSheetComment.svelte.d.ts +22 -0
- package/dist/SvSheetConditionalFormat.svelte +162 -0
- package/dist/SvSheetConditionalFormat.svelte.d.ts +14 -0
- package/dist/SvSheetDataValidation.svelte +249 -0
- package/dist/SvSheetDataValidation.svelte.d.ts +14 -0
- package/dist/SvSheetFilterMenu.svelte +217 -0
- package/dist/SvSheetFilterMenu.svelte.d.ts +17 -0
- package/dist/SvSheetFindReplace.svelte +132 -0
- package/dist/SvSheetFindReplace.svelte.d.ts +10 -0
- package/dist/SvSheetFormatCells.svelte +421 -0
- package/dist/SvSheetFormatCells.svelte.d.ts +17 -0
- package/dist/SvSheetGoalSeek.svelte +144 -0
- package/dist/SvSheetGoalSeek.svelte.d.ts +12 -0
- package/dist/SvSheetInsertFunction.svelte +147 -0
- package/dist/SvSheetInsertFunction.svelte.d.ts +8 -0
- package/dist/SvSheetListPicker.svelte +89 -0
- package/dist/SvSheetListPicker.svelte.d.ts +16 -0
- package/dist/SvSheetManageRules.svelte +185 -0
- package/dist/SvSheetManageRules.svelte.d.ts +16 -0
- package/dist/SvSheetNameManager.svelte +225 -0
- package/dist/SvSheetNameManager.svelte.d.ts +11 -0
- package/dist/SvSheetPasteSpecial.svelte +105 -0
- package/dist/SvSheetPasteSpecial.svelte.d.ts +12 -0
- package/dist/SvSheetRemoveDuplicates.svelte +149 -0
- package/dist/SvSheetRemoveDuplicates.svelte.d.ts +12 -0
- package/dist/SvSheetRibbon.svelte +1201 -0
- package/dist/SvSheetRibbon.svelte.d.ts +34 -0
- package/dist/SvSheetSizeDialog.svelte +58 -0
- package/dist/SvSheetSizeDialog.svelte.d.ts +11 -0
- package/dist/SvSheetTabs.svelte +441 -0
- package/dist/SvSheetTabs.svelte.d.ts +36 -0
- package/dist/SvSheetTabs.test-harness.svelte.d.ts +17 -0
- package/dist/SvSheetTabs.test-harness.svelte.js +22 -0
- package/dist/SvSheetTextToColumns.svelte +172 -0
- package/dist/SvSheetTextToColumns.svelte.d.ts +13 -0
- package/dist/SvSheetValidationAlert.svelte +83 -0
- package/dist/SvSheetValidationAlert.svelte.d.ts +15 -0
- package/dist/cdn/svgrid-enterprise.svelte-external.js +18997 -5396
- package/dist/designer/assets/GridMenus-BlsvL_aU.js +8 -0
- package/dist/designer/assets/SvChartMenu-BYO9cYdy.css +1 -0
- package/dist/designer/assets/SvChartMenu-D9hEeHar.js +1 -0
- package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +1 -0
- package/dist/designer/assets/SvDateTimePicker-DARvKUL2.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +4 -0
- package/dist/designer/assets/SvGridChart-B8j1SSy3.js +3 -0
- package/dist/designer/assets/SvGridChart-BgLuPO7I.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-08YWWeqY.css +1 -0
- package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +1 -0
- package/dist/designer/assets/SvGridChartPanel-4Pc8y5yO.css +1 -0
- package/dist/designer/assets/SvGridChartPanel-pT6ITc2P.js +4 -0
- package/dist/designer/assets/{SvGridChartView-DUc-1RUf.css → SvGridChartView-7msfnWZT.css} +1 -1
- package/dist/designer/assets/SvGridChartView-bg281UA1.js +1 -0
- package/dist/designer/assets/SvListBox-BDS66lmZ.js +110 -0
- package/dist/designer/assets/SvMenuList-DJ47ba1h.css +1 -0
- package/dist/designer/assets/SvMenuList-Dgq2D5VN.js +1 -0
- package/dist/designer/assets/SvTabs-DKmwb5p1.css +1 -0
- package/dist/designer/assets/SvTabs-UFCMTHXS.js +1 -0
- package/dist/designer/assets/chart-cIPUsbM5.js +1 -0
- package/dist/designer/assets/chart-export-B6bkQ3X8.js +11 -0
- package/dist/designer/assets/chart-export-pdf-BbA28-gt.js +11 -0
- package/dist/designer/assets/chart-panel-messages-DtTxpBUU.js +1 -0
- package/dist/designer/assets/chart-summary-Czu_5epb.js +1 -0
- package/dist/designer/assets/chart-trend-D5I5VEe8.js +1 -0
- package/dist/designer/assets/chart-validate-BMqM-qkF.js +1 -0
- package/dist/designer/assets/column-resize-BYDzR1Ve.js +1 -0
- package/dist/designer/assets/date-format-B8JbEtIG.js +1 -0
- package/dist/designer/assets/date-format-DKS-z0xS.css +1 -0
- package/dist/designer/assets/editor-contract-Bpoa7A5g.js +1 -0
- package/dist/designer/assets/focus-trap-CAreAhlU.js +1 -0
- package/dist/designer/assets/index-CMFgAoVV.css +1 -0
- package/dist/designer/assets/{index-C_a9Cj8Y.js → index-DwU7CAMm.js} +291 -290
- package/dist/designer/assets/list-option-BP_dWS66.js +1 -0
- package/dist/designer/assets/row-resize-DLktcqUK.js +1 -0
- package/dist/designer/assets/src-CITpcm6b.js +1 -0
- package/dist/designer/assets/src-EdI3Xqds.js +58 -0
- package/dist/designer/index.html +23 -14
- package/dist/export-pdf.d.ts +89 -0
- package/dist/export-pdf.js +130 -0
- package/dist/export.js +17 -10
- package/dist/index.d.ts +64 -1
- package/dist/index.js +68 -1
- package/dist/install.js +5 -0
- package/dist/node/studio.js +64 -2
- package/dist/pivot-chart.d.ts +25 -1
- package/dist/pivot-chart.js +25 -69
- package/dist/sheet/address.d.ts +43 -0
- package/dist/sheet/address.js +81 -0
- package/dist/sheet/ast.d.ts +94 -0
- package/dist/sheet/ast.js +56 -0
- package/dist/sheet/auto-filter.d.ts +66 -0
- package/dist/sheet/auto-filter.js +147 -0
- package/dist/sheet/autocomplete.d.ts +42 -0
- package/dist/sheet/autocomplete.js +162 -0
- package/dist/sheet/coerce.d.ts +31 -0
- package/dist/sheet/coerce.js +122 -0
- package/dist/sheet/commands.d.ts +89 -0
- package/dist/sheet/commands.js +230 -0
- package/dist/sheet/comments.d.ts +29 -0
- package/dist/sheet/comments.js +73 -0
- package/dist/sheet/conditional-formats.d.ts +144 -0
- package/dist/sheet/conditional-formats.js +316 -0
- package/dist/sheet/deps.d.ts +60 -0
- package/dist/sheet/deps.js +199 -0
- package/dist/sheet/document.d.ts +150 -0
- package/dist/sheet/document.js +264 -0
- package/dist/sheet/edit-keys.d.ts +14 -0
- package/dist/sheet/edit-keys.js +40 -0
- package/dist/sheet/entry.d.ts +31 -0
- package/dist/sheet/entry.js +76 -0
- package/dist/sheet/evaluate.d.ts +61 -0
- package/dist/sheet/evaluate.js +337 -0
- package/dist/sheet/find-replace.d.ts +62 -0
- package/dist/sheet/find-replace.js +152 -0
- package/dist/sheet/format-store.d.ts +99 -0
- package/dist/sheet/format-store.js +233 -0
- package/dist/sheet/freeze.d.ts +54 -0
- package/dist/sheet/freeze.js +58 -0
- package/dist/sheet/function-catalog.d.ts +10 -0
- package/dist/sheet/function-catalog.js +94 -0
- package/dist/sheet/functions.d.ts +28 -0
- package/dist/sheet/functions.js +362 -0
- package/dist/sheet/goal-seek.d.ts +73 -0
- package/dist/sheet/goal-seek.js +153 -0
- package/dist/sheet/index.d.ts +53 -0
- package/dist/sheet/index.js +53 -0
- package/dist/sheet/merges.d.ts +71 -0
- package/dist/sheet/merges.js +137 -0
- package/dist/sheet/names.d.ts +31 -0
- package/dist/sheet/names.js +87 -0
- package/dist/sheet/navigate.d.ts +52 -0
- package/dist/sheet/navigate.js +135 -0
- package/dist/sheet/number-format.d.ts +63 -0
- package/dist/sheet/number-format.js +508 -0
- package/dist/sheet/palette.d.ts +19 -0
- package/dist/sheet/palette.js +84 -0
- package/dist/sheet/parse.d.ts +13 -0
- package/dist/sheet/parse.js +108 -0
- package/dist/sheet/paste-special.d.ts +164 -0
- package/dist/sheet/paste-special.js +592 -0
- package/dist/sheet/protection.d.ts +21 -0
- package/dist/sheet/protection.js +48 -0
- package/dist/sheet/rects.d.ts +45 -0
- package/dist/sheet/rects.js +112 -0
- package/dist/sheet/refs.d.ts +82 -0
- package/dist/sheet/refs.js +266 -0
- package/dist/sheet/ribbon-icons.d.ts +31 -0
- package/dist/sheet/ribbon-icons.js +350 -0
- package/dist/sheet/ribbon.d.ts +173 -0
- package/dist/sheet/ribbon.js +753 -0
- package/dist/sheet/shortcuts.d.ts +164 -0
- package/dist/sheet/shortcuts.js +623 -0
- package/dist/sheet/structure.d.ts +87 -0
- package/dist/sheet/structure.js +166 -0
- package/dist/sheet/tables.d.ts +70 -0
- package/dist/sheet/tables.js +147 -0
- package/dist/sheet/tokenize.d.ts +54 -0
- package/dist/sheet/tokenize.js +304 -0
- package/dist/sheet/transforms.d.ts +76 -0
- package/dist/sheet/transforms.js +154 -0
- package/dist/sheet/validation.d.ts +92 -0
- package/dist/sheet/validation.js +241 -0
- package/dist/sheet/workbook.d.ts +72 -0
- package/dist/sheet/workbook.js +409 -0
- package/dist/sheet-enable.d.ts +6 -0
- package/dist/sheet-enable.js +46 -0
- package/dist/studio/ui-components.generated.js +63 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -4
- package/dist/designer/assets/GridMenus-B1JtIN21.js +0 -8
- package/dist/designer/assets/SvDateRangeInput-CjO7nn_x.js +0 -1
- package/dist/designer/assets/SvDateTimePicker-ByBD_ov6.js +0 -1
- package/dist/designer/assets/SvField-YDLipR6O.css +0 -1
- package/dist/designer/assets/SvField-cj1PFNgZ.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-0W1L2hJ1.js +0 -2
- package/dist/designer/assets/SvGridChart-DBOR4xcz.js +0 -2
- package/dist/designer/assets/SvGridChart-fkMWeh1J.css +0 -1
- package/dist/designer/assets/SvGridChartPanel-symrhNnR.js +0 -11
- package/dist/designer/assets/SvGridChartView-BmRxq6mU.js +0 -1
- package/dist/designer/assets/SvListBox-1oUF6Iav.js +0 -110
- package/dist/designer/assets/chart-DXFganU8.js +0 -1
- package/dist/designer/assets/column-resize-C_inr5zT.js +0 -1
- package/dist/designer/assets/date-format-DGagAqRc.css +0 -1
- package/dist/designer/assets/date-format-onlYDsrY.js +0 -1
- package/dist/designer/assets/easing-zaypKrr0.js +0 -3
- package/dist/designer/assets/index-k2stW6L8.css +0 -1
- package/dist/designer/assets/row-resize-B0FWHcNr.js +0 -1
- package/dist/designer/assets/src-D9p27OGC.js +0 -58
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import { applyFormat, toggleFormat, preset, autoSum, structural, clearFormats, getFormatTarget, getWorkbook, withFormatUndo, nudgeFontSize, activeEntry, FONT_SIZES, DEFAULT_FONT_SIZE, applyBorders, formatAllowed, raiseRibbonAction, } from './shortcuts.js';
|
|
2
|
+
export { FONT_SIZES, applyBorders } from './shortcuts.js';
|
|
3
|
+
import { insertRows, deleteRows, getStructureTarget } from './structure.js';
|
|
4
|
+
import { fillDown, fillRight, targetRect } from './commands.js';
|
|
5
|
+
import { FORMAT_PRESETS, formatCategory } from './number-format.js';
|
|
6
|
+
import { ALL_COLOURS } from './palette.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Shared predicates
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/** True when there is at least one cell to act on. */
|
|
11
|
+
function hasTarget(cmd) {
|
|
12
|
+
return targetRect(cmd) !== null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The rectangles an action should write to: the selection when there is one,
|
|
16
|
+
* otherwise the active cell alone.
|
|
17
|
+
*
|
|
18
|
+
* The same fallback `applyFormat` makes, so a button with nothing
|
|
19
|
+
* range-selected behaves like the key it mirrors rather than declining.
|
|
20
|
+
*/
|
|
21
|
+
function rectsOf(cmd) {
|
|
22
|
+
if (cmd.ranges.length)
|
|
23
|
+
return cmd.ranges;
|
|
24
|
+
const rect = targetRect(cmd);
|
|
25
|
+
return rect ? [rect] : [];
|
|
26
|
+
}
|
|
27
|
+
/** Formatting needs somewhere to write. Without a store the keyboard
|
|
28
|
+
* bindings decline, so the buttons grey out for the same reason. */
|
|
29
|
+
function canFormat(cmd) {
|
|
30
|
+
const target = getFormatTarget();
|
|
31
|
+
if (!target || !hasTarget(cmd))
|
|
32
|
+
return false;
|
|
33
|
+
return target.guard?.(rectsOf(cmd)) !== false;
|
|
34
|
+
}
|
|
35
|
+
/** Insert and Delete grey out where the sheet refuses structural edits. */
|
|
36
|
+
function canRestructure() {
|
|
37
|
+
const target = getStructureTarget();
|
|
38
|
+
return !target || target.canApply?.({ kind: 'insertRows', at: 0, count: 1 }) !== false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether every cell in the selection already carries `field`.
|
|
42
|
+
*
|
|
43
|
+
* Excel's own rule for a toggle button's pressed state, and the same rule
|
|
44
|
+
* `store.toggle` uses to decide which way the next press goes - so the button
|
|
45
|
+
* lights up exactly when pressing it would turn the attribute OFF.
|
|
46
|
+
*/
|
|
47
|
+
function everyCellHas(cmd, test) {
|
|
48
|
+
const target = getFormatTarget();
|
|
49
|
+
if (!target)
|
|
50
|
+
return false;
|
|
51
|
+
const rects = rectsOf(cmd);
|
|
52
|
+
if (!rects.length)
|
|
53
|
+
return false;
|
|
54
|
+
let seen = 0;
|
|
55
|
+
for (const [minRow, minCol, maxRow, maxCol] of rects) {
|
|
56
|
+
for (let r = minRow; r <= maxRow; r += 1) {
|
|
57
|
+
for (let c = minCol; c <= maxCol; c += 1) {
|
|
58
|
+
const rowId = target.lookup.rowIdAt(r);
|
|
59
|
+
const columnId = target.lookup.columnIdAt(c);
|
|
60
|
+
if (rowId == null || columnId == null)
|
|
61
|
+
continue;
|
|
62
|
+
seen += 1;
|
|
63
|
+
if (!test(target.store.get(rowId, columnId)))
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return seen > 0;
|
|
69
|
+
}
|
|
70
|
+
function styleToggle(field) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'toggle',
|
|
73
|
+
run: (cmd) => toggleFormat(cmd, field),
|
|
74
|
+
isOn: (cmd) => everyCellHas(cmd, (entry) => entry?.[field] === true),
|
|
75
|
+
isEnabled: canFormat,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function alignItem(align, title) {
|
|
79
|
+
return {
|
|
80
|
+
id: `align-${align}`,
|
|
81
|
+
label: '≡',
|
|
82
|
+
icon: `align-${align}`,
|
|
83
|
+
title,
|
|
84
|
+
kind: 'toggle',
|
|
85
|
+
run: (cmd) => applyFormat(cmd, { align }),
|
|
86
|
+
isOn: (cmd) => everyCellHas(cmd, (entry) => entry?.align === align),
|
|
87
|
+
isEnabled: canFormat,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function presetItem(id, label, title, name, keys) {
|
|
91
|
+
const run = preset(name);
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
label,
|
|
95
|
+
title,
|
|
96
|
+
keys,
|
|
97
|
+
kind: 'toggle',
|
|
98
|
+
run: (cmd) => run(cmd, new KeyboardEvent('keydown')),
|
|
99
|
+
isOn: (cmd) => everyCellHas(cmd, (entry) => entry?.numFmt === FORMAT_PRESETS[name]),
|
|
100
|
+
isEnabled: canFormat,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** A command written for the keymap, adapted to a button. The event is only
|
|
104
|
+
* there because `SheetCommand` takes one; none of these read it. */
|
|
105
|
+
function fromCommand(command) {
|
|
106
|
+
return (cmd) => command(cmd, new KeyboardEvent('keydown'));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Excel's Increase/Decrease Decimal.
|
|
110
|
+
*
|
|
111
|
+
* Reads the active cell's current pattern rather than assuming one, so
|
|
112
|
+
* pressing it on a currency cell keeps the currency and only moves the point.
|
|
113
|
+
* A cell with no pattern starts from General, which Excel treats as 0 decimals.
|
|
114
|
+
*/
|
|
115
|
+
function nudgeDecimals(delta) {
|
|
116
|
+
return (cmd) => {
|
|
117
|
+
const target = getFormatTarget();
|
|
118
|
+
const active = cmd.activeCell;
|
|
119
|
+
if (!target || !active)
|
|
120
|
+
return false;
|
|
121
|
+
const rowId = target.lookup.rowIdAt(active.rowIndex);
|
|
122
|
+
const columnId = target.lookup.columnIdAt(active.colIndex);
|
|
123
|
+
const current = rowId != null && columnId != null
|
|
124
|
+
? target.store.get(rowId, columnId)?.numFmt
|
|
125
|
+
: undefined;
|
|
126
|
+
const next = withDecimals(current ?? '0', delta);
|
|
127
|
+
return next === null ? false : applyFormat(cmd, { numFmt: next });
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Move the decimal count of a number-format pattern by `delta`.
|
|
132
|
+
*
|
|
133
|
+
* Exported because it is the fiddly part and deserves its own tests: the
|
|
134
|
+
* pattern can have a prefix and a suffix (`$#,##0.00`, `0.0%`), can have no
|
|
135
|
+
* decimal section at all, and must not go below zero or run away past the 30
|
|
136
|
+
* places Excel allows.
|
|
137
|
+
*/
|
|
138
|
+
export function withDecimals(pattern, delta) {
|
|
139
|
+
// Every section moves together. The currency preset is
|
|
140
|
+
// '$#,##0.00;($#,##0.00)' - positives and negatives - and moving only the
|
|
141
|
+
// first half would leave a sheet where -1234.5 printed to a different
|
|
142
|
+
// precision than 1234.5. Excel moves both, so this does too.
|
|
143
|
+
//
|
|
144
|
+
// A quoted literal can contain a semicolon, which would make splitting on
|
|
145
|
+
// ';' cut a section in half, so a pattern with quotes declines instead of
|
|
146
|
+
// being mangled. Nothing in FORMAT_PRESETS has one.
|
|
147
|
+
if (pattern.includes('"'))
|
|
148
|
+
return null;
|
|
149
|
+
const sections = pattern.split(';');
|
|
150
|
+
const moved = [];
|
|
151
|
+
for (const section of sections) {
|
|
152
|
+
const next = sectionWithDecimals(section, delta);
|
|
153
|
+
// All or nothing: a partial rewrite is the very thing this guards.
|
|
154
|
+
if (next === null)
|
|
155
|
+
return null;
|
|
156
|
+
moved.push(next);
|
|
157
|
+
}
|
|
158
|
+
return moved.join(';');
|
|
159
|
+
}
|
|
160
|
+
function sectionWithDecimals(section, delta) {
|
|
161
|
+
const dot = section.indexOf('.');
|
|
162
|
+
const places = dot < 0 ? 0 : countLeading(section.slice(dot + 1), '0');
|
|
163
|
+
const next = places + delta;
|
|
164
|
+
if (next < 0 || next > 30)
|
|
165
|
+
return null;
|
|
166
|
+
const head = dot < 0 ? section : section.slice(0, dot);
|
|
167
|
+
const tail = dot < 0 ? '' : section.slice(dot + 1 + places);
|
|
168
|
+
return next === 0 ? head + tail : `${head}.${'0'.repeat(next)}${tail}`;
|
|
169
|
+
}
|
|
170
|
+
function countLeading(text, ch) {
|
|
171
|
+
let n = 0;
|
|
172
|
+
while (n < text.length && text[n] === ch)
|
|
173
|
+
n += 1;
|
|
174
|
+
return n;
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Fonts, sizes, borders
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
/**
|
|
180
|
+
* The fonts a spreadsheet user reaches for, with the stacks that make each
|
|
181
|
+
* render on a machine that lacks it. The empty value is the theme's own
|
|
182
|
+
* font, which is what "Default" means here and what a fresh cell has.
|
|
183
|
+
*/
|
|
184
|
+
export const FONT_FAMILIES = [
|
|
185
|
+
{ value: '', label: 'Default' },
|
|
186
|
+
{ value: 'Aptos, "Aptos Narrow", "Segoe UI", sans-serif', label: 'Aptos' },
|
|
187
|
+
{ value: 'Calibri, Carlito, "Segoe UI", sans-serif', label: 'Calibri' },
|
|
188
|
+
{ value: 'Arial, Helvetica, sans-serif', label: 'Arial' },
|
|
189
|
+
{ value: '"Segoe UI", system-ui, sans-serif', label: 'Segoe UI' },
|
|
190
|
+
{ value: 'Verdana, Geneva, sans-serif', label: 'Verdana' },
|
|
191
|
+
{ value: 'Georgia, "Times New Roman", serif', label: 'Georgia' },
|
|
192
|
+
{ value: '"Times New Roman", Times, serif', label: 'Times New Roman' },
|
|
193
|
+
{ value: '"Courier New", Courier, monospace', label: 'Courier New' },
|
|
194
|
+
{ value: 'ui-monospace, Menlo, Consolas, monospace', label: 'Consolas' },
|
|
195
|
+
];
|
|
196
|
+
const NUMBER_FORMATS = [
|
|
197
|
+
{ value: 'general', label: 'General' },
|
|
198
|
+
{ value: 'number', label: 'Number' },
|
|
199
|
+
{ value: 'currency', label: 'Currency' },
|
|
200
|
+
{ value: 'percent', label: 'Percentage' },
|
|
201
|
+
{ value: 'date', label: 'Date' },
|
|
202
|
+
{ value: 'time', label: 'Time' },
|
|
203
|
+
{ value: 'scientific', label: 'Scientific' },
|
|
204
|
+
];
|
|
205
|
+
const BORDER_OPTIONS = [
|
|
206
|
+
{ value: 'bottom', label: 'Bottom Border', icon: 'border-bottom' },
|
|
207
|
+
{ value: 'top', label: 'Top Border', icon: 'border-top' },
|
|
208
|
+
{ value: 'left', label: 'Left Border', icon: 'border-left' },
|
|
209
|
+
{ value: 'right', label: 'Right Border', icon: 'border-right' },
|
|
210
|
+
{ value: 'none', label: 'No Border', icon: 'border-none' },
|
|
211
|
+
{ value: 'all', label: 'All Borders', icon: 'border-all' },
|
|
212
|
+
{ value: 'outside', label: 'Outside Borders', icon: 'border-outside' },
|
|
213
|
+
{ value: 'thick-bottom', label: 'Thick Bottom Border', icon: 'border-thick-bottom' },
|
|
214
|
+
];
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// The tabs
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
const small = (row, item) => ({ ...item, row });
|
|
219
|
+
const HOME = {
|
|
220
|
+
id: 'home',
|
|
221
|
+
label: 'Home',
|
|
222
|
+
groups: [
|
|
223
|
+
{
|
|
224
|
+
id: 'clipboard',
|
|
225
|
+
icon: 'paste',
|
|
226
|
+
label: 'Clipboard',
|
|
227
|
+
launcher: 'paste-special',
|
|
228
|
+
items: [
|
|
229
|
+
// Excel's Clipboard group: the large Paste, and beside it Cut, Copy and
|
|
230
|
+
// Format Painter as icons alone, which is how Excel draws them at
|
|
231
|
+
// every width but the widest. Paste is a split button: the face
|
|
232
|
+
// pastes, the arrow opens the paste kinds with Paste Special... last.
|
|
233
|
+
{
|
|
234
|
+
id: 'paste', label: 'Paste', title: 'Paste', keys: 'Ctrl+V', icon: 'paste',
|
|
235
|
+
kind: 'dropdown', split: true, size: 'large',
|
|
236
|
+
run: (cmd) => { void cmd.paste(); return true; },
|
|
237
|
+
options: [
|
|
238
|
+
{ value: 'paste', label: 'Paste', keys: 'Ctrl+V', icon: 'paste' },
|
|
239
|
+
{ value: 'paste-formulas', label: 'Formulas', emits: 'paste-formulas' },
|
|
240
|
+
{ value: 'paste-values', label: 'Values', emits: 'paste-values' },
|
|
241
|
+
{ value: 'paste-formats', label: 'Formatting', emits: 'paste-formats' },
|
|
242
|
+
{ value: 'paste-transpose', label: 'Transpose', emits: 'paste-transpose' },
|
|
243
|
+
{ value: 'paste-special', label: 'Paste Special...', keys: 'Ctrl+Shift+V', emits: 'paste-special' },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
// The same copy and cut as Ctrl+C and Ctrl+X: the selection, cell
|
|
247
|
+
// by cell through the sheet's clipboard hook. They went through the
|
|
248
|
+
// api's copyToClipboard, which is the export, and put the whole
|
|
249
|
+
// sheet with its column letters on the clipboard whatever was
|
|
250
|
+
// selected; Cut then blanked the selection by hand.
|
|
251
|
+
small(1, {
|
|
252
|
+
id: 'cut', label: 'Cut', title: 'Cut', keys: 'Ctrl+X', icon: 'cut', kind: 'button',
|
|
253
|
+
isEnabled: hasTarget,
|
|
254
|
+
run: (cmd) => { void cmd.cut(); return true; },
|
|
255
|
+
}),
|
|
256
|
+
small(2, {
|
|
257
|
+
id: 'copy', label: 'Copy', title: 'Copy', keys: 'Ctrl+C', icon: 'copy', kind: 'button',
|
|
258
|
+
isEnabled: hasTarget,
|
|
259
|
+
run: (cmd) => { cmd.copy(); return true; },
|
|
260
|
+
}),
|
|
261
|
+
// Raised: the shell holds the copied formats and paints them onto the
|
|
262
|
+
// next selection. The button reads pressed while it is armed.
|
|
263
|
+
small(3, {
|
|
264
|
+
id: 'format-painter', label: 'Format Painter', title: 'Format Painter',
|
|
265
|
+
icon: 'format-painter', kind: 'button', emits: 'format-painter',
|
|
266
|
+
}),
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: 'font',
|
|
271
|
+
icon: 'font',
|
|
272
|
+
label: 'Font',
|
|
273
|
+
layout: 'flow',
|
|
274
|
+
launcher: 'format-cells',
|
|
275
|
+
items: [
|
|
276
|
+
small(1, {
|
|
277
|
+
id: 'font-family', label: 'Font', title: 'Font', kind: 'select',
|
|
278
|
+
options: FONT_FAMILIES, isEnabled: canFormat,
|
|
279
|
+
current: (cmd) => activeEntry(cmd)?.fontFamily ?? '',
|
|
280
|
+
run: (cmd, value) => applyFormat(cmd, { fontFamily: value || undefined }),
|
|
281
|
+
}),
|
|
282
|
+
small(1, {
|
|
283
|
+
id: 'font-size', label: 'Size', title: 'Font Size', kind: 'select',
|
|
284
|
+
options: FONT_SIZES.map((n) => ({ value: String(n), label: String(n) })),
|
|
285
|
+
isEnabled: canFormat,
|
|
286
|
+
current: (cmd) => String(activeEntry(cmd)?.fontSize ?? DEFAULT_FONT_SIZE),
|
|
287
|
+
run: (cmd, value) => applyFormat(cmd, { fontSize: Number(value) === DEFAULT_FONT_SIZE ? undefined : Number(value) }),
|
|
288
|
+
}),
|
|
289
|
+
small(1, {
|
|
290
|
+
id: 'font-grow', label: 'A', title: 'Increase Font Size', keys: 'Ctrl+Shift+>',
|
|
291
|
+
icon: 'font-grow', kind: 'button', isEnabled: canFormat, run: fromCommand(nudgeFontSize(1)),
|
|
292
|
+
}),
|
|
293
|
+
small(1, {
|
|
294
|
+
id: 'font-shrink', label: 'A', title: 'Decrease Font Size', keys: 'Ctrl+Shift+<',
|
|
295
|
+
icon: 'font-shrink', kind: 'button', isEnabled: canFormat, run: fromCommand(nudgeFontSize(-1)),
|
|
296
|
+
}),
|
|
297
|
+
small(2, { id: 'bold', label: 'B', title: 'Bold', keys: 'Ctrl+B', ...styleToggle('bold') }),
|
|
298
|
+
small(2, { id: 'italic', label: 'I', title: 'Italic', keys: 'Ctrl+I', ...styleToggle('italic') }),
|
|
299
|
+
small(2, { id: 'underline', label: 'U', title: 'Underline', keys: 'Ctrl+U', ...styleToggle('underline') }),
|
|
300
|
+
small(2, { id: 'strike', label: 'S', title: 'Strikethrough', keys: 'Ctrl+5', ...styleToggle('strike') }),
|
|
301
|
+
small(2, {
|
|
302
|
+
id: 'borders', label: 'Borders', title: 'Borders', icon: 'borders', kind: 'menu',
|
|
303
|
+
options: BORDER_OPTIONS, isEnabled: canFormat,
|
|
304
|
+
run: (cmd, value) => applyBorders(cmd, (value || 'bottom')),
|
|
305
|
+
}),
|
|
306
|
+
small(2, {
|
|
307
|
+
id: 'fill', label: 'Fill Color', title: 'Fill Color', icon: 'fill-colour', kind: 'menu',
|
|
308
|
+
palette: true, none: { value: 'transparent', label: 'No Fill' }, initial: '#FFFF00',
|
|
309
|
+
options: ALL_COLOURS, isEnabled: canFormat,
|
|
310
|
+
run: (cmd, value) => applyFormat(cmd, { fill: !value || value === 'transparent' ? undefined : value }),
|
|
311
|
+
}),
|
|
312
|
+
small(2, {
|
|
313
|
+
id: 'text-colour', label: 'A', title: 'Font Color', icon: 'font-colour', kind: 'menu',
|
|
314
|
+
palette: true, none: { value: 'inherit', label: 'Automatic' }, initial: '#FF0000',
|
|
315
|
+
options: ALL_COLOURS, isEnabled: canFormat,
|
|
316
|
+
run: (cmd, value) => applyFormat(cmd, { color: !value || value === 'inherit' ? undefined : value }),
|
|
317
|
+
}),
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'alignment',
|
|
322
|
+
icon: 'align-center',
|
|
323
|
+
label: 'Alignment',
|
|
324
|
+
layout: 'flow',
|
|
325
|
+
launcher: 'format-cells',
|
|
326
|
+
items: [
|
|
327
|
+
small(1, {
|
|
328
|
+
id: 'wrap', label: 'Wrap Text', title: 'Wrap Text', icon: 'wrap', kind: 'toggle', wide: true,
|
|
329
|
+
run: (cmd) => toggleWrap(cmd),
|
|
330
|
+
isOn: (cmd) => everyCellHas(cmd, (entry) => entry?.wrap === true),
|
|
331
|
+
isEnabled: canFormat,
|
|
332
|
+
}),
|
|
333
|
+
small(2, alignItem('left', 'Align Left')),
|
|
334
|
+
small(2, alignItem('center', 'Center')),
|
|
335
|
+
small(2, alignItem('right', 'Align Right')),
|
|
336
|
+
// Excel's split button: the face merges and centres, the arrow
|
|
337
|
+
// opens the kinds. Raised for the shell, which keeps the merges
|
|
338
|
+
// per sheet and hands them to the grid.
|
|
339
|
+
small(3, {
|
|
340
|
+
id: 'merge', label: 'Merge & Center', title: 'Merge & Center', icon: 'merge',
|
|
341
|
+
kind: 'dropdown', split: true, wide: true, lit: 'merge-center',
|
|
342
|
+
// Greyed on a protected sheet, as Excel's is: merging is a format.
|
|
343
|
+
isEnabled: canFormat,
|
|
344
|
+
run: (cmd) => raiseRibbonAction('merge-center', cmd),
|
|
345
|
+
options: [
|
|
346
|
+
{ value: 'merge-center', label: 'Merge & Center', icon: 'merge', emits: 'merge-center' },
|
|
347
|
+
{ value: 'merge-across', label: 'Merge Across', emits: 'merge-across' },
|
|
348
|
+
{ value: 'merge-cells', label: 'Merge Cells', emits: 'merge-cells' },
|
|
349
|
+
{ value: 'unmerge-cells', label: 'Unmerge Cells', icon: 'unmerge', emits: 'unmerge-cells' },
|
|
350
|
+
],
|
|
351
|
+
}),
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'number',
|
|
356
|
+
icon: 'number',
|
|
357
|
+
label: 'Number',
|
|
358
|
+
layout: 'flow',
|
|
359
|
+
launcher: 'format-cells',
|
|
360
|
+
items: [
|
|
361
|
+
small(1, {
|
|
362
|
+
id: 'num-format', label: 'General', title: 'Number Format', kind: 'select',
|
|
363
|
+
options: NUMBER_FORMATS, isEnabled: canFormat,
|
|
364
|
+
// The category, not the exact pattern: a typed 12% carries 0%,
|
|
365
|
+
// and Excel's combo calls that Percentage too. A pattern outside
|
|
366
|
+
// every category shows as itself.
|
|
367
|
+
current: (cmd) => {
|
|
368
|
+
const fmt = activeEntry(cmd)?.numFmt;
|
|
369
|
+
const { category } = formatCategory(fmt);
|
|
370
|
+
return category === 'custom' ? fmt ?? 'general' : category;
|
|
371
|
+
},
|
|
372
|
+
run: (cmd, value) => applyFormat(cmd, { numFmt: FORMAT_PRESETS[value] }),
|
|
373
|
+
}),
|
|
374
|
+
small(2, presetItem('fmt-currency', '$', 'Accounting Number Format', 'currency', 'Ctrl+Shift+4')),
|
|
375
|
+
small(2, presetItem('fmt-percent', '%', 'Percent Style', 'percent', 'Ctrl+Shift+5')),
|
|
376
|
+
small(2, presetItem('fmt-number', ',', 'Comma Style', 'number', 'Ctrl+Shift+1')),
|
|
377
|
+
small(2, {
|
|
378
|
+
id: 'dec-more', label: '.00', title: 'Increase Decimal', icon: 'dec-more', kind: 'button',
|
|
379
|
+
isEnabled: canFormat, run: nudgeDecimals(1),
|
|
380
|
+
}),
|
|
381
|
+
small(2, {
|
|
382
|
+
id: 'dec-less', label: '.0', title: 'Decrease Decimal', icon: 'dec-less', kind: 'button',
|
|
383
|
+
isEnabled: canFormat, run: nudgeDecimals(-1),
|
|
384
|
+
}),
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: 'cells',
|
|
389
|
+
icon: 'table',
|
|
390
|
+
label: 'Cells',
|
|
391
|
+
items: [
|
|
392
|
+
small(1, {
|
|
393
|
+
id: 'insert', label: 'Insert', title: 'Insert Cells', keys: 'Ctrl+Shift++',
|
|
394
|
+
icon: 'insert-cells', kind: 'button', wide: true,
|
|
395
|
+
// A plain cell is ambiguous for the keystroke, which declines; the
|
|
396
|
+
// button behaves as Excel's does with a cell selected and inserts
|
|
397
|
+
// a row, shifting the sheet down.
|
|
398
|
+
run: (cmd) => structural(cmd, 'insert') || insertRows(cmd),
|
|
399
|
+
isEnabled: canRestructure,
|
|
400
|
+
}),
|
|
401
|
+
small(2, {
|
|
402
|
+
id: 'delete', label: 'Delete', title: 'Delete Cells', keys: 'Ctrl+-',
|
|
403
|
+
icon: 'delete-cells', kind: 'button', wide: true,
|
|
404
|
+
run: (cmd) => structural(cmd, 'delete') || deleteRows(cmd),
|
|
405
|
+
isEnabled: canRestructure,
|
|
406
|
+
}),
|
|
407
|
+
// Excel's Format menu: Cell Size, Visibility, and Format Cells at
|
|
408
|
+
// the end. Each entry is raised for the shell, which owns the size
|
|
409
|
+
// dialogs and keeps hidden lines per sheet.
|
|
410
|
+
small(3, {
|
|
411
|
+
id: 'format', label: 'Format', title: 'Format', icon: 'format-cells', kind: 'dropdown', wide: true,
|
|
412
|
+
options: [
|
|
413
|
+
{ value: 'cell-size', label: 'Cell Size', heading: true },
|
|
414
|
+
{ value: 'row-height', label: 'Row Height...', emits: 'row-height' },
|
|
415
|
+
{ value: 'autofit-rows', label: 'AutoFit Row Height', emits: 'autofit-rows' },
|
|
416
|
+
{ value: 'column-width', label: 'Column Width...', emits: 'column-width' },
|
|
417
|
+
{ value: 'autofit-columns', label: 'AutoFit Column Width', emits: 'autofit-columns' },
|
|
418
|
+
{ value: 'visibility', label: 'Visibility', heading: true },
|
|
419
|
+
{ value: 'hide-rows', label: 'Hide Rows', keys: 'Ctrl+9', emits: 'hide-rows' },
|
|
420
|
+
{ value: 'hide-columns', label: 'Hide Columns', keys: 'Ctrl+0', emits: 'hide-columns' },
|
|
421
|
+
{ value: 'unhide-rows', label: 'Unhide Rows', keys: 'Ctrl+Shift+9', emits: 'unhide-rows' },
|
|
422
|
+
{ value: 'unhide-columns', label: 'Unhide Columns', keys: 'Ctrl+Shift+0', emits: 'unhide-columns' },
|
|
423
|
+
{ value: 'protection', label: 'Protection', heading: true },
|
|
424
|
+
{ value: 'lock-cell', label: 'Lock Cell', icon: 'lock', emits: 'toggle-lock', toggle: true },
|
|
425
|
+
{ value: 'format-cells', label: 'Format Cells...', keys: 'Ctrl+1', emits: 'format-cells' },
|
|
426
|
+
],
|
|
427
|
+
}),
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
// Excel's Styles group, the part with something behind it: one large
|
|
432
|
+
// Conditional Formatting dropdown. Its entries are raised for the
|
|
433
|
+
// shell, which owns the small dialogs and the rules per sheet; the
|
|
434
|
+
// headings are Excel's submenus, flattened.
|
|
435
|
+
id: 'styles',
|
|
436
|
+
icon: 'cf',
|
|
437
|
+
label: 'Styles',
|
|
438
|
+
items: [
|
|
439
|
+
{
|
|
440
|
+
id: 'conditional-formatting', label: 'Conditional Formatting', title: 'Conditional Formatting',
|
|
441
|
+
icon: 'cf', kind: 'dropdown', size: 'large',
|
|
442
|
+
options: [
|
|
443
|
+
{ value: 'h', label: 'Highlight Cells Rules', heading: true },
|
|
444
|
+
{ value: 'cf-greater', label: 'Greater Than...', icon: 'cf-greater', emits: 'cf-greater' },
|
|
445
|
+
{ value: 'cf-less', label: 'Less Than...', icon: 'cf-less', emits: 'cf-less' },
|
|
446
|
+
{ value: 'cf-between', label: 'Between...', icon: 'cf-between', emits: 'cf-between' },
|
|
447
|
+
{ value: 'cf-equal', label: 'Equal To...', icon: 'cf-equal', emits: 'cf-equal' },
|
|
448
|
+
{ value: 'cf-text', label: 'Text that Contains...', icon: 'cf-text', emits: 'cf-text' },
|
|
449
|
+
{ value: 'cf-duplicates', label: 'Duplicate Values...', icon: 'cf-duplicates', emits: 'cf-duplicates' },
|
|
450
|
+
{ value: 't', label: 'Top/Bottom Rules', heading: true },
|
|
451
|
+
{ value: 'cf-top10', label: 'Top 10 Items...', icon: 'cf-top', emits: 'cf-top10' },
|
|
452
|
+
{ value: 'cf-bottom10', label: 'Bottom 10 Items...', icon: 'cf-bottom', emits: 'cf-bottom10' },
|
|
453
|
+
{ value: 'cf-above-average', label: 'Above Average...', icon: 'cf-above', emits: 'cf-above-average' },
|
|
454
|
+
{ value: 'cf-below-average', label: 'Below Average...', icon: 'cf-below', emits: 'cf-below-average' },
|
|
455
|
+
{ value: 'b', label: 'Data Bars, Color Scales, Icon Sets', heading: true },
|
|
456
|
+
{ value: 'cf-data-bar', label: 'Data Bar', icon: 'cf-bar', emits: 'cf-data-bar' },
|
|
457
|
+
{ value: 'cf-color-scale-3', label: 'Green - Yellow - Red Color Scale', icon: 'cf-scale', emits: 'cf-color-scale-3' },
|
|
458
|
+
{ value: 'cf-color-scale-2', label: 'Green - White Color Scale', icon: 'cf-scale', emits: 'cf-color-scale-2' },
|
|
459
|
+
{ value: 'cf-icon-set', label: 'Icon Set (3 Arrows)', icon: 'cf-icons', emits: 'cf-icon-set' },
|
|
460
|
+
{ value: 'c', label: 'Clear Rules', heading: true },
|
|
461
|
+
{ value: 'cf-clear-selection', label: 'Clear Rules from Selected Cells', emits: 'cf-clear-selection' },
|
|
462
|
+
{ value: 'cf-clear-sheet', label: 'Clear Rules from Entire Sheet', emits: 'cf-clear-sheet' },
|
|
463
|
+
{ value: 'm', label: 'Manage', heading: true },
|
|
464
|
+
{ value: 'cf-manage', label: 'Manage Rules...', emits: 'cf-manage' },
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: 'editing',
|
|
471
|
+
icon: 'find',
|
|
472
|
+
label: 'Editing',
|
|
473
|
+
items: [
|
|
474
|
+
// Undo and Redo open the group as a column of two small icons, the
|
|
475
|
+
// way Cut and Copy stand beside Paste; the owner wanted them here
|
|
476
|
+
// rather than in a group of their own.
|
|
477
|
+
small(1, {
|
|
478
|
+
id: 'undo', label: 'Undo', title: 'Undo', keys: 'Ctrl+Z', icon: 'undo', kind: 'button',
|
|
479
|
+
isEnabled: (cmd) => cmd.api.canUndo(),
|
|
480
|
+
run: (cmd) => cmd.api.undo(),
|
|
481
|
+
}),
|
|
482
|
+
small(2, {
|
|
483
|
+
id: 'redo', label: 'Redo', title: 'Redo', keys: 'Ctrl+Y', icon: 'redo', kind: 'button',
|
|
484
|
+
isEnabled: (cmd) => cmd.api.canRedo(),
|
|
485
|
+
run: (cmd) => cmd.api.redo(),
|
|
486
|
+
}),
|
|
487
|
+
small(1, {
|
|
488
|
+
id: 'autosum', label: 'AutoSum', title: 'AutoSum', keys: 'Alt+=',
|
|
489
|
+
icon: 'autosum', kind: 'button', wide: true, run: fromCommand(autoSum),
|
|
490
|
+
}),
|
|
491
|
+
small(2, {
|
|
492
|
+
id: 'fill-down', label: 'Fill Down', title: 'Fill Down', keys: 'Ctrl+D',
|
|
493
|
+
icon: 'fill-down', kind: 'button', wide: true, run: (cmd) => fillDown(cmd),
|
|
494
|
+
}),
|
|
495
|
+
small(3, {
|
|
496
|
+
id: 'fill-right', label: 'Fill Right', title: 'Fill Right', keys: 'Ctrl+R',
|
|
497
|
+
icon: 'fill-right', kind: 'button', wide: true, run: (cmd) => fillRight(cmd),
|
|
498
|
+
}),
|
|
499
|
+
small(1, {
|
|
500
|
+
id: 'clear-formats', label: 'Clear', title: 'Clear Formats (keeps values)',
|
|
501
|
+
icon: 'clear', kind: 'button', wide: true, isEnabled: canFormat,
|
|
502
|
+
run: clearFormats,
|
|
503
|
+
}),
|
|
504
|
+
small(2, {
|
|
505
|
+
id: 'filter', label: 'Filter', title: 'Filter', keys: 'Ctrl+Shift+L',
|
|
506
|
+
icon: 'filter', kind: 'toggle', wide: true, emits: 'toggle-filter',
|
|
507
|
+
}),
|
|
508
|
+
small(3, {
|
|
509
|
+
id: 'find', label: 'Find & Select', title: 'Find and Replace', keys: 'Ctrl+H',
|
|
510
|
+
icon: 'find', kind: 'button', wide: true, emits: 'find-replace',
|
|
511
|
+
}),
|
|
512
|
+
],
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
};
|
|
516
|
+
const INSERT = {
|
|
517
|
+
id: 'insert',
|
|
518
|
+
label: 'Insert',
|
|
519
|
+
groups: [
|
|
520
|
+
{
|
|
521
|
+
id: 'tables',
|
|
522
|
+
icon: 'table',
|
|
523
|
+
label: 'Tables',
|
|
524
|
+
items: [
|
|
525
|
+
{ id: 'table', label: 'Table', title: 'Format the selection as a table', keys: 'Ctrl+T', icon: 'table', kind: 'button', size: 'large', emits: 'insert-table' },
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: 'charts',
|
|
530
|
+
icon: 'chart',
|
|
531
|
+
label: 'Charts',
|
|
532
|
+
items: [
|
|
533
|
+
{ id: 'chart', label: 'Chart', title: 'Chart the selected range', icon: 'chart', kind: 'button', size: 'large', emits: 'insert-chart' },
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
id: 'insert-cells',
|
|
538
|
+
icon: 'insert-cells',
|
|
539
|
+
label: 'Cells',
|
|
540
|
+
items: [
|
|
541
|
+
small(1, {
|
|
542
|
+
id: 'insert-rows', label: 'Insert', title: 'Insert rows or columns', icon: 'insert-cells', kind: 'button', wide: true,
|
|
543
|
+
run: (cmd) => structural(cmd, 'insert') || insertRows(cmd),
|
|
544
|
+
isEnabled: canRestructure,
|
|
545
|
+
}),
|
|
546
|
+
small(2, {
|
|
547
|
+
id: 'delete-rows', label: 'Delete', title: 'Delete rows or columns', icon: 'delete-cells', kind: 'button', wide: true,
|
|
548
|
+
run: (cmd) => structural(cmd, 'delete') || deleteRows(cmd),
|
|
549
|
+
isEnabled: canRestructure,
|
|
550
|
+
}),
|
|
551
|
+
],
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: 'insert-function',
|
|
555
|
+
icon: 'function',
|
|
556
|
+
label: 'Functions',
|
|
557
|
+
items: [
|
|
558
|
+
{ id: 'function', label: 'Insert Function', title: 'Insert Function', keys: 'Shift+F3', icon: 'function', kind: 'button', size: 'large', emits: 'insert-function' },
|
|
559
|
+
],
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: 'insert-sheet',
|
|
563
|
+
icon: 'new-sheet',
|
|
564
|
+
label: 'Sheets',
|
|
565
|
+
items: [
|
|
566
|
+
{
|
|
567
|
+
id: 'new-sheet', label: 'New Sheet', title: 'New sheet', keys: 'Shift+F11',
|
|
568
|
+
icon: 'new-sheet', kind: 'button', size: 'large',
|
|
569
|
+
isEnabled: () => getWorkbook() !== null,
|
|
570
|
+
run: () => {
|
|
571
|
+
const wb = getWorkbook();
|
|
572
|
+
if (!wb)
|
|
573
|
+
return false;
|
|
574
|
+
wb.addSheet();
|
|
575
|
+
return true;
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
};
|
|
582
|
+
const FORMULAS = {
|
|
583
|
+
id: 'formulas',
|
|
584
|
+
label: 'Formulas',
|
|
585
|
+
groups: [
|
|
586
|
+
{
|
|
587
|
+
id: 'function-library',
|
|
588
|
+
icon: 'function',
|
|
589
|
+
label: 'Function Library',
|
|
590
|
+
items: [
|
|
591
|
+
{ id: 'f-insert', label: 'Insert Function', title: 'Insert Function', keys: 'Shift+F3', icon: 'function', kind: 'button', size: 'large', emits: 'insert-function' },
|
|
592
|
+
{ id: 'f-autosum', label: 'AutoSum', title: 'AutoSum', keys: 'Alt+=', icon: 'autosum', kind: 'button', size: 'large', run: fromCommand(autoSum) },
|
|
593
|
+
],
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
id: 'defined-names',
|
|
597
|
+
icon: 'name-manager',
|
|
598
|
+
label: 'Defined Names',
|
|
599
|
+
items: [
|
|
600
|
+
{ id: 'name-manager', label: 'Name Manager', title: 'Name Manager', keys: 'Ctrl+F3', icon: 'name-manager', kind: 'button', size: 'large', emits: 'name-manager' },
|
|
601
|
+
],
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
id: 'auditing',
|
|
605
|
+
icon: 'show-formulas',
|
|
606
|
+
label: 'Formula Auditing',
|
|
607
|
+
items: [
|
|
608
|
+
small(1, { id: 'show-formulas', label: 'Show Formulas', title: 'Show formulas instead of their results', keys: 'Ctrl+`', icon: 'show-formulas', kind: 'toggle', wide: true, emits: 'toggle-formulas' }),
|
|
609
|
+
],
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
id: 'calculation',
|
|
613
|
+
icon: 'calculate',
|
|
614
|
+
label: 'Calculation',
|
|
615
|
+
items: [
|
|
616
|
+
{ id: 'recalc', label: 'Calculate Now', title: 'Recalculate the workbook', keys: 'F9', icon: 'calculate', kind: 'button', size: 'large', emits: 'recalculate' },
|
|
617
|
+
small(1, { id: 'goal-seek-f', label: 'Goal Seek', title: 'Goal Seek', icon: 'goal-seek', kind: 'button', wide: true, emits: 'goal-seek' }),
|
|
618
|
+
],
|
|
619
|
+
},
|
|
620
|
+
],
|
|
621
|
+
};
|
|
622
|
+
const DATA = {
|
|
623
|
+
id: 'data',
|
|
624
|
+
label: 'Data',
|
|
625
|
+
groups: [
|
|
626
|
+
{
|
|
627
|
+
id: 'sort-filter',
|
|
628
|
+
icon: 'filter',
|
|
629
|
+
label: 'Sort & Filter',
|
|
630
|
+
items: [
|
|
631
|
+
small(1, { id: 'sort-asc', label: 'Sort A to Z', title: 'Sort A to Z', icon: 'sort-asc', kind: 'button', emits: 'sort-asc' }),
|
|
632
|
+
small(2, { id: 'sort-desc', label: 'Sort Z to A', title: 'Sort Z to A', icon: 'sort-desc', kind: 'button', emits: 'sort-desc' }),
|
|
633
|
+
{ id: 'filter-data', label: 'Filter', title: 'Filter', keys: 'Ctrl+Shift+L', icon: 'filter', kind: 'toggle', size: 'large', emits: 'toggle-filter' },
|
|
634
|
+
],
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: 'data-tools',
|
|
638
|
+
icon: 'text-to-columns',
|
|
639
|
+
label: 'Data Tools',
|
|
640
|
+
items: [
|
|
641
|
+
{ id: 'text-to-columns', label: 'Text to Columns', title: 'Split the selected column on a delimiter', icon: 'text-to-columns', kind: 'button', size: 'large', emits: 'text-to-columns' },
|
|
642
|
+
{ id: 'remove-duplicates', label: 'Remove Duplicates', title: 'Remove duplicate rows', icon: 'remove-duplicates', kind: 'button', size: 'large', emits: 'remove-duplicates' },
|
|
643
|
+
{ id: 'data-validation', label: 'Data Validation', title: 'Data Validation: what may be typed into the selected cells', icon: 'validation', kind: 'button', size: 'large', emits: 'data-validation' },
|
|
644
|
+
],
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
id: 'forecast',
|
|
648
|
+
icon: 'goal-seek',
|
|
649
|
+
label: 'Forecast',
|
|
650
|
+
items: [
|
|
651
|
+
{ id: 'goal-seek', label: 'Goal Seek', title: 'Change one input until a formula hits a target', icon: 'goal-seek', kind: 'button', size: 'large', emits: 'goal-seek' },
|
|
652
|
+
],
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
};
|
|
656
|
+
function toggleWrap(cmd) {
|
|
657
|
+
const target = getFormatTarget();
|
|
658
|
+
if (!target)
|
|
659
|
+
return false;
|
|
660
|
+
const rects = rectsOf(cmd);
|
|
661
|
+
if (!rects.length)
|
|
662
|
+
return false;
|
|
663
|
+
if (!formatAllowed(target, rects))
|
|
664
|
+
return false;
|
|
665
|
+
withFormatUndo(cmd, target, rects, () => target.store.toggle(rects, 'wrap', target.lookup));
|
|
666
|
+
target.onChange?.();
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Excel's Review tab, the parts with something behind them. Comments are
|
|
671
|
+
* the sheet's notes: New Comment opens the editor on the active cell (and
|
|
672
|
+
* edits the one there), Delete, Previous and Next walk them, Show All
|
|
673
|
+
* Comments lists them. Protect Sheet and Unprotect Sheet are one slot, the
|
|
674
|
+
* shell leaving off whichever does not apply. There is no password, as the
|
|
675
|
+
* sheet documents.
|
|
676
|
+
*/
|
|
677
|
+
const REVIEW = {
|
|
678
|
+
id: 'review',
|
|
679
|
+
label: 'Review',
|
|
680
|
+
groups: [
|
|
681
|
+
{
|
|
682
|
+
id: 'comments',
|
|
683
|
+
icon: 'comment',
|
|
684
|
+
label: 'Comments',
|
|
685
|
+
items: [
|
|
686
|
+
{ id: 'new-comment', label: 'New Comment', title: 'New Comment', keys: 'Shift+F2', icon: 'comment', kind: 'button', size: 'large', emits: 'new-comment' },
|
|
687
|
+
small(1, { id: 'delete-comment', label: 'Delete', title: 'Delete Comment', icon: 'comment-delete', kind: 'button', wide: true, emits: 'delete-comment' }),
|
|
688
|
+
small(2, { id: 'prev-comment', label: 'Previous', title: 'Previous Comment', icon: 'comment-prev', kind: 'button', wide: true, emits: 'prev-comment' }),
|
|
689
|
+
small(3, { id: 'next-comment', label: 'Next', title: 'Next Comment', icon: 'comment-next', kind: 'button', wide: true, emits: 'next-comment' }),
|
|
690
|
+
{ id: 'toggle-comments', label: 'Show All Comments', title: 'Show All Comments', icon: 'comments-all', kind: 'toggle', size: 'large', emits: 'toggle-comments' },
|
|
691
|
+
],
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: 'protect',
|
|
695
|
+
icon: 'protect',
|
|
696
|
+
label: 'Protect',
|
|
697
|
+
items: [
|
|
698
|
+
{
|
|
699
|
+
id: 'protect-sheet', label: 'Protect Sheet', title: 'Protect Sheet: locked cells can no longer be changed',
|
|
700
|
+
icon: 'protect', kind: 'button', size: 'large', emits: 'protect-sheet',
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
id: 'unprotect-sheet', label: 'Unprotect Sheet', title: 'Unprotect Sheet',
|
|
704
|
+
icon: 'unprotect', kind: 'button', size: 'large', emits: 'unprotect-sheet',
|
|
705
|
+
},
|
|
706
|
+
],
|
|
707
|
+
},
|
|
708
|
+
],
|
|
709
|
+
};
|
|
710
|
+
/**
|
|
711
|
+
* Excel's View tab, the part with something behind it: Window > Freeze
|
|
712
|
+
* Panes, Excel's dropdown of Freeze Panes / Freeze Top Row / Freeze First
|
|
713
|
+
* Column / Unfreeze Panes. Raised rather than run here: the sheet shell
|
|
714
|
+
* keeps the freeze per sheet and puts it back when the sheet comes up
|
|
715
|
+
* again.
|
|
716
|
+
*/
|
|
717
|
+
const VIEW = {
|
|
718
|
+
id: 'view',
|
|
719
|
+
label: 'View',
|
|
720
|
+
groups: [
|
|
721
|
+
{
|
|
722
|
+
id: 'show',
|
|
723
|
+
icon: 'gridlines',
|
|
724
|
+
label: 'Show',
|
|
725
|
+
items: [
|
|
726
|
+
small(1, { id: 'gridlines', label: 'Gridlines', title: 'Show or hide the gridlines', icon: 'gridlines', kind: 'toggle', wide: true, emits: 'toggle-gridlines' }),
|
|
727
|
+
small(2, { id: 'formula-bar', label: 'Formula Bar', title: 'Show or hide the formula bar', icon: 'formula-bar', kind: 'toggle', wide: true, emits: 'toggle-formula-bar' }),
|
|
728
|
+
small(3, { id: 'headings', label: 'Headings', title: 'Show or hide the row numbers and column letters', icon: 'headings', kind: 'toggle', wide: true, emits: 'toggle-headings' }),
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
id: 'window',
|
|
733
|
+
icon: 'freeze',
|
|
734
|
+
label: 'Window',
|
|
735
|
+
items: [
|
|
736
|
+
{
|
|
737
|
+
id: 'freeze', label: 'Freeze Panes', title: 'Freeze Panes', icon: 'freeze', kind: 'dropdown', size: 'large',
|
|
738
|
+
options: [
|
|
739
|
+
{ value: 'freeze-panes', label: 'Freeze Panes', icon: 'freeze', emits: 'freeze-panes' },
|
|
740
|
+
{ value: 'freeze-top-row', label: 'Freeze Top Row', emits: 'freeze-top-row' },
|
|
741
|
+
{ value: 'freeze-first-column', label: 'Freeze First Column', emits: 'freeze-first-column' },
|
|
742
|
+
{ value: 'unfreeze-panes', label: 'Unfreeze Panes', icon: 'unfreeze', emits: 'unfreeze-panes' },
|
|
743
|
+
],
|
|
744
|
+
},
|
|
745
|
+
],
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
};
|
|
749
|
+
export const RIBBON_TABS = [HOME, INSERT, FORMULAS, DATA, REVIEW, VIEW];
|
|
750
|
+
/** Every item across every tab, for tests and for a command palette. */
|
|
751
|
+
export function ribbonItems() {
|
|
752
|
+
return RIBBON_TABS.flatMap((tab) => tab.groups.flatMap((group) => group.items));
|
|
753
|
+
}
|