@bendyline/squisq 2.10.0 → 2.11.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/dist/{Doc-DTyeaFuW.d.ts → Doc-CRPQCkHn.d.ts} +34 -2
- package/dist/{ImageEditDoc-dFEclQ9l.d.ts → ImageEditDoc-MbvJM1HE.d.ts} +1 -1
- package/dist/{chunk-IO5XO6XH.js → chunk-64I44JAP.js} +1 -1
- package/dist/{chunk-XSW4QTMB.js → chunk-EH4S7HXE.js} +193 -3
- package/dist/{chunk-7YEZFHLS.js → chunk-O7HLQ22N.js} +11 -0
- package/dist/{chunk-NBKAXPSX.js → chunk-TMOHAFYE.js} +1 -0
- package/dist/{chunk-MIKKUKOD.js → chunk-UBFT2CDQ.js} +30 -15
- package/dist/{chunk-FCG3BIZN.js → chunk-UNVHAK3A.js} +5 -1
- package/dist/{chunk-NFHMHYUV.js → chunk-ZWID3HM5.js} +1 -1
- package/dist/{chunk-DRFJU4MK.js → chunk-ZYCSRDSX.js} +352 -2
- package/dist/doc/index.d.ts +123 -6
- package/dist/doc/index.js +12 -4
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +7 -6
- package/dist/index.js +20 -8
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/markdown/index.js +3 -3
- package/dist/{materializePageSection-BRNIINlU.d.ts → materializePageSection-BeUPHeCl.d.ts} +1 -1
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +3 -3
- package/dist/schemas/index.d.ts +4 -4
- package/dist/storage/index.js +1 -1
- package/dist/table/index.d.ts +123 -0
- package/dist/table/index.js +298 -0
- package/dist/{themeLibrary-xaxN5UYu.d.ts → themeLibrary-BG3jr6mT.d.ts} +1 -1
- package/dist/transform/index.d.ts +2 -2
- package/dist/versions/index.d.ts +15 -1
- package/dist/versions/index.js +5 -1
- package/dist/viewState-lTZS-hP_.d.ts +91 -0
- package/package.json +6 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// src/table/viewState.ts
|
|
2
|
+
var TEXT_FILTER_OPS = ["=", "!=", "~", "!~", "^~", "$~"];
|
|
3
|
+
var EMPTY_TABLE_VIEW_STATE = Object.freeze({
|
|
4
|
+
sort: [],
|
|
5
|
+
filter: []
|
|
6
|
+
});
|
|
7
|
+
function isEmptyViewState(view) {
|
|
8
|
+
return view.sort.length === 0 && view.filter.length === 0;
|
|
9
|
+
}
|
|
10
|
+
function isNumericCellText(value) {
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if (trimmed === "") return false;
|
|
13
|
+
if (/^0\d/.test(trimmed)) return false;
|
|
14
|
+
return Number.isFinite(Number(trimmed));
|
|
15
|
+
}
|
|
16
|
+
function inferNumericColumn(rows, col) {
|
|
17
|
+
let sawValue = false;
|
|
18
|
+
for (const row of rows) {
|
|
19
|
+
const cell = row[col] ?? "";
|
|
20
|
+
if (cell.trim() === "") continue;
|
|
21
|
+
if (!isNumericCellText(cell)) return false;
|
|
22
|
+
sawValue = true;
|
|
23
|
+
}
|
|
24
|
+
return sawValue;
|
|
25
|
+
}
|
|
26
|
+
function makeTableCollator() {
|
|
27
|
+
return new Intl.Collator(void 0, { numeric: true });
|
|
28
|
+
}
|
|
29
|
+
var NEEDS_INNER_QUOTING_RE = /[,:;=!<>~"^$*]|^\s|\s$/;
|
|
30
|
+
function quoteViewToken(text) {
|
|
31
|
+
if (text === "" || NEEDS_INNER_QUOTING_RE.test(text)) {
|
|
32
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
33
|
+
}
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
function readQuoted(scanner) {
|
|
37
|
+
let out = "";
|
|
38
|
+
let i = scanner.pos + 1;
|
|
39
|
+
const { text } = scanner;
|
|
40
|
+
while (i < text.length) {
|
|
41
|
+
const ch = text[i];
|
|
42
|
+
if (ch === '"') {
|
|
43
|
+
if (text[i + 1] === '"') {
|
|
44
|
+
out += '"';
|
|
45
|
+
i += 2;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
scanner.pos = i + 1;
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
out += ch;
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function readBare(scanner, stops) {
|
|
57
|
+
const { text } = scanner;
|
|
58
|
+
let i = scanner.pos;
|
|
59
|
+
while (i < text.length && !stops.includes(text[i])) i++;
|
|
60
|
+
const out = text.slice(scanner.pos, i);
|
|
61
|
+
scanner.pos = i;
|
|
62
|
+
return out.trim();
|
|
63
|
+
}
|
|
64
|
+
function skipSpaces(scanner) {
|
|
65
|
+
while (scanner.text[scanner.pos] === " ") scanner.pos++;
|
|
66
|
+
}
|
|
67
|
+
var FILTER_OP_TOKENS = [
|
|
68
|
+
{ token: ">=", op: ">=", caseSensitive: false },
|
|
69
|
+
{ token: "<=", op: "<=", caseSensitive: false },
|
|
70
|
+
{ token: "!~*", op: "!~", caseSensitive: true },
|
|
71
|
+
{ token: "!~", op: "!~", caseSensitive: false },
|
|
72
|
+
{ token: "!=*", op: "!=", caseSensitive: true },
|
|
73
|
+
{ token: "!=", op: "!=", caseSensitive: false },
|
|
74
|
+
{ token: "^~*", op: "^~", caseSensitive: true },
|
|
75
|
+
{ token: "^~", op: "^~", caseSensitive: false },
|
|
76
|
+
{ token: "$~*", op: "$~", caseSensitive: true },
|
|
77
|
+
{ token: "$~", op: "$~", caseSensitive: false },
|
|
78
|
+
{ token: "~*", op: "~", caseSensitive: true },
|
|
79
|
+
{ token: "~", op: "~", caseSensitive: false },
|
|
80
|
+
{ token: "=*", op: "=", caseSensitive: true },
|
|
81
|
+
{ token: "=", op: "=", caseSensitive: false },
|
|
82
|
+
{ token: ">", op: ">", caseSensitive: false },
|
|
83
|
+
{ token: "<", op: "<", caseSensitive: false }
|
|
84
|
+
];
|
|
85
|
+
function parseSortParam(raw) {
|
|
86
|
+
const scanner = { text: raw, pos: 0 };
|
|
87
|
+
const terms = [];
|
|
88
|
+
for (; ; ) {
|
|
89
|
+
skipSpaces(scanner);
|
|
90
|
+
const column = scanner.text[scanner.pos] === '"' ? readQuoted(scanner) : readBare(scanner, ":,");
|
|
91
|
+
if (column === null || column === "") return null;
|
|
92
|
+
let dir = "asc";
|
|
93
|
+
skipSpaces(scanner);
|
|
94
|
+
if (scanner.text[scanner.pos] === ":") {
|
|
95
|
+
scanner.pos++;
|
|
96
|
+
skipSpaces(scanner);
|
|
97
|
+
const dirToken = readBare(scanner, ",").toLowerCase();
|
|
98
|
+
if (dirToken !== "asc" && dirToken !== "desc") return null;
|
|
99
|
+
dir = dirToken;
|
|
100
|
+
}
|
|
101
|
+
terms.push({ column, dir });
|
|
102
|
+
skipSpaces(scanner);
|
|
103
|
+
if (scanner.pos >= scanner.text.length) return terms;
|
|
104
|
+
if (scanner.text[scanner.pos] !== ",") return null;
|
|
105
|
+
scanner.pos++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function parseFilterParam(raw) {
|
|
109
|
+
const scanner = { text: raw, pos: 0 };
|
|
110
|
+
const clauses = [];
|
|
111
|
+
for (; ; ) {
|
|
112
|
+
skipSpaces(scanner);
|
|
113
|
+
const column = scanner.text[scanner.pos] === '"' ? readQuoted(scanner) : readBare(scanner, "=!<>~^$;");
|
|
114
|
+
if (column === null || column === "") return null;
|
|
115
|
+
skipSpaces(scanner);
|
|
116
|
+
const rest = scanner.text.slice(scanner.pos);
|
|
117
|
+
const match = FILTER_OP_TOKENS.find((candidate) => rest.startsWith(candidate.token));
|
|
118
|
+
if (!match) return null;
|
|
119
|
+
scanner.pos += match.token.length;
|
|
120
|
+
skipSpaces(scanner);
|
|
121
|
+
const value = scanner.text[scanner.pos] === '"' ? readQuoted(scanner) : readBare(scanner, ";");
|
|
122
|
+
if (value === null) return null;
|
|
123
|
+
const clause = { column, op: match.op, value };
|
|
124
|
+
if (match.caseSensitive) clause.caseSensitive = true;
|
|
125
|
+
clauses.push(clause);
|
|
126
|
+
skipSpaces(scanner);
|
|
127
|
+
if (scanner.pos >= scanner.text.length) return clauses;
|
|
128
|
+
if (scanner.text[scanner.pos] !== ";") return null;
|
|
129
|
+
scanner.pos++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function parseTableViewState(sortRaw, filterRaw, headers) {
|
|
133
|
+
const issues = [];
|
|
134
|
+
const known = new Set(headers);
|
|
135
|
+
let sort = [];
|
|
136
|
+
if (sortRaw !== void 0 && sortRaw.trim() !== "") {
|
|
137
|
+
const parsed = parseSortParam(sortRaw);
|
|
138
|
+
if (parsed === null) {
|
|
139
|
+
issues.push({ code: "data-view-invalid", message: `sort "${sortRaw}" could not be parsed` });
|
|
140
|
+
} else {
|
|
141
|
+
sort = parsed.filter((term) => {
|
|
142
|
+
if (known.has(term.column)) return true;
|
|
143
|
+
issues.push({
|
|
144
|
+
code: "data-view-unknown-column",
|
|
145
|
+
message: `sort column "${term.column}" is not a header`
|
|
146
|
+
});
|
|
147
|
+
return false;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let filter = [];
|
|
152
|
+
if (filterRaw !== void 0 && filterRaw.trim() !== "") {
|
|
153
|
+
const parsed = parseFilterParam(filterRaw);
|
|
154
|
+
if (parsed === null) {
|
|
155
|
+
issues.push({
|
|
156
|
+
code: "data-view-invalid",
|
|
157
|
+
message: `filter "${filterRaw}" could not be parsed`
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
filter = parsed.filter((clause) => {
|
|
161
|
+
if (known.has(clause.column)) return true;
|
|
162
|
+
issues.push({
|
|
163
|
+
code: "data-view-unknown-column",
|
|
164
|
+
message: `filter column "${clause.column}" is not a header`
|
|
165
|
+
});
|
|
166
|
+
return false;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { view: { sort, filter }, issues };
|
|
171
|
+
}
|
|
172
|
+
function serializeTableViewState(view) {
|
|
173
|
+
const out = {};
|
|
174
|
+
if (view.sort.length > 0) {
|
|
175
|
+
out.sort = view.sort.map((term) => `${quoteViewToken(term.column)}${term.dir === "desc" ? ":desc" : ""}`).join(",");
|
|
176
|
+
}
|
|
177
|
+
if (view.filter.length > 0) {
|
|
178
|
+
out.filter = view.filter.map((clause) => {
|
|
179
|
+
const star = clause.caseSensitive === true && TEXT_FILTER_OPS.includes(clause.op) ? "*" : "";
|
|
180
|
+
return `${quoteViewToken(clause.column)}${clause.op}${star}${quoteViewToken(clause.value)}`;
|
|
181
|
+
}).join(";");
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/table/applyTableViewState.ts
|
|
187
|
+
function matchesClause(cell, clause, numericColumn, collator) {
|
|
188
|
+
const cellTrim = cell.trim();
|
|
189
|
+
const valueTrim = clause.value.trim();
|
|
190
|
+
const blank = cellTrim === "";
|
|
191
|
+
const fold = (text) => clause.caseSensitive ? text : text.toLowerCase();
|
|
192
|
+
switch (clause.op) {
|
|
193
|
+
case "=":
|
|
194
|
+
if (valueTrim === "") return blank;
|
|
195
|
+
if (blank) return false;
|
|
196
|
+
if (numericColumn && isNumericCellText(valueTrim)) {
|
|
197
|
+
return Number(cellTrim) === Number(valueTrim);
|
|
198
|
+
}
|
|
199
|
+
return fold(cellTrim) === fold(valueTrim);
|
|
200
|
+
case "!=":
|
|
201
|
+
return !matchesClause(cell, { ...clause, op: "=" }, numericColumn, collator);
|
|
202
|
+
case "~":
|
|
203
|
+
return !blank && fold(cellTrim).includes(fold(valueTrim));
|
|
204
|
+
case "!~":
|
|
205
|
+
return !matchesClause(cell, { ...clause, op: "~" }, numericColumn, collator);
|
|
206
|
+
case "^~":
|
|
207
|
+
return !blank && fold(cellTrim).startsWith(fold(valueTrim));
|
|
208
|
+
case "$~":
|
|
209
|
+
return !blank && fold(cellTrim).endsWith(fold(valueTrim));
|
|
210
|
+
case ">":
|
|
211
|
+
case "<":
|
|
212
|
+
case ">=":
|
|
213
|
+
case "<=": {
|
|
214
|
+
if (blank) return false;
|
|
215
|
+
let comparison;
|
|
216
|
+
if (numericColumn && isNumericCellText(valueTrim)) {
|
|
217
|
+
comparison = Number(cellTrim) - Number(valueTrim);
|
|
218
|
+
} else {
|
|
219
|
+
comparison = collator.compare(cellTrim, valueTrim);
|
|
220
|
+
}
|
|
221
|
+
if (clause.op === ">") return comparison > 0;
|
|
222
|
+
if (clause.op === "<") return comparison < 0;
|
|
223
|
+
if (clause.op === ">=") return comparison >= 0;
|
|
224
|
+
return comparison <= 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function applyTableViewState(headers, rows, view) {
|
|
229
|
+
const asMutable = rows;
|
|
230
|
+
if (isEmptyViewState(view)) {
|
|
231
|
+
return {
|
|
232
|
+
rows: [...asMutable],
|
|
233
|
+
rowIds: rows.map((_, index) => index),
|
|
234
|
+
unfilteredRowCount: rows.length
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const collator = makeTableCollator();
|
|
238
|
+
const columnIndex = new Map(headers.map((header, index) => [header, index]));
|
|
239
|
+
const numericByCol = /* @__PURE__ */ new Map();
|
|
240
|
+
const numericFor = (col) => {
|
|
241
|
+
let cached = numericByCol.get(col);
|
|
242
|
+
if (cached === void 0) {
|
|
243
|
+
cached = inferNumericColumn(rows, col);
|
|
244
|
+
numericByCol.set(col, cached);
|
|
245
|
+
}
|
|
246
|
+
return cached;
|
|
247
|
+
};
|
|
248
|
+
const clauses = view.filter.map((clause) => ({ clause, col: columnIndex.get(clause.column) })).filter((entry) => entry.col !== void 0);
|
|
249
|
+
let rowIds = [];
|
|
250
|
+
for (let index = 0; index < rows.length; index++) {
|
|
251
|
+
const row = rows[index];
|
|
252
|
+
let pass = true;
|
|
253
|
+
for (const { clause, col } of clauses) {
|
|
254
|
+
if (!matchesClause(row[col] ?? "", clause, numericFor(col), collator)) {
|
|
255
|
+
pass = false;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (pass) rowIds.push(index);
|
|
260
|
+
}
|
|
261
|
+
const terms = view.sort.map((term) => ({ term, col: columnIndex.get(term.column) })).filter(
|
|
262
|
+
(entry) => entry.col !== void 0
|
|
263
|
+
);
|
|
264
|
+
if (terms.length > 0) {
|
|
265
|
+
for (const { col } of terms) numericFor(col);
|
|
266
|
+
rowIds = [...rowIds].sort((a, b) => {
|
|
267
|
+
for (const { term, col } of terms) {
|
|
268
|
+
const left = (rows[a][col] ?? "").trim();
|
|
269
|
+
const right = (rows[b][col] ?? "").trim();
|
|
270
|
+
const leftBlank = left === "";
|
|
271
|
+
const rightBlank = right === "";
|
|
272
|
+
if (leftBlank || rightBlank) {
|
|
273
|
+
if (leftBlank && rightBlank) continue;
|
|
274
|
+
return leftBlank ? 1 : -1;
|
|
275
|
+
}
|
|
276
|
+
const comparison = numericByCol.get(col) ? Number(left) - Number(right) : collator.compare(left, right);
|
|
277
|
+
if (comparison !== 0) return term.dir === "desc" ? -comparison : comparison;
|
|
278
|
+
}
|
|
279
|
+
return a - b;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
rows: rowIds.map((id) => asMutable[id]),
|
|
284
|
+
rowIds,
|
|
285
|
+
unfilteredRowCount: rows.length
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export {
|
|
289
|
+
EMPTY_TABLE_VIEW_STATE,
|
|
290
|
+
applyTableViewState,
|
|
291
|
+
inferNumericColumn,
|
|
292
|
+
isEmptyViewState,
|
|
293
|
+
isNumericCellText,
|
|
294
|
+
makeTableCollator,
|
|
295
|
+
parseTableViewState,
|
|
296
|
+
quoteViewToken,
|
|
297
|
+
serializeTableViewState
|
|
298
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as ColorScheme, O as Doc, k as Block } from '../Doc-
|
|
2
|
-
import { i as PageTransformHints } from '../materializePageSection-
|
|
1
|
+
import { r as ColorScheme, O as Doc, k as Block } from '../Doc-CRPQCkHn.js';
|
|
2
|
+
import { i as PageTransformHints } from '../materializePageSection-BeUPHeCl.js';
|
|
3
3
|
import { d as ExtractionType, E as ExtractedElement, b as ExtractionOptions } from '../contentExtractor-BNfVJV2U.js';
|
|
4
4
|
import '../types-Be6sBUBx.js';
|
|
5
5
|
|
package/dist/versions/index.d.ts
CHANGED
|
@@ -128,5 +128,19 @@ declare function parseVersionPath(path: string): {
|
|
|
128
128
|
timestamp: Date;
|
|
129
129
|
collision: number;
|
|
130
130
|
} | null;
|
|
131
|
+
/** Subfolder holding pre-save backups of DATA sidecar files. */
|
|
132
|
+
declare const DATA_BACKUP_PREFIX = ".versions/data/";
|
|
133
|
+
/**
|
|
134
|
+
* Build a pre-save backup path for a data sidecar file:
|
|
135
|
+
* `.versions/data/<path with '/' → '__'>.<stamp>[-n].<ext>`.
|
|
136
|
+
*
|
|
137
|
+
* The grid's save flow writes the ORIGINAL bytes here before overwriting a
|
|
138
|
+
* sidecar in place — the only cross-save undo the grid offers. Unlike
|
|
139
|
+
* `buildVersionPath` (markdown-only, single-basename), the flattened full
|
|
140
|
+
* path keeps same-named files in different folders distinct, and the
|
|
141
|
+
* extension follows the sidecar. The flattened name goes through the same
|
|
142
|
+
* safety rules as document snapshots (via a per-call strategy).
|
|
143
|
+
*/
|
|
144
|
+
declare function buildDataBackupPath(sidecarPath: string, date: Date, collision?: number): string;
|
|
131
145
|
|
|
132
|
-
export { CoalesceOptions, DocumentVersionManager, type DocumentVersionManagerOptions, PrunePolicy, RevertOptions, RevertResult, SaveVersionOptions, SaveVersionResult, VERSIONS_PREFIX, VERSION_TIMESTAMP_PATTERN, Version, buildVersionPath, coalesceVersions, formatVersionTimestamp, getDocBasename, listVersions, parseVersionPath, parseVersionTimestamp, pruneVersions, readVersion, revertToVersion, saveVersion };
|
|
146
|
+
export { CoalesceOptions, DATA_BACKUP_PREFIX, DocumentVersionManager, type DocumentVersionManagerOptions, PrunePolicy, RevertOptions, RevertResult, SaveVersionOptions, SaveVersionResult, VERSIONS_PREFIX, VERSION_TIMESTAMP_PATTERN, Version, buildDataBackupPath, buildVersionPath, coalesceVersions, formatVersionTimestamp, getDocBasename, listVersions, parseVersionPath, parseVersionTimestamp, pruneVersions, readVersion, revertToVersion, saveVersion };
|
package/dist/versions/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DATA_BACKUP_PREFIX,
|
|
2
3
|
DocumentVersionManager,
|
|
3
4
|
VERSIONS_PREFIX,
|
|
5
|
+
buildDataBackupPath,
|
|
4
6
|
buildVersionPath,
|
|
5
7
|
coalesceVersions,
|
|
6
8
|
getDocBasename,
|
|
@@ -10,16 +12,18 @@ import {
|
|
|
10
12
|
readVersion,
|
|
11
13
|
revertToVersion,
|
|
12
14
|
saveVersion
|
|
13
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-O7HLQ22N.js";
|
|
14
16
|
import {
|
|
15
17
|
VERSION_TIMESTAMP_PATTERN,
|
|
16
18
|
formatVersionTimestamp,
|
|
17
19
|
parseVersionTimestamp
|
|
18
20
|
} from "../chunk-OPO5K4DZ.js";
|
|
19
21
|
export {
|
|
22
|
+
DATA_BACKUP_PREFIX,
|
|
20
23
|
DocumentVersionManager,
|
|
21
24
|
VERSIONS_PREFIX,
|
|
22
25
|
VERSION_TIMESTAMP_PATTERN,
|
|
26
|
+
buildDataBackupPath,
|
|
23
27
|
buildVersionPath,
|
|
24
28
|
coalesceVersions,
|
|
25
29
|
formatVersionTimestamp,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table view state — the non-destructive sort/filter vocabulary that rides a
|
|
3
|
+
* `{[dataTable src=… sort=… filter=…]}` annotation. The data bytes are never
|
|
4
|
+
* touched; view state is part of the DOCUMENT (an author's sort shapes the
|
|
5
|
+
* bounded previews and exports), exactly like `sheet`/`anchor` are part of
|
|
6
|
+
* the reference.
|
|
7
|
+
*
|
|
8
|
+
* Grammar (inside the param value; the whole value then rides the ordinary
|
|
9
|
+
* `{[…]}` attr quoting):
|
|
10
|
+
*
|
|
11
|
+
* sort := term (',' term)*
|
|
12
|
+
* term := colName (':' ('asc'|'desc'))? // default asc
|
|
13
|
+
* filter := clause (';' clause)* // conjunction (AND) only
|
|
14
|
+
* clause := colName op value
|
|
15
|
+
* op := textOp '*'? | cmpOp
|
|
16
|
+
* textOp := '=' | '!=' | '~' | '!~' | '^~' | '$~'
|
|
17
|
+
* cmpOp := '>' | '<' | '>=' | '<='
|
|
18
|
+
*
|
|
19
|
+
* Text-matching ops (`= != ~ !~ ^~ $~`) are CASE-INSENSITIVE by default
|
|
20
|
+
* (Excel AutoFilter semantics); a `*` suffix makes the match case-sensitive
|
|
21
|
+
* (`Region=*West`). `~` is contains, `^~` starts-with, `$~` ends-with (the
|
|
22
|
+
* `~` marks the text family; `^`/`$` are the regex-anchor mnemonics).
|
|
23
|
+
* Comparison ops compare numerically on numeric columns and via the shared
|
|
24
|
+
* collator otherwise, and take no `*`. Column names address headers by exact
|
|
25
|
+
* text. Names/values containing structural characters are quoted CSV-style —
|
|
26
|
+
* double quotes with `""` doubling, NO backslashes (the outer `{[…]}` layer
|
|
27
|
+
* already owns backslash escaping inside quoted attr values; stacking a
|
|
28
|
+
* second backslash grammar inside would be unreadable and fragile):
|
|
29
|
+
*
|
|
30
|
+
* sort=Revenue:desc,Region
|
|
31
|
+
* sort="Ratio A:B":desc
|
|
32
|
+
* filter=Region=West;Revenue>=1000
|
|
33
|
+
* filter=Note~"a;b"
|
|
34
|
+
*
|
|
35
|
+
* Parsing NEVER throws: an unknown column drops that term/clause with a
|
|
36
|
+
* `data-view-unknown-column` issue; malformed syntax drops the whole param
|
|
37
|
+
* with `data-view-invalid`. Renderers fall back to natural order.
|
|
38
|
+
*/
|
|
39
|
+
interface SortTerm {
|
|
40
|
+
column: string;
|
|
41
|
+
dir: 'asc' | 'desc';
|
|
42
|
+
}
|
|
43
|
+
type FilterOp = '=' | '!=' | '>' | '<' | '>=' | '<=' | '~' | '!~' | '^~' | '$~';
|
|
44
|
+
interface FilterClause {
|
|
45
|
+
column: string;
|
|
46
|
+
op: FilterOp;
|
|
47
|
+
value: string;
|
|
48
|
+
/**
|
|
49
|
+
* Case-sensitive matching for text ops (serialized as a `*` op suffix).
|
|
50
|
+
* Absent/false = the default case-insensitive match. Meaningless on the
|
|
51
|
+
* comparison ops and never serialized for them.
|
|
52
|
+
*/
|
|
53
|
+
caseSensitive?: boolean;
|
|
54
|
+
}
|
|
55
|
+
interface TableViewState {
|
|
56
|
+
sort: SortTerm[];
|
|
57
|
+
filter: FilterClause[];
|
|
58
|
+
}
|
|
59
|
+
interface ViewIssue {
|
|
60
|
+
code: 'data-view-unknown-column' | 'data-view-invalid';
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
interface ParsedTableViewState {
|
|
64
|
+
view: TableViewState;
|
|
65
|
+
issues: ViewIssue[];
|
|
66
|
+
}
|
|
67
|
+
declare const EMPTY_TABLE_VIEW_STATE: TableViewState;
|
|
68
|
+
/** True when the view state changes nothing. */
|
|
69
|
+
declare function isEmptyViewState(view: TableViewState): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* A column is numeric iff every non-blank trimmed cell parses to a finite
|
|
72
|
+
* number AND is not a leading-zero string (`"007"` stays text — zip codes,
|
|
73
|
+
* ids). Blank cells don't vote.
|
|
74
|
+
*/
|
|
75
|
+
declare function isNumericCellText(value: string): boolean;
|
|
76
|
+
declare function inferNumericColumn(rows: readonly (readonly string[])[], col: number): boolean;
|
|
77
|
+
/** The one collator both sides of the parity contract use. */
|
|
78
|
+
declare function makeTableCollator(): Intl.Collator;
|
|
79
|
+
declare function quoteViewToken(text: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Parse the raw `sort`/`filter` param values against real headers.
|
|
82
|
+
* Never throws; degradation is per the module contract.
|
|
83
|
+
*/
|
|
84
|
+
declare function parseTableViewState(sortRaw: string | undefined, filterRaw: string | undefined, headers: readonly string[]): ParsedTableViewState;
|
|
85
|
+
/** Serialize to `{ sort?, filter? }` raw param values (undefined = omit). */
|
|
86
|
+
declare function serializeTableViewState(view: TableViewState): {
|
|
87
|
+
sort?: string;
|
|
88
|
+
filter?: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export { EMPTY_TABLE_VIEW_STATE as E, type FilterClause as F, type ParsedTableViewState as P, type SortTerm as S, type TableViewState as T, type ViewIssue as V, type FilterOp as a, isEmptyViewState as b, isNumericCellText as c, inferNumericColumn as i, makeTableCollator as m, parseTableViewState as p, quoteViewToken as q, serializeTableViewState as s };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Headless utilities for doc/block rendering, spatial math, Markdown, and storage",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -131,6 +131,11 @@
|
|
|
131
131
|
"types": "./dist/proof/index.d.ts",
|
|
132
132
|
"import": "./dist/proof/index.js",
|
|
133
133
|
"default": "./dist/proof/index.js"
|
|
134
|
+
},
|
|
135
|
+
"./table": {
|
|
136
|
+
"types": "./dist/table/index.d.ts",
|
|
137
|
+
"import": "./dist/table/index.js",
|
|
138
|
+
"default": "./dist/table/index.js"
|
|
134
139
|
}
|
|
135
140
|
},
|
|
136
141
|
"scripts": {
|