@alaarab/ogrid-js 2.3.0 → 2.4.1
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/esm/index.js
CHANGED
|
@@ -81,6 +81,17 @@ function getCellValue(item, col) {
|
|
|
81
81
|
function isColumnEditable(col, item) {
|
|
82
82
|
return col.editable === true || typeof col.editable === "function" && col.editable(item);
|
|
83
83
|
}
|
|
84
|
+
function createGridDataAccessor(items, flatColumns) {
|
|
85
|
+
return {
|
|
86
|
+
getCellValue: (col, row) => {
|
|
87
|
+
if (row < 0 || row >= items.length) return null;
|
|
88
|
+
if (col < 0 || col >= flatColumns.length) return null;
|
|
89
|
+
return getCellValue(items[row], flatColumns[col]);
|
|
90
|
+
},
|
|
91
|
+
getRowCount: () => items.length,
|
|
92
|
+
getColumnCount: () => flatColumns.length
|
|
93
|
+
};
|
|
94
|
+
}
|
|
84
95
|
function isColumnGroupDef(c) {
|
|
85
96
|
return "children" in c && Array.isArray(c.children);
|
|
86
97
|
}
|
|
@@ -1107,7 +1118,7 @@ var _CellDescriptorCache = class _CellDescriptorCache2 {
|
|
|
1107
1118
|
const sr = input.selectionRange;
|
|
1108
1119
|
const cr = input.cutRange;
|
|
1109
1120
|
const cp = input.copyRange;
|
|
1110
|
-
return (ec ? `${String(ec.rowId)}\0${ec.columnId}` : "") + "" + (ac ? `${ac.rowIndex}\0${ac.columnIndex}` : "") + "" + (sr ? `${sr.startRow}\0${sr.startCol}\0${sr.endRow}\0${sr.endCol}` : "") + "" + (cr ? `${cr.startRow}\0${cr.startCol}\0${cr.endRow}\0${cr.endCol}` : "") + "" + (cp ? `${cp.startRow}\0${cp.startCol}\0${cp.endRow}\0${cp.endCol}` : "") + "" + (input.isDragging ? "1" : "0") + "" + (input.editable !== false ? "1" : "0") + "" + (input.onCellValueChanged ? "1" : "0");
|
|
1121
|
+
return (ec ? `${String(ec.rowId)}\0${ec.columnId}` : "") + "" + (ac ? `${ac.rowIndex}\0${ac.columnIndex}` : "") + "" + (sr ? `${sr.startRow}\0${sr.startCol}\0${sr.endRow}\0${sr.endCol}` : "") + "" + (cr ? `${cr.startRow}\0${cr.startCol}\0${cr.endRow}\0${cr.endCol}` : "") + "" + (cp ? `${cp.startRow}\0${cp.startCol}\0${cp.endRow}\0${cp.endCol}` : "") + "" + (input.isDragging ? "1" : "0") + "" + (input.editable !== false ? "1" : "0") + "" + (input.onCellValueChanged ? "1" : "0") + "" + (input.formulaVersion ?? 0);
|
|
1111
1122
|
}
|
|
1112
1123
|
/**
|
|
1113
1124
|
* Get a cached descriptor or compute a new one.
|
|
@@ -1178,6 +1189,7 @@ function computeCellDescriptor(item, col, rowIndex, colIdx, input) {
|
|
|
1178
1189
|
const isPinned = col.pinned != null;
|
|
1179
1190
|
const pinnedSide = col.pinned ?? void 0;
|
|
1180
1191
|
const cellValue = getCellValue(item, col);
|
|
1192
|
+
const formulaDisplay = input.hasFormula?.(colIdx, rowIndex) ? input.getFormulaValue?.(colIdx, rowIndex) : void 0;
|
|
1181
1193
|
let mode = "display";
|
|
1182
1194
|
let editorType;
|
|
1183
1195
|
if (isEditing && canEditInline) {
|
|
@@ -1209,7 +1221,8 @@ function computeCellDescriptor(item, col, rowIndex, colIdx, input) {
|
|
|
1209
1221
|
globalColIndex,
|
|
1210
1222
|
rowId,
|
|
1211
1223
|
rowIndex,
|
|
1212
|
-
displayValue: cellValue
|
|
1224
|
+
displayValue: formulaDisplay !== void 0 ? formulaDisplay : cellValue,
|
|
1225
|
+
columnType: col.type
|
|
1213
1226
|
};
|
|
1214
1227
|
}
|
|
1215
1228
|
function resolveCellDisplayContent(col, item, displayValue) {
|
|
@@ -1224,7 +1237,7 @@ function resolveCellDisplayContent(col, item, displayValue) {
|
|
|
1224
1237
|
if (displayValue == null) return null;
|
|
1225
1238
|
if (col.type === "date") {
|
|
1226
1239
|
const d = new Date(String(displayValue));
|
|
1227
|
-
if (!Number.isNaN(d.getTime())) return d.toLocaleDateString();
|
|
1240
|
+
if (!Number.isNaN(d.getTime())) return d.toLocaleDateString(void 0, { timeZone: "UTC" });
|
|
1228
1241
|
}
|
|
1229
1242
|
if (col.type === "boolean") {
|
|
1230
1243
|
return displayValue ? "True" : "False";
|
|
@@ -1342,6 +1355,8 @@ var CHECKBOX_COLUMN_WIDTH = 48;
|
|
|
1342
1355
|
var ROW_NUMBER_COLUMN_WIDTH = 50;
|
|
1343
1356
|
var DEFAULT_MIN_COLUMN_WIDTH = 80;
|
|
1344
1357
|
var CELL_PADDING = 16;
|
|
1358
|
+
var ROW_NUMBER_COLUMN_MIN_WIDTH = 30;
|
|
1359
|
+
var ROW_NUMBER_COLUMN_ID = "__row_number__";
|
|
1345
1360
|
var GRID_BORDER_RADIUS = 6;
|
|
1346
1361
|
var AUTOSIZE_EXTRA_PX = 16;
|
|
1347
1362
|
var AUTOSIZE_MAX_PX = 520;
|
|
@@ -1994,59 +2009,6 @@ function validateRowIds(items, getRowId) {
|
|
|
1994
2009
|
ids.add(id);
|
|
1995
2010
|
}
|
|
1996
2011
|
}
|
|
1997
|
-
var DEFAULT_DEBOUNCE_MS = 300;
|
|
1998
|
-
var PEOPLE_SEARCH_DEBOUNCE_MS = DEFAULT_DEBOUNCE_MS;
|
|
1999
|
-
var SIDEBAR_TRANSITION_MS = 300;
|
|
2000
|
-
var Z_INDEX = {
|
|
2001
|
-
/** Column resize drag handle */
|
|
2002
|
-
RESIZE_HANDLE: 1,
|
|
2003
|
-
/** Active/editing cell outline */
|
|
2004
|
-
ACTIVE_CELL: 2,
|
|
2005
|
-
/** Fill handle dot */
|
|
2006
|
-
FILL_HANDLE: 3,
|
|
2007
|
-
/** Selection range overlay (marching ants) */
|
|
2008
|
-
SELECTION_OVERLAY: 4,
|
|
2009
|
-
/** Row number column */
|
|
2010
|
-
ROW_NUMBER: 5,
|
|
2011
|
-
/** Clipboard overlay (copy/cut animation) */
|
|
2012
|
-
CLIPBOARD_OVERLAY: 5,
|
|
2013
|
-
/** Sticky pinned body cells */
|
|
2014
|
-
PINNED: 6,
|
|
2015
|
-
/** Selection checkbox column in body */
|
|
2016
|
-
SELECTION_CELL: 7,
|
|
2017
|
-
/** Sticky thead row */
|
|
2018
|
-
THEAD: 8,
|
|
2019
|
-
/** Pinned header cells (sticky both axes) */
|
|
2020
|
-
PINNED_HEADER: 10,
|
|
2021
|
-
/** Focused header cell */
|
|
2022
|
-
HEADER_FOCUS: 11,
|
|
2023
|
-
/** Checkbox column in sticky header (sticky both axes) */
|
|
2024
|
-
SELECTION_HEADER_PINNED: 12,
|
|
2025
|
-
/** Loading overlay within table */
|
|
2026
|
-
LOADING: 2,
|
|
2027
|
-
/** Column reorder drop indicator */
|
|
2028
|
-
DROP_INDICATOR: 100,
|
|
2029
|
-
/** Dropdown menus (column chooser, pagination size select) */
|
|
2030
|
-
DROPDOWN: 1e3,
|
|
2031
|
-
/** Filter popovers */
|
|
2032
|
-
FILTER_POPOVER: 1e3,
|
|
2033
|
-
/** Modal dialogs */
|
|
2034
|
-
MODAL: 2e3,
|
|
2035
|
-
/** Fullscreen grid container */
|
|
2036
|
-
FULLSCREEN: 9999,
|
|
2037
|
-
/** Context menus (right-click grid menu) */
|
|
2038
|
-
CONTEXT_MENU: 1e4
|
|
2039
|
-
};
|
|
2040
|
-
var REF_ERROR = new FormulaError("#REF!", "Invalid cell reference");
|
|
2041
|
-
var DIV_ZERO_ERROR = new FormulaError("#DIV/0!", "Division by zero");
|
|
2042
|
-
var VALUE_ERROR = new FormulaError("#VALUE!", "Wrong value type");
|
|
2043
|
-
var NAME_ERROR = new FormulaError("#NAME?", "Unknown function or name");
|
|
2044
|
-
var CIRC_ERROR = new FormulaError("#CIRC!", "Circular reference");
|
|
2045
|
-
var GENERAL_ERROR = new FormulaError("#ERROR!", "Formula error");
|
|
2046
|
-
var NA_ERROR = new FormulaError("#N/A", "No match found");
|
|
2047
|
-
function isFormulaError(value) {
|
|
2048
|
-
return value instanceof FormulaError;
|
|
2049
|
-
}
|
|
2050
2012
|
var CELL_REF_PATTERN = /^\$?[A-Za-z]+\$?\d+$/;
|
|
2051
2013
|
var SINGLE_CHAR_OPERATORS = {
|
|
2052
2014
|
"+": "PLUS",
|
|
@@ -2168,6 +2130,12 @@ function tokenize(input) {
|
|
|
2168
2130
|
while (pos < input.length && (input[pos] >= "A" && input[pos] <= "Z" || input[pos] >= "a" && input[pos] <= "z" || input[pos] >= "0" && input[pos] <= "9" || input[pos] === "$" || input[pos] === "_")) {
|
|
2169
2131
|
pos++;
|
|
2170
2132
|
}
|
|
2133
|
+
if (pos < input.length && input[pos] === "." && pos + 1 < input.length && (input[pos + 1] >= "A" && input[pos + 1] <= "Z" || input[pos + 1] >= "a" && input[pos + 1] <= "z")) {
|
|
2134
|
+
pos++;
|
|
2135
|
+
while (pos < input.length && (input[pos] >= "A" && input[pos] <= "Z" || input[pos] >= "a" && input[pos] <= "z" || input[pos] >= "0" && input[pos] <= "9" || input[pos] === "_")) {
|
|
2136
|
+
pos++;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2171
2139
|
const word = input.slice(start, pos);
|
|
2172
2140
|
if (pos < input.length && input[pos] === "!") {
|
|
2173
2141
|
pos++;
|
|
@@ -2195,6 +2163,190 @@ function tokenize(input) {
|
|
|
2195
2163
|
tokens.push({ type: "EOF", value: "", position: pos });
|
|
2196
2164
|
return tokens;
|
|
2197
2165
|
}
|
|
2166
|
+
var CELL_REF_RE2 = /^\$?([A-Za-z]+)\$?(\d+)$/;
|
|
2167
|
+
function parseCellRefCoords(ref) {
|
|
2168
|
+
const m = ref.match(CELL_REF_RE2);
|
|
2169
|
+
if (!m) return null;
|
|
2170
|
+
return { col: columnLetterToIndex(m[1]), row: parseInt(m[2], 10) - 1 };
|
|
2171
|
+
}
|
|
2172
|
+
function handleFormulaBarKeyDown(key, preventDefault, onCommit, onCancel) {
|
|
2173
|
+
if (key === "Enter") {
|
|
2174
|
+
preventDefault();
|
|
2175
|
+
onCommit();
|
|
2176
|
+
} else if (key === "Escape") {
|
|
2177
|
+
preventDefault();
|
|
2178
|
+
onCancel();
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
function processFormulaBarCommit(text, col, row, setFormula, onCellValueChanged) {
|
|
2182
|
+
const trimmed = text.trim();
|
|
2183
|
+
if (trimmed.startsWith("=")) {
|
|
2184
|
+
setFormula(col, row, trimmed);
|
|
2185
|
+
} else {
|
|
2186
|
+
setFormula(col, row, null);
|
|
2187
|
+
onCellValueChanged?.(col, row, trimmed);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function deriveFormulaBarText(col, row, getFormula, getRawValue) {
|
|
2191
|
+
if (col == null || row == null) return "";
|
|
2192
|
+
const formula = getFormula?.(col, row);
|
|
2193
|
+
if (formula) return formula;
|
|
2194
|
+
const raw = getRawValue?.(col, row);
|
|
2195
|
+
return raw != null ? String(raw) : "";
|
|
2196
|
+
}
|
|
2197
|
+
function extractFormulaReferences(formula) {
|
|
2198
|
+
if (!formula || formula[0] !== "=") return [];
|
|
2199
|
+
const refs = [];
|
|
2200
|
+
let colorIdx = 0;
|
|
2201
|
+
try {
|
|
2202
|
+
const tokens = tokenize(formula.substring(1));
|
|
2203
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2204
|
+
const tok = tokens[i];
|
|
2205
|
+
if (tok.type === "CELL_REF") {
|
|
2206
|
+
if (i + 2 < tokens.length && tokens[i + 1].type === "COLON" && tokens[i + 2].type === "CELL_REF") {
|
|
2207
|
+
const start = parseCellRefCoords(tok.value);
|
|
2208
|
+
const end = parseCellRefCoords(tokens[i + 2].value);
|
|
2209
|
+
if (start && end) {
|
|
2210
|
+
refs.push({
|
|
2211
|
+
type: "range",
|
|
2212
|
+
col: start.col,
|
|
2213
|
+
row: start.row,
|
|
2214
|
+
endCol: end.col,
|
|
2215
|
+
endRow: end.row,
|
|
2216
|
+
colorIndex: colorIdx++ % 6
|
|
2217
|
+
});
|
|
2218
|
+
i += 2;
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
const coords = parseCellRefCoords(tok.value);
|
|
2223
|
+
if (coords) {
|
|
2224
|
+
refs.push({
|
|
2225
|
+
type: "cell",
|
|
2226
|
+
col: coords.col,
|
|
2227
|
+
row: coords.row,
|
|
2228
|
+
colorIndex: colorIdx++ % 6
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
} catch {
|
|
2234
|
+
}
|
|
2235
|
+
return refs;
|
|
2236
|
+
}
|
|
2237
|
+
var DEFAULT_DEBOUNCE_MS = 300;
|
|
2238
|
+
var PEOPLE_SEARCH_DEBOUNCE_MS = DEFAULT_DEBOUNCE_MS;
|
|
2239
|
+
var SIDEBAR_TRANSITION_MS = 300;
|
|
2240
|
+
var Z_INDEX = {
|
|
2241
|
+
/** Column resize drag handle */
|
|
2242
|
+
RESIZE_HANDLE: 1,
|
|
2243
|
+
/** Active/editing cell outline */
|
|
2244
|
+
ACTIVE_CELL: 2,
|
|
2245
|
+
/** Fill handle dot */
|
|
2246
|
+
FILL_HANDLE: 3,
|
|
2247
|
+
/** Selection range overlay (marching ants) */
|
|
2248
|
+
SELECTION_OVERLAY: 4,
|
|
2249
|
+
/** Row number column */
|
|
2250
|
+
ROW_NUMBER: 5,
|
|
2251
|
+
/** Clipboard overlay (copy/cut animation) */
|
|
2252
|
+
CLIPBOARD_OVERLAY: 5,
|
|
2253
|
+
/** Sticky pinned body cells */
|
|
2254
|
+
PINNED: 6,
|
|
2255
|
+
/** Selection checkbox column in body */
|
|
2256
|
+
SELECTION_CELL: 7,
|
|
2257
|
+
/** Sticky thead row */
|
|
2258
|
+
THEAD: 8,
|
|
2259
|
+
/** Pinned header cells (sticky both axes) */
|
|
2260
|
+
PINNED_HEADER: 10,
|
|
2261
|
+
/** Focused header cell */
|
|
2262
|
+
HEADER_FOCUS: 11,
|
|
2263
|
+
/** Checkbox column in sticky header (sticky both axes) */
|
|
2264
|
+
SELECTION_HEADER_PINNED: 12,
|
|
2265
|
+
/** Loading overlay within table */
|
|
2266
|
+
LOADING: 2,
|
|
2267
|
+
/** Column reorder drop indicator */
|
|
2268
|
+
DROP_INDICATOR: 100,
|
|
2269
|
+
/** Dropdown menus (column chooser, pagination size select) */
|
|
2270
|
+
DROPDOWN: 1e3,
|
|
2271
|
+
/** Filter popovers */
|
|
2272
|
+
FILTER_POPOVER: 1e3,
|
|
2273
|
+
/** Modal dialogs */
|
|
2274
|
+
MODAL: 2e3,
|
|
2275
|
+
/** Fullscreen grid container */
|
|
2276
|
+
FULLSCREEN: 9999,
|
|
2277
|
+
/** Context menus (right-click grid menu) */
|
|
2278
|
+
CONTEXT_MENU: 1e4
|
|
2279
|
+
};
|
|
2280
|
+
var FORMULA_REF_COLORS = [
|
|
2281
|
+
"var(--ogrid-formula-ref-0, #4285f4)",
|
|
2282
|
+
"var(--ogrid-formula-ref-1, #ea4335)",
|
|
2283
|
+
"var(--ogrid-formula-ref-2, #34a853)",
|
|
2284
|
+
"var(--ogrid-formula-ref-3, #9334e6)",
|
|
2285
|
+
"var(--ogrid-formula-ref-4, #ff6d01)",
|
|
2286
|
+
"var(--ogrid-formula-ref-5, #46bdc6)"
|
|
2287
|
+
];
|
|
2288
|
+
var FORMULA_BAR_CSS = {
|
|
2289
|
+
bar: "display:flex;align-items:center;border-bottom:1px solid var(--ogrid-border, #e0e0e0);background:var(--ogrid-bg, #fff);min-height:28px;font-size:13px;",
|
|
2290
|
+
nameBox: "font-family:monospace;font-size:12px;font-weight:500;padding:2px 8px;border-right:1px solid var(--ogrid-border, #e0e0e0);background:var(--ogrid-bg, #fff);color:var(--ogrid-fg, #242424);min-width:52px;text-align:center;line-height:24px;user-select:none;white-space:nowrap;",
|
|
2291
|
+
fxLabel: "padding:2px 8px;font-style:italic;font-weight:600;color:var(--ogrid-muted-fg, #888);user-select:none;border-right:1px solid var(--ogrid-border, #e0e0e0);line-height:24px;font-size:12px;",
|
|
2292
|
+
input: "flex:1;border:none;outline:none;padding:2px 8px;font-family:monospace;font-size:12px;line-height:24px;background:transparent;color:var(--ogrid-fg, #242424);min-width:0;"
|
|
2293
|
+
};
|
|
2294
|
+
var FORMULA_BAR_STYLES = {
|
|
2295
|
+
bar: {
|
|
2296
|
+
display: "flex",
|
|
2297
|
+
alignItems: "center",
|
|
2298
|
+
borderBottom: "1px solid var(--ogrid-border, #e0e0e0)",
|
|
2299
|
+
background: "var(--ogrid-bg, #fff)",
|
|
2300
|
+
minHeight: "28px",
|
|
2301
|
+
fontSize: "13px"
|
|
2302
|
+
},
|
|
2303
|
+
nameBox: {
|
|
2304
|
+
fontFamily: "monospace",
|
|
2305
|
+
fontSize: "12px",
|
|
2306
|
+
fontWeight: 500,
|
|
2307
|
+
padding: "2px 8px",
|
|
2308
|
+
borderRight: "1px solid var(--ogrid-border, #e0e0e0)",
|
|
2309
|
+
background: "var(--ogrid-bg, #fff)",
|
|
2310
|
+
color: "var(--ogrid-fg, #242424)",
|
|
2311
|
+
minWidth: "52px",
|
|
2312
|
+
textAlign: "center",
|
|
2313
|
+
lineHeight: "24px",
|
|
2314
|
+
userSelect: "none",
|
|
2315
|
+
whiteSpace: "nowrap"
|
|
2316
|
+
},
|
|
2317
|
+
fxLabel: {
|
|
2318
|
+
padding: "2px 8px",
|
|
2319
|
+
fontStyle: "italic",
|
|
2320
|
+
fontWeight: 600,
|
|
2321
|
+
color: "var(--ogrid-muted-fg, #888)",
|
|
2322
|
+
userSelect: "none",
|
|
2323
|
+
borderRight: "1px solid var(--ogrid-border, #e0e0e0)",
|
|
2324
|
+
lineHeight: "24px",
|
|
2325
|
+
fontSize: "12px"
|
|
2326
|
+
},
|
|
2327
|
+
input: {
|
|
2328
|
+
flex: 1,
|
|
2329
|
+
border: "none",
|
|
2330
|
+
outline: "none",
|
|
2331
|
+
padding: "2px 8px",
|
|
2332
|
+
fontFamily: "monospace",
|
|
2333
|
+
fontSize: "12px",
|
|
2334
|
+
lineHeight: "24px",
|
|
2335
|
+
background: "transparent",
|
|
2336
|
+
color: "var(--ogrid-fg, #242424)",
|
|
2337
|
+
minWidth: 0
|
|
2338
|
+
}
|
|
2339
|
+
};
|
|
2340
|
+
var REF_ERROR = new FormulaError("#REF!", "Invalid cell reference");
|
|
2341
|
+
var DIV_ZERO_ERROR = new FormulaError("#DIV/0!", "Division by zero");
|
|
2342
|
+
var VALUE_ERROR = new FormulaError("#VALUE!", "Wrong value type");
|
|
2343
|
+
var NAME_ERROR = new FormulaError("#NAME?", "Unknown function or name");
|
|
2344
|
+
var CIRC_ERROR = new FormulaError("#CIRC!", "Circular reference");
|
|
2345
|
+
var GENERAL_ERROR = new FormulaError("#ERROR!", "Formula error");
|
|
2346
|
+
var NA_ERROR = new FormulaError("#N/A", "No match found");
|
|
2347
|
+
function isFormulaError(value) {
|
|
2348
|
+
return value instanceof FormulaError;
|
|
2349
|
+
}
|
|
2198
2350
|
function parse(tokens, namedRanges) {
|
|
2199
2351
|
let pos = 0;
|
|
2200
2352
|
function peek() {
|
|
@@ -2839,7 +2991,7 @@ var DependencyGraph = class {
|
|
|
2839
2991
|
if (cellDependents) {
|
|
2840
2992
|
for (const dependent of cellDependents) {
|
|
2841
2993
|
if (affected.has(dependent)) {
|
|
2842
|
-
const newDegree = inDegree.get(dependent) - 1;
|
|
2994
|
+
const newDegree = (inDegree.get(dependent) ?? 0) - 1;
|
|
2843
2995
|
inDegree.set(dependent, newDegree);
|
|
2844
2996
|
if (newDegree === 0) {
|
|
2845
2997
|
queue.push(dependent);
|
|
@@ -3149,7 +3301,7 @@ function registerMathFunctions(registry) {
|
|
|
3149
3301
|
registry.set("SUMPRODUCT", {
|
|
3150
3302
|
minArgs: 1,
|
|
3151
3303
|
maxArgs: -1,
|
|
3152
|
-
evaluate(args, context,
|
|
3304
|
+
evaluate(args, context, _evaluator) {
|
|
3153
3305
|
const arrays = [];
|
|
3154
3306
|
for (const arg of args) {
|
|
3155
3307
|
if (arg.kind !== "range") {
|
|
@@ -3368,6 +3520,162 @@ function registerMathFunctions(registry) {
|
|
|
3368
3520
|
return Math.floor(Math.random() * (hi - lo + 1)) + lo;
|
|
3369
3521
|
}
|
|
3370
3522
|
});
|
|
3523
|
+
registry.set("MROUND", {
|
|
3524
|
+
minArgs: 2,
|
|
3525
|
+
maxArgs: 2,
|
|
3526
|
+
evaluate(args, context, evaluator) {
|
|
3527
|
+
const rawNum = evaluator.evaluate(args[0], context);
|
|
3528
|
+
if (rawNum instanceof FormulaError) return rawNum;
|
|
3529
|
+
const num = toNumber(rawNum);
|
|
3530
|
+
if (num instanceof FormulaError) return num;
|
|
3531
|
+
const rawMul = evaluator.evaluate(args[1], context);
|
|
3532
|
+
if (rawMul instanceof FormulaError) return rawMul;
|
|
3533
|
+
const multiple = toNumber(rawMul);
|
|
3534
|
+
if (multiple instanceof FormulaError) return multiple;
|
|
3535
|
+
if (multiple === 0) return 0;
|
|
3536
|
+
if (num > 0 && multiple < 0 || num < 0 && multiple > 0) {
|
|
3537
|
+
return new FormulaError("#NUM!", "MROUND: number and multiple must have the same sign");
|
|
3538
|
+
}
|
|
3539
|
+
return Math.round(num / multiple) * multiple;
|
|
3540
|
+
}
|
|
3541
|
+
});
|
|
3542
|
+
registry.set("QUOTIENT", {
|
|
3543
|
+
minArgs: 2,
|
|
3544
|
+
maxArgs: 2,
|
|
3545
|
+
evaluate(args, context, evaluator) {
|
|
3546
|
+
const rawNum = evaluator.evaluate(args[0], context);
|
|
3547
|
+
if (rawNum instanceof FormulaError) return rawNum;
|
|
3548
|
+
const num = toNumber(rawNum);
|
|
3549
|
+
if (num instanceof FormulaError) return num;
|
|
3550
|
+
const rawDen = evaluator.evaluate(args[1], context);
|
|
3551
|
+
if (rawDen instanceof FormulaError) return rawDen;
|
|
3552
|
+
const den = toNumber(rawDen);
|
|
3553
|
+
if (den instanceof FormulaError) return den;
|
|
3554
|
+
if (den === 0) return new FormulaError("#DIV/0!", "QUOTIENT: division by zero");
|
|
3555
|
+
return Math.trunc(num / den);
|
|
3556
|
+
}
|
|
3557
|
+
});
|
|
3558
|
+
registry.set("COMBIN", {
|
|
3559
|
+
minArgs: 2,
|
|
3560
|
+
maxArgs: 2,
|
|
3561
|
+
evaluate(args, context, evaluator) {
|
|
3562
|
+
const rawN = evaluator.evaluate(args[0], context);
|
|
3563
|
+
if (rawN instanceof FormulaError) return rawN;
|
|
3564
|
+
const n = toNumber(rawN);
|
|
3565
|
+
if (n instanceof FormulaError) return n;
|
|
3566
|
+
const rawK = evaluator.evaluate(args[1], context);
|
|
3567
|
+
if (rawK instanceof FormulaError) return rawK;
|
|
3568
|
+
const k = toNumber(rawK);
|
|
3569
|
+
if (k instanceof FormulaError) return k;
|
|
3570
|
+
const ni = Math.trunc(n);
|
|
3571
|
+
const ki = Math.trunc(k);
|
|
3572
|
+
if (ni < 0 || ki < 0) return new FormulaError("#NUM!", "COMBIN: n and k must be non-negative");
|
|
3573
|
+
if (ki > ni) return new FormulaError("#NUM!", "COMBIN: k must be <= n");
|
|
3574
|
+
if (ki === 0 || ki === ni) return 1;
|
|
3575
|
+
const kk = Math.min(ki, ni - ki);
|
|
3576
|
+
let result = 1;
|
|
3577
|
+
for (let i = 0; i < kk; i++) {
|
|
3578
|
+
result = result * (ni - i) / (i + 1);
|
|
3579
|
+
}
|
|
3580
|
+
return Math.round(result);
|
|
3581
|
+
}
|
|
3582
|
+
});
|
|
3583
|
+
registry.set("PERMUT", {
|
|
3584
|
+
minArgs: 2,
|
|
3585
|
+
maxArgs: 2,
|
|
3586
|
+
evaluate(args, context, evaluator) {
|
|
3587
|
+
const rawN = evaluator.evaluate(args[0], context);
|
|
3588
|
+
if (rawN instanceof FormulaError) return rawN;
|
|
3589
|
+
const n = toNumber(rawN);
|
|
3590
|
+
if (n instanceof FormulaError) return n;
|
|
3591
|
+
const rawK = evaluator.evaluate(args[1], context);
|
|
3592
|
+
if (rawK instanceof FormulaError) return rawK;
|
|
3593
|
+
const k = toNumber(rawK);
|
|
3594
|
+
if (k instanceof FormulaError) return k;
|
|
3595
|
+
const ni = Math.trunc(n);
|
|
3596
|
+
const ki = Math.trunc(k);
|
|
3597
|
+
if (ni < 0 || ki < 0) return new FormulaError("#NUM!", "PERMUT: n and k must be non-negative");
|
|
3598
|
+
if (ki > ni) return new FormulaError("#NUM!", "PERMUT: k must be <= n");
|
|
3599
|
+
let result = 1;
|
|
3600
|
+
for (let i = 0; i < ki; i++) {
|
|
3601
|
+
result *= ni - i;
|
|
3602
|
+
}
|
|
3603
|
+
return result;
|
|
3604
|
+
}
|
|
3605
|
+
});
|
|
3606
|
+
registry.set("FACT", {
|
|
3607
|
+
minArgs: 1,
|
|
3608
|
+
maxArgs: 1,
|
|
3609
|
+
evaluate(args, context, evaluator) {
|
|
3610
|
+
const rawVal = evaluator.evaluate(args[0], context);
|
|
3611
|
+
if (rawVal instanceof FormulaError) return rawVal;
|
|
3612
|
+
const num = toNumber(rawVal);
|
|
3613
|
+
if (num instanceof FormulaError) return num;
|
|
3614
|
+
const n = Math.trunc(num);
|
|
3615
|
+
if (n < 0) return new FormulaError("#NUM!", "FACT: argument must be non-negative");
|
|
3616
|
+
if (n > 170) return new FormulaError("#NUM!", "FACT: argument too large (>170)");
|
|
3617
|
+
let result = 1;
|
|
3618
|
+
for (let i = 2; i <= n; i++) {
|
|
3619
|
+
result *= i;
|
|
3620
|
+
}
|
|
3621
|
+
return result;
|
|
3622
|
+
}
|
|
3623
|
+
});
|
|
3624
|
+
registry.set("GCD", {
|
|
3625
|
+
minArgs: 1,
|
|
3626
|
+
maxArgs: -1,
|
|
3627
|
+
evaluate(args, context, evaluator) {
|
|
3628
|
+
const nums = [];
|
|
3629
|
+
for (const arg of args) {
|
|
3630
|
+
const rawVal = evaluator.evaluate(arg, context);
|
|
3631
|
+
if (rawVal instanceof FormulaError) return rawVal;
|
|
3632
|
+
const v = toNumber(rawVal);
|
|
3633
|
+
if (v instanceof FormulaError) return v;
|
|
3634
|
+
const n = Math.trunc(Math.abs(v));
|
|
3635
|
+
nums.push(n);
|
|
3636
|
+
}
|
|
3637
|
+
if (nums.length === 0) return new FormulaError("#NUM!", "GCD: no arguments");
|
|
3638
|
+
let result = nums[0];
|
|
3639
|
+
for (let i = 1; i < nums.length; i++) {
|
|
3640
|
+
result = gcdTwo(result, nums[i]);
|
|
3641
|
+
}
|
|
3642
|
+
return result;
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3645
|
+
registry.set("LCM", {
|
|
3646
|
+
minArgs: 1,
|
|
3647
|
+
maxArgs: -1,
|
|
3648
|
+
evaluate(args, context, evaluator) {
|
|
3649
|
+
const nums = [];
|
|
3650
|
+
for (const arg of args) {
|
|
3651
|
+
const rawVal = evaluator.evaluate(arg, context);
|
|
3652
|
+
if (rawVal instanceof FormulaError) return rawVal;
|
|
3653
|
+
const v = toNumber(rawVal);
|
|
3654
|
+
if (v instanceof FormulaError) return v;
|
|
3655
|
+
const n = Math.trunc(Math.abs(v));
|
|
3656
|
+
nums.push(n);
|
|
3657
|
+
}
|
|
3658
|
+
if (nums.length === 0) return new FormulaError("#NUM!", "LCM: no arguments");
|
|
3659
|
+
let result = nums[0];
|
|
3660
|
+
for (let i = 1; i < nums.length; i++) {
|
|
3661
|
+
const g = gcdTwo(result, nums[i]);
|
|
3662
|
+
if (g === 0) {
|
|
3663
|
+
result = 0;
|
|
3664
|
+
break;
|
|
3665
|
+
}
|
|
3666
|
+
result = result / g * nums[i];
|
|
3667
|
+
}
|
|
3668
|
+
return result;
|
|
3669
|
+
}
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
function gcdTwo(a, b) {
|
|
3673
|
+
while (b !== 0) {
|
|
3674
|
+
const t = b;
|
|
3675
|
+
b = a % b;
|
|
3676
|
+
a = t;
|
|
3677
|
+
}
|
|
3678
|
+
return a;
|
|
3371
3679
|
}
|
|
3372
3680
|
function flattenArgs2(args, context, evaluator) {
|
|
3373
3681
|
const result = [];
|
|
@@ -4202,48 +4510,194 @@ function registerTextFunctions(registry) {
|
|
|
4202
4510
|
return parts.join(delimiter);
|
|
4203
4511
|
}
|
|
4204
4512
|
});
|
|
4205
|
-
|
|
4206
|
-
function toDate(val) {
|
|
4207
|
-
if (val instanceof FormulaError) return val;
|
|
4208
|
-
if (val instanceof Date) {
|
|
4209
|
-
if (isNaN(val.getTime())) return new FormulaError("#VALUE!", "Invalid date");
|
|
4210
|
-
return val;
|
|
4211
|
-
}
|
|
4212
|
-
if (typeof val === "string") {
|
|
4213
|
-
const d = new Date(val);
|
|
4214
|
-
if (isNaN(d.getTime())) return new FormulaError("#VALUE!", `Cannot parse "${val}" as date`);
|
|
4215
|
-
return d;
|
|
4216
|
-
}
|
|
4217
|
-
if (typeof val === "number") {
|
|
4218
|
-
const d = new Date(val);
|
|
4219
|
-
if (isNaN(d.getTime())) return new FormulaError("#VALUE!", "Invalid numeric date");
|
|
4220
|
-
return d;
|
|
4221
|
-
}
|
|
4222
|
-
return new FormulaError("#VALUE!", "Cannot convert value to date");
|
|
4223
|
-
}
|
|
4224
|
-
function registerDateFunctions(registry) {
|
|
4225
|
-
registry.set("TODAY", {
|
|
4226
|
-
minArgs: 0,
|
|
4227
|
-
maxArgs: 0,
|
|
4228
|
-
evaluate(_args, context) {
|
|
4229
|
-
const now = context.now();
|
|
4230
|
-
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
4231
|
-
}
|
|
4232
|
-
});
|
|
4233
|
-
registry.set("NOW", {
|
|
4234
|
-
minArgs: 0,
|
|
4235
|
-
maxArgs: 0,
|
|
4236
|
-
evaluate(_args, context) {
|
|
4237
|
-
return context.now();
|
|
4238
|
-
}
|
|
4239
|
-
});
|
|
4240
|
-
registry.set("YEAR", {
|
|
4513
|
+
registry.set("DOLLAR", {
|
|
4241
4514
|
minArgs: 1,
|
|
4242
|
-
maxArgs:
|
|
4515
|
+
maxArgs: 2,
|
|
4243
4516
|
evaluate(args, context, evaluator) {
|
|
4244
|
-
const
|
|
4245
|
-
if (
|
|
4246
|
-
const
|
|
4517
|
+
const rawNum = evaluator.evaluate(args[0], context);
|
|
4518
|
+
if (rawNum instanceof FormulaError) return rawNum;
|
|
4519
|
+
const num = toNumber(rawNum);
|
|
4520
|
+
if (num instanceof FormulaError) return num;
|
|
4521
|
+
let decimals = 2;
|
|
4522
|
+
if (args.length >= 2) {
|
|
4523
|
+
const rawDec = evaluator.evaluate(args[1], context);
|
|
4524
|
+
if (rawDec instanceof FormulaError) return rawDec;
|
|
4525
|
+
const d = toNumber(rawDec);
|
|
4526
|
+
if (d instanceof FormulaError) return d;
|
|
4527
|
+
decimals = Math.trunc(d);
|
|
4528
|
+
}
|
|
4529
|
+
const absNum = Math.abs(num);
|
|
4530
|
+
const rounded = decimals >= 0 ? absNum.toFixed(decimals) : (Math.round(absNum / Math.pow(10, -decimals)) * Math.pow(10, -decimals)).toFixed(0);
|
|
4531
|
+
const [intPart, decPart] = rounded.split(".");
|
|
4532
|
+
const withCommas = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
4533
|
+
const formatted = decPart !== void 0 ? `${withCommas}.${decPart}` : withCommas;
|
|
4534
|
+
return num < 0 ? `($${formatted})` : `$${formatted}`;
|
|
4535
|
+
}
|
|
4536
|
+
});
|
|
4537
|
+
registry.set("FIXED", {
|
|
4538
|
+
minArgs: 1,
|
|
4539
|
+
maxArgs: 3,
|
|
4540
|
+
evaluate(args, context, evaluator) {
|
|
4541
|
+
const rawNum = evaluator.evaluate(args[0], context);
|
|
4542
|
+
if (rawNum instanceof FormulaError) return rawNum;
|
|
4543
|
+
const num = toNumber(rawNum);
|
|
4544
|
+
if (num instanceof FormulaError) return num;
|
|
4545
|
+
let decimals = 2;
|
|
4546
|
+
if (args.length >= 2) {
|
|
4547
|
+
const rawDec = evaluator.evaluate(args[1], context);
|
|
4548
|
+
if (rawDec instanceof FormulaError) return rawDec;
|
|
4549
|
+
const d = toNumber(rawDec);
|
|
4550
|
+
if (d instanceof FormulaError) return d;
|
|
4551
|
+
decimals = Math.trunc(d);
|
|
4552
|
+
}
|
|
4553
|
+
let noCommas = false;
|
|
4554
|
+
if (args.length >= 3) {
|
|
4555
|
+
const rawNoCommas = evaluator.evaluate(args[2], context);
|
|
4556
|
+
if (rawNoCommas instanceof FormulaError) return rawNoCommas;
|
|
4557
|
+
noCommas = !!rawNoCommas;
|
|
4558
|
+
}
|
|
4559
|
+
const absNum = Math.abs(num);
|
|
4560
|
+
const rounded = decimals >= 0 ? absNum.toFixed(decimals) : (Math.round(absNum / Math.pow(10, -decimals)) * Math.pow(10, -decimals)).toFixed(0);
|
|
4561
|
+
if (noCommas) {
|
|
4562
|
+
return num < 0 ? `-${rounded}` : rounded;
|
|
4563
|
+
}
|
|
4564
|
+
const [intPart, decPart] = rounded.split(".");
|
|
4565
|
+
const withCommas = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
4566
|
+
const formatted = decPart !== void 0 ? `${withCommas}.${decPart}` : withCommas;
|
|
4567
|
+
return num < 0 ? `-${formatted}` : formatted;
|
|
4568
|
+
}
|
|
4569
|
+
});
|
|
4570
|
+
registry.set("T", {
|
|
4571
|
+
minArgs: 1,
|
|
4572
|
+
maxArgs: 1,
|
|
4573
|
+
evaluate(args, context, evaluator) {
|
|
4574
|
+
const val = evaluator.evaluate(args[0], context);
|
|
4575
|
+
if (val instanceof FormulaError) return val;
|
|
4576
|
+
return typeof val === "string" ? val : "";
|
|
4577
|
+
}
|
|
4578
|
+
});
|
|
4579
|
+
registry.set("N", {
|
|
4580
|
+
minArgs: 1,
|
|
4581
|
+
maxArgs: 1,
|
|
4582
|
+
evaluate(args, context, evaluator) {
|
|
4583
|
+
const val = evaluator.evaluate(args[0], context);
|
|
4584
|
+
if (val instanceof FormulaError) return val;
|
|
4585
|
+
if (typeof val === "number") return val;
|
|
4586
|
+
if (typeof val === "boolean") return val ? 1 : 0;
|
|
4587
|
+
if (val instanceof Date) return val.getTime();
|
|
4588
|
+
return 0;
|
|
4589
|
+
}
|
|
4590
|
+
});
|
|
4591
|
+
registry.set("FORMULATEXT", {
|
|
4592
|
+
minArgs: 1,
|
|
4593
|
+
maxArgs: 1,
|
|
4594
|
+
evaluate(args, context, _evaluator) {
|
|
4595
|
+
const arg = args[0];
|
|
4596
|
+
if (arg.kind !== "cellRef") {
|
|
4597
|
+
return new FormulaError("#N/A", "FORMULATEXT requires a cell reference");
|
|
4598
|
+
}
|
|
4599
|
+
if (!context.getCellFormula) {
|
|
4600
|
+
return new FormulaError("#N/A", "FORMULATEXT not supported in this context");
|
|
4601
|
+
}
|
|
4602
|
+
const formula = context.getCellFormula(arg.address);
|
|
4603
|
+
if (formula === void 0) {
|
|
4604
|
+
return new FormulaError("#N/A", "Cell does not contain a formula");
|
|
4605
|
+
}
|
|
4606
|
+
return formula;
|
|
4607
|
+
}
|
|
4608
|
+
});
|
|
4609
|
+
registry.set("NUMBERVALUE", {
|
|
4610
|
+
minArgs: 1,
|
|
4611
|
+
maxArgs: 3,
|
|
4612
|
+
evaluate(args, context, evaluator) {
|
|
4613
|
+
const rawText = evaluator.evaluate(args[0], context);
|
|
4614
|
+
if (rawText instanceof FormulaError) return rawText;
|
|
4615
|
+
if (typeof rawText === "number") return rawText;
|
|
4616
|
+
let text = toString(rawText).trim();
|
|
4617
|
+
let decimalSep = ".";
|
|
4618
|
+
let groupSep = ",";
|
|
4619
|
+
const hasDecimalArg = args.length >= 2;
|
|
4620
|
+
const hasGroupArg = args.length >= 3;
|
|
4621
|
+
if (hasDecimalArg) {
|
|
4622
|
+
const rawDec = evaluator.evaluate(args[1], context);
|
|
4623
|
+
if (rawDec instanceof FormulaError) return rawDec;
|
|
4624
|
+
decimalSep = toString(rawDec);
|
|
4625
|
+
if (decimalSep.length !== 1) return new FormulaError("#VALUE!", "NUMBERVALUE decimal separator must be 1 character");
|
|
4626
|
+
if (!hasGroupArg) {
|
|
4627
|
+
groupSep = decimalSep === "," ? "." : ",";
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
if (hasGroupArg) {
|
|
4631
|
+
const rawGrp = evaluator.evaluate(args[2], context);
|
|
4632
|
+
if (rawGrp instanceof FormulaError) return rawGrp;
|
|
4633
|
+
groupSep = toString(rawGrp);
|
|
4634
|
+
if (groupSep.length !== 1) return new FormulaError("#VALUE!", "NUMBERVALUE group separator must be 1 character");
|
|
4635
|
+
}
|
|
4636
|
+
if (decimalSep === groupSep) return new FormulaError("#VALUE!", "NUMBERVALUE separators must be different");
|
|
4637
|
+
const isPercent = text.endsWith("%");
|
|
4638
|
+
if (isPercent) text = text.slice(0, -1).trim();
|
|
4639
|
+
const escapedGroup = groupSep.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4640
|
+
text = text.replace(new RegExp(escapedGroup, "g"), "");
|
|
4641
|
+
if (decimalSep !== ".") {
|
|
4642
|
+
const escapedDec = decimalSep.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4643
|
+
text = text.replace(new RegExp(escapedDec), ".");
|
|
4644
|
+
}
|
|
4645
|
+
const n = Number(text);
|
|
4646
|
+
if (isNaN(n)) return new FormulaError("#VALUE!", `NUMBERVALUE cannot parse "${toString(rawText)}"`);
|
|
4647
|
+
return isPercent ? n / 100 : n;
|
|
4648
|
+
}
|
|
4649
|
+
});
|
|
4650
|
+
registry.set("PHONETIC", {
|
|
4651
|
+
minArgs: 1,
|
|
4652
|
+
maxArgs: 1,
|
|
4653
|
+
evaluate(args, context, evaluator) {
|
|
4654
|
+
const val = evaluator.evaluate(args[0], context);
|
|
4655
|
+
if (val instanceof FormulaError) return val;
|
|
4656
|
+
return toString(val);
|
|
4657
|
+
}
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
function toDate(val) {
|
|
4661
|
+
if (val instanceof FormulaError) return val;
|
|
4662
|
+
if (val instanceof Date) {
|
|
4663
|
+
if (isNaN(val.getTime())) return new FormulaError("#VALUE!", "Invalid date");
|
|
4664
|
+
return val;
|
|
4665
|
+
}
|
|
4666
|
+
if (typeof val === "string") {
|
|
4667
|
+
const d = new Date(val);
|
|
4668
|
+
if (isNaN(d.getTime())) return new FormulaError("#VALUE!", `Cannot parse "${val}" as date`);
|
|
4669
|
+
return d;
|
|
4670
|
+
}
|
|
4671
|
+
if (typeof val === "number") {
|
|
4672
|
+
const d = new Date(val);
|
|
4673
|
+
if (isNaN(d.getTime())) return new FormulaError("#VALUE!", "Invalid numeric date");
|
|
4674
|
+
return d;
|
|
4675
|
+
}
|
|
4676
|
+
return new FormulaError("#VALUE!", "Cannot convert value to date");
|
|
4677
|
+
}
|
|
4678
|
+
function registerDateFunctions(registry) {
|
|
4679
|
+
registry.set("TODAY", {
|
|
4680
|
+
minArgs: 0,
|
|
4681
|
+
maxArgs: 0,
|
|
4682
|
+
evaluate(_args, context) {
|
|
4683
|
+
const now = context.now();
|
|
4684
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
4685
|
+
}
|
|
4686
|
+
});
|
|
4687
|
+
registry.set("NOW", {
|
|
4688
|
+
minArgs: 0,
|
|
4689
|
+
maxArgs: 0,
|
|
4690
|
+
evaluate(_args, context) {
|
|
4691
|
+
return context.now();
|
|
4692
|
+
}
|
|
4693
|
+
});
|
|
4694
|
+
registry.set("YEAR", {
|
|
4695
|
+
minArgs: 1,
|
|
4696
|
+
maxArgs: 1,
|
|
4697
|
+
evaluate(args, context, evaluator) {
|
|
4698
|
+
const val = evaluator.evaluate(args[0], context);
|
|
4699
|
+
if (val instanceof FormulaError) return val;
|
|
4700
|
+
const date = toDate(val);
|
|
4247
4701
|
if (date instanceof FormulaError) return date;
|
|
4248
4702
|
return date.getFullYear();
|
|
4249
4703
|
}
|
|
@@ -4449,6 +4903,311 @@ function registerDateFunctions(registry) {
|
|
|
4449
4903
|
return count * sign;
|
|
4450
4904
|
}
|
|
4451
4905
|
});
|
|
4906
|
+
registry.set("DAYS", {
|
|
4907
|
+
minArgs: 2,
|
|
4908
|
+
maxArgs: 2,
|
|
4909
|
+
evaluate(args, context, evaluator) {
|
|
4910
|
+
const rawEnd = evaluator.evaluate(args[0], context);
|
|
4911
|
+
if (rawEnd instanceof FormulaError) return rawEnd;
|
|
4912
|
+
const endDate = toDate(rawEnd);
|
|
4913
|
+
if (endDate instanceof FormulaError) return endDate;
|
|
4914
|
+
const rawStart = evaluator.evaluate(args[1], context);
|
|
4915
|
+
if (rawStart instanceof FormulaError) return rawStart;
|
|
4916
|
+
const startDate = toDate(rawStart);
|
|
4917
|
+
if (startDate instanceof FormulaError) return startDate;
|
|
4918
|
+
const endMs = Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
|
4919
|
+
const startMs = Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
|
4920
|
+
return Math.round((endMs - startMs) / 864e5);
|
|
4921
|
+
}
|
|
4922
|
+
});
|
|
4923
|
+
registry.set("DAYS360", {
|
|
4924
|
+
minArgs: 2,
|
|
4925
|
+
maxArgs: 3,
|
|
4926
|
+
evaluate(args, context, evaluator) {
|
|
4927
|
+
const rawStart = evaluator.evaluate(args[0], context);
|
|
4928
|
+
if (rawStart instanceof FormulaError) return rawStart;
|
|
4929
|
+
const startDate = toDate(rawStart);
|
|
4930
|
+
if (startDate instanceof FormulaError) return startDate;
|
|
4931
|
+
const rawEnd = evaluator.evaluate(args[1], context);
|
|
4932
|
+
if (rawEnd instanceof FormulaError) return rawEnd;
|
|
4933
|
+
const endDate = toDate(rawEnd);
|
|
4934
|
+
if (endDate instanceof FormulaError) return endDate;
|
|
4935
|
+
let method = false;
|
|
4936
|
+
if (args.length >= 3) {
|
|
4937
|
+
const rawMethod = evaluator.evaluate(args[2], context);
|
|
4938
|
+
if (rawMethod instanceof FormulaError) return rawMethod;
|
|
4939
|
+
method = !!rawMethod;
|
|
4940
|
+
}
|
|
4941
|
+
const sm = startDate.getMonth() + 1;
|
|
4942
|
+
const em = endDate.getMonth() + 1;
|
|
4943
|
+
let sd = startDate.getDate();
|
|
4944
|
+
let ed = endDate.getDate();
|
|
4945
|
+
const sy = startDate.getFullYear();
|
|
4946
|
+
const ey = endDate.getFullYear();
|
|
4947
|
+
if (!method) {
|
|
4948
|
+
if (sd === 31) sd = 30;
|
|
4949
|
+
if (ed === 31 && sd === 30) ed = 30;
|
|
4950
|
+
} else {
|
|
4951
|
+
if (sd === 31) sd = 30;
|
|
4952
|
+
if (ed === 31) ed = 30;
|
|
4953
|
+
}
|
|
4954
|
+
return (ey - sy) * 360 + (em - sm) * 30 + (ed - sd);
|
|
4955
|
+
}
|
|
4956
|
+
});
|
|
4957
|
+
registry.set("ISOWEEKNUM", {
|
|
4958
|
+
minArgs: 1,
|
|
4959
|
+
maxArgs: 1,
|
|
4960
|
+
evaluate(args, context, evaluator) {
|
|
4961
|
+
const rawDate = evaluator.evaluate(args[0], context);
|
|
4962
|
+
if (rawDate instanceof FormulaError) return rawDate;
|
|
4963
|
+
const date = toDate(rawDate);
|
|
4964
|
+
if (date instanceof FormulaError) return date;
|
|
4965
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
4966
|
+
const day = d.getUTCDay() || 7;
|
|
4967
|
+
d.setUTCDate(d.getUTCDate() + 4 - day);
|
|
4968
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
4969
|
+
return Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
4970
|
+
}
|
|
4971
|
+
});
|
|
4972
|
+
registry.set("YEARFRAC", {
|
|
4973
|
+
minArgs: 2,
|
|
4974
|
+
maxArgs: 3,
|
|
4975
|
+
evaluate(args, context, evaluator) {
|
|
4976
|
+
const rawStart = evaluator.evaluate(args[0], context);
|
|
4977
|
+
if (rawStart instanceof FormulaError) return rawStart;
|
|
4978
|
+
const startDate = toDate(rawStart);
|
|
4979
|
+
if (startDate instanceof FormulaError) return startDate;
|
|
4980
|
+
const rawEnd = evaluator.evaluate(args[1], context);
|
|
4981
|
+
if (rawEnd instanceof FormulaError) return rawEnd;
|
|
4982
|
+
const endDate = toDate(rawEnd);
|
|
4983
|
+
if (endDate instanceof FormulaError) return endDate;
|
|
4984
|
+
let basis = 0;
|
|
4985
|
+
if (args.length >= 3) {
|
|
4986
|
+
const rawBasis = evaluator.evaluate(args[2], context);
|
|
4987
|
+
if (rawBasis instanceof FormulaError) return rawBasis;
|
|
4988
|
+
const b = toNumber(rawBasis);
|
|
4989
|
+
if (b instanceof FormulaError) return b;
|
|
4990
|
+
basis = Math.trunc(b);
|
|
4991
|
+
}
|
|
4992
|
+
const sy = startDate.getFullYear();
|
|
4993
|
+
const sm = startDate.getMonth() + 1;
|
|
4994
|
+
const sd = startDate.getDate();
|
|
4995
|
+
const ey = endDate.getFullYear();
|
|
4996
|
+
const em = endDate.getMonth() + 1;
|
|
4997
|
+
const ed = endDate.getDate();
|
|
4998
|
+
switch (basis) {
|
|
4999
|
+
case 0: {
|
|
5000
|
+
const startDay = sd === 31 ? 30 : sd;
|
|
5001
|
+
const endDay = ed === 31 && startDay === 30 ? 30 : ed;
|
|
5002
|
+
const days360 = (ey - sy) * 360 + (em - sm) * 30 + (endDay - startDay);
|
|
5003
|
+
return days360 / 360;
|
|
5004
|
+
}
|
|
5005
|
+
case 1: {
|
|
5006
|
+
const diffMs = Date.UTC(ey, em - 1, ed) - Date.UTC(sy, sm - 1, sd);
|
|
5007
|
+
const diffDays = diffMs / 864e5;
|
|
5008
|
+
const avgYear = ey === sy ? isLeapYear(sy) ? 366 : 365 : (Date.UTC(ey + 1, 0, 1) - Date.UTC(sy, 0, 1)) / 864e5 / (ey - sy + 1);
|
|
5009
|
+
return diffDays / avgYear;
|
|
5010
|
+
}
|
|
5011
|
+
case 3: {
|
|
5012
|
+
const diffMs = Date.UTC(ey, em - 1, ed) - Date.UTC(sy, sm - 1, sd);
|
|
5013
|
+
return diffMs / 864e5 / 365;
|
|
5014
|
+
}
|
|
5015
|
+
default:
|
|
5016
|
+
return new FormulaError("#VALUE!", "YEARFRAC basis must be 0, 1, or 3");
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
});
|
|
5020
|
+
registry.set("DATEVALUE", {
|
|
5021
|
+
minArgs: 1,
|
|
5022
|
+
maxArgs: 1,
|
|
5023
|
+
evaluate(args, context, evaluator) {
|
|
5024
|
+
const rawVal = evaluator.evaluate(args[0], context);
|
|
5025
|
+
if (rawVal instanceof FormulaError) return rawVal;
|
|
5026
|
+
const str = typeof rawVal === "string" ? rawVal : String(rawVal);
|
|
5027
|
+
const d = new Date(str);
|
|
5028
|
+
if (isNaN(d.getTime())) return new FormulaError("#VALUE!", `DATEVALUE cannot parse "${str}"`);
|
|
5029
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
5030
|
+
}
|
|
5031
|
+
});
|
|
5032
|
+
registry.set("TIMEVALUE", {
|
|
5033
|
+
minArgs: 1,
|
|
5034
|
+
maxArgs: 1,
|
|
5035
|
+
evaluate(args, context, evaluator) {
|
|
5036
|
+
const rawVal = evaluator.evaluate(args[0], context);
|
|
5037
|
+
if (rawVal instanceof FormulaError) return rawVal;
|
|
5038
|
+
const str = typeof rawVal === "string" ? rawVal : String(rawVal);
|
|
5039
|
+
const match = str.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*(AM|PM))?$/i);
|
|
5040
|
+
if (!match) return new FormulaError("#VALUE!", `TIMEVALUE cannot parse "${str}"`);
|
|
5041
|
+
let hours = parseInt(match[1], 10);
|
|
5042
|
+
const minutes = parseInt(match[2], 10);
|
|
5043
|
+
const seconds = match[3] ? parseInt(match[3], 10) : 0;
|
|
5044
|
+
const ampm = match[4] ? match[4].toUpperCase() : null;
|
|
5045
|
+
if (ampm === "PM" && hours < 12) hours += 12;
|
|
5046
|
+
if (ampm === "AM" && hours === 12) hours = 0;
|
|
5047
|
+
if (hours > 23 || minutes > 59 || seconds > 59) {
|
|
5048
|
+
return new FormulaError("#VALUE!", "TIMEVALUE: invalid time component");
|
|
5049
|
+
}
|
|
5050
|
+
return (hours * 3600 + minutes * 60 + seconds) / 86400;
|
|
5051
|
+
}
|
|
5052
|
+
});
|
|
5053
|
+
registry.set("TIME", {
|
|
5054
|
+
minArgs: 3,
|
|
5055
|
+
maxArgs: 3,
|
|
5056
|
+
evaluate(args, context, evaluator) {
|
|
5057
|
+
const rawH = evaluator.evaluate(args[0], context);
|
|
5058
|
+
if (rawH instanceof FormulaError) return rawH;
|
|
5059
|
+
const h = toNumber(rawH);
|
|
5060
|
+
if (h instanceof FormulaError) return h;
|
|
5061
|
+
const rawM = evaluator.evaluate(args[1], context);
|
|
5062
|
+
if (rawM instanceof FormulaError) return rawM;
|
|
5063
|
+
const m = toNumber(rawM);
|
|
5064
|
+
if (m instanceof FormulaError) return m;
|
|
5065
|
+
const rawS = evaluator.evaluate(args[2], context);
|
|
5066
|
+
if (rawS instanceof FormulaError) return rawS;
|
|
5067
|
+
const s = toNumber(rawS);
|
|
5068
|
+
if (s instanceof FormulaError) return s;
|
|
5069
|
+
const totalSeconds = Math.trunc(h) * 3600 + Math.trunc(m) * 60 + Math.trunc(s);
|
|
5070
|
+
return totalSeconds % 86400 / 86400;
|
|
5071
|
+
}
|
|
5072
|
+
});
|
|
5073
|
+
registry.set("WORKDAY", {
|
|
5074
|
+
minArgs: 2,
|
|
5075
|
+
maxArgs: 3,
|
|
5076
|
+
evaluate(args, context, evaluator) {
|
|
5077
|
+
const rawStart = evaluator.evaluate(args[0], context);
|
|
5078
|
+
if (rawStart instanceof FormulaError) return rawStart;
|
|
5079
|
+
const startDate = toDate(rawStart);
|
|
5080
|
+
if (startDate instanceof FormulaError) return startDate;
|
|
5081
|
+
const rawDays = evaluator.evaluate(args[1], context);
|
|
5082
|
+
if (rawDays instanceof FormulaError) return rawDays;
|
|
5083
|
+
const daysNum = toNumber(rawDays);
|
|
5084
|
+
if (daysNum instanceof FormulaError) return daysNum;
|
|
5085
|
+
const days = Math.trunc(daysNum);
|
|
5086
|
+
const holidaySet = /* @__PURE__ */ new Set();
|
|
5087
|
+
if (args.length >= 3) {
|
|
5088
|
+
const rawHol = args[2];
|
|
5089
|
+
let holVals;
|
|
5090
|
+
if (rawHol.kind === "range") {
|
|
5091
|
+
holVals = context.getRangeValues({ start: rawHol.start, end: rawHol.end }).flat();
|
|
5092
|
+
} else {
|
|
5093
|
+
holVals = [evaluator.evaluate(rawHol, context)];
|
|
5094
|
+
}
|
|
5095
|
+
for (const hv of holVals) {
|
|
5096
|
+
if (hv === null || hv === void 0) continue;
|
|
5097
|
+
const hd = toDate(hv);
|
|
5098
|
+
if (!(hd instanceof FormulaError)) {
|
|
5099
|
+
holidaySet.add(`${hd.getFullYear()}-${hd.getMonth()}-${hd.getDate()}`);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
const current = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
|
5104
|
+
const step = days >= 0 ? 1 : -1;
|
|
5105
|
+
let remaining = Math.abs(days);
|
|
5106
|
+
while (remaining > 0) {
|
|
5107
|
+
current.setDate(current.getDate() + step);
|
|
5108
|
+
const dow = current.getDay();
|
|
5109
|
+
if (dow === 0 || dow === 6) continue;
|
|
5110
|
+
const key = `${current.getFullYear()}-${current.getMonth()}-${current.getDate()}`;
|
|
5111
|
+
if (holidaySet.has(key)) continue;
|
|
5112
|
+
remaining--;
|
|
5113
|
+
}
|
|
5114
|
+
return current;
|
|
5115
|
+
}
|
|
5116
|
+
});
|
|
5117
|
+
registry.set("WORKDAY.INTL", {
|
|
5118
|
+
minArgs: 2,
|
|
5119
|
+
maxArgs: 4,
|
|
5120
|
+
evaluate(args, context, evaluator) {
|
|
5121
|
+
const rawStart = evaluator.evaluate(args[0], context);
|
|
5122
|
+
if (rawStart instanceof FormulaError) return rawStart;
|
|
5123
|
+
const startDate = toDate(rawStart);
|
|
5124
|
+
if (startDate instanceof FormulaError) return startDate;
|
|
5125
|
+
const rawDays = evaluator.evaluate(args[1], context);
|
|
5126
|
+
if (rawDays instanceof FormulaError) return rawDays;
|
|
5127
|
+
const daysNum = toNumber(rawDays);
|
|
5128
|
+
if (daysNum instanceof FormulaError) return daysNum;
|
|
5129
|
+
const days = Math.trunc(daysNum);
|
|
5130
|
+
let weekendMask = [false, false, false, false, false, true, true];
|
|
5131
|
+
if (args.length >= 3) {
|
|
5132
|
+
const rawWeekend = evaluator.evaluate(args[2], context);
|
|
5133
|
+
if (rawWeekend instanceof FormulaError) return rawWeekend;
|
|
5134
|
+
if (typeof rawWeekend === "string" && /^[01]{7}$/.test(rawWeekend)) {
|
|
5135
|
+
weekendMask = rawWeekend.split("").map((c) => c === "1");
|
|
5136
|
+
} else {
|
|
5137
|
+
const wn = toNumber(rawWeekend);
|
|
5138
|
+
if (wn instanceof FormulaError) return wn;
|
|
5139
|
+
const parsed = parseWeekendNumber(Math.trunc(wn));
|
|
5140
|
+
if (!parsed) return new FormulaError("#VALUE!", "WORKDAY.INTL invalid weekend number");
|
|
5141
|
+
weekendMask = parsed;
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
const holidaySet = /* @__PURE__ */ new Set();
|
|
5145
|
+
if (args.length >= 4) {
|
|
5146
|
+
const rawHol = args[3];
|
|
5147
|
+
let holVals;
|
|
5148
|
+
if (rawHol.kind === "range") {
|
|
5149
|
+
holVals = context.getRangeValues({ start: rawHol.start, end: rawHol.end }).flat();
|
|
5150
|
+
} else {
|
|
5151
|
+
holVals = [evaluator.evaluate(rawHol, context)];
|
|
5152
|
+
}
|
|
5153
|
+
for (const hv of holVals) {
|
|
5154
|
+
if (hv === null || hv === void 0) continue;
|
|
5155
|
+
const hd = toDate(hv);
|
|
5156
|
+
if (!(hd instanceof FormulaError)) {
|
|
5157
|
+
holidaySet.add(`${hd.getFullYear()}-${hd.getMonth()}-${hd.getDate()}`);
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
}
|
|
5161
|
+
const current = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
|
5162
|
+
const step = days >= 0 ? 1 : -1;
|
|
5163
|
+
let remaining = Math.abs(days);
|
|
5164
|
+
while (remaining > 0) {
|
|
5165
|
+
current.setDate(current.getDate() + step);
|
|
5166
|
+
const dow = current.getDay();
|
|
5167
|
+
const maskIndex = dow === 0 ? 6 : dow - 1;
|
|
5168
|
+
if (weekendMask[maskIndex]) continue;
|
|
5169
|
+
const key = `${current.getFullYear()}-${current.getMonth()}-${current.getDate()}`;
|
|
5170
|
+
if (holidaySet.has(key)) continue;
|
|
5171
|
+
remaining--;
|
|
5172
|
+
}
|
|
5173
|
+
return current;
|
|
5174
|
+
}
|
|
5175
|
+
});
|
|
5176
|
+
}
|
|
5177
|
+
function isLeapYear(year) {
|
|
5178
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
5179
|
+
}
|
|
5180
|
+
function parseWeekendNumber(n) {
|
|
5181
|
+
const twoDay = [
|
|
5182
|
+
[5, 6],
|
|
5183
|
+
// 1: Sat+Sun
|
|
5184
|
+
[6, 0],
|
|
5185
|
+
// 2: Sun+Mon (Sun=index6, Mon=index0)
|
|
5186
|
+
[0, 1],
|
|
5187
|
+
// 3: Mon+Tue
|
|
5188
|
+
[1, 2],
|
|
5189
|
+
// 4: Tue+Wed
|
|
5190
|
+
[2, 3],
|
|
5191
|
+
// 5: Wed+Thu
|
|
5192
|
+
[3, 4],
|
|
5193
|
+
// 6: Thu+Fri
|
|
5194
|
+
[4, 5]
|
|
5195
|
+
// 7: Fri+Sat
|
|
5196
|
+
];
|
|
5197
|
+
if (n >= 1 && n <= 7) {
|
|
5198
|
+
const mask = [false, false, false, false, false, false, false];
|
|
5199
|
+
const [a, b] = twoDay[n - 1];
|
|
5200
|
+
mask[a] = true;
|
|
5201
|
+
mask[b] = true;
|
|
5202
|
+
return mask;
|
|
5203
|
+
}
|
|
5204
|
+
if (n >= 11 && n <= 17) {
|
|
5205
|
+
const mask = [false, false, false, false, false, false, false];
|
|
5206
|
+
const singleDay = [6, 0, 1, 2, 3, 4, 5];
|
|
5207
|
+
mask[singleDay[n - 11]] = true;
|
|
5208
|
+
return mask;
|
|
5209
|
+
}
|
|
5210
|
+
return null;
|
|
4452
5211
|
}
|
|
4453
5212
|
function makeCriteria(op, value) {
|
|
4454
5213
|
return { op, value, valueLower: typeof value === "string" ? value.toLowerCase() : null };
|
|
@@ -4779,36 +5538,1076 @@ function registerInfoFunctions(registry) {
|
|
|
4779
5538
|
return typeof val === "string";
|
|
4780
5539
|
}
|
|
4781
5540
|
});
|
|
4782
|
-
registry.set("ISERROR", {
|
|
5541
|
+
registry.set("ISERROR", {
|
|
5542
|
+
minArgs: 1,
|
|
5543
|
+
maxArgs: 1,
|
|
5544
|
+
evaluate(args, context, evaluator) {
|
|
5545
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5546
|
+
return val instanceof FormulaError;
|
|
5547
|
+
}
|
|
5548
|
+
});
|
|
5549
|
+
registry.set("ISNA", {
|
|
5550
|
+
minArgs: 1,
|
|
5551
|
+
maxArgs: 1,
|
|
5552
|
+
evaluate(args, context, evaluator) {
|
|
5553
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5554
|
+
return val instanceof FormulaError && val.type === "#N/A";
|
|
5555
|
+
}
|
|
5556
|
+
});
|
|
5557
|
+
registry.set("TYPE", {
|
|
5558
|
+
minArgs: 1,
|
|
5559
|
+
maxArgs: 1,
|
|
5560
|
+
evaluate(args, context, evaluator) {
|
|
5561
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5562
|
+
if (val instanceof FormulaError) return 16;
|
|
5563
|
+
if (typeof val === "number") return 1;
|
|
5564
|
+
if (typeof val === "string") return 2;
|
|
5565
|
+
if (typeof val === "boolean") return 4;
|
|
5566
|
+
if (val === null || val === void 0) return 1;
|
|
5567
|
+
return 1;
|
|
5568
|
+
}
|
|
5569
|
+
});
|
|
5570
|
+
registry.set("ISODD", {
|
|
5571
|
+
minArgs: 1,
|
|
5572
|
+
maxArgs: 1,
|
|
5573
|
+
evaluate(args, context, evaluator) {
|
|
5574
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5575
|
+
if (val instanceof FormulaError) return val;
|
|
5576
|
+
if (typeof val === "boolean") return new FormulaError("#VALUE!", "ISODD requires a number");
|
|
5577
|
+
const n = toNumber(val);
|
|
5578
|
+
if (n instanceof FormulaError) return n;
|
|
5579
|
+
return Math.trunc(n) % 2 !== 0;
|
|
5580
|
+
}
|
|
5581
|
+
});
|
|
5582
|
+
registry.set("ISEVEN", {
|
|
5583
|
+
minArgs: 1,
|
|
5584
|
+
maxArgs: 1,
|
|
5585
|
+
evaluate(args, context, evaluator) {
|
|
5586
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5587
|
+
if (val instanceof FormulaError) return val;
|
|
5588
|
+
if (typeof val === "boolean") return new FormulaError("#VALUE!", "ISEVEN requires a number");
|
|
5589
|
+
const n = toNumber(val);
|
|
5590
|
+
if (n instanceof FormulaError) return n;
|
|
5591
|
+
return Math.trunc(n) % 2 === 0;
|
|
5592
|
+
}
|
|
5593
|
+
});
|
|
5594
|
+
registry.set("ISFORMULA", {
|
|
5595
|
+
minArgs: 1,
|
|
5596
|
+
maxArgs: 1,
|
|
5597
|
+
evaluate(args, context, _evaluator) {
|
|
5598
|
+
const arg = args[0];
|
|
5599
|
+
if (arg.kind !== "cellRef") return false;
|
|
5600
|
+
if (!context.getCellFormula) return false;
|
|
5601
|
+
const formula = context.getCellFormula(arg.address);
|
|
5602
|
+
return formula !== void 0;
|
|
5603
|
+
}
|
|
5604
|
+
});
|
|
5605
|
+
registry.set("ISLOGICAL", {
|
|
5606
|
+
minArgs: 1,
|
|
5607
|
+
maxArgs: 1,
|
|
5608
|
+
evaluate(args, context, evaluator) {
|
|
5609
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5610
|
+
if (val instanceof FormulaError) return false;
|
|
5611
|
+
return typeof val === "boolean";
|
|
5612
|
+
}
|
|
5613
|
+
});
|
|
5614
|
+
registry.set("ISNONTEXT", {
|
|
5615
|
+
minArgs: 1,
|
|
5616
|
+
maxArgs: 1,
|
|
5617
|
+
evaluate(args, context, evaluator) {
|
|
5618
|
+
const val = evaluator.evaluate(args[0], context);
|
|
5619
|
+
if (val instanceof FormulaError) return true;
|
|
5620
|
+
return typeof val !== "string";
|
|
5621
|
+
}
|
|
5622
|
+
});
|
|
5623
|
+
registry.set("ISREF", {
|
|
5624
|
+
minArgs: 1,
|
|
5625
|
+
maxArgs: 1,
|
|
5626
|
+
evaluate(args, _context, _evaluator) {
|
|
5627
|
+
const arg = args[0];
|
|
5628
|
+
return arg.kind === "cellRef" || arg.kind === "range";
|
|
5629
|
+
}
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
function registerFinancialFunctions(registry) {
|
|
5633
|
+
registry.set("PMT", {
|
|
5634
|
+
minArgs: 3,
|
|
5635
|
+
maxArgs: 5,
|
|
5636
|
+
evaluate(args, context, evaluator) {
|
|
5637
|
+
const rawRate = evaluator.evaluate(args[0], context);
|
|
5638
|
+
if (rawRate instanceof FormulaError) return rawRate;
|
|
5639
|
+
const rate = toNumber(rawRate);
|
|
5640
|
+
if (rate instanceof FormulaError) return rate;
|
|
5641
|
+
const rawNper = evaluator.evaluate(args[1], context);
|
|
5642
|
+
if (rawNper instanceof FormulaError) return rawNper;
|
|
5643
|
+
const nper = toNumber(rawNper);
|
|
5644
|
+
if (nper instanceof FormulaError) return nper;
|
|
5645
|
+
const rawPv = evaluator.evaluate(args[2], context);
|
|
5646
|
+
if (rawPv instanceof FormulaError) return rawPv;
|
|
5647
|
+
const pv = toNumber(rawPv);
|
|
5648
|
+
if (pv instanceof FormulaError) return pv;
|
|
5649
|
+
let fv = 0;
|
|
5650
|
+
if (args.length >= 4) {
|
|
5651
|
+
const rawFv = evaluator.evaluate(args[3], context);
|
|
5652
|
+
if (rawFv instanceof FormulaError) return rawFv;
|
|
5653
|
+
const fvNum = toNumber(rawFv);
|
|
5654
|
+
if (fvNum instanceof FormulaError) return fvNum;
|
|
5655
|
+
fv = fvNum;
|
|
5656
|
+
}
|
|
5657
|
+
let type = 0;
|
|
5658
|
+
if (args.length >= 5) {
|
|
5659
|
+
const rawType = evaluator.evaluate(args[4], context);
|
|
5660
|
+
if (rawType instanceof FormulaError) return rawType;
|
|
5661
|
+
const typeNum = toNumber(rawType);
|
|
5662
|
+
if (typeNum instanceof FormulaError) return typeNum;
|
|
5663
|
+
type = typeNum;
|
|
5664
|
+
}
|
|
5665
|
+
if (nper === 0) return new FormulaError("#NUM!", "PMT: nper cannot be 0");
|
|
5666
|
+
if (rate === 0) {
|
|
5667
|
+
return -(pv + fv) / nper;
|
|
5668
|
+
}
|
|
5669
|
+
const factor = Math.pow(1 + rate, nper);
|
|
5670
|
+
const typeAdj = type !== 0 ? 1 + rate : 1;
|
|
5671
|
+
return -(pv * factor + fv) / ((factor - 1) / rate * typeAdj);
|
|
5672
|
+
}
|
|
5673
|
+
});
|
|
5674
|
+
registry.set("FV", {
|
|
5675
|
+
minArgs: 3,
|
|
5676
|
+
maxArgs: 5,
|
|
5677
|
+
evaluate(args, context, evaluator) {
|
|
5678
|
+
const rawRate = evaluator.evaluate(args[0], context);
|
|
5679
|
+
if (rawRate instanceof FormulaError) return rawRate;
|
|
5680
|
+
const rate = toNumber(rawRate);
|
|
5681
|
+
if (rate instanceof FormulaError) return rate;
|
|
5682
|
+
const rawNper = evaluator.evaluate(args[1], context);
|
|
5683
|
+
if (rawNper instanceof FormulaError) return rawNper;
|
|
5684
|
+
const nper = toNumber(rawNper);
|
|
5685
|
+
if (nper instanceof FormulaError) return nper;
|
|
5686
|
+
const rawPmt = evaluator.evaluate(args[2], context);
|
|
5687
|
+
if (rawPmt instanceof FormulaError) return rawPmt;
|
|
5688
|
+
const pmt = toNumber(rawPmt);
|
|
5689
|
+
if (pmt instanceof FormulaError) return pmt;
|
|
5690
|
+
let pv = 0;
|
|
5691
|
+
if (args.length >= 4) {
|
|
5692
|
+
const rawPv = evaluator.evaluate(args[3], context);
|
|
5693
|
+
if (rawPv instanceof FormulaError) return rawPv;
|
|
5694
|
+
const pvNum = toNumber(rawPv);
|
|
5695
|
+
if (pvNum instanceof FormulaError) return pvNum;
|
|
5696
|
+
pv = pvNum;
|
|
5697
|
+
}
|
|
5698
|
+
let type = 0;
|
|
5699
|
+
if (args.length >= 5) {
|
|
5700
|
+
const rawType = evaluator.evaluate(args[4], context);
|
|
5701
|
+
if (rawType instanceof FormulaError) return rawType;
|
|
5702
|
+
const typeNum = toNumber(rawType);
|
|
5703
|
+
if (typeNum instanceof FormulaError) return typeNum;
|
|
5704
|
+
type = typeNum;
|
|
5705
|
+
}
|
|
5706
|
+
if (rate === 0) {
|
|
5707
|
+
return -(pv + pmt * nper);
|
|
5708
|
+
}
|
|
5709
|
+
const factor = Math.pow(1 + rate, nper);
|
|
5710
|
+
const typeAdj = type !== 0 ? 1 + rate : 1;
|
|
5711
|
+
return -(pv * factor + pmt * typeAdj * (factor - 1) / rate);
|
|
5712
|
+
}
|
|
5713
|
+
});
|
|
5714
|
+
registry.set("PV", {
|
|
5715
|
+
minArgs: 3,
|
|
5716
|
+
maxArgs: 5,
|
|
5717
|
+
evaluate(args, context, evaluator) {
|
|
5718
|
+
const rawRate = evaluator.evaluate(args[0], context);
|
|
5719
|
+
if (rawRate instanceof FormulaError) return rawRate;
|
|
5720
|
+
const rate = toNumber(rawRate);
|
|
5721
|
+
if (rate instanceof FormulaError) return rate;
|
|
5722
|
+
const rawNper = evaluator.evaluate(args[1], context);
|
|
5723
|
+
if (rawNper instanceof FormulaError) return rawNper;
|
|
5724
|
+
const nper = toNumber(rawNper);
|
|
5725
|
+
if (nper instanceof FormulaError) return nper;
|
|
5726
|
+
const rawPmt = evaluator.evaluate(args[2], context);
|
|
5727
|
+
if (rawPmt instanceof FormulaError) return rawPmt;
|
|
5728
|
+
const pmt = toNumber(rawPmt);
|
|
5729
|
+
if (pmt instanceof FormulaError) return pmt;
|
|
5730
|
+
let fv = 0;
|
|
5731
|
+
if (args.length >= 4) {
|
|
5732
|
+
const rawFv = evaluator.evaluate(args[3], context);
|
|
5733
|
+
if (rawFv instanceof FormulaError) return rawFv;
|
|
5734
|
+
const fvNum = toNumber(rawFv);
|
|
5735
|
+
if (fvNum instanceof FormulaError) return fvNum;
|
|
5736
|
+
fv = fvNum;
|
|
5737
|
+
}
|
|
5738
|
+
let type = 0;
|
|
5739
|
+
if (args.length >= 5) {
|
|
5740
|
+
const rawType = evaluator.evaluate(args[4], context);
|
|
5741
|
+
if (rawType instanceof FormulaError) return rawType;
|
|
5742
|
+
const typeNum = toNumber(rawType);
|
|
5743
|
+
if (typeNum instanceof FormulaError) return typeNum;
|
|
5744
|
+
type = typeNum;
|
|
5745
|
+
}
|
|
5746
|
+
if (rate === 0) {
|
|
5747
|
+
return -pmt * nper - fv;
|
|
5748
|
+
}
|
|
5749
|
+
const factor = Math.pow(1 + rate, nper);
|
|
5750
|
+
const typeAdj = type !== 0 ? 1 + rate : 1;
|
|
5751
|
+
return -(fv + pmt * typeAdj * (factor - 1) / rate) / factor;
|
|
5752
|
+
}
|
|
5753
|
+
});
|
|
5754
|
+
registry.set("NPER", {
|
|
5755
|
+
minArgs: 3,
|
|
5756
|
+
maxArgs: 5,
|
|
5757
|
+
evaluate(args, context, evaluator) {
|
|
5758
|
+
const rawRate = evaluator.evaluate(args[0], context);
|
|
5759
|
+
if (rawRate instanceof FormulaError) return rawRate;
|
|
5760
|
+
const rate = toNumber(rawRate);
|
|
5761
|
+
if (rate instanceof FormulaError) return rate;
|
|
5762
|
+
const rawPmt = evaluator.evaluate(args[1], context);
|
|
5763
|
+
if (rawPmt instanceof FormulaError) return rawPmt;
|
|
5764
|
+
const pmt = toNumber(rawPmt);
|
|
5765
|
+
if (pmt instanceof FormulaError) return pmt;
|
|
5766
|
+
const rawPv = evaluator.evaluate(args[2], context);
|
|
5767
|
+
if (rawPv instanceof FormulaError) return rawPv;
|
|
5768
|
+
const pv = toNumber(rawPv);
|
|
5769
|
+
if (pv instanceof FormulaError) return pv;
|
|
5770
|
+
let fv = 0;
|
|
5771
|
+
if (args.length >= 4) {
|
|
5772
|
+
const rawFv = evaluator.evaluate(args[3], context);
|
|
5773
|
+
if (rawFv instanceof FormulaError) return rawFv;
|
|
5774
|
+
const fvNum = toNumber(rawFv);
|
|
5775
|
+
if (fvNum instanceof FormulaError) return fvNum;
|
|
5776
|
+
fv = fvNum;
|
|
5777
|
+
}
|
|
5778
|
+
let type = 0;
|
|
5779
|
+
if (args.length >= 5) {
|
|
5780
|
+
const rawType = evaluator.evaluate(args[4], context);
|
|
5781
|
+
if (rawType instanceof FormulaError) return rawType;
|
|
5782
|
+
const typeNum = toNumber(rawType);
|
|
5783
|
+
if (typeNum instanceof FormulaError) return typeNum;
|
|
5784
|
+
type = typeNum;
|
|
5785
|
+
}
|
|
5786
|
+
if (rate === 0) {
|
|
5787
|
+
if (pmt === 0) return new FormulaError("#NUM!", "NPER: pmt cannot be 0 when rate is 0");
|
|
5788
|
+
return -(pv + fv) / pmt;
|
|
5789
|
+
}
|
|
5790
|
+
const typeAdj = type !== 0 ? 1 + rate : 1;
|
|
5791
|
+
const pmtAdj = pmt * typeAdj;
|
|
5792
|
+
const num = pmtAdj - fv * rate;
|
|
5793
|
+
const den = pmtAdj + pv * rate;
|
|
5794
|
+
if (den === 0) return new FormulaError("#DIV/0!", "NPER: division by zero");
|
|
5795
|
+
if (num / den <= 0) return new FormulaError("#NUM!", "NPER: logarithm of non-positive number");
|
|
5796
|
+
return Math.log(num / den) / Math.log(1 + rate);
|
|
5797
|
+
}
|
|
5798
|
+
});
|
|
5799
|
+
registry.set("RATE", {
|
|
5800
|
+
minArgs: 3,
|
|
5801
|
+
maxArgs: 6,
|
|
5802
|
+
evaluate(args, context, evaluator) {
|
|
5803
|
+
const rawNper = evaluator.evaluate(args[0], context);
|
|
5804
|
+
if (rawNper instanceof FormulaError) return rawNper;
|
|
5805
|
+
const nper = toNumber(rawNper);
|
|
5806
|
+
if (nper instanceof FormulaError) return nper;
|
|
5807
|
+
const rawPmt = evaluator.evaluate(args[1], context);
|
|
5808
|
+
if (rawPmt instanceof FormulaError) return rawPmt;
|
|
5809
|
+
const pmt = toNumber(rawPmt);
|
|
5810
|
+
if (pmt instanceof FormulaError) return pmt;
|
|
5811
|
+
const rawPv = evaluator.evaluate(args[2], context);
|
|
5812
|
+
if (rawPv instanceof FormulaError) return rawPv;
|
|
5813
|
+
const pv = toNumber(rawPv);
|
|
5814
|
+
if (pv instanceof FormulaError) return pv;
|
|
5815
|
+
let fv = 0;
|
|
5816
|
+
if (args.length >= 4) {
|
|
5817
|
+
const rawFv = evaluator.evaluate(args[3], context);
|
|
5818
|
+
if (rawFv instanceof FormulaError) return rawFv;
|
|
5819
|
+
const fvNum = toNumber(rawFv);
|
|
5820
|
+
if (fvNum instanceof FormulaError) return fvNum;
|
|
5821
|
+
fv = fvNum;
|
|
5822
|
+
}
|
|
5823
|
+
let type = 0;
|
|
5824
|
+
if (args.length >= 5) {
|
|
5825
|
+
const rawType = evaluator.evaluate(args[4], context);
|
|
5826
|
+
if (rawType instanceof FormulaError) return rawType;
|
|
5827
|
+
const typeNum = toNumber(rawType);
|
|
5828
|
+
if (typeNum instanceof FormulaError) return typeNum;
|
|
5829
|
+
type = typeNum;
|
|
5830
|
+
}
|
|
5831
|
+
let guess = 0.1;
|
|
5832
|
+
if (args.length >= 6) {
|
|
5833
|
+
const rawGuess = evaluator.evaluate(args[5], context);
|
|
5834
|
+
if (rawGuess instanceof FormulaError) return rawGuess;
|
|
5835
|
+
const guessNum = toNumber(rawGuess);
|
|
5836
|
+
if (guessNum instanceof FormulaError) return guessNum;
|
|
5837
|
+
guess = guessNum;
|
|
5838
|
+
}
|
|
5839
|
+
const MAX_ITER = 20;
|
|
5840
|
+
const TOLERANCE = 1e-7;
|
|
5841
|
+
let r = guess;
|
|
5842
|
+
for (let i = 0; i < MAX_ITER; i++) {
|
|
5843
|
+
if (r <= -1) return new FormulaError("#NUM!", "RATE: rate converged to invalid value");
|
|
5844
|
+
const factor = Math.pow(1 + r, nper);
|
|
5845
|
+
const typeAdj = type !== 0 ? 1 + r : 1;
|
|
5846
|
+
let f;
|
|
5847
|
+
let df;
|
|
5848
|
+
if (Math.abs(r) < 1e-10) {
|
|
5849
|
+
f = pv + pmt * nper + fv;
|
|
5850
|
+
df = pv * nper + pmt * nper * (nper - 1) / 2;
|
|
5851
|
+
} else {
|
|
5852
|
+
f = pv * factor + pmt * typeAdj * (factor - 1) / r + fv;
|
|
5853
|
+
const dfactor = nper * Math.pow(1 + r, nper - 1);
|
|
5854
|
+
const dTypeAdj = type !== 0 ? 1 : 0;
|
|
5855
|
+
df = pv * dfactor + pmt * (dTypeAdj * (factor - 1) / r + typeAdj * (dfactor * r - (factor - 1)) / (r * r));
|
|
5856
|
+
}
|
|
5857
|
+
if (Math.abs(df) < 1e-15) return new FormulaError("#NUM!", "RATE: derivative too small, no convergence");
|
|
5858
|
+
const delta = f / df;
|
|
5859
|
+
r = r - delta;
|
|
5860
|
+
if (Math.abs(delta) < TOLERANCE) return r;
|
|
5861
|
+
}
|
|
5862
|
+
return new FormulaError("#NUM!", "RATE: did not converge");
|
|
5863
|
+
}
|
|
5864
|
+
});
|
|
5865
|
+
registry.set("NPV", {
|
|
5866
|
+
minArgs: 2,
|
|
5867
|
+
maxArgs: -1,
|
|
5868
|
+
evaluate(args, context, evaluator) {
|
|
5869
|
+
const rawRate = evaluator.evaluate(args[0], context);
|
|
5870
|
+
if (rawRate instanceof FormulaError) return rawRate;
|
|
5871
|
+
const rate = toNumber(rawRate);
|
|
5872
|
+
if (rate instanceof FormulaError) return rate;
|
|
5873
|
+
const values = flattenArgs(args.slice(1), context, evaluator);
|
|
5874
|
+
let npv = 0;
|
|
5875
|
+
let period = 1;
|
|
5876
|
+
for (const val of values) {
|
|
5877
|
+
if (val instanceof FormulaError) return val;
|
|
5878
|
+
if (typeof val === "number" || typeof val === "boolean") {
|
|
5879
|
+
const n = typeof val === "boolean" ? val ? 1 : 0 : val;
|
|
5880
|
+
npv += n / Math.pow(1 + rate, period);
|
|
5881
|
+
period++;
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
return npv;
|
|
5885
|
+
}
|
|
5886
|
+
});
|
|
5887
|
+
registry.set("IRR", {
|
|
5888
|
+
minArgs: 1,
|
|
5889
|
+
maxArgs: 2,
|
|
5890
|
+
evaluate(args, context, evaluator) {
|
|
5891
|
+
const cashFlows = flattenArgs([args[0]], context, evaluator);
|
|
5892
|
+
const nums = [];
|
|
5893
|
+
for (const val of cashFlows) {
|
|
5894
|
+
if (val instanceof FormulaError) return val;
|
|
5895
|
+
if (typeof val === "number") nums.push(val);
|
|
5896
|
+
else if (typeof val === "boolean") nums.push(val ? 1 : 0);
|
|
5897
|
+
}
|
|
5898
|
+
if (nums.length === 0) return new FormulaError("#NUM!", "IRR: no values");
|
|
5899
|
+
let guess = 0.1;
|
|
5900
|
+
if (args.length >= 2) {
|
|
5901
|
+
const rawGuess = evaluator.evaluate(args[1], context);
|
|
5902
|
+
if (rawGuess instanceof FormulaError) return rawGuess;
|
|
5903
|
+
const guessNum = toNumber(rawGuess);
|
|
5904
|
+
if (guessNum instanceof FormulaError) return guessNum;
|
|
5905
|
+
guess = guessNum;
|
|
5906
|
+
}
|
|
5907
|
+
const MAX_ITER = 20;
|
|
5908
|
+
const TOLERANCE = 1e-7;
|
|
5909
|
+
let r = guess;
|
|
5910
|
+
for (let i = 0; i < MAX_ITER; i++) {
|
|
5911
|
+
if (r <= -1) return new FormulaError("#NUM!", "IRR: rate converged below -1");
|
|
5912
|
+
let f = 0;
|
|
5913
|
+
let df = 0;
|
|
5914
|
+
for (let j = 0; j < nums.length; j++) {
|
|
5915
|
+
const factor = Math.pow(1 + r, j);
|
|
5916
|
+
f += nums[j] / factor;
|
|
5917
|
+
if (j > 0) {
|
|
5918
|
+
df -= j * nums[j] / Math.pow(1 + r, j + 1);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
if (Math.abs(df) < 1e-15) return new FormulaError("#NUM!", "IRR: derivative too small");
|
|
5922
|
+
const delta = f / df;
|
|
5923
|
+
r = r - delta;
|
|
5924
|
+
if (Math.abs(delta) < TOLERANCE) return r;
|
|
5925
|
+
}
|
|
5926
|
+
return new FormulaError("#NUM!", "IRR: did not converge");
|
|
5927
|
+
}
|
|
5928
|
+
});
|
|
5929
|
+
registry.set("SLN", {
|
|
5930
|
+
minArgs: 3,
|
|
5931
|
+
maxArgs: 3,
|
|
5932
|
+
evaluate(args, context, evaluator) {
|
|
5933
|
+
const rawCost = evaluator.evaluate(args[0], context);
|
|
5934
|
+
if (rawCost instanceof FormulaError) return rawCost;
|
|
5935
|
+
const cost = toNumber(rawCost);
|
|
5936
|
+
if (cost instanceof FormulaError) return cost;
|
|
5937
|
+
const rawSalvage = evaluator.evaluate(args[1], context);
|
|
5938
|
+
if (rawSalvage instanceof FormulaError) return rawSalvage;
|
|
5939
|
+
const salvage = toNumber(rawSalvage);
|
|
5940
|
+
if (salvage instanceof FormulaError) return salvage;
|
|
5941
|
+
const rawLife = evaluator.evaluate(args[2], context);
|
|
5942
|
+
if (rawLife instanceof FormulaError) return rawLife;
|
|
5943
|
+
const life = toNumber(rawLife);
|
|
5944
|
+
if (life instanceof FormulaError) return life;
|
|
5945
|
+
if (life === 0) return new FormulaError("#DIV/0!", "SLN: life cannot be 0");
|
|
5946
|
+
return (cost - salvage) / life;
|
|
5947
|
+
}
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
function extractNums(args, context, evaluator) {
|
|
5951
|
+
const values = flattenArgs(args, context, evaluator);
|
|
5952
|
+
const nums = [];
|
|
5953
|
+
for (const val of values) {
|
|
5954
|
+
if (val instanceof FormulaError) return val;
|
|
5955
|
+
if (typeof val === "number") nums.push(val);
|
|
5956
|
+
else if (typeof val === "boolean") nums.push(val ? 1 : 0);
|
|
5957
|
+
}
|
|
5958
|
+
return nums;
|
|
5959
|
+
}
|
|
5960
|
+
function mean(nums) {
|
|
5961
|
+
let sum = 0;
|
|
5962
|
+
for (const n of nums) sum += n;
|
|
5963
|
+
return sum / nums.length;
|
|
5964
|
+
}
|
|
5965
|
+
function registerStatisticalExtendedFunctions(registry) {
|
|
5966
|
+
const stdevImpl = {
|
|
5967
|
+
minArgs: 1,
|
|
5968
|
+
maxArgs: -1,
|
|
5969
|
+
evaluate(args, context, evaluator) {
|
|
5970
|
+
const nums = extractNums(args, context, evaluator);
|
|
5971
|
+
if (nums instanceof FormulaError) return nums;
|
|
5972
|
+
if (nums.length < 2) return new FormulaError("#DIV/0!", "STDEV requires at least 2 values");
|
|
5973
|
+
const m = mean(nums);
|
|
5974
|
+
let sum = 0;
|
|
5975
|
+
for (const n of nums) sum += (n - m) * (n - m);
|
|
5976
|
+
return Math.sqrt(sum / (nums.length - 1));
|
|
5977
|
+
}
|
|
5978
|
+
};
|
|
5979
|
+
registry.set("STDEV", stdevImpl);
|
|
5980
|
+
registry.set("STDEV.S", stdevImpl);
|
|
5981
|
+
const stdevpImpl = {
|
|
5982
|
+
minArgs: 1,
|
|
5983
|
+
maxArgs: -1,
|
|
5984
|
+
evaluate(args, context, evaluator) {
|
|
5985
|
+
const nums = extractNums(args, context, evaluator);
|
|
5986
|
+
if (nums instanceof FormulaError) return nums;
|
|
5987
|
+
if (nums.length === 0) return new FormulaError("#DIV/0!", "STDEVP requires at least 1 value");
|
|
5988
|
+
const m = mean(nums);
|
|
5989
|
+
let sum = 0;
|
|
5990
|
+
for (const n of nums) sum += (n - m) * (n - m);
|
|
5991
|
+
return Math.sqrt(sum / nums.length);
|
|
5992
|
+
}
|
|
5993
|
+
};
|
|
5994
|
+
registry.set("STDEVP", stdevpImpl);
|
|
5995
|
+
registry.set("STDEV.P", stdevpImpl);
|
|
5996
|
+
const varImpl = {
|
|
5997
|
+
minArgs: 1,
|
|
5998
|
+
maxArgs: -1,
|
|
5999
|
+
evaluate(args, context, evaluator) {
|
|
6000
|
+
const nums = extractNums(args, context, evaluator);
|
|
6001
|
+
if (nums instanceof FormulaError) return nums;
|
|
6002
|
+
if (nums.length < 2) return new FormulaError("#DIV/0!", "VAR requires at least 2 values");
|
|
6003
|
+
const m = mean(nums);
|
|
6004
|
+
let sum = 0;
|
|
6005
|
+
for (const n of nums) sum += (n - m) * (n - m);
|
|
6006
|
+
return sum / (nums.length - 1);
|
|
6007
|
+
}
|
|
6008
|
+
};
|
|
6009
|
+
registry.set("VAR", varImpl);
|
|
6010
|
+
registry.set("VAR.S", varImpl);
|
|
6011
|
+
const varpImpl = {
|
|
6012
|
+
minArgs: 1,
|
|
6013
|
+
maxArgs: -1,
|
|
6014
|
+
evaluate(args, context, evaluator) {
|
|
6015
|
+
const nums = extractNums(args, context, evaluator);
|
|
6016
|
+
if (nums instanceof FormulaError) return nums;
|
|
6017
|
+
if (nums.length === 0) return new FormulaError("#DIV/0!", "VARP requires at least 1 value");
|
|
6018
|
+
const m = mean(nums);
|
|
6019
|
+
let sum = 0;
|
|
6020
|
+
for (const n of nums) sum += (n - m) * (n - m);
|
|
6021
|
+
return sum / nums.length;
|
|
6022
|
+
}
|
|
6023
|
+
};
|
|
6024
|
+
registry.set("VARP", varpImpl);
|
|
6025
|
+
registry.set("VAR.P", varpImpl);
|
|
6026
|
+
registry.set("CORREL", {
|
|
6027
|
+
minArgs: 2,
|
|
6028
|
+
maxArgs: 2,
|
|
6029
|
+
evaluate(args, context, evaluator) {
|
|
6030
|
+
const nums1 = extractNums([args[0]], context, evaluator);
|
|
6031
|
+
if (nums1 instanceof FormulaError) return nums1;
|
|
6032
|
+
const nums2 = extractNums([args[1]], context, evaluator);
|
|
6033
|
+
if (nums2 instanceof FormulaError) return nums2;
|
|
6034
|
+
if (nums1.length !== nums2.length) {
|
|
6035
|
+
return new FormulaError("#N/A", "CORREL: arrays must have same number of values");
|
|
6036
|
+
}
|
|
6037
|
+
if (nums1.length < 2) {
|
|
6038
|
+
return new FormulaError("#DIV/0!", "CORREL requires at least 2 paired values");
|
|
6039
|
+
}
|
|
6040
|
+
const m1 = mean(nums1);
|
|
6041
|
+
const m2 = mean(nums2);
|
|
6042
|
+
let cov = 0;
|
|
6043
|
+
let var1 = 0;
|
|
6044
|
+
let var2 = 0;
|
|
6045
|
+
for (let i = 0; i < nums1.length; i++) {
|
|
6046
|
+
const d1 = nums1[i] - m1;
|
|
6047
|
+
const d2 = nums2[i] - m2;
|
|
6048
|
+
cov += d1 * d2;
|
|
6049
|
+
var1 += d1 * d1;
|
|
6050
|
+
var2 += d2 * d2;
|
|
6051
|
+
}
|
|
6052
|
+
const denom = Math.sqrt(var1 * var2);
|
|
6053
|
+
if (denom === 0) return new FormulaError("#DIV/0!", "CORREL: zero variance");
|
|
6054
|
+
return cov / denom;
|
|
6055
|
+
}
|
|
6056
|
+
});
|
|
6057
|
+
function percentileCalc(nums, k) {
|
|
6058
|
+
if (k < 0 || k > 1) return new FormulaError("#NUM!", "PERCENTILE: k must be between 0 and 1");
|
|
6059
|
+
if (nums.length === 0) return new FormulaError("#NUM!", "PERCENTILE: empty array");
|
|
6060
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
6061
|
+
const idx = k * (sorted.length - 1);
|
|
6062
|
+
const low = Math.floor(idx);
|
|
6063
|
+
const high = Math.ceil(idx);
|
|
6064
|
+
if (low === high) return sorted[low];
|
|
6065
|
+
const frac = idx - low;
|
|
6066
|
+
return sorted[low] + frac * (sorted[high] - sorted[low]);
|
|
6067
|
+
}
|
|
6068
|
+
const percentileImpl = {
|
|
6069
|
+
minArgs: 2,
|
|
6070
|
+
maxArgs: 2,
|
|
6071
|
+
evaluate(args, context, evaluator) {
|
|
6072
|
+
const nums = extractNums([args[0]], context, evaluator);
|
|
6073
|
+
if (nums instanceof FormulaError) return nums;
|
|
6074
|
+
const rawK = evaluator.evaluate(args[1], context);
|
|
6075
|
+
if (rawK instanceof FormulaError) return rawK;
|
|
6076
|
+
const k = toNumber(rawK);
|
|
6077
|
+
if (k instanceof FormulaError) return k;
|
|
6078
|
+
return percentileCalc(nums, k);
|
|
6079
|
+
}
|
|
6080
|
+
};
|
|
6081
|
+
registry.set("PERCENTILE", percentileImpl);
|
|
6082
|
+
registry.set("PERCENTILE.INC", percentileImpl);
|
|
6083
|
+
const quartileImpl = {
|
|
6084
|
+
minArgs: 2,
|
|
6085
|
+
maxArgs: 2,
|
|
6086
|
+
evaluate(args, context, evaluator) {
|
|
6087
|
+
const nums = extractNums([args[0]], context, evaluator);
|
|
6088
|
+
if (nums instanceof FormulaError) return nums;
|
|
6089
|
+
const rawQuart = evaluator.evaluate(args[1], context);
|
|
6090
|
+
if (rawQuart instanceof FormulaError) return rawQuart;
|
|
6091
|
+
const quart = toNumber(rawQuart);
|
|
6092
|
+
if (quart instanceof FormulaError) return quart;
|
|
6093
|
+
const quartInt = Math.trunc(quart);
|
|
6094
|
+
if (quartInt < 0 || quartInt > 4) {
|
|
6095
|
+
return new FormulaError("#NUM!", "QUARTILE: quart must be 0, 1, 2, 3, or 4");
|
|
6096
|
+
}
|
|
6097
|
+
return percentileCalc(nums, quartInt * 0.25);
|
|
6098
|
+
}
|
|
6099
|
+
};
|
|
6100
|
+
registry.set("QUARTILE", quartileImpl);
|
|
6101
|
+
registry.set("QUARTILE.INC", quartileImpl);
|
|
6102
|
+
const modeImpl = {
|
|
6103
|
+
minArgs: 1,
|
|
6104
|
+
maxArgs: -1,
|
|
6105
|
+
evaluate(args, context, evaluator) {
|
|
6106
|
+
const nums = extractNums(args, context, evaluator);
|
|
6107
|
+
if (nums instanceof FormulaError) return nums;
|
|
6108
|
+
if (nums.length === 0) return new FormulaError("#N/A", "MODE: no numeric values");
|
|
6109
|
+
const freq = /* @__PURE__ */ new Map();
|
|
6110
|
+
for (const n of nums) {
|
|
6111
|
+
freq.set(n, (freq.get(n) ?? 0) + 1);
|
|
6112
|
+
}
|
|
6113
|
+
let maxFreq = 0;
|
|
6114
|
+
let modeVal = null;
|
|
6115
|
+
for (const n of nums) {
|
|
6116
|
+
const f = freq.get(n) ?? 0;
|
|
6117
|
+
if (f > maxFreq) {
|
|
6118
|
+
maxFreq = f;
|
|
6119
|
+
modeVal = n;
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
if (maxFreq < 1 || modeVal === null) return new FormulaError("#N/A", "MODE: no values");
|
|
6123
|
+
return modeVal;
|
|
6124
|
+
}
|
|
6125
|
+
};
|
|
6126
|
+
registry.set("MODE", modeImpl);
|
|
6127
|
+
registry.set("MODE.SNGL", modeImpl);
|
|
6128
|
+
registry.set("GEOMEAN", {
|
|
6129
|
+
minArgs: 1,
|
|
6130
|
+
maxArgs: -1,
|
|
6131
|
+
evaluate(args, context, evaluator) {
|
|
6132
|
+
const nums = extractNums(args, context, evaluator);
|
|
6133
|
+
if (nums instanceof FormulaError) return nums;
|
|
6134
|
+
if (nums.length === 0) return new FormulaError("#NUM!", "GEOMEAN: no numeric values");
|
|
6135
|
+
let sumLn = 0;
|
|
6136
|
+
for (const n of nums) {
|
|
6137
|
+
if (n <= 0) return new FormulaError("#NUM!", "GEOMEAN: all values must be positive");
|
|
6138
|
+
sumLn += Math.log(n);
|
|
6139
|
+
}
|
|
6140
|
+
return Math.exp(sumLn / nums.length);
|
|
6141
|
+
}
|
|
6142
|
+
});
|
|
6143
|
+
registry.set("HARMEAN", {
|
|
6144
|
+
minArgs: 1,
|
|
6145
|
+
maxArgs: -1,
|
|
6146
|
+
evaluate(args, context, evaluator) {
|
|
6147
|
+
const nums = extractNums(args, context, evaluator);
|
|
6148
|
+
if (nums instanceof FormulaError) return nums;
|
|
6149
|
+
if (nums.length === 0) return new FormulaError("#NUM!", "HARMEAN: no numeric values");
|
|
6150
|
+
let sumRecip = 0;
|
|
6151
|
+
for (const n of nums) {
|
|
6152
|
+
if (n <= 0) return new FormulaError("#NUM!", "HARMEAN: all values must be positive");
|
|
6153
|
+
sumRecip += 1 / n;
|
|
6154
|
+
}
|
|
6155
|
+
if (sumRecip === 0) return new FormulaError("#DIV/0!", "HARMEAN: sum of reciprocals is zero");
|
|
6156
|
+
return nums.length / sumRecip;
|
|
6157
|
+
}
|
|
6158
|
+
});
|
|
6159
|
+
}
|
|
6160
|
+
function registerReferenceFunctions(registry) {
|
|
6161
|
+
registry.set("INDIRECT", {
|
|
6162
|
+
minArgs: 1,
|
|
6163
|
+
maxArgs: 2,
|
|
6164
|
+
evaluate(args, context, evaluator) {
|
|
6165
|
+
const rawRef = evaluator.evaluate(args[0], context);
|
|
6166
|
+
if (rawRef instanceof FormulaError) return rawRef;
|
|
6167
|
+
const refText = String(rawRef ?? "");
|
|
6168
|
+
const range = parseRange(refText);
|
|
6169
|
+
if (range) {
|
|
6170
|
+
const start = range.start;
|
|
6171
|
+
const end = range.end;
|
|
6172
|
+
if (start.row === end.row && start.col === end.col) {
|
|
6173
|
+
return context.getCellValue(start);
|
|
6174
|
+
}
|
|
6175
|
+
return context.getCellValue(start);
|
|
6176
|
+
}
|
|
6177
|
+
const addr = parseCellRef(refText);
|
|
6178
|
+
if (!addr) {
|
|
6179
|
+
return new FormulaError("#REF!", `INDIRECT: invalid reference "${refText}"`);
|
|
6180
|
+
}
|
|
6181
|
+
return context.getCellValue(addr);
|
|
6182
|
+
}
|
|
6183
|
+
});
|
|
6184
|
+
registry.set("OFFSET", {
|
|
6185
|
+
minArgs: 3,
|
|
6186
|
+
maxArgs: 5,
|
|
6187
|
+
evaluate(args, context, evaluator) {
|
|
6188
|
+
let baseCol;
|
|
6189
|
+
let baseRow;
|
|
6190
|
+
if (args[0].kind === "cellRef") {
|
|
6191
|
+
baseCol = args[0].address.col;
|
|
6192
|
+
baseRow = args[0].address.row;
|
|
6193
|
+
} else if (args[0].kind === "range") {
|
|
6194
|
+
baseCol = args[0].start.col;
|
|
6195
|
+
baseRow = args[0].start.row;
|
|
6196
|
+
} else {
|
|
6197
|
+
return new FormulaError("#VALUE!", "OFFSET: first argument must be a cell reference");
|
|
6198
|
+
}
|
|
6199
|
+
const rawRows = evaluator.evaluate(args[1], context);
|
|
6200
|
+
if (rawRows instanceof FormulaError) return rawRows;
|
|
6201
|
+
const rowOffset = toNumber(rawRows);
|
|
6202
|
+
if (rowOffset instanceof FormulaError) return rowOffset;
|
|
6203
|
+
const rawCols = evaluator.evaluate(args[2], context);
|
|
6204
|
+
if (rawCols instanceof FormulaError) return rawCols;
|
|
6205
|
+
const colOffset = toNumber(rawCols);
|
|
6206
|
+
if (colOffset instanceof FormulaError) return colOffset;
|
|
6207
|
+
const targetRow = baseRow + Math.trunc(rowOffset);
|
|
6208
|
+
const targetCol = baseCol + Math.trunc(colOffset);
|
|
6209
|
+
if (targetRow < 0 || targetCol < 0) {
|
|
6210
|
+
return new FormulaError("#REF!", "OFFSET: reference out of bounds");
|
|
6211
|
+
}
|
|
6212
|
+
let height = 1;
|
|
6213
|
+
if (args.length >= 4) {
|
|
6214
|
+
const rawH = evaluator.evaluate(args[3], context);
|
|
6215
|
+
if (rawH instanceof FormulaError) return rawH;
|
|
6216
|
+
const h = toNumber(rawH);
|
|
6217
|
+
if (h instanceof FormulaError) return h;
|
|
6218
|
+
height = Math.trunc(h);
|
|
6219
|
+
}
|
|
6220
|
+
let width = 1;
|
|
6221
|
+
if (args.length >= 5) {
|
|
6222
|
+
const rawW = evaluator.evaluate(args[4], context);
|
|
6223
|
+
if (rawW instanceof FormulaError) return rawW;
|
|
6224
|
+
const w = toNumber(rawW);
|
|
6225
|
+
if (w instanceof FormulaError) return w;
|
|
6226
|
+
width = Math.trunc(w);
|
|
6227
|
+
}
|
|
6228
|
+
if (height <= 0 || width <= 0) {
|
|
6229
|
+
return new FormulaError("#VALUE!", "OFFSET: height and width must be >= 1");
|
|
6230
|
+
}
|
|
6231
|
+
if (height === 1 && width === 1) {
|
|
6232
|
+
return context.getCellValue({ col: targetCol, row: targetRow, absCol: false, absRow: false });
|
|
6233
|
+
}
|
|
6234
|
+
return context.getCellValue({ col: targetCol, row: targetRow, absCol: false, absRow: false });
|
|
6235
|
+
}
|
|
6236
|
+
});
|
|
6237
|
+
registry.set("ADDRESS", {
|
|
6238
|
+
minArgs: 2,
|
|
6239
|
+
maxArgs: 5,
|
|
6240
|
+
evaluate(args, context, evaluator) {
|
|
6241
|
+
const rawRow = evaluator.evaluate(args[0], context);
|
|
6242
|
+
if (rawRow instanceof FormulaError) return rawRow;
|
|
6243
|
+
const rowNum = toNumber(rawRow);
|
|
6244
|
+
if (rowNum instanceof FormulaError) return rowNum;
|
|
6245
|
+
const rawCol = evaluator.evaluate(args[1], context);
|
|
6246
|
+
if (rawCol instanceof FormulaError) return rawCol;
|
|
6247
|
+
const colNum = toNumber(rawCol);
|
|
6248
|
+
if (colNum instanceof FormulaError) return colNum;
|
|
6249
|
+
const row = Math.trunc(rowNum);
|
|
6250
|
+
const col = Math.trunc(colNum);
|
|
6251
|
+
if (row < 1 || col < 1) {
|
|
6252
|
+
return new FormulaError("#VALUE!", "ADDRESS: row and column must be >= 1");
|
|
6253
|
+
}
|
|
6254
|
+
let absNum = 1;
|
|
6255
|
+
if (args.length >= 3) {
|
|
6256
|
+
const rawAbs = evaluator.evaluate(args[2], context);
|
|
6257
|
+
if (rawAbs instanceof FormulaError) return rawAbs;
|
|
6258
|
+
const a = toNumber(rawAbs);
|
|
6259
|
+
if (a instanceof FormulaError) return a;
|
|
6260
|
+
absNum = Math.trunc(a);
|
|
6261
|
+
}
|
|
6262
|
+
let sheetText = "";
|
|
6263
|
+
if (args.length >= 5) {
|
|
6264
|
+
const rawSheet = evaluator.evaluate(args[4], context);
|
|
6265
|
+
if (rawSheet instanceof FormulaError) return rawSheet;
|
|
6266
|
+
if (rawSheet !== null && rawSheet !== void 0 && rawSheet !== false) {
|
|
6267
|
+
sheetText = String(rawSheet);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
const colLetter = indexToColumnLetter(col - 1);
|
|
6271
|
+
let address;
|
|
6272
|
+
switch (absNum) {
|
|
6273
|
+
case 1:
|
|
6274
|
+
address = `$${colLetter}$${row}`;
|
|
6275
|
+
break;
|
|
6276
|
+
// $A$1
|
|
6277
|
+
case 2:
|
|
6278
|
+
address = `${colLetter}$${row}`;
|
|
6279
|
+
break;
|
|
6280
|
+
// A$1
|
|
6281
|
+
case 3:
|
|
6282
|
+
address = `$${colLetter}${row}`;
|
|
6283
|
+
break;
|
|
6284
|
+
// $A1
|
|
6285
|
+
case 4:
|
|
6286
|
+
address = `${colLetter}${row}`;
|
|
6287
|
+
break;
|
|
6288
|
+
// A1
|
|
6289
|
+
default:
|
|
6290
|
+
address = `$${colLetter}$${row}`;
|
|
6291
|
+
}
|
|
6292
|
+
if (sheetText) {
|
|
6293
|
+
const quoted = sheetText.includes(" ") ? `'${sheetText}'` : sheetText;
|
|
6294
|
+
return `${quoted}!${address}`;
|
|
6295
|
+
}
|
|
6296
|
+
return address;
|
|
6297
|
+
}
|
|
6298
|
+
});
|
|
6299
|
+
registry.set("ROW", {
|
|
6300
|
+
minArgs: 0,
|
|
6301
|
+
maxArgs: 1,
|
|
6302
|
+
evaluate(args, context, evaluator) {
|
|
6303
|
+
if (args.length === 0) {
|
|
6304
|
+
return 1;
|
|
6305
|
+
}
|
|
6306
|
+
const arg = args[0];
|
|
6307
|
+
if (arg.kind === "cellRef") {
|
|
6308
|
+
return arg.address.row + 1;
|
|
6309
|
+
}
|
|
6310
|
+
if (arg.kind === "range") {
|
|
6311
|
+
return arg.start.row + 1;
|
|
6312
|
+
}
|
|
6313
|
+
const rawRef = evaluator.evaluate(arg, context);
|
|
6314
|
+
if (rawRef instanceof FormulaError) return rawRef;
|
|
6315
|
+
const refText = String(rawRef ?? "");
|
|
6316
|
+
const addr = parseCellRef(refText);
|
|
6317
|
+
if (!addr) {
|
|
6318
|
+
const rng = parseRange(refText);
|
|
6319
|
+
if (rng) return rng.start.row + 1;
|
|
6320
|
+
return new FormulaError("#VALUE!", "ROW: invalid reference");
|
|
6321
|
+
}
|
|
6322
|
+
return addr.row + 1;
|
|
6323
|
+
}
|
|
6324
|
+
});
|
|
6325
|
+
registry.set("COLUMN", {
|
|
6326
|
+
minArgs: 0,
|
|
6327
|
+
maxArgs: 1,
|
|
6328
|
+
evaluate(args, context, evaluator) {
|
|
6329
|
+
if (args.length === 0) {
|
|
6330
|
+
return 1;
|
|
6331
|
+
}
|
|
6332
|
+
const arg = args[0];
|
|
6333
|
+
if (arg.kind === "cellRef") {
|
|
6334
|
+
return arg.address.col + 1;
|
|
6335
|
+
}
|
|
6336
|
+
if (arg.kind === "range") {
|
|
6337
|
+
return arg.start.col + 1;
|
|
6338
|
+
}
|
|
6339
|
+
const rawRef = evaluator.evaluate(arg, context);
|
|
6340
|
+
if (rawRef instanceof FormulaError) return rawRef;
|
|
6341
|
+
const refText = String(rawRef ?? "");
|
|
6342
|
+
const addr = parseCellRef(refText);
|
|
6343
|
+
if (!addr) {
|
|
6344
|
+
const rng = parseRange(refText);
|
|
6345
|
+
if (rng) return rng.start.col + 1;
|
|
6346
|
+
return new FormulaError("#VALUE!", "COLUMN: invalid reference");
|
|
6347
|
+
}
|
|
6348
|
+
return addr.col + 1;
|
|
6349
|
+
}
|
|
6350
|
+
});
|
|
6351
|
+
registry.set("ROWS", {
|
|
6352
|
+
minArgs: 1,
|
|
6353
|
+
maxArgs: 1,
|
|
6354
|
+
evaluate(args, _context, _evaluator) {
|
|
6355
|
+
const arg = args[0];
|
|
6356
|
+
if (arg.kind === "range") {
|
|
6357
|
+
return Math.abs(arg.end.row - arg.start.row) + 1;
|
|
6358
|
+
}
|
|
6359
|
+
if (arg.kind === "cellRef") {
|
|
6360
|
+
return 1;
|
|
6361
|
+
}
|
|
6362
|
+
return new FormulaError("#VALUE!", "ROWS: argument must be a range reference");
|
|
6363
|
+
}
|
|
6364
|
+
});
|
|
6365
|
+
registry.set("COLUMNS", {
|
|
6366
|
+
minArgs: 1,
|
|
6367
|
+
maxArgs: 1,
|
|
6368
|
+
evaluate(args, _context, _evaluator) {
|
|
6369
|
+
const arg = args[0];
|
|
6370
|
+
if (arg.kind === "range") {
|
|
6371
|
+
return Math.abs(arg.end.col - arg.start.col) + 1;
|
|
6372
|
+
}
|
|
6373
|
+
if (arg.kind === "cellRef") {
|
|
6374
|
+
return 1;
|
|
6375
|
+
}
|
|
6376
|
+
return new FormulaError("#VALUE!", "COLUMNS: argument must be a range reference");
|
|
6377
|
+
}
|
|
6378
|
+
});
|
|
6379
|
+
registry.set("SEQUENCE", {
|
|
6380
|
+
minArgs: 1,
|
|
6381
|
+
maxArgs: 4,
|
|
6382
|
+
evaluate(args, context, evaluator) {
|
|
6383
|
+
const rawRows = evaluator.evaluate(args[0], context);
|
|
6384
|
+
if (rawRows instanceof FormulaError) return rawRows;
|
|
6385
|
+
const rows = toNumber(rawRows);
|
|
6386
|
+
if (rows instanceof FormulaError) return rows;
|
|
6387
|
+
let cols = 1;
|
|
6388
|
+
if (args.length >= 2) {
|
|
6389
|
+
const rawCols = evaluator.evaluate(args[1], context);
|
|
6390
|
+
if (rawCols instanceof FormulaError) return rawCols;
|
|
6391
|
+
const c = toNumber(rawCols);
|
|
6392
|
+
if (c instanceof FormulaError) return c;
|
|
6393
|
+
cols = Math.trunc(c);
|
|
6394
|
+
}
|
|
6395
|
+
let start = 1;
|
|
6396
|
+
if (args.length >= 3) {
|
|
6397
|
+
const rawStart = evaluator.evaluate(args[2], context);
|
|
6398
|
+
if (rawStart instanceof FormulaError) return rawStart;
|
|
6399
|
+
const s = toNumber(rawStart);
|
|
6400
|
+
if (s instanceof FormulaError) return s;
|
|
6401
|
+
start = s;
|
|
6402
|
+
}
|
|
6403
|
+
let step = 1;
|
|
6404
|
+
if (args.length >= 4) {
|
|
6405
|
+
const rawStep = evaluator.evaluate(args[3], context);
|
|
6406
|
+
if (rawStep instanceof FormulaError) return rawStep;
|
|
6407
|
+
const st = toNumber(rawStep);
|
|
6408
|
+
if (st instanceof FormulaError) return st;
|
|
6409
|
+
step = st;
|
|
6410
|
+
}
|
|
6411
|
+
const rowCount = Math.trunc(rows);
|
|
6412
|
+
const colCount = Math.max(1, cols);
|
|
6413
|
+
if (rowCount < 1) {
|
|
6414
|
+
return new FormulaError("#VALUE!", "SEQUENCE: rows must be >= 1");
|
|
6415
|
+
}
|
|
6416
|
+
const result = [];
|
|
6417
|
+
let current = start;
|
|
6418
|
+
for (let r = 0; r < rowCount; r++) {
|
|
6419
|
+
const row = [];
|
|
6420
|
+
for (let c = 0; c < colCount; c++) {
|
|
6421
|
+
row.push(current);
|
|
6422
|
+
current += step;
|
|
6423
|
+
}
|
|
6424
|
+
result.push(row);
|
|
6425
|
+
}
|
|
6426
|
+
if (rowCount === 1 && colCount === 1) {
|
|
6427
|
+
return result[0][0];
|
|
6428
|
+
}
|
|
6429
|
+
return result[0][0];
|
|
6430
|
+
}
|
|
6431
|
+
});
|
|
6432
|
+
registry.set("TRANSPOSE", {
|
|
4783
6433
|
minArgs: 1,
|
|
4784
6434
|
maxArgs: 1,
|
|
4785
|
-
evaluate(args, context,
|
|
4786
|
-
|
|
4787
|
-
|
|
6435
|
+
evaluate(args, context, _evaluator) {
|
|
6436
|
+
if (args[0].kind !== "range") {
|
|
6437
|
+
return new FormulaError("#VALUE!", "TRANSPOSE: argument must be a range");
|
|
6438
|
+
}
|
|
6439
|
+
const data = context.getRangeValues({ start: args[0].start, end: args[0].end });
|
|
6440
|
+
if (data.length === 0) return null;
|
|
6441
|
+
const rows = data.length;
|
|
6442
|
+
const cols = data[0].length;
|
|
6443
|
+
const transposed = [];
|
|
6444
|
+
for (let c = 0; c < cols; c++) {
|
|
6445
|
+
const newRow = [];
|
|
6446
|
+
for (let r = 0; r < rows; r++) {
|
|
6447
|
+
newRow.push(data[r][c]);
|
|
6448
|
+
}
|
|
6449
|
+
transposed.push(newRow);
|
|
6450
|
+
}
|
|
6451
|
+
return transposed[0][0] ?? null;
|
|
4788
6452
|
}
|
|
4789
6453
|
});
|
|
4790
|
-
registry.set("
|
|
6454
|
+
registry.set("MMULT", {
|
|
6455
|
+
minArgs: 2,
|
|
6456
|
+
maxArgs: 2,
|
|
6457
|
+
evaluate(args, context, _evaluator) {
|
|
6458
|
+
if (args[0].kind !== "range") {
|
|
6459
|
+
return new FormulaError("#VALUE!", "MMULT: array1 must be a range");
|
|
6460
|
+
}
|
|
6461
|
+
if (args[1].kind !== "range") {
|
|
6462
|
+
return new FormulaError("#VALUE!", "MMULT: array2 must be a range");
|
|
6463
|
+
}
|
|
6464
|
+
const a = context.getRangeValues({ start: args[0].start, end: args[0].end });
|
|
6465
|
+
const b = context.getRangeValues({ start: args[1].start, end: args[1].end });
|
|
6466
|
+
if (a.length === 0 || b.length === 0) {
|
|
6467
|
+
return new FormulaError("#VALUE!", "MMULT: empty array");
|
|
6468
|
+
}
|
|
6469
|
+
const aRows = a.length;
|
|
6470
|
+
const aCols = a[0].length;
|
|
6471
|
+
const bRows = b.length;
|
|
6472
|
+
const bCols = b[0].length;
|
|
6473
|
+
if (aCols !== bRows) {
|
|
6474
|
+
return new FormulaError("#VALUE!", `MMULT: columns of array1 (${aCols}) must equal rows of array2 (${bRows})`);
|
|
6475
|
+
}
|
|
6476
|
+
const result = [];
|
|
6477
|
+
for (let r = 0; r < aRows; r++) {
|
|
6478
|
+
const row = [];
|
|
6479
|
+
for (let c = 0; c < bCols; c++) {
|
|
6480
|
+
let sum = 0;
|
|
6481
|
+
for (let k = 0; k < aCols; k++) {
|
|
6482
|
+
const av = toNumber(a[r][k]);
|
|
6483
|
+
const bv = toNumber(b[k][c]);
|
|
6484
|
+
if (av instanceof FormulaError) return av;
|
|
6485
|
+
if (bv instanceof FormulaError) return bv;
|
|
6486
|
+
sum += av * bv;
|
|
6487
|
+
}
|
|
6488
|
+
row.push(sum);
|
|
6489
|
+
}
|
|
6490
|
+
result.push(row);
|
|
6491
|
+
}
|
|
6492
|
+
return result[0][0];
|
|
6493
|
+
}
|
|
6494
|
+
});
|
|
6495
|
+
registry.set("MDETERM", {
|
|
4791
6496
|
minArgs: 1,
|
|
4792
6497
|
maxArgs: 1,
|
|
4793
|
-
evaluate(args, context,
|
|
4794
|
-
|
|
4795
|
-
|
|
6498
|
+
evaluate(args, context, _evaluator) {
|
|
6499
|
+
if (args[0].kind !== "range") {
|
|
6500
|
+
return new FormulaError("#VALUE!", "MDETERM: argument must be a range");
|
|
6501
|
+
}
|
|
6502
|
+
const data = context.getRangeValues({ start: args[0].start, end: args[0].end });
|
|
6503
|
+
if (data.length === 0) return new FormulaError("#VALUE!", "MDETERM: empty array");
|
|
6504
|
+
const n = data.length;
|
|
6505
|
+
if (data.some((row) => row.length !== n)) {
|
|
6506
|
+
return new FormulaError("#VALUE!", "MDETERM: array must be square");
|
|
6507
|
+
}
|
|
6508
|
+
const matrix = [];
|
|
6509
|
+
for (let r = 0; r < n; r++) {
|
|
6510
|
+
const row = [];
|
|
6511
|
+
for (let c = 0; c < n; c++) {
|
|
6512
|
+
const v = toNumber(data[r][c]);
|
|
6513
|
+
if (v instanceof FormulaError) return v;
|
|
6514
|
+
row.push(v);
|
|
6515
|
+
}
|
|
6516
|
+
matrix.push(row);
|
|
6517
|
+
}
|
|
6518
|
+
return determinant(matrix);
|
|
4796
6519
|
}
|
|
4797
6520
|
});
|
|
4798
|
-
registry.set("
|
|
6521
|
+
registry.set("MINVERSE", {
|
|
4799
6522
|
minArgs: 1,
|
|
4800
6523
|
maxArgs: 1,
|
|
4801
|
-
evaluate(args, context,
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
if (
|
|
4807
|
-
|
|
4808
|
-
|
|
6524
|
+
evaluate(args, context, _evaluator) {
|
|
6525
|
+
if (args[0].kind !== "range") {
|
|
6526
|
+
return new FormulaError("#VALUE!", "MINVERSE: argument must be a range");
|
|
6527
|
+
}
|
|
6528
|
+
const data = context.getRangeValues({ start: args[0].start, end: args[0].end });
|
|
6529
|
+
if (data.length === 0) return new FormulaError("#VALUE!", "MINVERSE: empty array");
|
|
6530
|
+
const n = data.length;
|
|
6531
|
+
if (data.some((row) => row.length !== n)) {
|
|
6532
|
+
return new FormulaError("#VALUE!", "MINVERSE: array must be square");
|
|
6533
|
+
}
|
|
6534
|
+
const matrix = [];
|
|
6535
|
+
for (let r = 0; r < n; r++) {
|
|
6536
|
+
const row = [];
|
|
6537
|
+
for (let c = 0; c < n; c++) {
|
|
6538
|
+
const v = toNumber(data[r][c]);
|
|
6539
|
+
if (v instanceof FormulaError) return v;
|
|
6540
|
+
row.push(v);
|
|
6541
|
+
}
|
|
6542
|
+
matrix.push(row);
|
|
6543
|
+
}
|
|
6544
|
+
const inv = matrixInverse(matrix, n);
|
|
6545
|
+
if (inv instanceof FormulaError) return inv;
|
|
6546
|
+
return inv[0][0];
|
|
4809
6547
|
}
|
|
4810
6548
|
});
|
|
4811
6549
|
}
|
|
6550
|
+
function determinant(m) {
|
|
6551
|
+
const n = m.length;
|
|
6552
|
+
if (n === 1) return m[0][0];
|
|
6553
|
+
if (n === 2) return m[0][0] * m[1][1] - m[0][1] * m[1][0];
|
|
6554
|
+
let det = 0;
|
|
6555
|
+
for (let c = 0; c < n; c++) {
|
|
6556
|
+
const minor = [];
|
|
6557
|
+
for (let r = 1; r < n; r++) {
|
|
6558
|
+
const row = [];
|
|
6559
|
+
for (let cc = 0; cc < n; cc++) {
|
|
6560
|
+
if (cc !== c) row.push(m[r][cc]);
|
|
6561
|
+
}
|
|
6562
|
+
minor.push(row);
|
|
6563
|
+
}
|
|
6564
|
+
det += (c % 2 === 0 ? 1 : -1) * m[0][c] * determinant(minor);
|
|
6565
|
+
}
|
|
6566
|
+
return det;
|
|
6567
|
+
}
|
|
6568
|
+
function matrixInverse(m, n) {
|
|
6569
|
+
const aug = [];
|
|
6570
|
+
for (let r = 0; r < n; r++) {
|
|
6571
|
+
const row = [...m[r]];
|
|
6572
|
+
for (let c = 0; c < n; c++) {
|
|
6573
|
+
row.push(c === r ? 1 : 0);
|
|
6574
|
+
}
|
|
6575
|
+
aug.push(row);
|
|
6576
|
+
}
|
|
6577
|
+
for (let col = 0; col < n; col++) {
|
|
6578
|
+
let pivotRow = -1;
|
|
6579
|
+
let pivotVal = 0;
|
|
6580
|
+
for (let r = col; r < n; r++) {
|
|
6581
|
+
if (Math.abs(aug[r][col]) > Math.abs(pivotVal)) {
|
|
6582
|
+
pivotVal = aug[r][col];
|
|
6583
|
+
pivotRow = r;
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
if (pivotRow === -1 || Math.abs(pivotVal) < 1e-12) {
|
|
6587
|
+
return new FormulaError("#NUM!", "MINVERSE: matrix is singular");
|
|
6588
|
+
}
|
|
6589
|
+
if (pivotRow !== col) {
|
|
6590
|
+
[aug[col], aug[pivotRow]] = [aug[pivotRow], aug[col]];
|
|
6591
|
+
}
|
|
6592
|
+
const scale = aug[col][col];
|
|
6593
|
+
for (let c = 0; c < 2 * n; c++) {
|
|
6594
|
+
aug[col][c] /= scale;
|
|
6595
|
+
}
|
|
6596
|
+
for (let r = 0; r < n; r++) {
|
|
6597
|
+
if (r !== col) {
|
|
6598
|
+
const factor = aug[r][col];
|
|
6599
|
+
for (let c = 0; c < 2 * n; c++) {
|
|
6600
|
+
aug[r][c] -= factor * aug[col][c];
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
const result = [];
|
|
6606
|
+
for (let r = 0; r < n; r++) {
|
|
6607
|
+
result.push(aug[r].slice(n));
|
|
6608
|
+
}
|
|
6609
|
+
return result;
|
|
6610
|
+
}
|
|
4812
6611
|
function createBuiltInFunctions() {
|
|
4813
6612
|
const registry = /* @__PURE__ */ new Map();
|
|
4814
6613
|
registerMathFunctions(registry);
|
|
@@ -4818,6 +6617,9 @@ function createBuiltInFunctions() {
|
|
|
4818
6617
|
registerDateFunctions(registry);
|
|
4819
6618
|
registerStatsFunctions(registry);
|
|
4820
6619
|
registerInfoFunctions(registry);
|
|
6620
|
+
registerFinancialFunctions(registry);
|
|
6621
|
+
registerStatisticalExtendedFunctions(registry);
|
|
6622
|
+
registerReferenceFunctions(registry);
|
|
4821
6623
|
return registry;
|
|
4822
6624
|
}
|
|
4823
6625
|
function extractDependencies(node) {
|
|
@@ -5170,8 +6972,7 @@ var FormulaEngine = class {
|
|
|
5170
6972
|
return {
|
|
5171
6973
|
getCellValue: (addr) => {
|
|
5172
6974
|
const key = toCellKey(addr.col, addr.row, addr.sheet);
|
|
5173
|
-
|
|
5174
|
-
if (cached !== void 0) return cached;
|
|
6975
|
+
if (this.values.has(key)) return this.values.get(key);
|
|
5175
6976
|
if (addr.sheet) {
|
|
5176
6977
|
const sheetAccessor = this.sheetAccessors.get(addr.sheet);
|
|
5177
6978
|
if (!sheetAccessor) return new FormulaError("#REF!", `Unknown sheet: ${addr.sheet}`);
|
|
@@ -5194,18 +6995,21 @@ var FormulaEngine = class {
|
|
|
5194
6995
|
const row = [];
|
|
5195
6996
|
for (let c = minCol; c <= maxCol; c++) {
|
|
5196
6997
|
const key = toCellKey(c, r, sheet);
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
row.push(cached);
|
|
6998
|
+
if (this.values.has(key)) {
|
|
6999
|
+
row.push(this.values.get(key));
|
|
5200
7000
|
} else {
|
|
5201
|
-
row.push(rangeAccessor
|
|
7001
|
+
row.push(rangeAccessor?.getCellValue(c, r));
|
|
5202
7002
|
}
|
|
5203
7003
|
}
|
|
5204
7004
|
result.push(row);
|
|
5205
7005
|
}
|
|
5206
7006
|
return result;
|
|
5207
7007
|
},
|
|
5208
|
-
now: () => contextNow
|
|
7008
|
+
now: () => contextNow,
|
|
7009
|
+
getCellFormula: (addr) => {
|
|
7010
|
+
const key = toCellKey(addr.col, addr.row, addr.sheet);
|
|
7011
|
+
return this.formulas.get(key);
|
|
7012
|
+
}
|
|
5209
7013
|
};
|
|
5210
7014
|
}
|
|
5211
7015
|
recalcCells(order, accessor, updatedCells) {
|
|
@@ -5694,9 +7498,13 @@ var TableRenderer = class {
|
|
|
5694
7498
|
this.lastColumnWidths = {};
|
|
5695
7499
|
this.lastHeaderSignature = "";
|
|
5696
7500
|
this.lastRenderedItems = null;
|
|
7501
|
+
this.formulaEngine = null;
|
|
5697
7502
|
this.container = container;
|
|
5698
7503
|
this.state = state;
|
|
5699
7504
|
}
|
|
7505
|
+
setFormulaEngine(engine) {
|
|
7506
|
+
this.formulaEngine = engine;
|
|
7507
|
+
}
|
|
5700
7508
|
setVirtualScrollState(vs) {
|
|
5701
7509
|
this.virtualScrollState = vs;
|
|
5702
7510
|
}
|
|
@@ -5774,12 +7582,13 @@ var TableRenderer = class {
|
|
|
5774
7582
|
const target = e.target;
|
|
5775
7583
|
if (target.classList.contains("ogrid-resize-handle")) {
|
|
5776
7584
|
e.stopPropagation();
|
|
7585
|
+
const handleColumnId = target.getAttribute("data-column-id");
|
|
5777
7586
|
const th = target.closest("th[data-column-id]");
|
|
5778
|
-
|
|
5779
|
-
const columnId = th.getAttribute("data-column-id");
|
|
7587
|
+
const columnId = handleColumnId ?? th?.getAttribute("data-column-id");
|
|
5780
7588
|
if (columnId) {
|
|
5781
|
-
const
|
|
5782
|
-
|
|
7589
|
+
const parentTh = target.closest("th");
|
|
7590
|
+
const rect = parentTh?.getBoundingClientRect();
|
|
7591
|
+
this.interactionState?.onResizeStart?.(columnId, e.clientX, rect?.width ?? ROW_NUMBER_COLUMN_WIDTH);
|
|
5783
7592
|
}
|
|
5784
7593
|
return;
|
|
5785
7594
|
}
|
|
@@ -5797,9 +7606,9 @@ var TableRenderer = class {
|
|
|
5797
7606
|
const target = e.target;
|
|
5798
7607
|
if (target.classList.contains("ogrid-resize-handle")) {
|
|
5799
7608
|
e.stopPropagation();
|
|
7609
|
+
const handleColumnId = target.getAttribute("data-column-id");
|
|
5800
7610
|
const th = target.closest("th[data-column-id]");
|
|
5801
|
-
|
|
5802
|
-
const columnId = th.getAttribute("data-column-id");
|
|
7611
|
+
const columnId = handleColumnId ?? th?.getAttribute("data-column-id");
|
|
5803
7612
|
if (columnId) {
|
|
5804
7613
|
this.interactionState?.onResizeDoubleClick?.(columnId);
|
|
5805
7614
|
}
|
|
@@ -5876,6 +7685,9 @@ var TableRenderer = class {
|
|
|
5876
7685
|
parts.push(`allSel:${is?.allSelected ?? ""}`);
|
|
5877
7686
|
parts.push(`someSel:${is?.someSelected ?? ""}`);
|
|
5878
7687
|
parts.push(`rn:${is?.showRowNumbers ?? ""}`);
|
|
7688
|
+
if (is?.showRowNumbers) {
|
|
7689
|
+
parts.push(`rnw:${is?.columnWidths[ROW_NUMBER_COLUMN_ID] ?? ""}`);
|
|
7690
|
+
}
|
|
5879
7691
|
parts.push(`cl:${is?.showColumnLetters ?? ""}`);
|
|
5880
7692
|
for (const [colId, config] of this.filterConfigs) {
|
|
5881
7693
|
const hasActive = this.headerFilterState?.hasActiveFilter(config);
|
|
@@ -6091,7 +7903,8 @@ var TableRenderer = class {
|
|
|
6091
7903
|
if (hasRowNumbers) {
|
|
6092
7904
|
const th = document.createElement("th");
|
|
6093
7905
|
th.className = "ogrid-column-letter-cell";
|
|
6094
|
-
|
|
7906
|
+
const rnw = this.interactionState?.columnWidths[ROW_NUMBER_COLUMN_ID] ?? ROW_NUMBER_COLUMN_WIDTH;
|
|
7907
|
+
th.style.width = `${rnw}px`;
|
|
6095
7908
|
letterTr.appendChild(th);
|
|
6096
7909
|
}
|
|
6097
7910
|
for (let colIdx = 0; colIdx < visibleCols.length; colIdx++) {
|
|
@@ -6104,17 +7917,49 @@ var TableRenderer = class {
|
|
|
6104
7917
|
}
|
|
6105
7918
|
const headerRows = buildHeaderRows(this.state.allColumns, this.state.visibleColumns);
|
|
6106
7919
|
if (headerRows.length > 1) {
|
|
6107
|
-
for (
|
|
7920
|
+
for (let rowIdx = 0; rowIdx < headerRows.length; rowIdx++) {
|
|
7921
|
+
const row = headerRows[rowIdx];
|
|
7922
|
+
const isLastRow = rowIdx === headerRows.length - 1;
|
|
6108
7923
|
const tr = document.createElement("tr");
|
|
6109
7924
|
if (hasCheckbox) {
|
|
6110
7925
|
const th = document.createElement("th");
|
|
6111
7926
|
th.className = "ogrid-header-cell ogrid-checkbox-header";
|
|
6112
7927
|
th.style.width = `${CHECKBOX_COLUMN_WIDTH}px`;
|
|
6113
|
-
if (
|
|
7928
|
+
if (isLastRow) {
|
|
6114
7929
|
this.appendSelectAllCheckbox(th);
|
|
6115
7930
|
}
|
|
6116
7931
|
tr.appendChild(th);
|
|
6117
7932
|
}
|
|
7933
|
+
if (hasRowNumbers) {
|
|
7934
|
+
if (isLastRow) {
|
|
7935
|
+
const rnw = this.interactionState?.columnWidths[ROW_NUMBER_COLUMN_ID] ?? ROW_NUMBER_COLUMN_WIDTH;
|
|
7936
|
+
const th = document.createElement("th");
|
|
7937
|
+
th.className = "ogrid-header-cell ogrid-row-number-header";
|
|
7938
|
+
th.style.width = `${rnw}px`;
|
|
7939
|
+
th.style.minWidth = `${rnw}px`;
|
|
7940
|
+
th.style.maxWidth = `${rnw}px`;
|
|
7941
|
+
th.style.textAlign = "center";
|
|
7942
|
+
th.style.position = th.style.position || "relative";
|
|
7943
|
+
th.textContent = "#";
|
|
7944
|
+
const resizeHandle = document.createElement("div");
|
|
7945
|
+
resizeHandle.className = "ogrid-resize-handle";
|
|
7946
|
+
resizeHandle.style.position = "absolute";
|
|
7947
|
+
resizeHandle.style.right = "0";
|
|
7948
|
+
resizeHandle.style.top = "0";
|
|
7949
|
+
resizeHandle.style.bottom = "0";
|
|
7950
|
+
resizeHandle.style.width = "4px";
|
|
7951
|
+
resizeHandle.style.cursor = "col-resize";
|
|
7952
|
+
resizeHandle.style.userSelect = "none";
|
|
7953
|
+
resizeHandle.setAttribute("data-column-id", ROW_NUMBER_COLUMN_ID);
|
|
7954
|
+
th.appendChild(resizeHandle);
|
|
7955
|
+
tr.appendChild(th);
|
|
7956
|
+
} else if (rowIdx === 0) {
|
|
7957
|
+
const th = document.createElement("th");
|
|
7958
|
+
th.rowSpan = headerRows.length - 1;
|
|
7959
|
+
th.style.padding = "0";
|
|
7960
|
+
tr.appendChild(th);
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
6118
7963
|
for (const cell of row) {
|
|
6119
7964
|
const th = document.createElement("th");
|
|
6120
7965
|
th.textContent = cell.label;
|
|
@@ -6149,11 +7994,26 @@ var TableRenderer = class {
|
|
|
6149
7994
|
tr.appendChild(th);
|
|
6150
7995
|
}
|
|
6151
7996
|
if (this.hasRowNumbersColumn()) {
|
|
7997
|
+
const rnw = this.interactionState?.columnWidths[ROW_NUMBER_COLUMN_ID] ?? ROW_NUMBER_COLUMN_WIDTH;
|
|
6152
7998
|
const th = document.createElement("th");
|
|
6153
7999
|
th.className = "ogrid-header-cell ogrid-row-number-header";
|
|
6154
|
-
th.style.width = `${
|
|
8000
|
+
th.style.width = `${rnw}px`;
|
|
8001
|
+
th.style.minWidth = `${rnw}px`;
|
|
8002
|
+
th.style.maxWidth = `${rnw}px`;
|
|
6155
8003
|
th.style.textAlign = "center";
|
|
8004
|
+
th.style.position = th.style.position || "relative";
|
|
6156
8005
|
th.textContent = "#";
|
|
8006
|
+
const resizeHandle = document.createElement("div");
|
|
8007
|
+
resizeHandle.className = "ogrid-resize-handle";
|
|
8008
|
+
resizeHandle.style.position = "absolute";
|
|
8009
|
+
resizeHandle.style.right = "0";
|
|
8010
|
+
resizeHandle.style.top = "0";
|
|
8011
|
+
resizeHandle.style.bottom = "0";
|
|
8012
|
+
resizeHandle.style.width = "4px";
|
|
8013
|
+
resizeHandle.style.cursor = "col-resize";
|
|
8014
|
+
resizeHandle.style.userSelect = "none";
|
|
8015
|
+
resizeHandle.setAttribute("data-column-id", ROW_NUMBER_COLUMN_ID);
|
|
8016
|
+
th.appendChild(resizeHandle);
|
|
6157
8017
|
tr.appendChild(th);
|
|
6158
8018
|
}
|
|
6159
8019
|
for (let colIdx = 0; colIdx < visibleCols.length; colIdx++) {
|
|
@@ -6325,9 +8185,12 @@ var TableRenderer = class {
|
|
|
6325
8185
|
tr.appendChild(td);
|
|
6326
8186
|
}
|
|
6327
8187
|
if (hasRowNumbers) {
|
|
8188
|
+
const rnw = this.interactionState?.columnWidths[ROW_NUMBER_COLUMN_ID] ?? ROW_NUMBER_COLUMN_WIDTH;
|
|
6328
8189
|
const td = document.createElement("td");
|
|
6329
8190
|
td.className = "ogrid-cell ogrid-row-number-cell";
|
|
6330
|
-
td.style.width = `${
|
|
8191
|
+
td.style.width = `${rnw}px`;
|
|
8192
|
+
td.style.minWidth = `${rnw}px`;
|
|
8193
|
+
td.style.maxWidth = `${rnw}px`;
|
|
6331
8194
|
td.style.textAlign = "center";
|
|
6332
8195
|
td.style.color = "var(--ogrid-fg-muted, #666)";
|
|
6333
8196
|
td.style.fontSize = "0.9em";
|
|
@@ -6377,11 +8240,22 @@ var TableRenderer = class {
|
|
|
6377
8240
|
}
|
|
6378
8241
|
}
|
|
6379
8242
|
if (col.renderCell) {
|
|
6380
|
-
const
|
|
8243
|
+
const rawValue = getCellValue(item, col);
|
|
8244
|
+
const value = this.formulaEngine?.isEnabled() && this.formulaEngine.hasFormula(colIndex, rowIndex) ? this.formulaEngine.getValue(colIndex, rowIndex) ?? rawValue : rawValue;
|
|
6381
8245
|
col.renderCell(td, item, value);
|
|
6382
8246
|
} else {
|
|
6383
|
-
const
|
|
6384
|
-
|
|
8247
|
+
const rawValue = getCellValue(item, col);
|
|
8248
|
+
const value = this.formulaEngine?.isEnabled() && this.formulaEngine.hasFormula(colIndex, rowIndex) ? this.formulaEngine.getValue(colIndex, rowIndex) ?? rawValue : rawValue;
|
|
8249
|
+
if (col.type === "boolean") {
|
|
8250
|
+
const checkbox = document.createElement("input");
|
|
8251
|
+
checkbox.type = "checkbox";
|
|
8252
|
+
checkbox.checked = Boolean(value);
|
|
8253
|
+
checkbox.disabled = true;
|
|
8254
|
+
checkbox.style.margin = "0";
|
|
8255
|
+
checkbox.style.pointerEvents = "none";
|
|
8256
|
+
checkbox.setAttribute("aria-label", value ? "True" : "False");
|
|
8257
|
+
td.appendChild(checkbox);
|
|
8258
|
+
} else if (col.valueFormatter) {
|
|
6385
8259
|
td.textContent = col.valueFormatter(value, item);
|
|
6386
8260
|
} else if (value != null) {
|
|
6387
8261
|
td.textContent = String(value);
|
|
@@ -8522,13 +10396,15 @@ var ColumnResizeState = class {
|
|
|
8522
10396
|
updateResize(clientX) {
|
|
8523
10397
|
if (!this.isResizing || !this.resizeColumnId) return null;
|
|
8524
10398
|
const delta = clientX - this.resizeStartX;
|
|
8525
|
-
const
|
|
10399
|
+
const minW = this.resizeColumnId === ROW_NUMBER_COLUMN_ID ? ROW_NUMBER_COLUMN_MIN_WIDTH : DEFAULT_MIN_COLUMN_WIDTH;
|
|
10400
|
+
const newWidth = Math.max(minW, this.resizeStartWidth + delta);
|
|
8526
10401
|
return newWidth;
|
|
8527
10402
|
}
|
|
8528
10403
|
endResize(clientX) {
|
|
8529
10404
|
if (!this.isResizing || !this.resizeColumnId) return;
|
|
8530
10405
|
const delta = clientX - this.resizeStartX;
|
|
8531
|
-
const
|
|
10406
|
+
const minW = this.resizeColumnId === ROW_NUMBER_COLUMN_ID ? ROW_NUMBER_COLUMN_MIN_WIDTH : DEFAULT_MIN_COLUMN_WIDTH;
|
|
10407
|
+
const newWidth = Math.max(minW, this.resizeStartWidth + delta);
|
|
8532
10408
|
this.columnWidths.set(this.resizeColumnId, newWidth);
|
|
8533
10409
|
this.emitter.emit("columnWidthChange", { columnId: this.resizeColumnId, widthPx: newWidth });
|
|
8534
10410
|
this.isResizing = false;
|
|
@@ -9121,6 +10997,8 @@ var InlineCellEditor = class {
|
|
|
9121
10997
|
e.stopPropagation();
|
|
9122
10998
|
this.onCancel?.();
|
|
9123
10999
|
this.closeEditor();
|
|
11000
|
+
} else if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
|
|
11001
|
+
e.stopPropagation();
|
|
9124
11002
|
} else if ((e.ctrlKey || e.metaKey) && ["c", "x", "v", "a", "z", "y"].includes(e.key)) {
|
|
9125
11003
|
e.stopPropagation();
|
|
9126
11004
|
}
|
|
@@ -9165,6 +11043,8 @@ var InlineCellEditor = class {
|
|
|
9165
11043
|
e.stopPropagation();
|
|
9166
11044
|
this.onCancel?.();
|
|
9167
11045
|
this.closeEditor();
|
|
11046
|
+
} else if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
|
|
11047
|
+
e.stopPropagation();
|
|
9168
11048
|
}
|
|
9169
11049
|
});
|
|
9170
11050
|
return input;
|
|
@@ -9263,6 +11143,7 @@ var InlineCellEditor = class {
|
|
|
9263
11143
|
};
|
|
9264
11144
|
buildOptions();
|
|
9265
11145
|
wrapper.addEventListener("keydown", (e) => {
|
|
11146
|
+
e.stopPropagation();
|
|
9266
11147
|
switch (e.key) {
|
|
9267
11148
|
case "ArrowDown": {
|
|
9268
11149
|
e.preventDefault();
|
|
@@ -9384,6 +11265,8 @@ var InlineCellEditor = class {
|
|
|
9384
11265
|
e.stopPropagation();
|
|
9385
11266
|
this.onCancel?.();
|
|
9386
11267
|
this.closeEditor();
|
|
11268
|
+
} else if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) {
|
|
11269
|
+
e.stopPropagation();
|
|
9387
11270
|
} else if ((e.ctrlKey || e.metaKey) && ["c", "x", "v", "a", "z", "y"].includes(e.key)) {
|
|
9388
11271
|
e.stopPropagation();
|
|
9389
11272
|
}
|
|
@@ -9917,90 +11800,296 @@ var OGridRendering = class {
|
|
|
9917
11800
|
this.renderSideBar();
|
|
9918
11801
|
this.renderLoadingOverlay();
|
|
9919
11802
|
}
|
|
9920
|
-
renderHeaderFilterPopover() {
|
|
9921
|
-
const { headerFilterState, headerFilterComponent, filterConfigs } = this.ctx;
|
|
9922
|
-
const openId = headerFilterState.openColumnId;
|
|
9923
|
-
const allBtns = this.ctx.tableContainer.querySelectorAll(".ogrid-filter-icon[aria-haspopup]");
|
|
9924
|
-
for (const btn of allBtns) {
|
|
9925
|
-
const colId = btn.closest("th[data-column-id]")?.getAttribute("data-column-id");
|
|
9926
|
-
btn.setAttribute("aria-expanded", colId === openId ? "true" : "false");
|
|
9927
|
-
}
|
|
9928
|
-
if (!openId) {
|
|
9929
|
-
headerFilterComponent.cleanup();
|
|
9930
|
-
return;
|
|
9931
|
-
}
|
|
9932
|
-
const config = filterConfigs.get(openId);
|
|
9933
|
-
if (!config) return;
|
|
9934
|
-
headerFilterComponent.render(config);
|
|
9935
|
-
const popoverEl = document.querySelector(".ogrid-header-filter-popover");
|
|
9936
|
-
headerFilterState.setPopoverEl(popoverEl);
|
|
11803
|
+
renderHeaderFilterPopover() {
|
|
11804
|
+
const { headerFilterState, headerFilterComponent, filterConfigs } = this.ctx;
|
|
11805
|
+
const openId = headerFilterState.openColumnId;
|
|
11806
|
+
const allBtns = this.ctx.tableContainer.querySelectorAll(".ogrid-filter-icon[aria-haspopup]");
|
|
11807
|
+
for (const btn of allBtns) {
|
|
11808
|
+
const colId = btn.closest("th[data-column-id]")?.getAttribute("data-column-id");
|
|
11809
|
+
btn.setAttribute("aria-expanded", colId === openId ? "true" : "false");
|
|
11810
|
+
}
|
|
11811
|
+
if (!openId) {
|
|
11812
|
+
headerFilterComponent.cleanup();
|
|
11813
|
+
return;
|
|
11814
|
+
}
|
|
11815
|
+
const config = filterConfigs.get(openId);
|
|
11816
|
+
if (!config) return;
|
|
11817
|
+
headerFilterComponent.render(config);
|
|
11818
|
+
const popoverEl = document.querySelector(".ogrid-header-filter-popover");
|
|
11819
|
+
headerFilterState.setPopoverEl(popoverEl);
|
|
11820
|
+
}
|
|
11821
|
+
renderSideBar() {
|
|
11822
|
+
const { sideBarComponent, sideBarState, state } = this.ctx;
|
|
11823
|
+
if (!sideBarComponent || !sideBarState) return;
|
|
11824
|
+
const columns = state.columns.map((c) => ({
|
|
11825
|
+
columnId: c.columnId,
|
|
11826
|
+
name: c.name,
|
|
11827
|
+
required: c.required === true
|
|
11828
|
+
}));
|
|
11829
|
+
const filterableColumns = state.columns.filter((c) => c.filterable && typeof c.filterable === "object" && c.filterable.type).map((c) => ({
|
|
11830
|
+
columnId: c.columnId,
|
|
11831
|
+
name: c.name,
|
|
11832
|
+
filterField: c.filterable.filterField ?? c.columnId,
|
|
11833
|
+
filterType: c.filterable.type
|
|
11834
|
+
}));
|
|
11835
|
+
sideBarComponent.setConfig({
|
|
11836
|
+
columns,
|
|
11837
|
+
visibleColumns: state.visibleColumns,
|
|
11838
|
+
onVisibilityChange: (columnKey, visible) => {
|
|
11839
|
+
const next = new Set(state.visibleColumns);
|
|
11840
|
+
if (visible) next.add(columnKey);
|
|
11841
|
+
else next.delete(columnKey);
|
|
11842
|
+
state.setVisibleColumns(next);
|
|
11843
|
+
},
|
|
11844
|
+
onSetVisibleColumns: (cols) => state.setVisibleColumns(cols),
|
|
11845
|
+
filterableColumns,
|
|
11846
|
+
filters: state.filters,
|
|
11847
|
+
onFilterChange: (key, value) => state.setFilter(key, value),
|
|
11848
|
+
filterOptions: state.filterOptions
|
|
11849
|
+
});
|
|
11850
|
+
sideBarComponent.render();
|
|
11851
|
+
}
|
|
11852
|
+
renderLoadingOverlay() {
|
|
11853
|
+
const { state, tableContainer } = this.ctx;
|
|
11854
|
+
if (state.isLoading) {
|
|
11855
|
+
const { items } = state.getProcessedItems();
|
|
11856
|
+
tableContainer.style.minHeight = !items || items.length === 0 ? "200px" : "";
|
|
11857
|
+
let loadingOverlay = this.ctx.loadingOverlay;
|
|
11858
|
+
if (!loadingOverlay) {
|
|
11859
|
+
loadingOverlay = document.createElement("div");
|
|
11860
|
+
loadingOverlay.className = "ogrid-loading-overlay";
|
|
11861
|
+
loadingOverlay.style.position = "absolute";
|
|
11862
|
+
loadingOverlay.style.top = "0";
|
|
11863
|
+
loadingOverlay.style.left = "0";
|
|
11864
|
+
loadingOverlay.style.right = "0";
|
|
11865
|
+
loadingOverlay.style.bottom = "0";
|
|
11866
|
+
loadingOverlay.style.display = "flex";
|
|
11867
|
+
loadingOverlay.style.alignItems = "center";
|
|
11868
|
+
loadingOverlay.style.justifyContent = "center";
|
|
11869
|
+
loadingOverlay.style.background = "var(--ogrid-loading-overlay, rgba(255, 255, 255, 0.7))";
|
|
11870
|
+
loadingOverlay.style.zIndex = "100";
|
|
11871
|
+
const spinner = document.createElement("div");
|
|
11872
|
+
spinner.className = "ogrid-loading-spinner";
|
|
11873
|
+
spinner.textContent = "Loading...";
|
|
11874
|
+
loadingOverlay.appendChild(spinner);
|
|
11875
|
+
this.ctx.setLoadingOverlay(loadingOverlay);
|
|
11876
|
+
}
|
|
11877
|
+
if (!tableContainer.contains(loadingOverlay)) {
|
|
11878
|
+
tableContainer.appendChild(loadingOverlay);
|
|
11879
|
+
}
|
|
11880
|
+
} else {
|
|
11881
|
+
tableContainer.style.minHeight = "";
|
|
11882
|
+
const loadingOverlay = this.ctx.loadingOverlay;
|
|
11883
|
+
if (loadingOverlay && tableContainer.contains(loadingOverlay)) {
|
|
11884
|
+
loadingOverlay.remove();
|
|
11885
|
+
}
|
|
11886
|
+
}
|
|
11887
|
+
}
|
|
11888
|
+
};
|
|
11889
|
+
|
|
11890
|
+
// src/state/FormulaEngineState.ts
|
|
11891
|
+
var FormulaEngineState = class {
|
|
11892
|
+
constructor(options) {
|
|
11893
|
+
this.emitter = new EventEmitter();
|
|
11894
|
+
this.engine = null;
|
|
11895
|
+
this.options = options;
|
|
11896
|
+
if (options.formulas) {
|
|
11897
|
+
this.engine = new FormulaEngine({
|
|
11898
|
+
customFunctions: options.formulaFunctions,
|
|
11899
|
+
namedRanges: options.namedRanges
|
|
11900
|
+
});
|
|
11901
|
+
if (options.sheets) {
|
|
11902
|
+
for (const [name, accessor] of Object.entries(options.sheets)) {
|
|
11903
|
+
this.engine.registerSheet(name, accessor);
|
|
11904
|
+
}
|
|
11905
|
+
}
|
|
11906
|
+
}
|
|
11907
|
+
}
|
|
11908
|
+
/**
|
|
11909
|
+
* Initialize with an accessor — loads `initialFormulas` if provided.
|
|
11910
|
+
* Must be called after the grid data is available so the accessor is valid.
|
|
11911
|
+
*/
|
|
11912
|
+
initialize(accessor) {
|
|
11913
|
+
if (!this.engine || !this.options.initialFormulas?.length) return;
|
|
11914
|
+
const result = this.engine.loadFormulas(this.options.initialFormulas, accessor);
|
|
11915
|
+
if (result.updatedCells.length > 0) {
|
|
11916
|
+
this.emitRecalc(result);
|
|
11917
|
+
}
|
|
11918
|
+
}
|
|
11919
|
+
/**
|
|
11920
|
+
* Set or clear a formula for a cell. Triggers recalculation of dependents
|
|
11921
|
+
* and emits `formulaRecalc`.
|
|
11922
|
+
*/
|
|
11923
|
+
setFormula(col, row, formula, accessor) {
|
|
11924
|
+
if (!this.engine) return void 0;
|
|
11925
|
+
const result = this.engine.setFormula(col, row, formula, accessor);
|
|
11926
|
+
if (result.updatedCells.length > 0) {
|
|
11927
|
+
this.emitRecalc(result);
|
|
11928
|
+
}
|
|
11929
|
+
return result;
|
|
11930
|
+
}
|
|
11931
|
+
/**
|
|
11932
|
+
* Notify the engine that a non-formula cell's value changed.
|
|
11933
|
+
* Triggers recalculation of any formulas that depend on the changed cell.
|
|
11934
|
+
*/
|
|
11935
|
+
onCellChanged(col, row, accessor) {
|
|
11936
|
+
if (!this.engine) return void 0;
|
|
11937
|
+
const result = this.engine.onCellChanged(col, row, accessor);
|
|
11938
|
+
if (result.updatedCells.length > 0) {
|
|
11939
|
+
this.emitRecalc(result);
|
|
11940
|
+
}
|
|
11941
|
+
return result;
|
|
11942
|
+
}
|
|
11943
|
+
/** Get the computed value for a formula cell (or undefined if no formula). */
|
|
11944
|
+
getValue(col, row) {
|
|
11945
|
+
return this.engine?.getValue(col, row);
|
|
11946
|
+
}
|
|
11947
|
+
/** Check if a cell has a formula. */
|
|
11948
|
+
hasFormula(col, row) {
|
|
11949
|
+
return this.engine?.hasFormula(col, row) ?? false;
|
|
11950
|
+
}
|
|
11951
|
+
/** Get the formula string for a cell (or undefined if no formula). */
|
|
11952
|
+
getFormula(col, row) {
|
|
11953
|
+
return this.engine?.getFormula(col, row);
|
|
11954
|
+
}
|
|
11955
|
+
/** Whether the formula engine is active. */
|
|
11956
|
+
isEnabled() {
|
|
11957
|
+
return this.engine !== null;
|
|
11958
|
+
}
|
|
11959
|
+
/** Define a named range. */
|
|
11960
|
+
defineNamedRange(name, ref) {
|
|
11961
|
+
this.engine?.defineNamedRange(name, ref);
|
|
11962
|
+
}
|
|
11963
|
+
/** Remove a named range. */
|
|
11964
|
+
removeNamedRange(name) {
|
|
11965
|
+
this.engine?.removeNamedRange(name);
|
|
11966
|
+
}
|
|
11967
|
+
/** Register a sheet accessor for cross-sheet references. */
|
|
11968
|
+
registerSheet(name, accessor) {
|
|
11969
|
+
this.engine?.registerSheet(name, accessor);
|
|
11970
|
+
}
|
|
11971
|
+
/** Unregister a sheet accessor. */
|
|
11972
|
+
unregisterSheet(name) {
|
|
11973
|
+
this.engine?.unregisterSheet(name);
|
|
11974
|
+
}
|
|
11975
|
+
/** Get all cells that a cell depends on (deep, transitive). */
|
|
11976
|
+
getPrecedents(col, row) {
|
|
11977
|
+
return this.engine?.getPrecedents(col, row) ?? [];
|
|
11978
|
+
}
|
|
11979
|
+
/** Get all cells that depend on a cell (deep, transitive). */
|
|
11980
|
+
getDependents(col, row) {
|
|
11981
|
+
return this.engine?.getDependents(col, row) ?? [];
|
|
11982
|
+
}
|
|
11983
|
+
/** Get full audit trail for a cell. */
|
|
11984
|
+
getAuditTrail(col, row) {
|
|
11985
|
+
return this.engine?.getAuditTrail(col, row) ?? null;
|
|
11986
|
+
}
|
|
11987
|
+
/** Subscribe to the `formulaRecalc` event. Returns an unsubscribe function. */
|
|
11988
|
+
onFormulaRecalc(handler) {
|
|
11989
|
+
this.emitter.on("formulaRecalc", handler);
|
|
11990
|
+
return () => this.emitter.off("formulaRecalc", handler);
|
|
9937
11991
|
}
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
columnId: c.columnId,
|
|
9943
|
-
name: c.name,
|
|
9944
|
-
required: c.required === true
|
|
9945
|
-
}));
|
|
9946
|
-
const filterableColumns = state.columns.filter((c) => c.filterable && typeof c.filterable === "object" && c.filterable.type).map((c) => ({
|
|
9947
|
-
columnId: c.columnId,
|
|
9948
|
-
name: c.name,
|
|
9949
|
-
filterField: c.filterable.filterField ?? c.columnId,
|
|
9950
|
-
filterType: c.filterable.type
|
|
9951
|
-
}));
|
|
9952
|
-
sideBarComponent.setConfig({
|
|
9953
|
-
columns,
|
|
9954
|
-
visibleColumns: state.visibleColumns,
|
|
9955
|
-
onVisibilityChange: (columnKey, visible) => {
|
|
9956
|
-
const next = new Set(state.visibleColumns);
|
|
9957
|
-
if (visible) next.add(columnKey);
|
|
9958
|
-
else next.delete(columnKey);
|
|
9959
|
-
state.setVisibleColumns(next);
|
|
9960
|
-
},
|
|
9961
|
-
onSetVisibleColumns: (cols) => state.setVisibleColumns(cols),
|
|
9962
|
-
filterableColumns,
|
|
9963
|
-
filters: state.filters,
|
|
9964
|
-
onFilterChange: (key, value) => state.setFilter(key, value),
|
|
9965
|
-
filterOptions: state.filterOptions
|
|
9966
|
-
});
|
|
9967
|
-
sideBarComponent.render();
|
|
11992
|
+
/** Clean up all listeners. */
|
|
11993
|
+
destroy() {
|
|
11994
|
+
this.engine = null;
|
|
11995
|
+
this.emitter.removeAllListeners();
|
|
9968
11996
|
}
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
spinner.textContent = "Loading...";
|
|
9991
|
-
loadingOverlay.appendChild(spinner);
|
|
9992
|
-
this.ctx.setLoadingOverlay(loadingOverlay);
|
|
9993
|
-
}
|
|
9994
|
-
if (!tableContainer.contains(loadingOverlay)) {
|
|
9995
|
-
tableContainer.appendChild(loadingOverlay);
|
|
11997
|
+
// --- Private ---
|
|
11998
|
+
emitRecalc(result) {
|
|
11999
|
+
this.options.onFormulaRecalc?.(result);
|
|
12000
|
+
this.emitter.emit("formulaRecalc", result);
|
|
12001
|
+
}
|
|
12002
|
+
};
|
|
12003
|
+
|
|
12004
|
+
// src/components/FormulaBar.ts
|
|
12005
|
+
var FormulaBar = class {
|
|
12006
|
+
constructor(callbacks) {
|
|
12007
|
+
this.el = null;
|
|
12008
|
+
this.nameBoxEl = null;
|
|
12009
|
+
this.inputEl = null;
|
|
12010
|
+
this.isEditing = false;
|
|
12011
|
+
// --- Private event handlers (arrow functions for stable `this`) ---
|
|
12012
|
+
this.handleKeyDown = (e) => {
|
|
12013
|
+
handleFormulaBarKeyDown(e.key, () => e.preventDefault(), this.callbacks.onCommit, this.callbacks.onCancel);
|
|
12014
|
+
};
|
|
12015
|
+
this.handleInput = () => {
|
|
12016
|
+
if (this.inputEl) {
|
|
12017
|
+
this.callbacks.onInputChange(this.inputEl.value);
|
|
9996
12018
|
}
|
|
9997
|
-
}
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
loadingOverlay.remove();
|
|
12019
|
+
};
|
|
12020
|
+
this.handleClick = () => {
|
|
12021
|
+
if (!this.isEditing) {
|
|
12022
|
+
this.callbacks.onStartEditing();
|
|
10002
12023
|
}
|
|
12024
|
+
};
|
|
12025
|
+
this.callbacks = callbacks;
|
|
12026
|
+
}
|
|
12027
|
+
/** Create the formula bar DOM and append it to the given container. */
|
|
12028
|
+
mount(container) {
|
|
12029
|
+
if (this.el) return;
|
|
12030
|
+
this.el = document.createElement("div");
|
|
12031
|
+
this.el.className = "ogrid-formula-bar";
|
|
12032
|
+
this.el.setAttribute("role", "toolbar");
|
|
12033
|
+
this.el.setAttribute("aria-label", "Formula bar");
|
|
12034
|
+
this.el.style.cssText = FORMULA_BAR_CSS.bar;
|
|
12035
|
+
this.nameBoxEl = document.createElement("div");
|
|
12036
|
+
this.nameBoxEl.className = "ogrid-formula-bar-name";
|
|
12037
|
+
this.nameBoxEl.setAttribute("aria-label", "Active cell reference");
|
|
12038
|
+
this.nameBoxEl.style.cssText = FORMULA_BAR_CSS.nameBox;
|
|
12039
|
+
this.nameBoxEl.textContent = "\u2014";
|
|
12040
|
+
this.el.appendChild(this.nameBoxEl);
|
|
12041
|
+
const fxLabel = document.createElement("div");
|
|
12042
|
+
fxLabel.className = "ogrid-formula-bar-fx";
|
|
12043
|
+
fxLabel.setAttribute("aria-hidden", "true");
|
|
12044
|
+
fxLabel.style.cssText = FORMULA_BAR_CSS.fxLabel;
|
|
12045
|
+
fxLabel.textContent = "fx";
|
|
12046
|
+
this.el.appendChild(fxLabel);
|
|
12047
|
+
this.inputEl = document.createElement("input");
|
|
12048
|
+
this.inputEl.type = "text";
|
|
12049
|
+
this.inputEl.className = "ogrid-formula-bar-input";
|
|
12050
|
+
this.inputEl.setAttribute("aria-label", "Formula input");
|
|
12051
|
+
this.inputEl.spellcheck = false;
|
|
12052
|
+
this.inputEl.autocomplete = "off";
|
|
12053
|
+
this.inputEl.readOnly = true;
|
|
12054
|
+
this.inputEl.style.cssText = FORMULA_BAR_CSS.input;
|
|
12055
|
+
this.inputEl.addEventListener("keydown", this.handleKeyDown);
|
|
12056
|
+
this.inputEl.addEventListener("input", this.handleInput);
|
|
12057
|
+
this.inputEl.addEventListener("click", this.handleClick);
|
|
12058
|
+
this.inputEl.addEventListener("dblclick", this.handleClick);
|
|
12059
|
+
this.el.appendChild(this.inputEl);
|
|
12060
|
+
container.appendChild(this.el);
|
|
12061
|
+
}
|
|
12062
|
+
/** Update the formula bar display with the current active cell ref and formula text. */
|
|
12063
|
+
update(cellRef, formulaText) {
|
|
12064
|
+
if (this.nameBoxEl) {
|
|
12065
|
+
this.nameBoxEl.textContent = cellRef ?? "\u2014";
|
|
12066
|
+
}
|
|
12067
|
+
if (this.inputEl) {
|
|
12068
|
+
this.inputEl.value = formulaText;
|
|
12069
|
+
}
|
|
12070
|
+
}
|
|
12071
|
+
/** Set editing state. When true, the input becomes editable and receives focus. */
|
|
12072
|
+
setEditing(editing) {
|
|
12073
|
+
this.isEditing = editing;
|
|
12074
|
+
if (this.inputEl) {
|
|
12075
|
+
this.inputEl.readOnly = !editing;
|
|
12076
|
+
if (editing) {
|
|
12077
|
+
this.inputEl.focus();
|
|
12078
|
+
}
|
|
12079
|
+
}
|
|
12080
|
+
}
|
|
12081
|
+
/** Remove the formula bar from the DOM and clean up event listeners. */
|
|
12082
|
+
destroy() {
|
|
12083
|
+
if (this.inputEl) {
|
|
12084
|
+
this.inputEl.removeEventListener("keydown", this.handleKeyDown);
|
|
12085
|
+
this.inputEl.removeEventListener("input", this.handleInput);
|
|
12086
|
+
this.inputEl.removeEventListener("click", this.handleClick);
|
|
12087
|
+
this.inputEl.removeEventListener("dblclick", this.handleClick);
|
|
10003
12088
|
}
|
|
12089
|
+
this.el?.remove();
|
|
12090
|
+
this.el = null;
|
|
12091
|
+
this.nameBoxEl = null;
|
|
12092
|
+
this.inputEl = null;
|
|
10004
12093
|
}
|
|
10005
12094
|
};
|
|
10006
12095
|
|
|
@@ -10134,6 +12223,13 @@ var OGrid = class {
|
|
|
10134
12223
|
this.marchingAnts = null;
|
|
10135
12224
|
this.cellEditor = null;
|
|
10136
12225
|
this.contextMenu = null;
|
|
12226
|
+
this.formulaEngine = null;
|
|
12227
|
+
this.formulaBar = null;
|
|
12228
|
+
this.formulaBarContainer = null;
|
|
12229
|
+
/** Tracks the text currently displayed/edited in the formula bar. */
|
|
12230
|
+
this.formulaBarText = "";
|
|
12231
|
+
/** Whether the formula bar input is currently in editing mode. */
|
|
12232
|
+
this.formulaBarEditing = false;
|
|
10137
12233
|
this.events = new EventEmitter();
|
|
10138
12234
|
this.unsubscribes = [];
|
|
10139
12235
|
this.isFullScreen = false;
|
|
@@ -10143,6 +12239,17 @@ var OGrid = class {
|
|
|
10143
12239
|
this.state = new GridState(options);
|
|
10144
12240
|
this.api = this.state.getApi();
|
|
10145
12241
|
this.eventWiringHelper = new OGridEventWiring();
|
|
12242
|
+
if (options.formulas) {
|
|
12243
|
+
this.formulaEngine = new FormulaEngineState({
|
|
12244
|
+
formulas: true,
|
|
12245
|
+
initialFormulas: options.initialFormulas,
|
|
12246
|
+
onFormulaRecalc: options.onFormulaRecalc,
|
|
12247
|
+
formulaFunctions: options.formulaFunctions,
|
|
12248
|
+
namedRanges: options.namedRanges,
|
|
12249
|
+
sheets: options.sheets
|
|
12250
|
+
});
|
|
12251
|
+
this.state.setFormulaEngine(this.formulaEngine);
|
|
12252
|
+
}
|
|
10146
12253
|
injectGlobalStyles("ogrid-theme-vars", OGRID_THEME_CSS);
|
|
10147
12254
|
this.containerEl = document.createElement("div");
|
|
10148
12255
|
this.containerEl.className = "ogrid-container";
|
|
@@ -10178,6 +12285,20 @@ var OGrid = class {
|
|
|
10178
12285
|
this.unsubscribes.push(() => document.removeEventListener("keydown", handleEscKey));
|
|
10179
12286
|
}
|
|
10180
12287
|
this.containerEl.appendChild(this.toolbarEl);
|
|
12288
|
+
if (options.formulas) {
|
|
12289
|
+
this.formulaBarContainer = document.createElement("div");
|
|
12290
|
+
this.formulaBarContainer.className = "ogrid-formula-bar-container";
|
|
12291
|
+
this.formulaBar = new FormulaBar({
|
|
12292
|
+
onCommit: () => this.handleFormulaBarCommit(),
|
|
12293
|
+
onCancel: () => this.handleFormulaBarCancel(),
|
|
12294
|
+
onInputChange: (text) => {
|
|
12295
|
+
this.formulaBarText = text;
|
|
12296
|
+
},
|
|
12297
|
+
onStartEditing: () => this.handleFormulaBarStartEditing()
|
|
12298
|
+
});
|
|
12299
|
+
this.formulaBar.mount(this.formulaBarContainer);
|
|
12300
|
+
this.containerEl.appendChild(this.formulaBarContainer);
|
|
12301
|
+
}
|
|
10181
12302
|
this.bodyArea = document.createElement("div");
|
|
10182
12303
|
this.bodyArea.className = "ogrid-body-area";
|
|
10183
12304
|
this.bodyArea.style.display = "flex";
|
|
@@ -10200,6 +12321,9 @@ var OGrid = class {
|
|
|
10200
12321
|
this.layoutState = new TableLayoutState();
|
|
10201
12322
|
this.layoutState.observeContainer(this.tableContainer);
|
|
10202
12323
|
this.renderer = new TableRenderer(this.tableContainer, this.state);
|
|
12324
|
+
if (this.formulaEngine) {
|
|
12325
|
+
this.renderer.setFormulaEngine(this.formulaEngine);
|
|
12326
|
+
}
|
|
10203
12327
|
this.pagination = new PaginationControls(this.paginationContainer, this.state);
|
|
10204
12328
|
this.statusBar = new StatusBar(this.statusBarContainer);
|
|
10205
12329
|
this.columnChooser = new ColumnChooser(this.toolbarEl, this.state);
|
|
@@ -10311,6 +12435,41 @@ var OGrid = class {
|
|
|
10311
12435
|
})
|
|
10312
12436
|
);
|
|
10313
12437
|
}
|
|
12438
|
+
if (this.formulaBar && this.selectionState && this.formulaEngine) {
|
|
12439
|
+
const fBar = this.formulaBar;
|
|
12440
|
+
const sel = this.selectionState;
|
|
12441
|
+
const fEngine = this.formulaEngine;
|
|
12442
|
+
let colOffset = 0;
|
|
12443
|
+
if (this.rowSelectionState) colOffset++;
|
|
12444
|
+
if (options.showRowNumbers || options.cellReferences || options.formulas) colOffset++;
|
|
12445
|
+
this.unsubscribes.push(
|
|
12446
|
+
sel.onSelectionChange(({ activeCell }) => {
|
|
12447
|
+
this.formulaBarEditing = false;
|
|
12448
|
+
fBar.setEditing(false);
|
|
12449
|
+
if (activeCell) {
|
|
12450
|
+
const dataCol = activeCell.columnIndex - colOffset;
|
|
12451
|
+
const dataRow = (this.state.page - 1) * this.state.pageSize + activeCell.rowIndex;
|
|
12452
|
+
const cellRef = formatCellReference(dataCol, dataRow + 1);
|
|
12453
|
+
const formula = fEngine.getFormula(dataCol, dataRow);
|
|
12454
|
+
if (formula) {
|
|
12455
|
+
this.formulaBarText = formula;
|
|
12456
|
+
fBar.update(cellRef, formula);
|
|
12457
|
+
} else {
|
|
12458
|
+
const { items } = this.state.getProcessedItems();
|
|
12459
|
+
const visibleCols = this.state.visibleColumnDefs;
|
|
12460
|
+
const item = items[activeCell.rowIndex];
|
|
12461
|
+
const col = visibleCols[dataCol];
|
|
12462
|
+
const value = item && col ? String(item[col.columnId] ?? "") : "";
|
|
12463
|
+
this.formulaBarText = value;
|
|
12464
|
+
fBar.update(cellRef, value);
|
|
12465
|
+
}
|
|
12466
|
+
} else {
|
|
12467
|
+
this.formulaBarText = "";
|
|
12468
|
+
fBar.update(null, "");
|
|
12469
|
+
}
|
|
12470
|
+
})
|
|
12471
|
+
);
|
|
12472
|
+
}
|
|
10314
12473
|
}
|
|
10315
12474
|
this.unsubscribes.push(
|
|
10316
12475
|
this.state.onStateChange(() => {
|
|
@@ -10535,6 +12694,83 @@ var OGrid = class {
|
|
|
10535
12694
|
this.headerFilterState.setFilterOptions(this.state.filterOptions);
|
|
10536
12695
|
this.headerFilterState.open(columnId, config, headerEl, tempPopover);
|
|
10537
12696
|
}
|
|
12697
|
+
// --- Formula bar handlers ---
|
|
12698
|
+
/** Build a grid data accessor for the formula engine from current state. */
|
|
12699
|
+
buildFormulaAccessor() {
|
|
12700
|
+
const { items } = this.state.getProcessedItems();
|
|
12701
|
+
const visibleCols = this.state.visibleColumnDefs;
|
|
12702
|
+
return {
|
|
12703
|
+
getCellValue: (col, row) => {
|
|
12704
|
+
const item = items[row];
|
|
12705
|
+
const colDef = visibleCols[col];
|
|
12706
|
+
if (!item || !colDef) return void 0;
|
|
12707
|
+
return item[colDef.columnId];
|
|
12708
|
+
},
|
|
12709
|
+
getRowCount: () => items.length,
|
|
12710
|
+
getColumnCount: () => visibleCols.length
|
|
12711
|
+
};
|
|
12712
|
+
}
|
|
12713
|
+
handleFormulaBarCommit() {
|
|
12714
|
+
if (!this.formulaEngine || !this.selectionState) return;
|
|
12715
|
+
const ac = this.selectionState.activeCell;
|
|
12716
|
+
if (!ac) return;
|
|
12717
|
+
let colOffset = 0;
|
|
12718
|
+
if (this.rowSelectionState) colOffset++;
|
|
12719
|
+
if (this.options.showRowNumbers || this.options.cellReferences || this.options.formulas) colOffset++;
|
|
12720
|
+
const dataCol = ac.columnIndex - colOffset;
|
|
12721
|
+
const dataRow = (this.state.page - 1) * this.state.pageSize + ac.rowIndex;
|
|
12722
|
+
const text = this.formulaBarText;
|
|
12723
|
+
const accessor = this.buildFormulaAccessor();
|
|
12724
|
+
if (text.startsWith("=")) {
|
|
12725
|
+
this.formulaEngine.setFormula(dataCol, dataRow, text, accessor);
|
|
12726
|
+
} else {
|
|
12727
|
+
if (this.formulaEngine.hasFormula(dataCol, dataRow)) {
|
|
12728
|
+
this.formulaEngine.setFormula(dataCol, dataRow, null, accessor);
|
|
12729
|
+
}
|
|
12730
|
+
const { items } = this.state.getProcessedItems();
|
|
12731
|
+
const visibleCols = this.state.visibleColumnDefs;
|
|
12732
|
+
const item = items[ac.rowIndex];
|
|
12733
|
+
const col = visibleCols[dataCol];
|
|
12734
|
+
if (item && col) {
|
|
12735
|
+
item[col.columnId] = text;
|
|
12736
|
+
}
|
|
12737
|
+
}
|
|
12738
|
+
this.formulaBarEditing = false;
|
|
12739
|
+
this.formulaBar?.setEditing(false);
|
|
12740
|
+
this.renderingHelper.updateRendererInteractionState();
|
|
12741
|
+
this.renderer.getWrapperElement()?.focus();
|
|
12742
|
+
}
|
|
12743
|
+
handleFormulaBarCancel() {
|
|
12744
|
+
if (this.selectionState?.activeCell && this.formulaEngine) {
|
|
12745
|
+
const ac = this.selectionState.activeCell;
|
|
12746
|
+
let colOffset = 0;
|
|
12747
|
+
if (this.rowSelectionState) colOffset++;
|
|
12748
|
+
if (this.options.showRowNumbers || this.options.cellReferences || this.options.formulas) colOffset++;
|
|
12749
|
+
const dataCol = ac.columnIndex - colOffset;
|
|
12750
|
+
const dataRow = (this.state.page - 1) * this.state.pageSize + ac.rowIndex;
|
|
12751
|
+
const formula = this.formulaEngine.getFormula(dataCol, dataRow);
|
|
12752
|
+
if (formula) {
|
|
12753
|
+
this.formulaBarText = formula;
|
|
12754
|
+
this.formulaBar?.update(formatCellReference(dataCol, dataRow + 1), formula);
|
|
12755
|
+
} else {
|
|
12756
|
+
const { items } = this.state.getProcessedItems();
|
|
12757
|
+
const visibleCols = this.state.visibleColumnDefs;
|
|
12758
|
+
const item = items[ac.rowIndex];
|
|
12759
|
+
const col = visibleCols[dataCol];
|
|
12760
|
+
const value = item && col ? String(item[col.columnId] ?? "") : "";
|
|
12761
|
+
this.formulaBarText = value;
|
|
12762
|
+
this.formulaBar?.update(formatCellReference(dataCol, dataRow + 1), value);
|
|
12763
|
+
}
|
|
12764
|
+
}
|
|
12765
|
+
this.formulaBarEditing = false;
|
|
12766
|
+
this.formulaBar?.setEditing(false);
|
|
12767
|
+
this.renderer.getWrapperElement()?.focus();
|
|
12768
|
+
}
|
|
12769
|
+
handleFormulaBarStartEditing() {
|
|
12770
|
+
if (!this.selectionState?.activeCell) return;
|
|
12771
|
+
this.formulaBarEditing = true;
|
|
12772
|
+
this.formulaBar?.setEditing(true);
|
|
12773
|
+
}
|
|
10538
12774
|
// Rendering methods delegated to OGridRendering helper:
|
|
10539
12775
|
// - updateRendererInteractionState() -> this.renderingHelper.updateRendererInteractionState()
|
|
10540
12776
|
// - updateDragAttributes() -> this.renderingHelper.updateDragAttributes()
|
|
@@ -10589,123 +12825,11 @@ var OGrid = class {
|
|
|
10589
12825
|
this.layoutState.destroy();
|
|
10590
12826
|
this.cellEditor?.closeEditor();
|
|
10591
12827
|
this.contextMenu?.close();
|
|
12828
|
+
this.formulaBar?.destroy();
|
|
12829
|
+
this.formulaEngine?.destroy();
|
|
10592
12830
|
this.events.removeAllListeners();
|
|
10593
12831
|
this.containerEl.remove();
|
|
10594
12832
|
}
|
|
10595
12833
|
};
|
|
10596
12834
|
|
|
10597
|
-
|
|
10598
|
-
var FormulaEngineState = class {
|
|
10599
|
-
constructor(options) {
|
|
10600
|
-
this.emitter = new EventEmitter();
|
|
10601
|
-
this.engine = null;
|
|
10602
|
-
this.options = options;
|
|
10603
|
-
if (options.formulas) {
|
|
10604
|
-
this.engine = new FormulaEngine({
|
|
10605
|
-
customFunctions: options.formulaFunctions,
|
|
10606
|
-
namedRanges: options.namedRanges
|
|
10607
|
-
});
|
|
10608
|
-
if (options.sheets) {
|
|
10609
|
-
for (const [name, accessor] of Object.entries(options.sheets)) {
|
|
10610
|
-
this.engine.registerSheet(name, accessor);
|
|
10611
|
-
}
|
|
10612
|
-
}
|
|
10613
|
-
}
|
|
10614
|
-
}
|
|
10615
|
-
/**
|
|
10616
|
-
* Initialize with an accessor — loads `initialFormulas` if provided.
|
|
10617
|
-
* Must be called after the grid data is available so the accessor is valid.
|
|
10618
|
-
*/
|
|
10619
|
-
initialize(accessor) {
|
|
10620
|
-
if (!this.engine || !this.options.initialFormulas?.length) return;
|
|
10621
|
-
const result = this.engine.loadFormulas(this.options.initialFormulas, accessor);
|
|
10622
|
-
if (result.updatedCells.length > 0) {
|
|
10623
|
-
this.emitRecalc(result);
|
|
10624
|
-
}
|
|
10625
|
-
}
|
|
10626
|
-
/**
|
|
10627
|
-
* Set or clear a formula for a cell. Triggers recalculation of dependents
|
|
10628
|
-
* and emits `formulaRecalc`.
|
|
10629
|
-
*/
|
|
10630
|
-
setFormula(col, row, formula, accessor) {
|
|
10631
|
-
if (!this.engine) return void 0;
|
|
10632
|
-
const result = this.engine.setFormula(col, row, formula, accessor);
|
|
10633
|
-
if (result.updatedCells.length > 0) {
|
|
10634
|
-
this.emitRecalc(result);
|
|
10635
|
-
}
|
|
10636
|
-
return result;
|
|
10637
|
-
}
|
|
10638
|
-
/**
|
|
10639
|
-
* Notify the engine that a non-formula cell's value changed.
|
|
10640
|
-
* Triggers recalculation of any formulas that depend on the changed cell.
|
|
10641
|
-
*/
|
|
10642
|
-
onCellChanged(col, row, accessor) {
|
|
10643
|
-
if (!this.engine) return void 0;
|
|
10644
|
-
const result = this.engine.onCellChanged(col, row, accessor);
|
|
10645
|
-
if (result.updatedCells.length > 0) {
|
|
10646
|
-
this.emitRecalc(result);
|
|
10647
|
-
}
|
|
10648
|
-
return result;
|
|
10649
|
-
}
|
|
10650
|
-
/** Get the computed value for a formula cell (or undefined if no formula). */
|
|
10651
|
-
getValue(col, row) {
|
|
10652
|
-
return this.engine?.getValue(col, row);
|
|
10653
|
-
}
|
|
10654
|
-
/** Check if a cell has a formula. */
|
|
10655
|
-
hasFormula(col, row) {
|
|
10656
|
-
return this.engine?.hasFormula(col, row) ?? false;
|
|
10657
|
-
}
|
|
10658
|
-
/** Get the formula string for a cell (or undefined if no formula). */
|
|
10659
|
-
getFormula(col, row) {
|
|
10660
|
-
return this.engine?.getFormula(col, row);
|
|
10661
|
-
}
|
|
10662
|
-
/** Whether the formula engine is active. */
|
|
10663
|
-
isEnabled() {
|
|
10664
|
-
return this.engine !== null;
|
|
10665
|
-
}
|
|
10666
|
-
/** Define a named range. */
|
|
10667
|
-
defineNamedRange(name, ref) {
|
|
10668
|
-
this.engine?.defineNamedRange(name, ref);
|
|
10669
|
-
}
|
|
10670
|
-
/** Remove a named range. */
|
|
10671
|
-
removeNamedRange(name) {
|
|
10672
|
-
this.engine?.removeNamedRange(name);
|
|
10673
|
-
}
|
|
10674
|
-
/** Register a sheet accessor for cross-sheet references. */
|
|
10675
|
-
registerSheet(name, accessor) {
|
|
10676
|
-
this.engine?.registerSheet(name, accessor);
|
|
10677
|
-
}
|
|
10678
|
-
/** Unregister a sheet accessor. */
|
|
10679
|
-
unregisterSheet(name) {
|
|
10680
|
-
this.engine?.unregisterSheet(name);
|
|
10681
|
-
}
|
|
10682
|
-
/** Get all cells that a cell depends on (deep, transitive). */
|
|
10683
|
-
getPrecedents(col, row) {
|
|
10684
|
-
return this.engine?.getPrecedents(col, row) ?? [];
|
|
10685
|
-
}
|
|
10686
|
-
/** Get all cells that depend on a cell (deep, transitive). */
|
|
10687
|
-
getDependents(col, row) {
|
|
10688
|
-
return this.engine?.getDependents(col, row) ?? [];
|
|
10689
|
-
}
|
|
10690
|
-
/** Get full audit trail for a cell. */
|
|
10691
|
-
getAuditTrail(col, row) {
|
|
10692
|
-
return this.engine?.getAuditTrail(col, row) ?? null;
|
|
10693
|
-
}
|
|
10694
|
-
/** Subscribe to the `formulaRecalc` event. Returns an unsubscribe function. */
|
|
10695
|
-
onFormulaRecalc(handler) {
|
|
10696
|
-
this.emitter.on("formulaRecalc", handler);
|
|
10697
|
-
return () => this.emitter.off("formulaRecalc", handler);
|
|
10698
|
-
}
|
|
10699
|
-
/** Clean up all listeners. */
|
|
10700
|
-
destroy() {
|
|
10701
|
-
this.engine = null;
|
|
10702
|
-
this.emitter.removeAllListeners();
|
|
10703
|
-
}
|
|
10704
|
-
// --- Private ---
|
|
10705
|
-
emitRecalc(result) {
|
|
10706
|
-
this.options.onFormulaRecalc?.(result);
|
|
10707
|
-
this.emitter.emit("formulaRecalc", result);
|
|
10708
|
-
}
|
|
10709
|
-
};
|
|
10710
|
-
|
|
10711
|
-
export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, CIRC_ERROR, COLUMN_HEADER_MENU_ITEMS, CellDescriptorCache, ClipboardState, ColumnChooser, ColumnPinningState, ColumnReorderState, ColumnResizeState, ContextMenu, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, DIV_ZERO_ERROR, DependencyGraph, EventEmitter, FillHandleState, FormulaEngine, FormulaEngineState, FormulaError, FormulaEvaluator, GENERAL_ERROR, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GridState, HeaderFilter, HeaderFilterState, InlineCellEditor, KeyboardNavState, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NAME_ERROR, NA_ERROR, OGrid, OGridEventWiring, OGridRendering, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, PaginationControls, REF_ERROR, ROW_NUMBER_COLUMN_WIDTH, RowSelectionState, SIDEBAR_TRANSITION_MS, SelectionState, SideBar, SideBarState, StatusBar, TableLayoutState, TableRenderer, UndoRedoStack, UndoRedoState, VALUE_ERROR, VirtualScrollState, Z_INDEX, adjustFormulaReferences, applyCellDeletion, applyCutClear, applyFillValues, applyPastedValues, applyRangeRowSelection, areGridRowPropsEqual, booleanParser, buildCellIndex, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, calculateDropTarget, clampSelectionToBounds, columnLetterToIndex, computeAggregations, computeArrowNavigation, computeAutoScrollSpeed, computeNextSortState, computeRowSelectionState, computeTabNavigation, computeTotalHeight, computeVisibleColumnRange, computeVisibleRange, createBuiltInFunctions, createSortFilterWorker, currencyParser, dateParser, debounce, deriveFilterOptionsFromData, emailParser, escapeCsvValue, exportToCsv, extractValueMatrix, findCtrlArrowTarget, flattenArgs, flattenColumns, formatAddress, formatCellReference, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, toString as formulaToString, fromCellKey, getCellRenderDescriptor, getCellValue, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getPinStateForColumn, getScrollTopForRow, getStatusBarParts, indexToColumnLetter, injectGlobalStyles, isColumnEditable, isFilterConfig, isFormulaError, isInSelectionRange, isRowInRange, measureColumnContentWidth, measureRange, mergeFilter, normalizeSelectionRange, numberParser, parse, parseCellRef, parseRange, parseTsvClipboard, parseValue, partitionColumnsForVirtualization, processClientSideData, processClientSideDataAsync, rangesEqual, reorderColumnArray, resolveCellDisplayContent, resolveCellStyle, terminateSortFilterWorker, toBoolean, toCellKey, toNumber, toUserLike, tokenize, triggerCsvDownload, validateColumns, validateRowIds, validateVirtualScrollConfig };
|
|
12835
|
+
export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, CIRC_ERROR, COLUMN_HEADER_MENU_ITEMS, CellDescriptorCache, ClipboardState, ColumnChooser, ColumnPinningState, ColumnReorderState, ColumnResizeState, ContextMenu, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, DIV_ZERO_ERROR, DependencyGraph, EventEmitter, FORMULA_BAR_CSS, FORMULA_BAR_STYLES, FORMULA_REF_COLORS, FillHandleState, FormulaBar, FormulaEngine, FormulaEngineState, FormulaError, FormulaEvaluator, GENERAL_ERROR, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GridState, HeaderFilter, HeaderFilterState, InlineCellEditor, KeyboardNavState, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NAME_ERROR, NA_ERROR, OGrid, OGridEventWiring, OGridRendering, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, PaginationControls, REF_ERROR, ROW_NUMBER_COLUMN_ID, ROW_NUMBER_COLUMN_MIN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, RowSelectionState, SIDEBAR_TRANSITION_MS, SelectionState, SideBar, SideBarState, StatusBar, TableLayoutState, TableRenderer, UndoRedoStack, UndoRedoState, VALUE_ERROR, VirtualScrollState, Z_INDEX, adjustFormulaReferences, applyCellDeletion, applyCutClear, applyFillValues, applyPastedValues, applyRangeRowSelection, areGridRowPropsEqual, booleanParser, buildCellIndex, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, calculateDropTarget, clampSelectionToBounds, columnLetterToIndex, computeAggregations, computeArrowNavigation, computeAutoScrollSpeed, computeNextSortState, computeRowSelectionState, computeTabNavigation, computeTotalHeight, computeVisibleColumnRange, computeVisibleRange, createBuiltInFunctions, createGridDataAccessor, createSortFilterWorker, currencyParser, dateParser, debounce, deriveFilterOptionsFromData, deriveFormulaBarText, emailParser, escapeCsvValue, exportToCsv, extractFormulaReferences, extractValueMatrix, findCtrlArrowTarget, flattenArgs, flattenColumns, formatAddress, formatCellReference, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, toString as formulaToString, fromCellKey, getCellRenderDescriptor, getCellValue, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getPinStateForColumn, getScrollTopForRow, getStatusBarParts, handleFormulaBarKeyDown, indexToColumnLetter, injectGlobalStyles, isColumnEditable, isFilterConfig, isFormulaError, isInSelectionRange, isRowInRange, measureColumnContentWidth, measureRange, mergeFilter, normalizeSelectionRange, numberParser, parse, parseCellRef, parseRange, parseTsvClipboard, parseValue, partitionColumnsForVirtualization, processClientSideData, processClientSideDataAsync, processFormulaBarCommit, rangesEqual, reorderColumnArray, resolveCellDisplayContent, resolveCellStyle, terminateSortFilterWorker, toBoolean, toCellKey, toNumber, toUserLike, tokenize, triggerCsvDownload, validateColumns, validateRowIds, validateVirtualScrollConfig };
|