@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/cell-types.js +20 -0
- package/dist/cell-types.js.map +1 -0
- package/dist/code/code-formula-bar.js +6 -0
- package/dist/code/code-formula-bar.js.map +1 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +12 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +51 -0
- package/dist/code/language.js.map +1 -0
- package/dist/code/style.d.ts +1 -0
- package/dist/code/style.js +2 -0
- package/dist/collaboration/anchor.js +143 -0
- package/dist/collaboration/anchor.js.map +1 -0
- package/dist/collaboration/index.js +770 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +87 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/collaboration/shared.js +450 -0
- package/dist/collaboration/shared.js.map +1 -0
- package/dist/components/cell-panel.js +6 -0
- package/dist/components/cell-panel.js.map +1 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/cell-renders.js +40 -0
- package/dist/components/cell-renders.js.map +1 -0
- package/dist/components/date-panel.js +6 -0
- package/dist/components/date-panel.js.map +1 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/filter-panel.js +6 -0
- package/dist/components/filter-panel.js.map +1 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-dialog.js +6 -0
- package/dist/components/format-dialog.js.map +1 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-list.js +6 -0
- package/dist/components/format-list.js.map +1 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-presets.js +97 -0
- package/dist/components/format-presets.js.map +1 -0
- package/dist/components/option-panel.js +6 -0
- package/dist/components/option-panel.js.map +1 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-comment.js +6 -0
- package/dist/components/sheet-comment.js.map +1 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-formula-bar.js +6 -0
- package/dist/components/sheet-formula-bar.js.map +1 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-link.js +6 -0
- package/dist/components/sheet-link.js.map +1 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-menu.js +6 -0
- package/dist/components/sheet-menu.js.map +1 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-name-box.js +6 -0
- package/dist/components/sheet-name-box.js.map +1 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-note.js +6 -0
- package/dist/components/sheet-note.js.map +1 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-outline.js +6 -0
- package/dist/components/sheet-outline.js.map +1 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-tabs.js +6 -0
- package/dist/components/sheet-tabs.js.map +1 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-toolbar.js +6 -0
- package/dist/components/sheet-toolbar.js.map +1 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/validation-dialog.js +6 -0
- package/dist/components/validation-dialog.js.map +1 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/core/address.js +256 -0
- package/dist/core/address.js.map +1 -0
- package/dist/core/axis.js +72 -0
- package/dist/core/axis.js.map +1 -0
- package/dist/core/cell-type.js +30 -0
- package/dist/core/cell-type.js.map +1 -0
- package/dist/core/clipboard.js +141 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/commands.js +63 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +74 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/conditional.js +362 -0
- package/dist/core/conditional.js.map +1 -0
- package/dist/core/csv.js +191 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/data.js +118 -0
- package/dist/core/data.js.map +1 -0
- package/dist/core/document.js +187 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/extension.js +60 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file.js +336 -0
- package/dist/core/file.js.map +1 -0
- package/dist/core/fill.js +153 -0
- package/dist/core/fill.js.map +1 -0
- package/dist/core/find.js +42 -0
- package/dist/core/find.js.map +1 -0
- package/dist/core/format.js +404 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/group.js +134 -0
- package/dist/core/group.js.map +1 -0
- package/dist/core/history.js +68 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/index.js +17 -0
- package/dist/core/link.js +34 -0
- package/dist/core/link.js.map +1 -0
- package/dist/core/merge.js +105 -0
- package/dist/core/merge.js.map +1 -0
- package/dist/core/model.js +48 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/nav.js +44 -0
- package/dist/core/nav.js.map +1 -0
- package/dist/core/paint.js +106 -0
- package/dist/core/paint.js.map +1 -0
- package/dist/core/plain-cell-types.js +54 -0
- package/dist/core/plain-cell-types.js.map +1 -0
- package/dist/core/protect.js +246 -0
- package/dist/core/protect.js.map +1 -0
- package/dist/core/selection.js +37 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/transaction.js +1582 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/validation.js +135 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/versions.js +161 -0
- package/dist/core/versions.js.map +1 -0
- package/dist/export/color.js +36 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/draw.js +316 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/export/index.js +390 -0
- package/dist/export/index.js.map +1 -0
- package/dist/formula/edit.js +210 -0
- package/dist/formula/edit.js.map +1 -0
- package/dist/formula/engine.js +526 -0
- package/dist/formula/engine.js.map +1 -0
- package/dist/formula/error.js +39 -0
- package/dist/formula/error.js.map +1 -0
- package/dist/formula/functions.js +1493 -0
- package/dist/formula/functions.js.map +1 -0
- package/dist/formula/help.js +408 -0
- package/dist/formula/help.js.map +1 -0
- package/dist/formula/index.js +7 -0
- package/dist/formula/parse.js +369 -0
- package/dist/formula/parse.js.map +1 -0
- package/dist/formula/values.js +123 -0
- package/dist/formula/values.js.map +1 -0
- package/dist/formulas.js +30 -0
- package/dist/formulas.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +1312 -0
- package/dist/locale.js.map +1 -0
- package/dist/sheet.js +6 -0
- package/dist/sheet.js.map +1 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +17 -0
- package/dist/types/cell-types.d.ts +2 -0
- package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +13 -0
- package/dist/types/collaboration/anchor.d.ts +65 -0
- package/dist/types/collaboration/index.d.ts +59 -0
- package/dist/types/collaboration/peers.d.ts +21 -0
- package/dist/types/collaboration/shared.d.ts +75 -0
- package/dist/types/components/cell-panel.vue.d.ts +20 -0
- package/dist/types/components/cell-renders.d.ts +9 -0
- package/dist/types/components/date-panel.vue.d.ts +9 -0
- package/dist/types/components/filter-panel.vue.d.ts +21 -0
- package/dist/types/components/format-dialog.vue.d.ts +24 -0
- package/dist/types/components/format-list.vue.d.ts +25 -0
- package/dist/types/components/format-presets.d.ts +30 -0
- package/dist/types/components/option-panel.vue.d.ts +13 -0
- package/dist/types/components/sheet-comment.vue.d.ts +29 -0
- package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
- package/dist/types/components/sheet-link.vue.d.ts +32 -0
- package/dist/types/components/sheet-menu.vue.d.ts +20 -0
- package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
- package/dist/types/components/sheet-note.vue.d.ts +22 -0
- package/dist/types/components/sheet-outline.vue.d.ts +36 -0
- package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
- package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
- package/dist/types/components/validation-dialog.vue.d.ts +25 -0
- package/dist/types/context.d.ts +189 -0
- package/dist/types/core/address.d.ts +109 -0
- package/dist/types/core/axis.d.ts +23 -0
- package/dist/types/core/cell-type.d.ts +157 -0
- package/dist/types/core/clipboard.d.ts +48 -0
- package/dist/types/core/commands.d.ts +76 -0
- package/dist/types/core/comments.d.ts +22 -0
- package/dist/types/core/conditional.d.ts +114 -0
- package/dist/types/core/csv.d.ts +93 -0
- package/dist/types/core/data.d.ts +26 -0
- package/dist/types/core/document.d.ts +49 -0
- package/dist/types/core/extension.d.ts +177 -0
- package/dist/types/core/file.d.ts +301 -0
- package/dist/types/core/fill.d.ts +23 -0
- package/dist/types/core/find.d.ts +39 -0
- package/dist/types/core/format.d.ts +47 -0
- package/dist/types/core/group.d.ts +76 -0
- package/dist/types/core/history.d.ts +27 -0
- package/dist/types/core/index.d.ts +33 -0
- package/dist/types/core/link.d.ts +14 -0
- package/dist/types/core/merge.d.ts +26 -0
- package/dist/types/core/model.d.ts +363 -0
- package/dist/types/core/nav.d.ts +15 -0
- package/dist/types/core/paint.d.ts +19 -0
- package/dist/types/core/plain-cell-types.d.ts +6 -0
- package/dist/types/core/protect.d.ts +34 -0
- package/dist/types/core/selection.d.ts +25 -0
- package/dist/types/core/transaction.d.ts +470 -0
- package/dist/types/core/validation.d.ts +55 -0
- package/dist/types/core/versions.d.ts +38 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/draw.d.ts +98 -0
- package/dist/types/export/index.d.ts +47 -0
- package/dist/types/formula/edit.d.ts +62 -0
- package/dist/types/formula/engine.d.ts +99 -0
- package/dist/types/formula/error.d.ts +30 -0
- package/dist/types/formula/functions.d.ts +62 -0
- package/dist/types/formula/help.d.ts +12 -0
- package/dist/types/formula/index.d.ts +13 -0
- package/dist/types/formula/parse.d.ts +92 -0
- package/dist/types/formula/values.d.ts +65 -0
- package/dist/types/formulas.d.ts +32 -0
- package/dist/types/index.d.ts +225 -0
- package/dist/types/locale.d.ts +531 -0
- package/dist/types/sheet.vue.d.ts +60 -0
- package/dist/types/types.d.ts +498 -0
- package/dist/types/use-comments.d.ts +47 -0
- package/dist/types/use-messages.d.ts +7 -0
- package/dist/use-comments.js +130 -0
- package/dist/use-comments.js.map +1 -0
- package/dist/use-messages.js +29 -0
- package/dist/use-messages.js.map +1 -0
- package/package.json +93 -4
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { boundsOf, parseRange, shiftFormula } from "./address.js";
|
|
2
|
+
import { isEmptyValue, meetsCondition, textCollator } from "./data.js";
|
|
3
|
+
// src/core/conditional.ts
|
|
4
|
+
var TONES = /* @__PURE__ */ new Set([
|
|
5
|
+
"neutral",
|
|
6
|
+
"primary",
|
|
7
|
+
"success",
|
|
8
|
+
"warning",
|
|
9
|
+
"danger"
|
|
10
|
+
]);
|
|
11
|
+
var isTone = (color) => TONES.has(color);
|
|
12
|
+
/** A scale's color: a tone's soft fill, which follows the color mode, or the color itself. */
|
|
13
|
+
function fillColor(color) {
|
|
14
|
+
if (!isTone(color)) return color;
|
|
15
|
+
return color === "neutral" ? "color-mix(in oklab, var(--dou-color-text) 16%, var(--dou-color-bg-surface))" : `color-mix(in oklab, var(--dou-color-${color}) 32%, var(--dou-color-bg-surface))`;
|
|
16
|
+
}
|
|
17
|
+
/** A bar's color: a tone's color, lighter so text stays readable over it, or the color itself. */
|
|
18
|
+
function barColor(color) {
|
|
19
|
+
if (!isTone(color)) return color;
|
|
20
|
+
return color === "neutral" ? "color-mix(in oklab, var(--dou-color-text) 14%, transparent)" : `color-mix(in oklab, var(--dou-color-${color}) 30%, transparent)`;
|
|
21
|
+
}
|
|
22
|
+
/** The color `t` (0–1) of the way from `a` to `b`. */
|
|
23
|
+
function mix(a, b, t) {
|
|
24
|
+
const p = Math.round(t * 100);
|
|
25
|
+
if (p <= 0) return a;
|
|
26
|
+
if (p >= 100) return b;
|
|
27
|
+
return `color-mix(in oklab, ${b} ${p}%, ${a})`;
|
|
28
|
+
}
|
|
29
|
+
var numberAt = (source, row, col) => {
|
|
30
|
+
const value = source.valueAt(row, col);
|
|
31
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
32
|
+
};
|
|
33
|
+
function numbersIn(source, areas) {
|
|
34
|
+
let size = 0;
|
|
35
|
+
for (const b of areas) size += (b.bottom - b.top + 1) * (b.right - b.left + 1);
|
|
36
|
+
const list = new Float64Array(size);
|
|
37
|
+
let length = 0;
|
|
38
|
+
for (const b of areas) for (let row = b.top; row <= b.bottom; row++) for (let col = b.left; col <= b.right; col++) {
|
|
39
|
+
const n = numberAt(source, row, col);
|
|
40
|
+
if (n != null) list[length++] = n;
|
|
41
|
+
}
|
|
42
|
+
return list.subarray(0, length);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The `k`th smallest of `list` (from 0), found without sorting it all:
|
|
46
|
+
* `list` is reordered around it.
|
|
47
|
+
*/
|
|
48
|
+
function select(list, k) {
|
|
49
|
+
let lo = 0;
|
|
50
|
+
let hi = list.length - 1;
|
|
51
|
+
while (hi > lo) {
|
|
52
|
+
const pivot = list[lo + hi >> 1];
|
|
53
|
+
let i = lo;
|
|
54
|
+
let j = hi;
|
|
55
|
+
while (i <= j) {
|
|
56
|
+
while (list[i] < pivot) i++;
|
|
57
|
+
while (list[j] > pivot) j--;
|
|
58
|
+
if (i <= j) {
|
|
59
|
+
const t = list[i];
|
|
60
|
+
list[i++] = list[j];
|
|
61
|
+
list[j--] = t;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (k <= j) hi = j;
|
|
65
|
+
else if (k >= i) lo = i;
|
|
66
|
+
else break;
|
|
67
|
+
}
|
|
68
|
+
return list[k];
|
|
69
|
+
}
|
|
70
|
+
/** A duplicates rule compares values as they show, without case. */
|
|
71
|
+
var keyOf = (source, row, col) => isEmptyValue(source.valueAt(row, col)) ? "" : source.textAt(row, col).toLocaleLowerCase();
|
|
72
|
+
function statsOf(rule, source) {
|
|
73
|
+
const f = rule.format;
|
|
74
|
+
const { areas } = rule.group;
|
|
75
|
+
if ("duplicates" in f || "unique" in f) {
|
|
76
|
+
const counts = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const b of areas) for (let row = b.top; row <= b.bottom; row++) for (let col = b.left; col <= b.right; col++) {
|
|
78
|
+
const key = keyOf(source, row, col);
|
|
79
|
+
if (key) counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
kind: "counts",
|
|
83
|
+
counts
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const numbers = numbersIn(source, areas);
|
|
87
|
+
if ("average" in f) {
|
|
88
|
+
let sum = 0;
|
|
89
|
+
for (const n of numbers) sum += n;
|
|
90
|
+
return {
|
|
91
|
+
kind: "threshold",
|
|
92
|
+
value: numbers.length ? sum / numbers.length : NaN
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const count = numbers.length;
|
|
96
|
+
if ("top" in f || "bottom" in f) {
|
|
97
|
+
const n = f.top ?? f.bottom;
|
|
98
|
+
const wanted = f.percent ? Math.max(1, Math.floor(count * n / 100)) : Math.max(0, Math.floor(n));
|
|
99
|
+
if (!wanted || !count) return {
|
|
100
|
+
kind: "threshold",
|
|
101
|
+
value: NaN
|
|
102
|
+
};
|
|
103
|
+
const k = Math.min(wanted, count);
|
|
104
|
+
return {
|
|
105
|
+
kind: "threshold",
|
|
106
|
+
value: f.top != null ? select(numbers, count - k) : select(numbers, k - 1)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (!count) return {
|
|
110
|
+
kind: "spread",
|
|
111
|
+
min: NaN,
|
|
112
|
+
mid: NaN,
|
|
113
|
+
max: NaN
|
|
114
|
+
};
|
|
115
|
+
let min = Infinity;
|
|
116
|
+
let max = -Infinity;
|
|
117
|
+
for (const n of numbers) {
|
|
118
|
+
if (n < min) min = n;
|
|
119
|
+
if (n > max) max = n;
|
|
120
|
+
}
|
|
121
|
+
let mid = NaN;
|
|
122
|
+
if ("scale" in f && f.scale.length === 3) {
|
|
123
|
+
const half = count >> 1;
|
|
124
|
+
mid = count % 2 ? select(numbers, half) : (select(numbers, half - 1) + select(numbers, half)) / 2;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
kind: "spread",
|
|
128
|
+
min,
|
|
129
|
+
mid,
|
|
130
|
+
max
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** A rule's look as a style: its name in `styles`, or the style itself. */
|
|
134
|
+
function styleOf(style, styles) {
|
|
135
|
+
return typeof style === "string" ? styles?.[style] : style;
|
|
136
|
+
}
|
|
137
|
+
/** A cell's own style with the look of its conditional formats over it. */
|
|
138
|
+
function layStyle(own, look) {
|
|
139
|
+
const style = { ...look };
|
|
140
|
+
if (own) addStyle(style, own);
|
|
141
|
+
return style;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Lays `extra` under `look`: parts the look already has stay. A tone and a
|
|
145
|
+
* color are one part (the text's color), as are a fill and a background.
|
|
146
|
+
*/
|
|
147
|
+
function addStyle(look, extra) {
|
|
148
|
+
for (const key of Object.keys(extra)) {
|
|
149
|
+
if (look[key] !== void 0 || extra[key] === void 0) continue;
|
|
150
|
+
if ((key === "tone" || key === "color") && (look.tone ?? look.color) !== void 0) continue;
|
|
151
|
+
if ((key === "fill" || key === "background") && (look.fill ?? look.background) !== void 0) continue;
|
|
152
|
+
look[key] = extra[key];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
var sameBounds = (a, b) => a.top === b.top && a.left === b.left && a.bottom === b.bottom && a.right === b.right;
|
|
156
|
+
var sameAreas = (a, b) => a.length === b.length && a.every((area, i) => sameBounds(area, b[i]));
|
|
157
|
+
/** The bounds of a range, kept inside the sheet; `null` when it isn't a range. */
|
|
158
|
+
function boundsIn(text, size) {
|
|
159
|
+
const range = parseRange(text);
|
|
160
|
+
if (!range) return null;
|
|
161
|
+
const b = boundsOf(range);
|
|
162
|
+
return {
|
|
163
|
+
top: b.top,
|
|
164
|
+
left: b.left,
|
|
165
|
+
bottom: Math.min(b.bottom, size.rows - 1),
|
|
166
|
+
right: Math.min(b.right, size.cols - 1)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** The ranges of a rule as a list. */
|
|
170
|
+
var rangesOf = (range) => Array.isArray(range) ? range : [range];
|
|
171
|
+
/** A rule's ranges as text, such as `'E2:E9, G2:J9'`. */
|
|
172
|
+
var ruleRangeText = (range) => rangesOf(range).join(", ");
|
|
173
|
+
/**
|
|
174
|
+
* Reads ranges written as text, separated by commas or spaces: one range
|
|
175
|
+
* as a string, several as a list; `null` when one of them isn't a range.
|
|
176
|
+
*/
|
|
177
|
+
function readRuleRange(text) {
|
|
178
|
+
const texts = text.toUpperCase().split(/[\s,,;;]+/).filter(Boolean);
|
|
179
|
+
if (!texts.length || texts.some((t) => !parseRange(t))) return null;
|
|
180
|
+
return texts.length === 1 ? texts[0] : texts;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The bounds of a rule's ranges inside the sheet, leaving out those that
|
|
184
|
+
* aren't ranges or lie outside it, and the cell its formula is written for:
|
|
185
|
+
* the top left of the first range.
|
|
186
|
+
*/
|
|
187
|
+
function areasIn(range, size) {
|
|
188
|
+
const areas = [];
|
|
189
|
+
let anchor;
|
|
190
|
+
for (const text of rangesOf(range)) {
|
|
191
|
+
const b = boundsIn(text, size);
|
|
192
|
+
if (!b) continue;
|
|
193
|
+
anchor ??= {
|
|
194
|
+
row: b.top,
|
|
195
|
+
col: b.left
|
|
196
|
+
};
|
|
197
|
+
if (b.top <= b.bottom && b.left <= b.right) areas.push(b);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
areas,
|
|
201
|
+
anchor: anchor ?? {
|
|
202
|
+
row: 0,
|
|
203
|
+
col: 0
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The looks a sheet's conditional formats give its cells. Make a new one
|
|
209
|
+
* when the workbook changes; within one, each cell is worked out once.
|
|
210
|
+
*/
|
|
211
|
+
var SheetLooks = class {
|
|
212
|
+
source;
|
|
213
|
+
rules = [];
|
|
214
|
+
looks = /* @__PURE__ */ new Map();
|
|
215
|
+
collator;
|
|
216
|
+
/**
|
|
217
|
+
* `previous`, the looks before a change, lends what its rules read of
|
|
218
|
+
* ranges whose rows are the same, so a change elsewhere doesn't read a
|
|
219
|
+
* large range again.
|
|
220
|
+
*/
|
|
221
|
+
constructor(formats, source, previous) {
|
|
222
|
+
this.source = source;
|
|
223
|
+
this.collator = textCollator(source.lang);
|
|
224
|
+
for (const format of formats ?? []) {
|
|
225
|
+
const { areas, anchor } = areasIn(format.range, source.size);
|
|
226
|
+
if (!areas.length) continue;
|
|
227
|
+
const style = "style" in format ? styleOf(format.style, source.styles) : void 0;
|
|
228
|
+
const group = { areas };
|
|
229
|
+
const old = previous?.rules.find((r) => r.format === format)?.group;
|
|
230
|
+
if (old?.stats && old.stamps && sameAreas(old.areas, areas) && this.sameCells(old)) {
|
|
231
|
+
group.stats = old.stats;
|
|
232
|
+
group.stamps = old.stamps;
|
|
233
|
+
}
|
|
234
|
+
for (const bounds of areas) this.rules.push({
|
|
235
|
+
format,
|
|
236
|
+
bounds,
|
|
237
|
+
anchor,
|
|
238
|
+
style,
|
|
239
|
+
group
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** Whether the cells of an old rule's ranges hold what they did. */
|
|
244
|
+
sameCells(old) {
|
|
245
|
+
const stamp = this.source.cellStamp;
|
|
246
|
+
if (!stamp) return false;
|
|
247
|
+
const stamps = old.stamps;
|
|
248
|
+
let i = 0;
|
|
249
|
+
for (const b of old.areas) for (let row = b.top; row <= b.bottom; row++) for (let col = b.left; col <= b.right; col++) if (stamp(row, col) !== stamps[i++]) return false;
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
/** Whether there are any rules. */
|
|
253
|
+
get empty() {
|
|
254
|
+
return !this.rules.length;
|
|
255
|
+
}
|
|
256
|
+
/** The columns of a row that rules cover, as `[left, right]` pairs. */
|
|
257
|
+
spansIn(row) {
|
|
258
|
+
const spans = [];
|
|
259
|
+
for (const r of this.rules) if (row >= r.bounds.top && row <= r.bounds.bottom) spans.push([r.bounds.left, r.bounds.right]);
|
|
260
|
+
return spans;
|
|
261
|
+
}
|
|
262
|
+
/** What the rules give a cell, or `undefined` when none applies. */
|
|
263
|
+
lookAt(row, col) {
|
|
264
|
+
if (!this.rules.length) return void 0;
|
|
265
|
+
const pos = row * 16384 + col;
|
|
266
|
+
let look = this.looks.get(pos);
|
|
267
|
+
if (look === void 0) {
|
|
268
|
+
look = this.work(row, col);
|
|
269
|
+
this.looks.set(pos, look);
|
|
270
|
+
}
|
|
271
|
+
return look ?? void 0;
|
|
272
|
+
}
|
|
273
|
+
work(row, col) {
|
|
274
|
+
let style;
|
|
275
|
+
let bar;
|
|
276
|
+
for (const rule of this.rules) {
|
|
277
|
+
const b = rule.bounds;
|
|
278
|
+
if (row < b.top || row > b.bottom || col < b.left || col > b.right) continue;
|
|
279
|
+
const f = rule.format;
|
|
280
|
+
if ("scale" in f) {
|
|
281
|
+
const fill = this.scaleAt(rule, row, col);
|
|
282
|
+
if (fill) addStyle(style ??= {}, { background: fill });
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if ("bar" in f) {
|
|
286
|
+
bar ??= this.barAt(rule, row, col);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (rule.style && this.meets(rule, row, col)) addStyle(style ??= {}, rule.style);
|
|
290
|
+
}
|
|
291
|
+
if (!style && !bar) return null;
|
|
292
|
+
return {
|
|
293
|
+
...style && { style },
|
|
294
|
+
...bar && { bar },
|
|
295
|
+
key: JSON.stringify([style, bar])
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
stats(rule) {
|
|
299
|
+
const group = rule.group;
|
|
300
|
+
if (!group.stats) {
|
|
301
|
+
group.stats = statsOf(rule, this.source);
|
|
302
|
+
const stamp = this.source.cellStamp;
|
|
303
|
+
if (stamp) {
|
|
304
|
+
const stamps = [];
|
|
305
|
+
for (const b of group.areas) for (let row = b.top; row <= b.bottom; row++) for (let col = b.left; col <= b.right; col++) stamps.push(stamp(row, col));
|
|
306
|
+
group.stamps = stamps;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return group.stats;
|
|
310
|
+
}
|
|
311
|
+
meets(rule, row, col) {
|
|
312
|
+
const f = rule.format;
|
|
313
|
+
const s = this.source;
|
|
314
|
+
if ("when" in f) return meetsCondition(f.when, s.valueAt(row, col), s.textAt(row, col), this.collator);
|
|
315
|
+
if ("formula" in f) {
|
|
316
|
+
const formula = shiftFormula(f.formula, row - rule.anchor.row, col - rule.anchor.col);
|
|
317
|
+
const result = s.evaluate(formula);
|
|
318
|
+
return result === true || typeof result === "number" && result !== 0;
|
|
319
|
+
}
|
|
320
|
+
if ("duplicates" in f || "unique" in f) {
|
|
321
|
+
const key = keyOf(s, row, col);
|
|
322
|
+
if (!key) return false;
|
|
323
|
+
return this.stats(rule).counts.get(key) > 1 === "duplicates" in f;
|
|
324
|
+
}
|
|
325
|
+
const n = numberAt(s, row, col);
|
|
326
|
+
if (n == null) return false;
|
|
327
|
+
const { value } = this.stats(rule);
|
|
328
|
+
if (Number.isNaN(value)) return false;
|
|
329
|
+
if ("average" in f) return f.average === "above" ? n > value : n < value;
|
|
330
|
+
return "top" in f && f.top != null ? n >= value : n <= value;
|
|
331
|
+
}
|
|
332
|
+
scaleAt(rule, row, col) {
|
|
333
|
+
const f = rule.format;
|
|
334
|
+
const n = numberAt(this.source, row, col);
|
|
335
|
+
if (n == null) return void 0;
|
|
336
|
+
const { min, mid, max } = this.stats(rule);
|
|
337
|
+
const colors = f.scale.map(fillColor);
|
|
338
|
+
if (!(max > min)) return colors[colors.length === 3 ? 1 : 0];
|
|
339
|
+
if (colors.length === 2) return mix(colors[0], colors[1], (n - min) / (max - min));
|
|
340
|
+
return n <= mid ? mix(colors[0], colors[1], mid > min ? (n - min) / (mid - min) : 1) : mix(colors[1], colors[2], max > mid ? (n - mid) / (max - mid) : 1);
|
|
341
|
+
}
|
|
342
|
+
barAt(rule, row, col) {
|
|
343
|
+
const f = rule.format;
|
|
344
|
+
const n = numberAt(this.source, row, col);
|
|
345
|
+
if (n == null) return void 0;
|
|
346
|
+
const { min, max } = this.stats(rule);
|
|
347
|
+
const lo = Math.min(0, min);
|
|
348
|
+
const hi = Math.max(0, max);
|
|
349
|
+
if (!(hi > lo)) return void 0;
|
|
350
|
+
const zero = (0 - lo) / (hi - lo);
|
|
351
|
+
const at = (n - lo) / (hi - lo);
|
|
352
|
+
const color = barColor(f.bar === true ? "neutral" : f.bar);
|
|
353
|
+
return {
|
|
354
|
+
from: Math.min(zero, at),
|
|
355
|
+
to: Math.max(zero, at),
|
|
356
|
+
color
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
export { SheetLooks, areasIn, barColor, boundsIn, fillColor, layStyle, rangesOf, readRuleRange, ruleRangeText };
|
|
361
|
+
|
|
362
|
+
//# sourceMappingURL=conditional.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditional.js","names":[],"sources":["../../src/core/conditional.ts"],"sourcesContent":["import type { Tone } from 'dou-ui'\nimport { boundsOf, parseRange, shiftFormula, type SheetBounds } from './address'\nimport { isEmptyValue, meetsCondition, textCollator } from './data'\nimport type {\n SheetColor,\n SheetConditionalFormat,\n SheetConditionalStyle,\n SheetRuleRange,\n SheetStyle,\n} from './model'\n\n/*\n * Conditional formats: which look each cell takes on from the rules of its\n * sheet. Rules that look at the whole range (ranks, averages, duplicates,\n * scales, bars) read it once, the first time a cell of it is asked about.\n */\n\n/** What the rules give a cell: a style, and a bar to draw from `from` to `to` (0–1 of its width). */\nexport interface SheetLook {\n style?: SheetStyle\n bar?: { from: number; to: number; color: string }\n /** The same for looks that are the same, to tell when a cell's look changed. */\n key: string\n}\n\n/** What working out the looks needs from the grid. */\nexport interface SheetLookSource {\n /** What a cell comes to: its formula's result, or its value. */\n valueAt: (row: number, col: number) => unknown\n /** A cell's text as it shows. */\n textAt: (row: number, col: number) => string\n /** Works out a formula as if it were in a cell of the sheet. */\n evaluate: (formula: string) => unknown\n /**\n * What a cell holds now, compared with `===`: the cell as written, or\n * its formula's result. While every cell of a range holds the same, what\n * a rule read of the range is kept from the looks before.\n */\n cellStamp?: (row: number, col: number) => unknown\n /** The workbook's named styles. */\n styles?: Record<string, SheetStyle>\n /** How many rows and columns the sheet has, to keep whole rows and columns inside it. */\n size: { rows: number; cols: number }\n lang?: string\n}\n\nconst TONES = new Set<string>(['neutral', 'primary', 'success', 'warning', 'danger'])\nconst isTone = (color: SheetColor): color is Tone => TONES.has(color)\n\n/** A scale's color: a tone's soft fill, which follows the color mode, or the color itself. */\nexport function fillColor(color: SheetColor) {\n if (!isTone(color)) return color\n return color === 'neutral'\n ? 'color-mix(in oklab, var(--dou-color-text) 16%, var(--dou-color-bg-surface))'\n : `color-mix(in oklab, var(--dou-color-${color}) 32%, var(--dou-color-bg-surface))`\n}\n\n/** A bar's color: a tone's color, lighter so text stays readable over it, or the color itself. */\nexport function barColor(color: SheetColor) {\n if (!isTone(color)) return color\n return color === 'neutral'\n ? 'color-mix(in oklab, var(--dou-color-text) 14%, transparent)'\n : `color-mix(in oklab, var(--dou-color-${color}) 30%, transparent)`\n}\n\n/** The color `t` (0–1) of the way from `a` to `b`. */\nfunction mix(a: string, b: string, t: number) {\n const p = Math.round(t * 100)\n if (p <= 0) return a\n if (p >= 100) return b\n return `color-mix(in oklab, ${b} ${p}%, ${a})`\n}\n\n/** A rule over one of its ranges; the ranges of a rule share its `group`. */\ninterface Rule {\n format: SheetConditionalFormat\n bounds: SheetBounds\n /** The cell its formula is written for. */\n anchor: { row: number; col: number }\n style?: SheetStyle\n group: RuleGroup\n}\n\ninterface RuleGroup {\n areas: SheetBounds[]\n /** What the rule read of all its ranges, once. */\n stats?: Stats\n /** What the ranges' cells held when `stats` was read, as `cellStamp` gave it, row by row. */\n stamps?: unknown[]\n}\n\ntype Stats =\n | { kind: 'threshold'; value: number }\n | { kind: 'counts'; counts: Map<string, number> }\n | { kind: 'spread'; min: number; mid: number; max: number }\n\nconst numberAt = (source: SheetLookSource, row: number, col: number) => {\n const value = source.valueAt(row, col)\n return typeof value === 'number' && Number.isFinite(value) ? value : null\n}\n\nfunction numbersIn(source: SheetLookSource, areas: readonly SheetBounds[]) {\n let size = 0\n for (const b of areas) size += (b.bottom - b.top + 1) * (b.right - b.left + 1)\n const list = new Float64Array(size)\n let length = 0\n for (const b of areas)\n for (let row = b.top; row <= b.bottom; row++)\n for (let col = b.left; col <= b.right; col++) {\n const n = numberAt(source, row, col)\n if (n != null) list[length++] = n\n }\n return list.subarray(0, length)\n}\n\n/**\n * The `k`th smallest of `list` (from 0), found without sorting it all:\n * `list` is reordered around it.\n */\nfunction select(list: Float64Array, k: number) {\n let lo = 0\n let hi = list.length - 1\n while (hi > lo) {\n const pivot = list[(lo + hi) >> 1]!\n let i = lo\n let j = hi\n while (i <= j) {\n while (list[i]! < pivot) i++\n while (list[j]! > pivot) j--\n if (i <= j) {\n const t = list[i]!\n list[i++] = list[j]!\n list[j--] = t\n }\n }\n if (k <= j) hi = j\n else if (k >= i) lo = i\n else break\n }\n return list[k]!\n}\n\n/** A duplicates rule compares values as they show, without case. */\nconst keyOf = (source: SheetLookSource, row: number, col: number) =>\n isEmptyValue(source.valueAt(row, col)) ? '' : source.textAt(row, col).toLocaleLowerCase()\n\nfunction statsOf(rule: Rule, source: SheetLookSource): Stats {\n const f = rule.format\n const { areas } = rule.group\n if ('duplicates' in f || 'unique' in f) {\n const counts = new Map<string, number>()\n for (const b of areas)\n for (let row = b.top; row <= b.bottom; row++)\n for (let col = b.left; col <= b.right; col++) {\n const key = keyOf(source, row, col)\n if (key) counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n return { kind: 'counts', counts }\n }\n const numbers = numbersIn(source, areas)\n if ('average' in f) {\n let sum = 0\n for (const n of numbers) sum += n\n return { kind: 'threshold', value: numbers.length ? sum / numbers.length : NaN }\n }\n const count = numbers.length\n if ('top' in f || 'bottom' in f) {\n const n = (f.top ?? f.bottom)!\n const wanted = f.percent\n ? Math.max(1, Math.floor((count * n) / 100))\n : Math.max(0, Math.floor(n))\n if (!wanted || !count) return { kind: 'threshold', value: NaN }\n const k = Math.min(wanted, count)\n const value = f.top != null ? select(numbers, count - k) : select(numbers, k - 1)\n return { kind: 'threshold', value }\n }\n if (!count) return { kind: 'spread', min: NaN, mid: NaN, max: NaN }\n let min = Infinity\n let max = -Infinity\n for (const n of numbers) {\n if (n < min) min = n\n if (n > max) max = n\n }\n // only a scale of three colors needs the middle\n let mid = NaN\n if ('scale' in f && f.scale.length === 3) {\n const half = count >> 1\n mid =\n count % 2 ? select(numbers, half) : (select(numbers, half - 1) + select(numbers, half)) / 2\n }\n return { kind: 'spread', min, mid, max }\n}\n\n/** A rule's look as a style: its name in `styles`, or the style itself. */\nfunction styleOf(style: SheetConditionalStyle, styles: Record<string, SheetStyle> | undefined) {\n return typeof style === 'string' ? styles?.[style] : style\n}\n\n/** A cell's own style with the look of its conditional formats over it. */\nexport function layStyle(own: SheetStyle | undefined, look: SheetStyle) {\n const style = { ...look }\n if (own) addStyle(style, own)\n return style\n}\n\n/**\n * Lays `extra` under `look`: parts the look already has stay. A tone and a\n * color are one part (the text's color), as are a fill and a background.\n */\nfunction addStyle(look: SheetStyle, extra: SheetStyle) {\n for (const key of Object.keys(extra) as (keyof SheetStyle)[]) {\n if (look[key] !== undefined || extra[key] === undefined) continue\n if ((key === 'tone' || key === 'color') && (look.tone ?? look.color) !== undefined) continue\n if ((key === 'fill' || key === 'background') && (look.fill ?? look.background) !== undefined)\n continue\n ;(look as Record<string, unknown>)[key] = extra[key]\n }\n}\n\nconst sameBounds = (a: SheetBounds, b: SheetBounds) =>\n a.top === b.top && a.left === b.left && a.bottom === b.bottom && a.right === b.right\nconst sameAreas = (a: readonly SheetBounds[], b: readonly SheetBounds[]) =>\n a.length === b.length && a.every((area, i) => sameBounds(area, b[i]!))\n\n/** The bounds of a range, kept inside the sheet; `null` when it isn't a range. */\nexport function boundsIn(text: string, size: { rows: number; cols: number }): SheetBounds | null {\n const range = parseRange(text)\n if (!range) return null\n const b = boundsOf(range)\n return {\n top: b.top,\n left: b.left,\n bottom: Math.min(b.bottom, size.rows - 1),\n right: Math.min(b.right, size.cols - 1),\n }\n}\n\n/** The ranges of a rule as a list. */\nexport const rangesOf = (range: SheetRuleRange): readonly string[] =>\n Array.isArray(range) ? range : [range]\n\n/** A rule's ranges as text, such as `'E2:E9, G2:J9'`. */\nexport const ruleRangeText = (range: SheetRuleRange) => rangesOf(range).join(', ')\n\n/**\n * Reads ranges written as text, separated by commas or spaces: one range\n * as a string, several as a list; `null` when one of them isn't a range.\n */\nexport function readRuleRange(text: string): SheetRuleRange | null {\n const texts = text\n .toUpperCase()\n .split(/[\\s,,;;]+/)\n .filter(Boolean)\n if (!texts.length || texts.some((t) => !parseRange(t))) return null\n return texts.length === 1 ? texts[0]! : texts\n}\n\n/**\n * The bounds of a rule's ranges inside the sheet, leaving out those that\n * aren't ranges or lie outside it, and the cell its formula is written for:\n * the top left of the first range.\n */\nexport function areasIn(range: SheetRuleRange, size: { rows: number; cols: number }) {\n const areas: SheetBounds[] = []\n let anchor: { row: number; col: number } | undefined\n for (const text of rangesOf(range)) {\n const b = boundsIn(text, size)\n if (!b) continue\n anchor ??= { row: b.top, col: b.left }\n if (b.top <= b.bottom && b.left <= b.right) areas.push(b)\n }\n return { areas, anchor: anchor ?? { row: 0, col: 0 } }\n}\n\n/**\n * The looks a sheet's conditional formats give its cells. Make a new one\n * when the workbook changes; within one, each cell is worked out once.\n */\nexport class SheetLooks {\n private rules: Rule[] = []\n private looks = new Map<number, SheetLook | null>()\n private collator: Intl.Collator\n\n /**\n * `previous`, the looks before a change, lends what its rules read of\n * ranges whose rows are the same, so a change elsewhere doesn't read a\n * large range again.\n */\n constructor(\n formats: readonly SheetConditionalFormat[] | undefined,\n private source: SheetLookSource,\n previous?: SheetLooks,\n ) {\n this.collator = textCollator(source.lang)\n for (const format of formats ?? []) {\n const { areas, anchor } = areasIn(format.range, source.size)\n if (!areas.length) continue\n const style = 'style' in format ? styleOf(format.style, source.styles) : undefined\n const group: RuleGroup = { areas }\n const old = previous?.rules.find((r) => r.format === format)?.group\n if (old?.stats && old.stamps && sameAreas(old.areas, areas) && this.sameCells(old)) {\n group.stats = old.stats\n group.stamps = old.stamps\n }\n for (const bounds of areas) this.rules.push({ format, bounds, anchor, style, group })\n }\n }\n\n /** Whether the cells of an old rule's ranges hold what they did. */\n private sameCells(old: RuleGroup) {\n const stamp = this.source.cellStamp\n if (!stamp) return false\n const stamps = old.stamps!\n let i = 0\n for (const b of old.areas)\n for (let row = b.top; row <= b.bottom; row++)\n for (let col = b.left; col <= b.right; col++)\n if (stamp(row, col) !== stamps[i++]) return false\n return true\n }\n\n /** Whether there are any rules. */\n get empty() {\n return !this.rules.length\n }\n\n /** The columns of a row that rules cover, as `[left, right]` pairs. */\n spansIn(row: number) {\n const spans: [number, number][] = []\n for (const r of this.rules)\n if (row >= r.bounds.top && row <= r.bounds.bottom) spans.push([r.bounds.left, r.bounds.right])\n return spans\n }\n\n /** What the rules give a cell, or `undefined` when none applies. */\n lookAt(row: number, col: number): SheetLook | undefined {\n if (!this.rules.length) return undefined\n const pos = row * 16384 + col\n let look = this.looks.get(pos)\n if (look === undefined) {\n look = this.work(row, col)\n this.looks.set(pos, look)\n }\n return look ?? undefined\n }\n\n private work(row: number, col: number): SheetLook | null {\n let style: SheetStyle | undefined\n let bar: SheetLook['bar']\n for (const rule of this.rules) {\n const b = rule.bounds\n if (row < b.top || row > b.bottom || col < b.left || col > b.right) continue\n const f = rule.format\n if ('scale' in f) {\n const fill = this.scaleAt(rule, row, col)\n if (fill) addStyle((style ??= {}), { background: fill })\n continue\n }\n if ('bar' in f) {\n bar ??= this.barAt(rule, row, col)\n continue\n }\n if (rule.style && this.meets(rule, row, col)) addStyle((style ??= {}), rule.style)\n }\n if (!style && !bar) return null\n return { ...(style && { style }), ...(bar && { bar }), key: JSON.stringify([style, bar]) }\n }\n\n private stats(rule: Rule) {\n const group = rule.group\n if (!group.stats) {\n group.stats = statsOf(rule, this.source)\n const stamp = this.source.cellStamp\n if (stamp) {\n const stamps: unknown[] = []\n for (const b of group.areas)\n for (let row = b.top; row <= b.bottom; row++)\n for (let col = b.left; col <= b.right; col++) stamps.push(stamp(row, col))\n group.stamps = stamps\n }\n }\n return group.stats\n }\n\n private meets(rule: Rule, row: number, col: number): boolean {\n const f = rule.format\n const s = this.source\n if ('when' in f)\n return meetsCondition(f.when, s.valueAt(row, col), s.textAt(row, col), this.collator)\n if ('formula' in f) {\n const formula = shiftFormula(f.formula, row - rule.anchor.row, col - rule.anchor.col)\n const result = s.evaluate(formula)\n return result === true || (typeof result === 'number' && result !== 0)\n }\n if ('duplicates' in f || 'unique' in f) {\n const key = keyOf(s, row, col)\n if (!key) return false\n const stats = this.stats(rule) as Extract<Stats, { kind: 'counts' }>\n return stats.counts.get(key)! > 1 === 'duplicates' in f\n }\n const n = numberAt(s, row, col)\n if (n == null) return false\n const { value } = this.stats(rule) as Extract<Stats, { kind: 'threshold' }>\n if (Number.isNaN(value)) return false\n if ('average' in f) return f.average === 'above' ? n > value : n < value\n return 'top' in f && f.top != null ? n >= value : n <= value\n }\n\n private scaleAt(rule: Rule, row: number, col: number) {\n const f = rule.format as Extract<SheetConditionalFormat, { scale: unknown }>\n const n = numberAt(this.source, row, col)\n if (n == null) return undefined\n const { min, mid, max } = this.stats(rule) as Extract<Stats, { kind: 'spread' }>\n const colors = f.scale.map(fillColor)\n if (!(max > min)) return colors[colors.length === 3 ? 1 : 0]\n if (colors.length === 2) return mix(colors[0]!, colors[1]!, (n - min) / (max - min))\n return n <= mid\n ? mix(colors[0]!, colors[1]!, mid > min ? (n - min) / (mid - min) : 1)\n : mix(colors[1]!, colors[2]!, max > mid ? (n - mid) / (max - mid) : 1)\n }\n\n private barAt(rule: Rule, row: number, col: number): SheetLook['bar'] {\n const f = rule.format as Extract<SheetConditionalFormat, { bar: unknown }>\n const n = numberAt(this.source, row, col)\n if (n == null) return undefined\n const { min, max } = this.stats(rule) as Extract<Stats, { kind: 'spread' }>\n const lo = Math.min(0, min)\n const hi = Math.max(0, max)\n if (!(hi > lo)) return undefined\n const zero = (0 - lo) / (hi - lo)\n const at = (n - lo) / (hi - lo)\n const color = barColor(f.bar === true ? 'neutral' : f.bar)\n return { from: Math.min(zero, at), to: Math.max(zero, at), color }\n }\n}\n"],"mappings":"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"}
|
package/dist/core/csv.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { boundsOf, parseRange } from "./address.js";
|
|
2
|
+
import { formatValue, generalNumber, inputText, parseInput } from "./format.js";
|
|
3
|
+
import { cellOf, valueOf } from "./model.js";
|
|
4
|
+
import { shownValue } from "../formula/values.js";
|
|
5
|
+
// src/core/csv.ts
|
|
6
|
+
var DELIMITERS = [
|
|
7
|
+
",",
|
|
8
|
+
";",
|
|
9
|
+
" ",
|
|
10
|
+
"|"
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* The delimiter a text most likely uses: of `,`, `;`, tab and `|`, the one
|
|
14
|
+
* found most often outside quotes in its first lines, and the same number
|
|
15
|
+
* of times on each. A comma when none is found.
|
|
16
|
+
*/
|
|
17
|
+
function detectDelimiter(text) {
|
|
18
|
+
const counts = new Map(DELIMITERS.map((d) => [d, [0]]));
|
|
19
|
+
let quoted = false;
|
|
20
|
+
let lines = 0;
|
|
21
|
+
for (let i = 0; i < text.length && lines < 10; i++) {
|
|
22
|
+
const char = text[i];
|
|
23
|
+
if (char === "\"") quoted = !quoted;
|
|
24
|
+
else if (quoted) continue;
|
|
25
|
+
else if (char === "\n") {
|
|
26
|
+
lines++;
|
|
27
|
+
for (const line of counts.values()) line.push(0);
|
|
28
|
+
} else {
|
|
29
|
+
const line = counts.get(char);
|
|
30
|
+
if (line) line[line.length - 1]++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
let best = ",";
|
|
34
|
+
let score = 0;
|
|
35
|
+
for (const [delimiter, perLine] of counts) {
|
|
36
|
+
const full = perLine.length > 1 && perLine[perLine.length - 1] === 0 ? perLine.slice(0, -1) : perLine;
|
|
37
|
+
const first = full[0] ?? 0;
|
|
38
|
+
if (!first) continue;
|
|
39
|
+
const value = first * (full.every((n) => n === first) ? 2 : 1);
|
|
40
|
+
if (value > score) {
|
|
41
|
+
score = value;
|
|
42
|
+
best = delimiter;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return best;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Splits delimited text into rows of fields, reading quoted fields; a
|
|
49
|
+
* byte order mark at the start is dropped. `delimiter` defaults to the one
|
|
50
|
+
* `detectDelimiter` finds.
|
|
51
|
+
*/
|
|
52
|
+
function parseCSV(text, delimiter = detectDelimiter(text)) {
|
|
53
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
54
|
+
const rows = [];
|
|
55
|
+
let row = [];
|
|
56
|
+
let field = "";
|
|
57
|
+
let quoted = false;
|
|
58
|
+
let wasQuoted = false;
|
|
59
|
+
for (let i = 0; i < text.length; i++) {
|
|
60
|
+
const char = text[i];
|
|
61
|
+
if (quoted) {
|
|
62
|
+
if (char === "\"" && text[i + 1] === "\"") {
|
|
63
|
+
field += "\"";
|
|
64
|
+
i++;
|
|
65
|
+
} else if (char === "\"") quoted = false;
|
|
66
|
+
else field += char;
|
|
67
|
+
} else if (char === "\"" && field === "") {
|
|
68
|
+
quoted = true;
|
|
69
|
+
wasQuoted = true;
|
|
70
|
+
} else if (char === delimiter) {
|
|
71
|
+
row.push(field);
|
|
72
|
+
field = "";
|
|
73
|
+
wasQuoted = false;
|
|
74
|
+
} else if (char === "\n" || char === "\r") {
|
|
75
|
+
if (char === "\r" && text[i + 1] === "\n") i++;
|
|
76
|
+
row.push(field);
|
|
77
|
+
rows.push(row);
|
|
78
|
+
row = [];
|
|
79
|
+
field = "";
|
|
80
|
+
wasQuoted = false;
|
|
81
|
+
} else field += char;
|
|
82
|
+
}
|
|
83
|
+
if (field !== "" || wasQuoted || row.length) {
|
|
84
|
+
row.push(field);
|
|
85
|
+
rows.push(row);
|
|
86
|
+
}
|
|
87
|
+
return rows;
|
|
88
|
+
}
|
|
89
|
+
/** A field that reads as a number but is a code: leading zeros, or more digits than a number keeps. */
|
|
90
|
+
var CODE = /^(?:[-+]?0\d+|[-+]?\d{16,})$/;
|
|
91
|
+
/** A number written with a decimal comma: `1,5`, `-1.280,5`, `12,5%`. */
|
|
92
|
+
var COMMA_NUMBER = /^([-+]?)(\d{1,3}(?:\.\d{3})+|\d+)(?:,(\d+))?(%?)$/;
|
|
93
|
+
/** The cells of a CSV, row by row, ready for a sheet's `cells` or `tr.setCells`. */
|
|
94
|
+
function readCSV(text, options = {}) {
|
|
95
|
+
const { typed = true, formulas = false } = options;
|
|
96
|
+
const delimiter = options.delimiter ?? detectDelimiter(text);
|
|
97
|
+
const comma = (options.decimal ?? (delimiter === ";" ? "," : ".")) === ",";
|
|
98
|
+
return parseCSV(text, delimiter).map((fields) => fields.map((field) => {
|
|
99
|
+
if (field === "") return void 0;
|
|
100
|
+
if (field.startsWith("=") && field.length > 1) return formulas && typed ? field : `'${field}`;
|
|
101
|
+
if (!typed || CODE.test(field.trim()) || field.startsWith("'")) return field;
|
|
102
|
+
let input = field;
|
|
103
|
+
if (comma) {
|
|
104
|
+
const number = COMMA_NUMBER.exec(field.trim());
|
|
105
|
+
if (number) {
|
|
106
|
+
const [, sign, whole, fraction, percent] = number;
|
|
107
|
+
input = `${sign}${whole.replaceAll(".", "")}${fraction ? `.${fraction}` : ""}${percent}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const { value, format } = parseInput(input);
|
|
111
|
+
if (value == null) return void 0;
|
|
112
|
+
return format ? {
|
|
113
|
+
value,
|
|
114
|
+
format
|
|
115
|
+
} : value;
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
/** What a spreadsheet program would start a formula with. */
|
|
119
|
+
var FORMULA_START = /^[=+\-@\t\r]/;
|
|
120
|
+
/** Where a sheet has data: up to its last row and column with something in them. */
|
|
121
|
+
function dataBounds(sheet) {
|
|
122
|
+
let bottom = -1;
|
|
123
|
+
let right = -1;
|
|
124
|
+
sheet.cells.forEach((line, row) => {
|
|
125
|
+
if (!line) return;
|
|
126
|
+
for (let col = line.length - 1; col >= 0; col--) if (valueOf(line[col]) != null && valueOf(line[col]) !== "") {
|
|
127
|
+
bottom = row;
|
|
128
|
+
right = Math.max(right, col);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return bottom === -1 ? null : {
|
|
133
|
+
top: 0,
|
|
134
|
+
left: 0,
|
|
135
|
+
bottom,
|
|
136
|
+
right
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** A sheet, or part of it, as CSV. */
|
|
140
|
+
function sheetToCSV(sheet, options = {}) {
|
|
141
|
+
const { delimiter = ",", text = false, formulas = false, locale, result, cellText, cellPlain, newline = "\r\n", bom = true, escapeFormulas = true } = options;
|
|
142
|
+
const range = typeof options.range === "string" ? parseRange(options.range) : options.range;
|
|
143
|
+
if (options.range != null && !range) throw new RangeError(`[@dou-ui/sheet] "${options.range}" isn't a range`);
|
|
144
|
+
const data = dataBounds(sheet);
|
|
145
|
+
const b = range ? boundsOf(range) : data;
|
|
146
|
+
if (!b || !data) return bom ? "" : "";
|
|
147
|
+
const quote = new RegExp(`["\\r\\n${delimiter.replace(/[\\\]^-]/g, "\\$&")}]`);
|
|
148
|
+
const field = (value) => quote.test(value) ? `"${value.replaceAll("\"", "\"\"")}"` : value;
|
|
149
|
+
const lines = [];
|
|
150
|
+
const bottom = Math.min(b.bottom, data.bottom);
|
|
151
|
+
const right = Math.min(b.right, data.right);
|
|
152
|
+
for (let row = b.top; row <= bottom; row++) {
|
|
153
|
+
const line = sheet.cells[row];
|
|
154
|
+
const fields = [];
|
|
155
|
+
for (let col = b.left; col <= right; col++) {
|
|
156
|
+
const input = line?.[col];
|
|
157
|
+
const stored = valueOf(input);
|
|
158
|
+
const formula = !text && formulas && typeof stored === "string" && stored.startsWith("=");
|
|
159
|
+
const value = formula ? stored : shownValue(stored, result, row, col);
|
|
160
|
+
let shown;
|
|
161
|
+
let textual;
|
|
162
|
+
if (formula) {
|
|
163
|
+
shown = stored;
|
|
164
|
+
textual = false;
|
|
165
|
+
} else if (text) {
|
|
166
|
+
const own = cellText?.(input, value, row, col);
|
|
167
|
+
shown = own ?? formatValue(value, cellOf(input)?.format, locale);
|
|
168
|
+
textual = typeof value === "string" || own !== void 0 && own !== formatValue(value, cellOf(input)?.format, locale);
|
|
169
|
+
} else {
|
|
170
|
+
const given = cellPlain?.(input, value, row, col);
|
|
171
|
+
const own = given === void 0 ? value : given;
|
|
172
|
+
shown = formulas ? inputText(own ?? void 0) : plainText(own);
|
|
173
|
+
textual = typeof own === "string";
|
|
174
|
+
}
|
|
175
|
+
const unsafe = escapeFormulas && textual && FORMULA_START.test(shown);
|
|
176
|
+
fields.push(field(unsafe ? `'${shown}` : shown));
|
|
177
|
+
}
|
|
178
|
+
lines.push(fields.join(delimiter));
|
|
179
|
+
}
|
|
180
|
+
return (bom ? "" : "") + lines.join(newline);
|
|
181
|
+
}
|
|
182
|
+
/** A value for another program: numbers in full, `TRUE` / `FALSE`, text as it is. */
|
|
183
|
+
function plainText(value) {
|
|
184
|
+
if (value == null) return "";
|
|
185
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
186
|
+
if (typeof value === "number") return generalNumber(value);
|
|
187
|
+
return value.startsWith("'=") ? value.slice(1) : value;
|
|
188
|
+
}
|
|
189
|
+
export { dataBounds, detectDelimiter, parseCSV, readCSV, sheetToCSV };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=csv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","names":[],"sources":["../../src/core/csv.ts"],"sourcesContent":["import { boundsOf, parseRange, type SheetBounds, type SheetRange } from './address'\nimport { formatValue, generalNumber, inputText, parseInput } from './format'\nimport {\n cellOf,\n valueOf,\n type SheetCellInput,\n type SheetData,\n type SheetRow,\n type SheetValue,\n} from './model'\nimport { shownValue, type SheetResult } from '../formula/values'\n\n/*\n * CSV and other delimited text: reading it into a sheet and writing a\n * sheet out, the way spreadsheet programs exchange plain tables\n * (RFC 4180: fields with the delimiter, a quote or a line break are quoted,\n * quotes inside doubled).\n */\n\nconst DELIMITERS = [',', ';', '\\t', '|'] as const\n\n/**\n * The delimiter a text most likely uses: of `,`, `;`, tab and `|`, the one\n * found most often outside quotes in its first lines, and the same number\n * of times on each. A comma when none is found.\n */\nexport function detectDelimiter(text: string) {\n const counts = new Map<string, number[]>(DELIMITERS.map((d) => [d, [0]]))\n let quoted = false\n let lines = 0\n for (let i = 0; i < text.length && lines < 10; i++) {\n const char = text[i]!\n if (char === '\"') quoted = !quoted\n else if (quoted) continue\n else if (char === '\\n') {\n lines++\n for (const line of counts.values()) line.push(0)\n } else {\n const line = counts.get(char)\n if (line) line[line.length - 1]!++\n }\n }\n let best: string = ','\n let score = 0\n for (const [delimiter, perLine] of counts) {\n // a last line cut short or left empty doesn't count\n const full =\n perLine.length > 1 && perLine[perLine.length - 1] === 0 ? perLine.slice(0, -1) : perLine\n const first = full[0] ?? 0\n if (!first) continue\n const steady = full.every((n) => n === first)\n const value = first * (steady ? 2 : 1)\n if (value > score) {\n score = value\n best = delimiter\n }\n }\n return best\n}\n\n/**\n * Splits delimited text into rows of fields, reading quoted fields; a\n * byte order mark at the start is dropped. `delimiter` defaults to the one\n * `detectDelimiter` finds.\n */\nexport function parseCSV(text: string, delimiter = detectDelimiter(text)): string[][] {\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1)\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let quoted = false\n // whether the field so far came from quotes: `\"\"` is an empty field, not a missing one\n let wasQuoted = false\n for (let i = 0; i < text.length; i++) {\n const char = text[i]!\n if (quoted) {\n if (char === '\"' && text[i + 1] === '\"') {\n field += '\"'\n i++\n } else if (char === '\"') quoted = false\n else field += char\n } else if (char === '\"' && field === '') {\n quoted = true\n wasQuoted = true\n } else if (char === delimiter) {\n row.push(field)\n field = ''\n wasQuoted = false\n } else if (char === '\\n' || char === '\\r') {\n if (char === '\\r' && text[i + 1] === '\\n') i++\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n wasQuoted = false\n } else field += char\n }\n if (field !== '' || wasQuoted || row.length) {\n row.push(field)\n rows.push(row)\n }\n return rows\n}\n\n/** A field that reads as a number but is a code: leading zeros, or more digits than a number keeps. */\nconst CODE = /^(?:[-+]?0\\d+|[-+]?\\d{16,})$/\n\nexport interface SheetCSVReadOptions {\n /** `,`, `;`, a tab…; found from the text when left out. */\n delimiter?: string\n /**\n * Reads numbers, dates, times, percentages and `TRUE` / `FALSE` as such,\n * the way typing them does; `false` keeps every field as text. Fields\n * that look like codes, `007` or a long card number, stay text either way.\n */\n typed?: boolean\n /**\n * The decimal mark of numbers: `,` reads `1,5` as 1.5 and `1.280,5` as\n * 1280.5. Defaults to `,` when fields are split by `;`, as files written\n * with a decimal comma are, otherwise `.`.\n */\n decimal?: '.' | ','\n /** Reads fields starting with `=` as formulas; by default they are text. */\n formulas?: boolean\n}\n\n/** A number written with a decimal comma: `1,5`, `-1.280,5`, `12,5%`. */\nconst COMMA_NUMBER = /^([-+]?)(\\d{1,3}(?:\\.\\d{3})+|\\d+)(?:,(\\d+))?(%?)$/\n\n/** The cells of a CSV, row by row, ready for a sheet's `cells` or `tr.setCells`. */\nexport function readCSV(text: string, options: SheetCSVReadOptions = {}): SheetRow[] {\n const { typed = true, formulas = false } = options\n const delimiter = options.delimiter ?? detectDelimiter(text)\n const comma = (options.decimal ?? (delimiter === ';' ? ',' : '.')) === ','\n return parseCSV(text, delimiter).map((fields) =>\n fields.map((field): SheetCellInput => {\n if (field === '') return undefined\n if (field.startsWith('=') && field.length > 1) return formulas && typed ? field : `'${field}`\n if (!typed || CODE.test(field.trim()) || field.startsWith(\"'\")) return field\n let input = field\n if (comma) {\n const number = COMMA_NUMBER.exec(field.trim())\n if (number) {\n const [, sign, whole, fraction, percent] = number\n input = `${sign}${whole!.replaceAll('.', '')}${fraction ? `.${fraction}` : ''}${percent}`\n }\n }\n const { value, format } = parseInput(input)\n if (value == null) return undefined\n return format ? { value, format } : value\n }),\n )\n}\n\nexport interface SheetCSVWriteOptions {\n /** `,` by default. */\n delimiter?: string\n /** Only these cells, such as `'A1:D20'`; by default up to the last row and column with data. */\n range?: string | SheetRange\n /**\n * Writes what each cell shows, with its format (`¥1,280.00`,\n * `2026年3月5日`), instead of values a spreadsheet can sort and add up\n * (`1280`, `2026-03-05`, what formulas come to). Off by default.\n */\n text?: boolean\n /**\n * Writes formulas as written (`=A1+B1`) instead of what they come to,\n * to read back with `readCSV(text, { formulas: true })`. Ignored with\n * `text`. Off by default.\n */\n formulas?: boolean\n /** The language dates are shown in, with `text`. */\n locale?: string\n /**\n * What a formula comes to, by its row and column in the sheet, e.g. from\n * `SheetFormulaEngine.result`. Without it a formula is written as text,\n * with a `'` before it unless `escapeFormulas` is off; for formulas a\n * spreadsheet can work out, use `formulas`.\n */\n result?: (row: number, col: number) => SheetResult\n /**\n * With `text`: a cell's text, e.g. as its cell type shows it; `undefined`\n * uses its format. `value` is its value, or its formula's result; `row`\n * and `col` are where it is in the sheet.\n */\n cellText?: (\n input: SheetCellInput,\n value: SheetValue,\n row: number,\n col: number,\n ) => string | undefined\n /**\n * Without `text`: the value to write for a cell, as a cell type's `plain`\n * gives it; `null` writes nothing, `undefined` writes its value, or its\n * formula's result. Not asked for formulas written with `formulas`.\n */\n cellPlain?: (\n input: SheetCellInput,\n value: SheetValue,\n row: number,\n col: number,\n ) => SheetValue | undefined\n /** Line breaks between rows; `\\r\\n` by default, as the format asks. */\n newline?: string\n /**\n * Starts with a byte order mark, so programs that guess the encoding read\n * it as UTF-8. On by default.\n */\n bom?: boolean\n /**\n * Puts a `'` before text starting with `=`, `+`, `-`, `@`, a tab or a\n * carriage return, so a spreadsheet program opening the file doesn't run\n * it as a formula. Numbers are written as they are, and so are this grid's\n * formulas with `formulas`. On by default; `false` writes such text as\n * it is.\n */\n escapeFormulas?: boolean\n}\n\n/** What a spreadsheet program would start a formula with. */\nconst FORMULA_START = /^[=+\\-@\\t\\r]/\n\n/** Where a sheet has data: up to its last row and column with something in them. */\nexport function dataBounds(sheet: SheetData): SheetBounds | null {\n let bottom = -1\n let right = -1\n sheet.cells.forEach((line, row) => {\n if (!line) return\n for (let col = line.length - 1; col >= 0; col--)\n if (valueOf(line[col]) != null && valueOf(line[col]) !== '') {\n bottom = row\n right = Math.max(right, col)\n break\n }\n })\n return bottom === -1 ? null : { top: 0, left: 0, bottom, right }\n}\n\n/** A sheet, or part of it, as CSV. */\nexport function sheetToCSV(sheet: SheetData, options: SheetCSVWriteOptions = {}) {\n const {\n delimiter = ',',\n text = false,\n formulas = false,\n locale,\n result,\n cellText,\n cellPlain,\n newline = '\\r\\n',\n bom = true,\n escapeFormulas = true,\n } = options\n const range = typeof options.range === 'string' ? parseRange(options.range) : options.range\n if (options.range != null && !range)\n throw new RangeError(`[@dou-ui/sheet] \"${options.range}\" isn't a range`)\n const data = dataBounds(sheet)\n const b = range ? boundsOf(range) : data\n if (!b || !data) return bom ? '\\ufeff' : ''\n const quote = new RegExp(`[\"\\\\r\\\\n${delimiter.replace(/[\\\\\\]^-]/g, '\\\\$&')}]`)\n const field = (value: string) => (quote.test(value) ? `\"${value.replaceAll('\"', '\"\"')}\"` : value)\n const lines: string[] = []\n // empty rows and columns past the data are left out, also of whole columns or rows\n const bottom = Math.min(b.bottom, data.bottom)\n const right = Math.min(b.right, data.right)\n for (let row = b.top; row <= bottom; row++) {\n const line = sheet.cells[row]\n const fields: string[] = []\n for (let col = b.left; col <= right; col++) {\n const input = line?.[col]\n const stored = valueOf(input)\n // written as written, this grid's formulas stay formulas, to be read back with `formulas`\n const formula = !text && formulas && typeof stored === 'string' && stored.startsWith('=')\n const value = formula ? stored : shownValue(stored, result, row, col)\n let shown: string\n // whether what is written is text, which a spreadsheet program might run as a formula\n let textual: boolean\n if (formula) {\n shown = stored\n textual = false\n } else if (text) {\n const own = cellText?.(input, value, row, col)\n shown = own ?? formatValue(value, cellOf(input)?.format, locale)\n // a cell type's own text is text, even for a number\n textual =\n typeof value === 'string' ||\n (own !== undefined && own !== formatValue(value, cellOf(input)?.format, locale))\n } else {\n const given = cellPlain?.(input, value, row, col)\n const own = given === undefined ? value : given\n // read back with `formulas`, text that would read as something else keeps its `'`\n shown = formulas ? inputText(own ?? undefined) : plainText(own)\n textual = typeof own === 'string'\n }\n const unsafe = escapeFormulas && textual && FORMULA_START.test(shown)\n fields.push(field(unsafe ? `'${shown}` : shown))\n }\n lines.push(fields.join(delimiter))\n }\n return (bom ? '\\ufeff' : '') + lines.join(newline)\n}\n\n/** A value for another program: numbers in full, `TRUE` / `FALSE`, text as it is. */\nfunction plainText(value: SheetValue | undefined) {\n if (value == null) return ''\n if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE'\n if (typeof value === 'number') return generalNumber(value)\n // text starting with `=` is stored with a `'` to tell it from a formula\n return value.startsWith(\"'=\") ? value.slice(1) : value\n}\n"],"mappings":";;;;;AAmBA,IAAM,aAAa;CAAC;CAAK;CAAK;CAAM;AAAG;;;;;;AAOvC,SAAgB,gBAAgB,MAAc;CAC5C,MAAM,SAAS,IAAI,IAAsB,WAAW,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;CACxE,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,UAAU,QAAQ,IAAI,KAAK;EAClD,MAAM,OAAO,KAAK;EAClB,IAAI,SAAS,MAAK,SAAS,CAAC;OACvB,IAAI,QAAQ;OACZ,IAAI,SAAS,MAAM;GACtB;GACA,KAAK,MAAM,QAAQ,OAAO,OAAO,GAAG,KAAK,KAAK,CAAC;EACjD,OAAO;GACL,MAAM,OAAO,OAAO,IAAI,IAAI;GAC5B,IAAI,MAAM,KAAK,KAAK,SAAS,EAAE;EACjC;CACF;CACA,IAAI,OAAe;CACnB,IAAI,QAAQ;CACZ,KAAK,MAAM,CAAC,WAAW,YAAY,QAAQ;EAEzC,MAAM,OACJ,QAAQ,SAAS,KAAK,QAAQ,QAAQ,SAAS,OAAO,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;EACnF,MAAM,QAAQ,KAAK,MAAM;EACzB,IAAI,CAAC,OAAO;EAEZ,MAAM,QAAQ,SADC,KAAK,OAAO,MAAM,MAAM,KAChB,IAAS,IAAI;EACpC,IAAI,QAAQ,OAAO;GACjB,QAAQ;GACR,OAAO;EACT;CACF;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,SAAS,MAAc,YAAY,gBAAgB,IAAI,GAAe;CACpF,IAAI,KAAK,WAAW,CAAC,MAAM,OAAQ,OAAO,KAAK,MAAM,CAAC;CACtD,MAAM,OAAmB,CAAC;CAC1B,IAAI,MAAgB,CAAC;CACrB,IAAI,QAAQ;CACZ,IAAI,SAAS;CAEb,IAAI,YAAY;CAChB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK;EAClB,IAAI,QAAQ;GACV,IAAI,SAAS,QAAO,KAAK,IAAI,OAAO,MAAK;IACvC,SAAS;IACT;GACF,OAAO,IAAI,SAAS,MAAK,SAAS;QAC7B,SAAS;EAChB,OAAO,IAAI,SAAS,QAAO,UAAU,IAAI;GACvC,SAAS;GACT,YAAY;EACd,OAAO,IAAI,SAAS,WAAW;GAC7B,IAAI,KAAK,KAAK;GACd,QAAQ;GACR,YAAY;EACd,OAAO,IAAI,SAAS,QAAQ,SAAS,MAAM;GACzC,IAAI,SAAS,QAAQ,KAAK,IAAI,OAAO,MAAM;GAC3C,IAAI,KAAK,KAAK;GACd,KAAK,KAAK,GAAG;GACb,MAAM,CAAC;GACP,QAAQ;GACR,YAAY;EACd,OAAO,SAAS;CAClB;CACA,IAAI,UAAU,MAAM,aAAa,IAAI,QAAQ;EAC3C,IAAI,KAAK,KAAK;EACd,KAAK,KAAK,GAAG;CACf;CACA,OAAO;AACT;;AAGA,IAAM,OAAO;;AAsBb,IAAM,eAAe;;AAGrB,SAAgB,QAAQ,MAAc,UAA+B,CAAC,GAAe;CACnF,MAAM,EAAE,QAAQ,MAAM,WAAW,UAAU;CAC3C,MAAM,YAAY,QAAQ,aAAa,gBAAgB,IAAI;CAC3D,MAAM,SAAS,QAAQ,YAAY,cAAc,MAAM,MAAM,UAAU;CACvE,OAAO,SAAS,MAAM,SAAS,CAAC,CAAC,KAAK,WACpC,OAAO,KAAK,UAA0B;EACpC,IAAI,UAAU,IAAI,OAAO,KAAA;EACzB,IAAI,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,OAAO,YAAY,QAAQ,QAAQ,IAAI;EACtF,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,KAAK,CAAC,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;EACvE,IAAI,QAAQ;EACZ,IAAI,OAAO;GACT,MAAM,SAAS,aAAa,KAAK,MAAM,KAAK,CAAC;GAC7C,IAAI,QAAQ;IACV,MAAM,GAAG,MAAM,OAAO,UAAU,WAAW;IAC3C,QAAQ,GAAG,OAAO,MAAO,WAAW,KAAK,EAAE,IAAI,WAAW,IAAI,aAAa,KAAK;GAClF;EACF;EACA,MAAM,EAAE,OAAO,WAAW,WAAW,KAAK;EAC1C,IAAI,SAAS,MAAM,OAAO,KAAA;EAC1B,OAAO,SAAS;GAAE;GAAO;EAAO,IAAI;CACtC,CAAC,CACH;AACF;;AAoEA,IAAM,gBAAgB;;AAGtB,SAAgB,WAAW,OAAsC;CAC/D,IAAI,SAAS;CACb,IAAI,QAAQ;CACZ,MAAM,MAAM,SAAS,MAAM,QAAQ;EACjC,IAAI,CAAC,MAAM;EACX,KAAK,IAAI,MAAM,KAAK,SAAS,GAAG,OAAO,GAAG,OACxC,IAAI,QAAQ,KAAK,IAAI,KAAK,QAAQ,QAAQ,KAAK,IAAI,MAAM,IAAI;GAC3D,SAAS;GACT,QAAQ,KAAK,IAAI,OAAO,GAAG;GAC3B;EACF;CACJ,CAAC;CACD,OAAO,WAAW,KAAK,OAAO;EAAE,KAAK;EAAG,MAAM;EAAG;EAAQ;CAAM;AACjE;;AAGA,SAAgB,WAAW,OAAkB,UAAgC,CAAC,GAAG;CAC/E,MAAM,EACJ,YAAY,KACZ,OAAO,OACP,WAAW,OACX,QACA,QACA,UACA,WACA,UAAU,QACV,MAAM,MACN,iBAAiB,SACf;CACJ,MAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW,WAAW,QAAQ,KAAK,IAAI,QAAQ;CACtF,IAAI,QAAQ,SAAS,QAAQ,CAAC,OAC5B,MAAM,IAAI,WAAW,oBAAoB,QAAQ,MAAM,gBAAgB;CACzE,MAAM,OAAO,WAAW,KAAK;CAC7B,MAAM,IAAI,QAAQ,SAAS,KAAK,IAAI;CACpC,IAAI,CAAC,KAAK,CAAC,MAAM,OAAO,MAAM,MAAW;CACzC,MAAM,QAAQ,IAAI,OAAO,WAAW,UAAU,QAAQ,aAAa,MAAM,EAAE,EAAE;CAC7E,MAAM,SAAS,UAAmB,MAAM,KAAK,KAAK,IAAI,IAAI,MAAM,WAAW,MAAK,MAAI,EAAE,KAAK;CAC3F,MAAM,QAAkB,CAAC;CAEzB,MAAM,SAAS,KAAK,IAAI,EAAE,QAAQ,KAAK,MAAM;CAC7C,MAAM,QAAQ,KAAK,IAAI,EAAE,OAAO,KAAK,KAAK;CAC1C,KAAK,IAAI,MAAM,EAAE,KAAK,OAAO,QAAQ,OAAO;EAC1C,MAAM,OAAO,MAAM,MAAM;EACzB,MAAM,SAAmB,CAAC;EAC1B,KAAK,IAAI,MAAM,EAAE,MAAM,OAAO,OAAO,OAAO;GAC1C,MAAM,QAAQ,OAAO;GACrB,MAAM,SAAS,QAAQ,KAAK;GAE5B,MAAM,UAAU,CAAC,QAAQ,YAAY,OAAO,WAAW,YAAY,OAAO,WAAW,GAAG;GACxF,MAAM,QAAQ,UAAU,SAAS,WAAW,QAAQ,QAAQ,KAAK,GAAG;GACpE,IAAI;GAEJ,IAAI;GACJ,IAAI,SAAS;IACX,QAAQ;IACR,UAAU;GACZ,OAAO,IAAI,MAAM;IACf,MAAM,MAAM,WAAW,OAAO,OAAO,KAAK,GAAG;IAC7C,QAAQ,OAAO,YAAY,OAAO,OAAO,KAAK,CAAC,EAAE,QAAQ,MAAM;IAE/D,UACE,OAAO,UAAU,YAChB,QAAQ,KAAA,KAAa,QAAQ,YAAY,OAAO,OAAO,KAAK,CAAC,EAAE,QAAQ,MAAM;GAClF,OAAO;IACL,MAAM,QAAQ,YAAY,OAAO,OAAO,KAAK,GAAG;IAChD,MAAM,MAAM,UAAU,KAAA,IAAY,QAAQ;IAE1C,QAAQ,WAAW,UAAU,OAAO,KAAA,CAAS,IAAI,UAAU,GAAG;IAC9D,UAAU,OAAO,QAAQ;GAC3B;GACA,MAAM,SAAS,kBAAkB,WAAW,cAAc,KAAK,KAAK;GACpE,OAAO,KAAK,MAAM,SAAS,IAAI,UAAU,KAAK,CAAC;EACjD;EACA,MAAM,KAAK,OAAO,KAAK,SAAS,CAAC;CACnC;CACA,QAAQ,MAAM,MAAW,MAAM,MAAM,KAAK,OAAO;AACnD;;AAGA,SAAS,UAAU,OAA+B;CAChD,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,SAAS;CACxD,IAAI,OAAO,UAAU,UAAU,OAAO,cAAc,KAAK;CAEzD,OAAO,MAAM,WAAW,IAAI,IAAI,MAAM,MAAM,CAAC,IAAI;AACnD"}
|