@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.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/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,118 @@
|
|
|
1
|
+
// src/core/data.ts
|
|
2
|
+
/** Kinds of values, in the order they sort in: numbers, text, false and true, errors. */
|
|
3
|
+
function rankOf(value) {
|
|
4
|
+
if (typeof value === "number") return 0;
|
|
5
|
+
if (typeof value === "string") return 1;
|
|
6
|
+
if (typeof value === "boolean") return 2;
|
|
7
|
+
return 3;
|
|
8
|
+
}
|
|
9
|
+
/** Whether a value counts as empty: empty cells sort last, either way. */
|
|
10
|
+
var isEmptyValue = (value) => value == null || value === "";
|
|
11
|
+
var collators = /* @__PURE__ */ new Map();
|
|
12
|
+
/** Compares text the way people of `lang` read it: numbers inside by size, case ignored. */
|
|
13
|
+
function textCollator(lang) {
|
|
14
|
+
const key = lang ?? "";
|
|
15
|
+
let collator = collators.get(key);
|
|
16
|
+
if (!collator) {
|
|
17
|
+
collator = new Intl.Collator(lang, {
|
|
18
|
+
numeric: true,
|
|
19
|
+
sensitivity: "base"
|
|
20
|
+
});
|
|
21
|
+
collators.set(key, collator);
|
|
22
|
+
}
|
|
23
|
+
return collator;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compares two values for sorting from small to large: numbers first, then
|
|
27
|
+
* text, then `FALSE` and `TRUE`, then errors. Empty values are not compared
|
|
28
|
+
* here; they go last whatever the order.
|
|
29
|
+
*/
|
|
30
|
+
function compareForSort(a, b, collator = textCollator()) {
|
|
31
|
+
const ra = rankOf(a);
|
|
32
|
+
const rb = rankOf(b);
|
|
33
|
+
if (ra !== rb) return ra - rb;
|
|
34
|
+
if (ra === 0) return a - b;
|
|
35
|
+
if (ra === 1) return collator.compare(a, b);
|
|
36
|
+
if (ra === 2) return Number(a) - Number(b);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The order to put rows in, sorted by `keys`: a list of the given rows.
|
|
41
|
+
* Equal rows keep their order; empty values go last in either order.
|
|
42
|
+
*/
|
|
43
|
+
function sortRows(rows, keys, valueAt, collator = textCollator()) {
|
|
44
|
+
const values = keys.map((key) => rows.map((row) => valueAt(row, key.col)));
|
|
45
|
+
const order = rows.map((_, i) => i);
|
|
46
|
+
order.sort((x, y) => {
|
|
47
|
+
for (const [k, key] of keys.entries()) {
|
|
48
|
+
const a = values[k][x];
|
|
49
|
+
const b = values[k][y];
|
|
50
|
+
const ea = isEmptyValue(a);
|
|
51
|
+
const eb = isEmptyValue(b);
|
|
52
|
+
if (ea || eb) {
|
|
53
|
+
if (ea !== eb) return ea ? 1 : -1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const c = compareForSort(a, b, collator);
|
|
57
|
+
if (c) return key.order === "desc" ? -c : c;
|
|
58
|
+
}
|
|
59
|
+
return x - y;
|
|
60
|
+
});
|
|
61
|
+
return order.map((i) => rows[i]);
|
|
62
|
+
}
|
|
63
|
+
/** A number a filter condition can compare with, or `null`. */
|
|
64
|
+
function numberOf(value) {
|
|
65
|
+
if (typeof value === "number") return value;
|
|
66
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
67
|
+
const n = Number(value.replace(/,/g, ""));
|
|
68
|
+
return Number.isFinite(n) ? n : null;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/** How a cell compares with a condition's operand: as numbers when both are, else as text. */
|
|
73
|
+
function compareTo(value, text, target, collator) {
|
|
74
|
+
const a = typeof value === "number" ? value : null;
|
|
75
|
+
const b = numberOf(target);
|
|
76
|
+
return a != null && b != null ? a - b : collator.compare(text, String(target));
|
|
77
|
+
}
|
|
78
|
+
/** Whether a cell meets a condition, given what it comes to and how it shows. */
|
|
79
|
+
function meetsCondition(condition, value, text, collator = textCollator()) {
|
|
80
|
+
const { op } = condition;
|
|
81
|
+
if (op === "blank") return isEmptyValue(value);
|
|
82
|
+
if (op === "notBlank") return !isEmptyValue(value);
|
|
83
|
+
const target = condition.value ?? "";
|
|
84
|
+
const shown = text.toLocaleLowerCase();
|
|
85
|
+
const wanted = String(target).toLocaleLowerCase();
|
|
86
|
+
switch (op) {
|
|
87
|
+
case "contains": return shown.includes(wanted);
|
|
88
|
+
case "notContains": return !shown.includes(wanted);
|
|
89
|
+
case "begins": return shown.startsWith(wanted);
|
|
90
|
+
case "ends": return shown.endsWith(wanted);
|
|
91
|
+
}
|
|
92
|
+
if (op === "between" || op === "notBetween") {
|
|
93
|
+
if (isEmptyValue(value)) return op === "notBetween";
|
|
94
|
+
const from = compareTo(value, text, target, collator);
|
|
95
|
+
const to = compareTo(value, text, condition.to ?? "", collator);
|
|
96
|
+
const inside = from >= 0 && to <= 0 || from <= 0 && to >= 0;
|
|
97
|
+
return op === "between" ? inside : !inside;
|
|
98
|
+
}
|
|
99
|
+
if (isEmptyValue(value)) return op === "ne" ? !isEmptyValue(target) : op === "eq" && isEmptyValue(target);
|
|
100
|
+
const c = compareTo(value, text, target, collator);
|
|
101
|
+
switch (op) {
|
|
102
|
+
case "eq": return c === 0;
|
|
103
|
+
case "ne": return c !== 0;
|
|
104
|
+
case "gt": return c > 0;
|
|
105
|
+
case "ge": return c >= 0;
|
|
106
|
+
case "lt": return c < 0;
|
|
107
|
+
case "le": return c <= 0;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/** Whether a cell is kept by a column's filter rule. */
|
|
112
|
+
function meetsRule(rule, value, text, collator = textCollator()) {
|
|
113
|
+
if ("values" in rule) return rule.values.includes(isEmptyValue(value) ? "" : text);
|
|
114
|
+
return meetsCondition(rule.condition, value, text, collator);
|
|
115
|
+
}
|
|
116
|
+
export { compareForSort, isEmptyValue, meetsCondition, meetsRule, sortRows, textCollator };
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.js","names":[],"sources":["../../src/core/data.ts"],"sourcesContent":["import type { SheetCondition, SheetFilterRule, SheetSortOrder } from './model'\n\n/*\n * Sorting and filtering rows by their values. The grid hands in what each\n * cell comes to (a formula's result) and how it shows; these only compare.\n */\n\n/** One column to sort by, the first deciding most. */\nexport interface SheetSortKey {\n /** The column, by number. */\n col: number\n order?: SheetSortOrder\n}\n\n/** Kinds of values, in the order they sort in: numbers, text, false and true, errors. */\nfunction rankOf(value: unknown) {\n if (typeof value === 'number') return 0\n if (typeof value === 'string') return 1\n if (typeof value === 'boolean') return 2\n return 3\n}\n\n/** Whether a value counts as empty: empty cells sort last, either way. */\nexport const isEmptyValue = (value: unknown) => value == null || value === ''\n\nconst collators = new Map<string, Intl.Collator>()\n/** Compares text the way people of `lang` read it: numbers inside by size, case ignored. */\nexport function textCollator(lang?: string) {\n const key = lang ?? ''\n let collator = collators.get(key)\n if (!collator) {\n collator = new Intl.Collator(lang, { numeric: true, sensitivity: 'base' })\n collators.set(key, collator)\n }\n return collator\n}\n\n/**\n * Compares two values for sorting from small to large: numbers first, then\n * text, then `FALSE` and `TRUE`, then errors. Empty values are not compared\n * here; they go last whatever the order.\n */\nexport function compareForSort(a: unknown, b: unknown, collator = textCollator()) {\n const ra = rankOf(a)\n const rb = rankOf(b)\n if (ra !== rb) return ra - rb\n if (ra === 0) return (a as number) - (b as number)\n if (ra === 1) return collator.compare(a as string, b as string)\n if (ra === 2) return Number(a) - Number(b)\n return 0\n}\n\n/**\n * The order to put rows in, sorted by `keys`: a list of the given rows.\n * Equal rows keep their order; empty values go last in either order.\n */\nexport function sortRows(\n rows: readonly number[],\n keys: readonly SheetSortKey[],\n valueAt: (row: number, col: number) => unknown,\n collator = textCollator(),\n): number[] {\n // read every value once\n const values = keys.map((key) => rows.map((row) => valueAt(row, key.col)))\n const order = rows.map((_, i) => i)\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n order.sort((x, y) => {\n for (const [k, key] of keys.entries()) {\n const a = values[k]![x]\n const b = values[k]![y]\n const ea = isEmptyValue(a)\n const eb = isEmptyValue(b)\n if (ea || eb) {\n if (ea !== eb) return ea ? 1 : -1\n continue\n }\n const c = compareForSort(a, b, collator)\n if (c) return key.order === 'desc' ? -c : c\n }\n return x - y\n })\n return order.map((i) => rows[i]!)\n}\n\n/** A number a filter condition can compare with, or `null`. */\nfunction numberOf(value: unknown): number | null {\n if (typeof value === 'number') return value\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value.replace(/,/g, ''))\n return Number.isFinite(n) ? n : null\n }\n return null\n}\n\n/** How a cell compares with a condition's operand: as numbers when both are, else as text. */\nfunction compareTo(value: unknown, text: string, target: unknown, collator: Intl.Collator) {\n const a = typeof value === 'number' ? value : null\n const b = numberOf(target)\n return a != null && b != null ? a - b : collator.compare(text, String(target))\n}\n\n/** Whether a cell meets a condition, given what it comes to and how it shows. */\nexport function meetsCondition(\n condition: SheetCondition,\n value: unknown,\n text: string,\n collator = textCollator(),\n) {\n const { op } = condition\n if (op === 'blank') return isEmptyValue(value)\n if (op === 'notBlank') return !isEmptyValue(value)\n const target = condition.value ?? ''\n const shown = text.toLocaleLowerCase()\n const wanted = String(target).toLocaleLowerCase()\n switch (op) {\n case 'contains':\n return shown.includes(wanted)\n case 'notContains':\n return !shown.includes(wanted)\n case 'begins':\n return shown.startsWith(wanted)\n case 'ends':\n return shown.endsWith(wanted)\n }\n if (op === 'between' || op === 'notBetween') {\n if (isEmptyValue(value)) return op === 'notBetween'\n const from = compareTo(value, text, target, collator)\n const to = compareTo(value, text, condition.to ?? '', collator)\n // either end may be the larger\n const inside = (from >= 0 && to <= 0) || (from <= 0 && to >= 0)\n return op === 'between' ? inside : !inside\n }\n if (isEmptyValue(value))\n return op === 'ne' ? !isEmptyValue(target) : op === 'eq' && isEmptyValue(target)\n const c = compareTo(value, text, target, collator)\n switch (op) {\n case 'eq':\n return c === 0\n case 'ne':\n return c !== 0\n case 'gt':\n return c > 0\n case 'ge':\n return c >= 0\n case 'lt':\n return c < 0\n case 'le':\n return c <= 0\n }\n return true\n}\n\n/** Whether a cell is kept by a column's filter rule. */\nexport function meetsRule(\n rule: SheetFilterRule,\n value: unknown,\n text: string,\n collator = textCollator(),\n) {\n if ('values' in rule) return rule.values.includes(isEmptyValue(value) ? '' : text)\n return meetsCondition(rule.condition, value, text, collator)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { boundsOf, parseRange } from "./address.js";
|
|
2
|
+
import { resolveSheetExtensions } from "./extension.js";
|
|
3
|
+
import { overlaps } from "./merge.js";
|
|
4
|
+
import { plainCellTypes } from "./plain-cell-types.js";
|
|
5
|
+
// src/core/document.ts
|
|
6
|
+
/** A workbook with no sheets, to start from when nothing is saved yet. */
|
|
7
|
+
var emptyWorkbook = () => ({
|
|
8
|
+
version: 1,
|
|
9
|
+
sheets: []
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* What is wrong with the shape itself: the workbook is not an object, or a
|
|
13
|
+
* list in it is not an array. Nothing else can be checked until it is fixed.
|
|
14
|
+
*/
|
|
15
|
+
function shapeProblems(value, lists) {
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [{
|
|
17
|
+
code: "shape",
|
|
18
|
+
path: "",
|
|
19
|
+
text: "",
|
|
20
|
+
message: "The workbook is not an object"
|
|
21
|
+
}];
|
|
22
|
+
return lists.filter((key) => !Array.isArray(value[key])).map((key) => ({
|
|
23
|
+
code: "shape",
|
|
24
|
+
path: key,
|
|
25
|
+
text: "",
|
|
26
|
+
message: `"${key}" is not a list`
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The problems of a workbook, before it is saved or shown: an unknown
|
|
31
|
+
* version, sheets without a name or sharing a name or id, cells using a
|
|
32
|
+
* style or type nobody defined, and merges that are not ranges or overlap
|
|
33
|
+
* another.
|
|
34
|
+
*/
|
|
35
|
+
function checkWorkbook(workbook, options = {}) {
|
|
36
|
+
const shape = shapeProblems(workbook, ["sheets"]);
|
|
37
|
+
if (shape.length) return shape;
|
|
38
|
+
const problems = [];
|
|
39
|
+
if (workbook.version !== 1) problems.push({
|
|
40
|
+
code: "version",
|
|
41
|
+
path: "version",
|
|
42
|
+
text: String(workbook.version),
|
|
43
|
+
message: `Version ${String(workbook.version)} is not known`
|
|
44
|
+
});
|
|
45
|
+
const cellTypes = new Set(plainCellTypes.map((type) => type.name));
|
|
46
|
+
for (const type of resolveSheetExtensions(options.extensions).cellTypes) cellTypes.add(type.name);
|
|
47
|
+
for (const [name, setup] of Object.entries(workbook.types ?? {})) if (!cellTypes.has(setup?.type)) problems.push({
|
|
48
|
+
code: "unknown-type",
|
|
49
|
+
path: `types.${name}`,
|
|
50
|
+
text: String(setup?.type),
|
|
51
|
+
message: `The type "${name}" sets up "${String(setup?.type)}", which no one defines`
|
|
52
|
+
});
|
|
53
|
+
const styles = new Set(Object.keys(workbook.styles ?? {}));
|
|
54
|
+
const types = /* @__PURE__ */ new Set([...cellTypes, ...Object.keys(workbook.types ?? {})]);
|
|
55
|
+
const names = /* @__PURE__ */ new Set();
|
|
56
|
+
const ids = /* @__PURE__ */ new Set();
|
|
57
|
+
workbook.sheets.forEach((sheet, i) => {
|
|
58
|
+
const path = `sheets[${i}]`;
|
|
59
|
+
if (!sheet.name) problems.push({
|
|
60
|
+
code: "missing-name",
|
|
61
|
+
path,
|
|
62
|
+
text: "",
|
|
63
|
+
message: `${path} has no name`
|
|
64
|
+
});
|
|
65
|
+
else if (names.has(sheet.name)) problems.push({
|
|
66
|
+
code: "duplicate-name",
|
|
67
|
+
sheet: sheet.name,
|
|
68
|
+
path,
|
|
69
|
+
text: sheet.name,
|
|
70
|
+
message: `The sheet name "${sheet.name}" is used more than once`
|
|
71
|
+
});
|
|
72
|
+
names.add(sheet.name);
|
|
73
|
+
if (sheet.id !== void 0) {
|
|
74
|
+
if (ids.has(sheet.id)) problems.push({
|
|
75
|
+
code: "duplicate-id",
|
|
76
|
+
sheet: sheet.name,
|
|
77
|
+
path,
|
|
78
|
+
text: sheet.id,
|
|
79
|
+
message: `The sheet id "${sheet.id}" is used more than once`
|
|
80
|
+
});
|
|
81
|
+
ids.add(sheet.id);
|
|
82
|
+
}
|
|
83
|
+
checkCells(sheet, path, styles, types, problems);
|
|
84
|
+
checkMerges(sheet, path, problems);
|
|
85
|
+
});
|
|
86
|
+
return problems;
|
|
87
|
+
}
|
|
88
|
+
function checkCells(sheet, path, styles, types, problems) {
|
|
89
|
+
sheet.cells.forEach((row, r) => row?.forEach((cell, c) => {
|
|
90
|
+
if (!cell || typeof cell !== "object") return;
|
|
91
|
+
const at = `${path}.cells[${r}][${c}]`;
|
|
92
|
+
if (cell.style !== void 0 && !styles.has(cell.style)) problems.push({
|
|
93
|
+
code: "unknown-style",
|
|
94
|
+
sheet: sheet.name,
|
|
95
|
+
path: at,
|
|
96
|
+
text: cell.style,
|
|
97
|
+
message: `No style "${cell.style}" is in the workbook's styles`
|
|
98
|
+
});
|
|
99
|
+
if (cell.type !== void 0 && !types.has(cell.type)) problems.push({
|
|
100
|
+
code: "unknown-type",
|
|
101
|
+
sheet: sheet.name,
|
|
102
|
+
path: at,
|
|
103
|
+
text: cell.type,
|
|
104
|
+
message: `No cell type "${cell.type}" is defined`
|
|
105
|
+
});
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
function checkMerges(sheet, path, problems) {
|
|
109
|
+
const seen = [];
|
|
110
|
+
sheet.merges?.forEach((text, m) => {
|
|
111
|
+
const range = typeof text === "string" ? parseRange(text) : null;
|
|
112
|
+
const bounds = range && boundsOf(range);
|
|
113
|
+
const whole = bounds && Number.isFinite(bounds.bottom) && Number.isFinite(bounds.right);
|
|
114
|
+
const clash = whole && seen.some((other) => overlaps(other, bounds));
|
|
115
|
+
if (whole && !clash) {
|
|
116
|
+
seen.push(bounds);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
problems.push({
|
|
120
|
+
code: "bad-merge",
|
|
121
|
+
sheet: sheet.name,
|
|
122
|
+
path: `${path}.merges[${m}]`,
|
|
123
|
+
text: String(text),
|
|
124
|
+
message: clash ? `The merge "${text}" overlaps another one` : `The merge "${String(text)}" is not a range of cells`
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** The saved data as an object of version 1, or an error saying why it can't be. */
|
|
129
|
+
function sourceOf(input) {
|
|
130
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error(`[@dou-ui/sheet] a workbook must be an object, not ${Array.isArray(input) ? "an array" : JSON.stringify(input) ?? String(input)}`);
|
|
131
|
+
const source = input;
|
|
132
|
+
const version = source.version ?? 1;
|
|
133
|
+
if (version !== 1) throw new Error(`[@dou-ui/sheet] unknown document version ${JSON.stringify(version) ?? String(version)} (expected 1); data saved by a newer @dou-ui/sheet needs that version to open`);
|
|
134
|
+
return source;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Brings a saved workbook up to the current version, and fills what an
|
|
138
|
+
* older or hand-written one may lack (`version`, `sheets`, each sheet's
|
|
139
|
+
* `cells`). Sheets that are not objects are dropped; anything else is kept
|
|
140
|
+
* as it is. Throws for data that is not an object
|
|
141
|
+
* (an array, `null`, a string) and for a version it does not know.
|
|
142
|
+
*/
|
|
143
|
+
function migrateWorkbook(input) {
|
|
144
|
+
const source = sourceOf(input);
|
|
145
|
+
const sheets = [];
|
|
146
|
+
for (const value of Array.isArray(source.sheets) ? source.sheets : []) {
|
|
147
|
+
if (!value || typeof value !== "object") continue;
|
|
148
|
+
const sheet = value;
|
|
149
|
+
sheets.push({
|
|
150
|
+
...sheet,
|
|
151
|
+
name: String(sheet.name ?? ""),
|
|
152
|
+
cells: Array.isArray(sheet.cells) ? sheet.cells : []
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...source,
|
|
157
|
+
version: 1,
|
|
158
|
+
sheets
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
var warned = /* @__PURE__ */ new Set();
|
|
162
|
+
/**
|
|
163
|
+
* The `v-model` as the sheet reads it. A workbook of the wrong shape (not
|
|
164
|
+
* an object, without `sheets`, without a known `version`) gets a warning in
|
|
165
|
+
* development and is read with what is missing left empty and the version
|
|
166
|
+
* as 1, so the sheet still opens and what it writes back carries
|
|
167
|
+
* `version: 1`.
|
|
168
|
+
*/
|
|
169
|
+
function readWorkbook(value) {
|
|
170
|
+
const plain = !!value && typeof value === "object" && !Array.isArray(value);
|
|
171
|
+
const source = plain ? value : {};
|
|
172
|
+
const sheets = Array.isArray(source.sheets);
|
|
173
|
+
const problem = !plain ? `is ${Array.isArray(value) ? "an array" : JSON.stringify(value) ?? String(value)}` : !sheets ? "has no sheets array" : source.version === void 0 ? "has no version" : source.version !== 1 ? `has version ${JSON.stringify(source.version)}` : "";
|
|
174
|
+
if (!problem) return value;
|
|
175
|
+
if (process.env.NODE_ENV !== "production" && !warned.has(problem)) {
|
|
176
|
+
warned.add(problem);
|
|
177
|
+
console.warn(`[@dou-ui/sheet] the v-model ${problem}; expected { version: 1, sheets: [...] }. What is missing is read as empty and the version as 1; read saved data with migrateWorkbook().`);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
...source,
|
|
181
|
+
version: 1,
|
|
182
|
+
sheets: sheets ? source.sheets : []
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export { checkWorkbook, emptyWorkbook, migrateWorkbook, readWorkbook };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","names":[],"sources":["../../src/core/document.ts"],"sourcesContent":["import { boundsOf, parseRange } from './address'\nimport { resolveSheetExtensions, type SheetExtension } from './extension'\nimport { overlaps } from './merge'\nimport type { SheetBounds } from './address'\nimport type { SheetData, SheetWorkbook } from './model'\nimport { plainCellTypes } from './plain-cell-types'\n\n/** A workbook with no sheets, to start from when nothing is saved yet. */\nexport const emptyWorkbook = (): SheetWorkbook => ({ version: 1, sheets: [] })\n\nexport interface WorkbookProblem {\n code:\n | 'version'\n | 'shape'\n | 'missing-name'\n | 'duplicate-name'\n | 'duplicate-id'\n | 'unknown-style'\n | 'unknown-type'\n | 'bad-merge'\n /** The name of the sheet at fault; none for the workbook's own problems. */\n sheet?: string\n /**\n * Where the problem is, as a path into the workbook, e.g. `sheets[1]`,\n * `sheets[0].cells[3][2]` or `types.status`; `version` for the version.\n */\n path: string\n /** The name, style, type or range at fault, for the editor's message. */\n text: string\n /** An English description, for logs. */\n message: string\n}\n\nexport interface CheckWorkbookOptions {\n /**\n * The sheet's `extensions`, for the cell types they add. Without them only\n * the built-in types (`date`, `select`, `checkbox`) and the workbook's own\n * `types` are known.\n */\n extensions?: readonly (SheetExtension | readonly SheetExtension[])[]\n}\n\n/**\n * What is wrong with the shape itself: the workbook is not an object, or a\n * list in it is not an array. Nothing else can be checked until it is fixed.\n */\nfunction shapeProblems(value: unknown, lists: string[]): WorkbookProblem[] {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return [{ code: 'shape', path: '', text: '', message: 'The workbook is not an object' }]\n return lists\n .filter((key) => !Array.isArray((value as Record<string, unknown>)[key]))\n .map((key) => ({ code: 'shape', path: key, text: '', message: `\"${key}\" is not a list` }))\n}\n\n/**\n * The problems of a workbook, before it is saved or shown: an unknown\n * version, sheets without a name or sharing a name or id, cells using a\n * style or type nobody defined, and merges that are not ranges or overlap\n * another.\n */\nexport function checkWorkbook(\n workbook: SheetWorkbook,\n options: CheckWorkbookOptions = {},\n): WorkbookProblem[] {\n const shape = shapeProblems(workbook, ['sheets'])\n if (shape.length) return shape\n const problems: WorkbookProblem[] = []\n if (workbook.version !== 1)\n problems.push({\n code: 'version',\n path: 'version',\n text: String(workbook.version),\n message: `Version ${String(workbook.version)} is not known`,\n })\n const cellTypes = new Set(plainCellTypes.map((type) => type.name))\n for (const type of resolveSheetExtensions(options.extensions).cellTypes) cellTypes.add(type.name)\n for (const [name, setup] of Object.entries(workbook.types ?? {}))\n if (!cellTypes.has(setup?.type))\n problems.push({\n code: 'unknown-type',\n path: `types.${name}`,\n text: String(setup?.type),\n message: `The type \"${name}\" sets up \"${String(setup?.type)}\", which no one defines`,\n })\n const styles = new Set(Object.keys(workbook.styles ?? {}))\n const types = new Set([...cellTypes, ...Object.keys(workbook.types ?? {})])\n\n const names = new Set<string>()\n const ids = new Set<string>()\n workbook.sheets.forEach((sheet, i) => {\n const path = `sheets[${i}]`\n if (!sheet.name)\n problems.push({ code: 'missing-name', path, text: '', message: `${path} has no name` })\n else if (names.has(sheet.name))\n problems.push({\n code: 'duplicate-name',\n sheet: sheet.name,\n path,\n text: sheet.name,\n message: `The sheet name \"${sheet.name}\" is used more than once`,\n })\n names.add(sheet.name)\n if (sheet.id !== undefined) {\n if (ids.has(sheet.id))\n problems.push({\n code: 'duplicate-id',\n sheet: sheet.name,\n path,\n text: sheet.id,\n message: `The sheet id \"${sheet.id}\" is used more than once`,\n })\n ids.add(sheet.id)\n }\n checkCells(sheet, path, styles, types, problems)\n checkMerges(sheet, path, problems)\n })\n return problems\n}\n\nfunction checkCells(\n sheet: SheetData,\n path: string,\n styles: Set<string>,\n types: Set<string>,\n problems: WorkbookProblem[],\n) {\n sheet.cells.forEach((row, r) =>\n row?.forEach((cell, c) => {\n if (!cell || typeof cell !== 'object') return\n const at = `${path}.cells[${r}][${c}]`\n if (cell.style !== undefined && !styles.has(cell.style))\n problems.push({\n code: 'unknown-style',\n sheet: sheet.name,\n path: at,\n text: cell.style,\n message: `No style \"${cell.style}\" is in the workbook's styles`,\n })\n if (cell.type !== undefined && !types.has(cell.type))\n problems.push({\n code: 'unknown-type',\n sheet: sheet.name,\n path: at,\n text: cell.type,\n message: `No cell type \"${cell.type}\" is defined`,\n })\n }),\n )\n}\n\nfunction checkMerges(sheet: SheetData, path: string, problems: WorkbookProblem[]) {\n const seen: SheetBounds[] = []\n sheet.merges?.forEach((text, m) => {\n const range = typeof text === 'string' ? parseRange(text) : null\n const bounds = range && boundsOf(range)\n const whole = bounds && Number.isFinite(bounds.bottom) && Number.isFinite(bounds.right)\n const clash = whole && seen.some((other) => overlaps(other, bounds))\n if (whole && !clash) {\n seen.push(bounds)\n return\n }\n problems.push({\n code: 'bad-merge',\n sheet: sheet.name,\n path: `${path}.merges[${m}]`,\n text: String(text),\n message: clash\n ? `The merge \"${text}\" overlaps another one`\n : `The merge \"${String(text)}\" is not a range of cells`,\n })\n })\n}\n\n/** The saved data as an object of version 1, or an error saying why it can't be. */\nfunction sourceOf(input: unknown): Record<string, unknown> {\n if (!input || typeof input !== 'object' || Array.isArray(input))\n throw new Error(\n `[@dou-ui/sheet] a workbook must be an object, not ${Array.isArray(input) ? 'an array' : (JSON.stringify(input) ?? String(input))}`,\n )\n const source = input as Record<string, unknown>\n const version = source.version ?? 1\n if (version !== 1)\n throw new Error(\n `[@dou-ui/sheet] unknown document version ${JSON.stringify(version) ?? String(version)} (expected 1); data saved by a newer @dou-ui/sheet needs that version to open`,\n )\n return source\n}\n\n/**\n * Brings a saved workbook up to the current version, and fills what an\n * older or hand-written one may lack (`version`, `sheets`, each sheet's\n * `cells`). Sheets that are not objects are dropped; anything else is kept\n * as it is. Throws for data that is not an object\n * (an array, `null`, a string) and for a version it does not know.\n */\nexport function migrateWorkbook(input: unknown): SheetWorkbook {\n const source = sourceOf(input)\n const sheets: SheetData[] = []\n for (const value of Array.isArray(source.sheets) ? source.sheets : []) {\n if (!value || typeof value !== 'object') continue\n const sheet = value as Record<string, unknown>\n sheets.push({\n ...sheet,\n name: String(sheet.name ?? ''),\n cells: Array.isArray(sheet.cells) ? sheet.cells : [],\n } as SheetData)\n }\n return { ...source, version: 1, sheets } as SheetWorkbook\n}\n\nconst warned = new Set<string>()\n\n/**\n * The `v-model` as the sheet reads it. A workbook of the wrong shape (not\n * an object, without `sheets`, without a known `version`) gets a warning in\n * development and is read with what is missing left empty and the version\n * as 1, so the sheet still opens and what it writes back carries\n * `version: 1`.\n */\nexport function readWorkbook(value: unknown): SheetWorkbook {\n const plain = !!value && typeof value === 'object' && !Array.isArray(value)\n const source = (plain ? value : {}) as Partial<SheetWorkbook>\n const sheets = Array.isArray(source.sheets)\n const problem = !plain\n ? `is ${Array.isArray(value) ? 'an array' : (JSON.stringify(value) ?? String(value))}`\n : !sheets\n ? 'has no sheets array'\n : source.version === undefined\n ? 'has no version'\n : source.version !== 1\n ? `has version ${JSON.stringify(source.version)}`\n : ''\n if (!problem) return value as SheetWorkbook\n if (process.env.NODE_ENV !== 'production' && !warned.has(problem)) {\n warned.add(problem)\n console.warn(\n `[@dou-ui/sheet] the v-model ${problem}; expected { version: 1, sheets: [...] }. What is missing is read as empty and the version as 1; read saved data with migrateWorkbook().`,\n )\n }\n return { ...source, version: 1, sheets: sheets ? source.sheets! : [] }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/core/extension.ts
|
|
2
|
+
/** Declares an extension; only gives it a type. */
|
|
3
|
+
function defineSheetExtension(extension) {
|
|
4
|
+
return extension;
|
|
5
|
+
}
|
|
6
|
+
function flatten(list) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const item of list) if (Array.isArray(item)) out.push(...flatten(item));
|
|
9
|
+
else out.push(item);
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
/** Sorts the extensions by priority and collects what they add. */
|
|
13
|
+
function resolveSheetExtensions(list = []) {
|
|
14
|
+
const extensions = flatten(list).map((extension, index) => ({
|
|
15
|
+
extension,
|
|
16
|
+
index
|
|
17
|
+
})).sort((a, b) => (b.extension.priority ?? 100) - (a.extension.priority ?? 100) || a.index - b.index).map((entry) => entry.extension);
|
|
18
|
+
const names = /* @__PURE__ */ new Set();
|
|
19
|
+
for (const extension of extensions) {
|
|
20
|
+
if (names.has(extension.name)) throw new Error(`[@dou-ui/sheet] the extension "${extension.name}" is added twice`);
|
|
21
|
+
names.add(extension.name);
|
|
22
|
+
}
|
|
23
|
+
const pick = (key) => extensions.flatMap((e) => e[key] ? [e[key]] : []);
|
|
24
|
+
const keepers = extensions.filter((extension) => extension.comments);
|
|
25
|
+
if (keepers.length > 1) throw new Error("[@dou-ui/sheet] only one extension may keep the comment threads");
|
|
26
|
+
return {
|
|
27
|
+
extensions,
|
|
28
|
+
cellTypes: extensions.flatMap((e) => e.cellTypes ?? []),
|
|
29
|
+
functions: extensions.flatMap((e) => e.functions ?? []).reverse(),
|
|
30
|
+
commands: extensions.flatMap((e) => e.commands ? [{
|
|
31
|
+
extension: e.name,
|
|
32
|
+
commands: e.commands
|
|
33
|
+
}] : []),
|
|
34
|
+
exporters: extensions.flatMap((e) => (e.exporters ?? []).map((exporter) => ({
|
|
35
|
+
extension: e.name,
|
|
36
|
+
exporter
|
|
37
|
+
}))),
|
|
38
|
+
importers: extensions.flatMap((e) => (e.importers ?? []).map((importer) => ({
|
|
39
|
+
extension: e.name,
|
|
40
|
+
importer
|
|
41
|
+
}))),
|
|
42
|
+
keymaps: pick("keymap"),
|
|
43
|
+
toolbars: pick("toolbar"),
|
|
44
|
+
menus: pick("contextMenu"),
|
|
45
|
+
setups: pick("setup"),
|
|
46
|
+
transactions: pick("transaction"),
|
|
47
|
+
updates: pick("update"),
|
|
48
|
+
pasteTransforms: pick("transformPaste"),
|
|
49
|
+
overlays: extensions.flatMap((e) => e.overlays ?? []),
|
|
50
|
+
layers: {
|
|
51
|
+
below: extensions.flatMap((e) => e.layers?.below ?? []),
|
|
52
|
+
above: extensions.flatMap((e) => e.layers?.above ?? [])
|
|
53
|
+
},
|
|
54
|
+
history: extensions.find((e) => e.history)?.history,
|
|
55
|
+
comments: keepers[0]?.comments
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export { defineSheetExtension, resolveSheetExtensions };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension.js","names":[],"sources":["../../src/core/extension.ts"],"sourcesContent":["import type { CommentAction, CommentThread } from 'dou-ui'\nimport type { Component, Ref } from 'vue'\nimport type { SheetContext } from '../context'\nimport type { SheetMenuContext, SheetMenuItem, SheetToolbarItem } from '../types'\nimport type { SheetCommand } from './commands'\nimport type { SheetExporter, SheetImporter } from './file'\nimport type { SheetFunction } from '../formula/functions'\nimport type { SheetCellType } from './cell-type'\nimport type { SheetClip } from './clipboard'\nimport type { SheetWorkbook } from './model'\nimport type { SheetTransaction } from './transaction'\n\n/** What an extension's hooks receive; the same as `useSheet()`. */\nexport type SheetExtensionContext = SheetContext\n\n/**\n * A key handler: return `false` to let the key through to the next handler\n * and the grid's own keys; anything else means it was handled.\n */\nexport type SheetKeyHandler = (event: KeyboardEvent) => boolean | void\n\n/** An undo history used instead of the grid's own, e.g. one that only takes back this user's changes. */\nexport interface SheetHistoryHandler {\n /** `false` when there was nothing to undo. */\n undo: () => boolean\n redo: () => boolean\n canUndo: () => boolean\n canRedo: () => boolean\n}\n\n/** A change the grid has made, as an extension's `update` sees it. */\nexport interface SheetUpdate {\n /** The workbook before the change. */\n before: SheetWorkbook\n /** The workbook now. */\n doc: SheetWorkbook\n /**\n * The change: typing, commands, undo and redo, `transact` and other\n * people's changes. `transaction.meta.action` says what made it, and\n * other people's changes in collaboration have `meta.origin` `'remote'`.\n * `null` when a new workbook came in through `v-model`.\n */\n transaction: SheetTransaction | null\n}\n\n/** Where the comment threads live, when not in `v-model:comments`. */\nexport interface SheetCommentsProvider {\n /** The threads, kept up to date. */\n threads: Readonly<Ref<readonly CommentThread[]>>\n /** Make a change this user asked for. */\n apply: (action: CommentAction) => void\n}\n\n/**\n * A piece of the sheet: cell types, keys, menu items, panels drawn over\n * the grid, and a say in every change.\n */\nexport interface SheetExtension {\n name: string\n /** Higher runs first: its keys and hooks come before others. Defaults to 100. */\n priority?: number\n /** Cell types cells can name with `type`; one with a built-in name replaces it. */\n cellTypes?: SheetCellType[]\n /**\n * Functions formulas can call (see `defineSheetFunction`); one with a\n * built-in name replaces it.\n */\n functions?: SheetFunction[]\n /**\n * Commands, by name: things toolbar items, menu items and keys can refer\n * to. Start the names with the extension's, e.g. `xlsx.export`. A name the\n * sheet already has replaces that command only with `override: true`.\n */\n commands?: Record<string, SheetCommand>\n /**\n * Ways to write files, offered in the toolbar's export menu and by\n * `exportFile`. One with a built-in name (`csv`, `png`, `svg`) replaces it.\n */\n exporters?: SheetExporter[]\n /**\n * Ways to read files, for the toolbar's import button, `importFile` and\n * dropped files. They are asked before the built-in CSV one.\n */\n importers?: SheetImporter[]\n /**\n * Keys, written as in `useHotkey` (e.g. `mod+b`, `alt+arrowdown`), while the\n * grid has focus and no cell is being edited: a handler, or the name of a\n * command to run. They come before the grid's own.\n */\n keymap?: (context: SheetExtensionContext) => Record<string, SheetKeyHandler | string>\n /** Items for the right-click menu, put before the built-in ones. */\n contextMenu?: (context: SheetExtensionContext, menu: SheetMenuContext) => SheetMenuItem[]\n /**\n * Items for `DouSheetToolbar`: after the built-in tools, or where its\n * `tools` names them by `key`.\n */\n toolbar?: (context: SheetExtensionContext) => SheetToolbarItem[]\n /**\n * Components drawn over the grid, not scrolled with it, e.g. a panel or\n * marks placed with `cellRect`. They can call `useSheet()`.\n */\n overlays?: Component[]\n /**\n * Components drawn over the cells, kept out of the headers: `below`\n * under the grid's own marks, such as the copied range and formula\n * references, `above` over them; the editor stays on top. They place marks with\n * `useSheet()`'s `cellRect` and `rangeRect`, which follow the scroll, and\n * take no clicks.\n */\n layers?: { below?: Component[]; above?: Component[] }\n /**\n * Keeps the comment threads somewhere else than `v-model:comments`, e.g.\n * in a shared document; `v-model:comments` then only mirrors them. Only\n * one extension may.\n */\n comments?: (context: SheetExtensionContext) => SheetCommentsProvider\n /** Replaces the grid's undo history; the first extension that has one wins. */\n history?: (context: SheetExtensionContext) => SheetHistoryHandler\n /** Runs once the grid is mounted. Return a function to run when it unmounts. */\n setup?: (context: SheetExtensionContext) => void | (() => void)\n /**\n * Sees every change made in the grid or with `transact` before it is\n * applied, in priority order, and may add steps to it. Undo takes back\n * the added steps with the rest.\n */\n transaction?: (tr: SheetTransaction, context: SheetExtensionContext) => void\n /**\n * Runs after every change, with the workbook already updated: changes in\n * the grid, `transact`, undo and redo, other people's changes and a new\n * `v-model` workbook. For keeping something else in step, such as a save\n * reminder; to change the change itself use `transaction`.\n */\n update?: (update: SheetUpdate, context: SheetExtensionContext) => void\n /**\n * Rewrites cells before they are pasted, or imported at the active cell,\n * in priority order; return `null` to stop it. A paste's `paste` event\n * comes after, with what the extensions made of it; an import has none.\n * Cut cells that come back rewritten still leave their place, in the\n * same step, but formulas pointing at them don't follow.\n */\n transformPaste?: (clip: SheetClip, context: SheetExtensionContext) => SheetClip | null\n}\n\n/** Declares an extension; only gives it a type. */\nexport function defineSheetExtension(extension: SheetExtension): SheetExtension {\n return extension\n}\n\n/** Everything the extensions contribute, gathered once per list. */\nexport interface ResolvedSheetExtensions {\n extensions: SheetExtension[]\n cellTypes: SheetCellType[]\n functions: SheetFunction[]\n commands: { extension: string; commands: Record<string, SheetCommand> }[]\n exporters: { extension: string; exporter: SheetExporter }[]\n importers: { extension: string; importer: SheetImporter }[]\n keymaps: NonNullable<SheetExtension['keymap']>[]\n toolbars: NonNullable<SheetExtension['toolbar']>[]\n menus: NonNullable<SheetExtension['contextMenu']>[]\n setups: NonNullable<SheetExtension['setup']>[]\n transactions: NonNullable<SheetExtension['transaction']>[]\n updates: NonNullable<SheetExtension['update']>[]\n pasteTransforms: NonNullable<SheetExtension['transformPaste']>[]\n overlays: Component[]\n layers: { below: Component[]; above: Component[] }\n history?: SheetExtension['history']\n comments?: SheetExtension['comments']\n}\n\ntype ExtensionList = readonly (SheetExtension | readonly SheetExtension[])[]\n\nfunction flatten(list: ExtensionList): SheetExtension[] {\n const out: SheetExtension[] = []\n for (const item of list) {\n if (Array.isArray(item)) out.push(...flatten(item))\n else out.push(item as SheetExtension)\n }\n return out\n}\n\n/** Sorts the extensions by priority and collects what they add. */\nexport function resolveSheetExtensions(list: ExtensionList = []): ResolvedSheetExtensions {\n const extensions = flatten(list)\n .map((extension, index) => ({ extension, index }))\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n .sort(\n (a, b) => (b.extension.priority ?? 100) - (a.extension.priority ?? 100) || a.index - b.index,\n )\n .map((entry) => entry.extension)\n\n const names = new Set<string>()\n for (const extension of extensions) {\n if (names.has(extension.name))\n throw new Error(`[@dou-ui/sheet] the extension \"${extension.name}\" is added twice`)\n names.add(extension.name)\n }\n\n const pick = <K extends keyof SheetExtension>(key: K) =>\n extensions.flatMap((e) => (e[key] ? [e[key]] : [])) as NonNullable<SheetExtension[K]>[]\n\n const keepers = extensions.filter((extension) => extension.comments)\n if (keepers.length > 1)\n throw new Error('[@dou-ui/sheet] only one extension may keep the comment threads')\n\n return {\n extensions,\n // the first (highest priority) extension to name a type wins\n cellTypes: extensions.flatMap((e) => e.cellTypes ?? []),\n // added in reverse, so the highest priority one is added last and wins\n // oxlint-disable-next-line unicorn/no-array-reverse -- reverses a fresh array\n functions: extensions.flatMap((e) => e.functions ?? []).reverse(),\n commands: extensions.flatMap((e) =>\n e.commands ? [{ extension: e.name, commands: e.commands }] : [],\n ),\n exporters: extensions.flatMap((e) =>\n (e.exporters ?? []).map((exporter) => ({ extension: e.name, exporter })),\n ),\n importers: extensions.flatMap((e) =>\n (e.importers ?? []).map((importer) => ({ extension: e.name, importer })),\n ),\n keymaps: pick('keymap'),\n toolbars: pick('toolbar'),\n menus: pick('contextMenu'),\n setups: pick('setup'),\n transactions: pick('transaction'),\n updates: pick('update'),\n pasteTransforms: pick('transformPaste'),\n overlays: extensions.flatMap((e) => e.overlays ?? []),\n layers: {\n below: extensions.flatMap((e) => e.layers?.below ?? []),\n above: extensions.flatMap((e) => e.layers?.above ?? []),\n },\n history: extensions.find((e) => e.history)?.history,\n comments: keepers[0]?.comments,\n }\n}\n"],"mappings":";;AAgJA,SAAgB,qBAAqB,WAA2C;CAC9E,OAAO;AACT;AAyBA,SAAS,QAAQ,MAAuC;CACtD,MAAM,MAAwB,CAAC;CAC/B,KAAK,MAAM,QAAQ,MACjB,IAAI,MAAM,QAAQ,IAAI,GAAG,IAAI,KAAK,GAAG,QAAQ,IAAI,CAAC;MAC7C,IAAI,KAAK,IAAsB;CAEtC,OAAO;AACT;;AAGA,SAAgB,uBAAuB,OAAsB,CAAC,GAA4B;CACxF,MAAM,aAAa,QAAQ,IAAI,CAAC,CAC7B,KAAK,WAAW,WAAW;EAAE;EAAW;CAAM,EAAE,CAAC,CAEjD,MACE,GAAG,OAAO,EAAE,UAAU,YAAY,QAAQ,EAAE,UAAU,YAAY,QAAQ,EAAE,QAAQ,EAAE,KACzF,CAAC,CACA,KAAK,UAAU,MAAM,SAAS;CAEjC,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,aAAa,YAAY;EAClC,IAAI,MAAM,IAAI,UAAU,IAAI,GAC1B,MAAM,IAAI,MAAM,kCAAkC,UAAU,KAAK,iBAAiB;EACpF,MAAM,IAAI,UAAU,IAAI;CAC1B;CAEA,MAAM,QAAwC,QAC5C,WAAW,SAAS,MAAO,EAAE,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,CAAE;CAEpD,MAAM,UAAU,WAAW,QAAQ,cAAc,UAAU,QAAQ;CACnE,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MAAM,iEAAiE;CAEnF,OAAO;EACL;EAEA,WAAW,WAAW,SAAS,MAAM,EAAE,aAAa,CAAC,CAAC;EAGtD,WAAW,WAAW,SAAS,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ;EAChE,UAAU,WAAW,SAAS,MAC5B,EAAE,WAAW,CAAC;GAAE,WAAW,EAAE;GAAM,UAAU,EAAE;EAAS,CAAC,IAAI,CAAC,CAChE;EACA,WAAW,WAAW,SAAS,OAC5B,EAAE,aAAa,CAAC,EAAA,CAAG,KAAK,cAAc;GAAE,WAAW,EAAE;GAAM;EAAS,EAAE,CACzE;EACA,WAAW,WAAW,SAAS,OAC5B,EAAE,aAAa,CAAC,EAAA,CAAG,KAAK,cAAc;GAAE,WAAW,EAAE;GAAM;EAAS,EAAE,CACzE;EACA,SAAS,KAAK,QAAQ;EACtB,UAAU,KAAK,SAAS;EACxB,OAAO,KAAK,aAAa;EACzB,QAAQ,KAAK,OAAO;EACpB,cAAc,KAAK,aAAa;EAChC,SAAS,KAAK,QAAQ;EACtB,iBAAiB,KAAK,gBAAgB;EACtC,UAAU,WAAW,SAAS,MAAM,EAAE,YAAY,CAAC,CAAC;EACpD,QAAQ;GACN,OAAO,WAAW,SAAS,MAAM,EAAE,QAAQ,SAAS,CAAC,CAAC;GACtD,OAAO,WAAW,SAAS,MAAM,EAAE,QAAQ,SAAS,CAAC,CAAC;EACxD;EACA,SAAS,WAAW,MAAM,MAAM,EAAE,OAAO,CAAC,EAAE;EAC5C,UAAU,QAAQ,EAAE,EAAE;CACxB;AACF"}
|