@atscript/ui-table 0.1.132 → 0.1.134
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/index.cjs +299 -16
- package/dist/index.d.cts +222 -8
- package/dist/index.d.mts +222 -8
- package/dist/index.mjs +289 -18
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -149,13 +149,22 @@ function unescapeRegex(input) {
|
|
|
149
149
|
//#region src/filters/filter-input-format.ts
|
|
150
150
|
/**
|
|
151
151
|
* Coerce a raw string value to the appropriate JS type for the column.
|
|
152
|
-
* Number columns get numeric values
|
|
152
|
+
* Number columns get numeric values, boolean columns understand the
|
|
153
|
+
* `true` / `false` vocabulary (case-insensitive) so the condition carries a
|
|
154
|
+
* real boolean and round-trips through the URL as one. Anything else stays
|
|
155
|
+
* a string. Since 0.1.133 for booleans.
|
|
153
156
|
*/
|
|
154
157
|
function coerceValue(raw, columnType) {
|
|
155
158
|
if (columnType === "number") {
|
|
156
159
|
const n = Number(raw);
|
|
157
160
|
return Number.isNaN(n) ? raw : n;
|
|
158
161
|
}
|
|
162
|
+
if (columnType === "boolean") {
|
|
163
|
+
const lower = raw.toLowerCase();
|
|
164
|
+
if (lower === "true") return true;
|
|
165
|
+
if (lower === "false") return false;
|
|
166
|
+
return raw;
|
|
167
|
+
}
|
|
159
168
|
return raw;
|
|
160
169
|
}
|
|
161
170
|
/** Default condition type when no symbol matches the input. */
|
|
@@ -580,6 +589,10 @@ function derivePresetAspects(content) {
|
|
|
580
589
|
* Convert in-memory dict-form snapshot to the wire form persisted on the
|
|
581
590
|
* server. Entries-arrays are sorted by `field` so consumers (server-side
|
|
582
591
|
* aspect derivation, dirty detection, equality checks) see a stable order.
|
|
592
|
+
*
|
|
593
|
+
* Empty aspects round-trip as empty (since 0.1.133): a saved view with no
|
|
594
|
+
* filters owns `filterOps` and clears them on apply, which is not the same
|
|
595
|
+
* thing as a view that never claimed the aspect.
|
|
583
596
|
*/
|
|
584
597
|
function toWireSnapshot(snapshot) {
|
|
585
598
|
const wire = {};
|
|
@@ -592,7 +605,7 @@ function toWireSnapshot(snapshot) {
|
|
|
592
605
|
field,
|
|
593
606
|
width: columnWidths[field]
|
|
594
607
|
});
|
|
595
|
-
|
|
608
|
+
columns.columnWidths = entries;
|
|
596
609
|
}
|
|
597
610
|
wire.columns = columns;
|
|
598
611
|
}
|
|
@@ -603,7 +616,7 @@ function toWireSnapshot(snapshot) {
|
|
|
603
616
|
field,
|
|
604
617
|
conditions: snapshot.filterOps[field]
|
|
605
618
|
});
|
|
606
|
-
|
|
619
|
+
wire.filterOps = entries;
|
|
607
620
|
}
|
|
608
621
|
if (snapshot.sorters) wire.sorters = snapshot.sorters;
|
|
609
622
|
if (snapshot.itemsPerPage !== void 0) wire.itemsPerPage = snapshot.itemsPerPage;
|
|
@@ -614,7 +627,7 @@ function fromWireSnapshot(wire) {
|
|
|
614
627
|
if (wire.columns) {
|
|
615
628
|
const { columnNames, columnWidths } = wire.columns;
|
|
616
629
|
const columns = { columnNames };
|
|
617
|
-
if (columnWidths
|
|
630
|
+
if (columnWidths) {
|
|
618
631
|
const dict = {};
|
|
619
632
|
for (const entry of columnWidths) dict[entry.field] = entry.width;
|
|
620
633
|
columns.columnWidths = dict;
|
|
@@ -622,7 +635,7 @@ function fromWireSnapshot(wire) {
|
|
|
622
635
|
snapshot.columns = columns;
|
|
623
636
|
}
|
|
624
637
|
if (wire.filters) snapshot.filters = wire.filters;
|
|
625
|
-
if (wire.filterOps
|
|
638
|
+
if (wire.filterOps) {
|
|
626
639
|
const dict = {};
|
|
627
640
|
for (const entry of wire.filterOps) dict[entry.field] = entry.conditions;
|
|
628
641
|
snapshot.filterOps = dict;
|
|
@@ -819,7 +832,7 @@ function draftMatchesPreset(draft, presetSnapshot, availableAspects) {
|
|
|
819
832
|
/**
|
|
820
833
|
* Framework-agnostic wrapper over `@atscript/db-client`'s `Client` for the
|
|
821
834
|
* `AsPresetEntry` table. Handles wire serialisation, list-splitting by
|
|
822
|
-
* `type`, capabilities side-channel, and 401/403 → `denied` semantics.
|
|
835
|
+
* `type`, capabilities side-channel, and 401/403/404 → `denied` semantics.
|
|
823
836
|
*
|
|
824
837
|
* Stateless: every method is a fresh request. The Vue composable layer
|
|
825
838
|
* holds reactive state; this class only translates intent → HTTP.
|
|
@@ -845,8 +858,8 @@ var PresetsClient = class {
|
|
|
845
858
|
* `(app, tableKey)`. By default also fetches `capabilities` in parallel —
|
|
846
859
|
* pass `{ capabilities: false }` for refresh-after-mutation calls (fav
|
|
847
860
|
* toggle, default change, save/save-as, public toggle, rename, delete)
|
|
848
|
-
* where role-derived capabilities can't have changed.
|
|
849
|
-
* (401/403) collapse to `denied: true` with empty data so the UI hides
|
|
861
|
+
* where role-derived capabilities can't have changed. Unavailable
|
|
862
|
+
* responses (401/403/404) collapse to `denied: true` with empty data so the UI hides
|
|
850
863
|
* itself silently.
|
|
851
864
|
*/
|
|
852
865
|
async list(opts = {}) {
|
|
@@ -858,7 +871,7 @@ var PresetsClient = class {
|
|
|
858
871
|
};
|
|
859
872
|
try {
|
|
860
873
|
const [rows, capabilities] = await Promise.all([this.client.query({ filter }), fetchCapabilities ? this.loadCapabilities().catch((err) => {
|
|
861
|
-
if (
|
|
874
|
+
if (isUnavailableError(err)) throw err;
|
|
862
875
|
return null;
|
|
863
876
|
}) : Promise.resolve(void 0)]);
|
|
864
877
|
const presets = [];
|
|
@@ -872,7 +885,7 @@ var PresetsClient = class {
|
|
|
872
885
|
denied: false
|
|
873
886
|
};
|
|
874
887
|
} catch (err) {
|
|
875
|
-
if (
|
|
888
|
+
if (isUnavailableError(err)) return {
|
|
876
889
|
presets: [],
|
|
877
890
|
userConf: null,
|
|
878
891
|
capabilities: null,
|
|
@@ -988,12 +1001,22 @@ var PresetsHttpError = class extends Error {
|
|
|
988
1001
|
this.name = "PresetsHttpError";
|
|
989
1002
|
}
|
|
990
1003
|
};
|
|
991
|
-
/**
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* True when the presets/app-prefs surface is simply not available to this
|
|
1006
|
+
* client: HTTP 401/403 (not signed in / not permitted) or 404 (the
|
|
1007
|
+
* controller is not mounted on this server). All three collapse to
|
|
1008
|
+
* `denied: true` — the UI hides itself silently instead of surfacing an
|
|
1009
|
+
* error. Covers both `ClientError` and `PresetsHttpError`.
|
|
1010
|
+
*/
|
|
1011
|
+
function isUnavailableError(err) {
|
|
1012
|
+
const status = err instanceof _atscript_db_client.ClientError || err instanceof PresetsHttpError ? err.status : void 0;
|
|
1013
|
+
return status === 401 || status === 403 || status === 404;
|
|
996
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* @deprecated Renamed to `isUnavailableError` in 0.1.133 — it also covers
|
|
1017
|
+
* 404 (feature not mounted), not just auth failures.
|
|
1018
|
+
*/
|
|
1019
|
+
const isAuthError = isUnavailableError;
|
|
997
1020
|
//#endregion
|
|
998
1021
|
//#region src/presets/app-prefs-client.ts
|
|
999
1022
|
/**
|
|
@@ -1026,7 +1049,7 @@ var AppPrefsClient = class {
|
|
|
1026
1049
|
denied: false
|
|
1027
1050
|
};
|
|
1028
1051
|
} catch (err) {
|
|
1029
|
-
if (
|
|
1052
|
+
if (isUnavailableError(err)) return {
|
|
1030
1053
|
row: null,
|
|
1031
1054
|
prefs: null,
|
|
1032
1055
|
denied: true
|
|
@@ -1232,6 +1255,36 @@ function stateToUrlQueryString(state, defaults) {
|
|
|
1232
1255
|
return (0, _uniqu_url_builder.buildUrl)(query);
|
|
1233
1256
|
}
|
|
1234
1257
|
/**
|
|
1258
|
+
* The `$`-controls {@link urlQueryStringToState} actually reads. Any other
|
|
1259
|
+
* `$key` is ignored by the parser, so it is NOT the table's to remove.
|
|
1260
|
+
*/
|
|
1261
|
+
const CONSUMED_CONTROLS = new Set([
|
|
1262
|
+
"$sort",
|
|
1263
|
+
"$search",
|
|
1264
|
+
"$relevance",
|
|
1265
|
+
"$skip"
|
|
1266
|
+
]);
|
|
1267
|
+
/** Characters that can only appear in a uniqu filter key, never in a page flag. */
|
|
1268
|
+
const FILTER_OPERATOR_CHAR = /[<>!~]/;
|
|
1269
|
+
/**
|
|
1270
|
+
* Whether {@link urlQueryStringToState} would consume the query key `key` —
|
|
1271
|
+
* i.e. whether the key belongs to the table rather than to the page hosting
|
|
1272
|
+
* it. This is the ownership rule the `useTableUrlQuery` router bridge reads
|
|
1273
|
+
* and writes by, so both directions agree: a key the parser ignores is never
|
|
1274
|
+
* removed, and a key it reads is the table's to remove.
|
|
1275
|
+
*
|
|
1276
|
+
* Shape-only, and deliberately conservative for bare `field=value` keys: a
|
|
1277
|
+
* filter on a column and a host flag are indistinguishable without the table
|
|
1278
|
+
* definition, so those are owned only once the bridge has written them
|
|
1279
|
+
* itself (or once a `prefix` namespaces them).
|
|
1280
|
+
*
|
|
1281
|
+
* @since 0.1.133
|
|
1282
|
+
*/
|
|
1283
|
+
function urlQueryConsumesKey(key) {
|
|
1284
|
+
if (key.startsWith("$")) return CONSUMED_CONTROLS.has(key);
|
|
1285
|
+
return FILTER_OPERATOR_CHAR.test(key);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1235
1288
|
* Parse a URL query string back into the table state subset.
|
|
1236
1289
|
*
|
|
1237
1290
|
* Robust by design — schema drift and copy-paste errors must not break the
|
|
@@ -1628,10 +1681,230 @@ function reorderColumnNames(names, fromPath, toPath, position) {
|
|
|
1628
1681
|
return without.slice(0, insertAt).concat(fromPath, without.slice(insertAt));
|
|
1629
1682
|
}
|
|
1630
1683
|
//#endregion
|
|
1684
|
+
//#region src/export/csv.ts
|
|
1685
|
+
const FORMULA_LEADERS = new Set([
|
|
1686
|
+
"=",
|
|
1687
|
+
"+",
|
|
1688
|
+
"-",
|
|
1689
|
+
"@",
|
|
1690
|
+
" ",
|
|
1691
|
+
"\r"
|
|
1692
|
+
]);
|
|
1693
|
+
/** RFC 4180 row separator. */
|
|
1694
|
+
const CRLF = "\r\n";
|
|
1695
|
+
/**
|
|
1696
|
+
* Render one value as an RFC 4180 field: formula-escaped (unless opted out),
|
|
1697
|
+
* then quoted when it contains the delimiter, a quote, CR or LF — with inner
|
|
1698
|
+
* quotes doubled.
|
|
1699
|
+
*/
|
|
1700
|
+
function csvCell(value, opts = {}) {
|
|
1701
|
+
const delimiter = opts.delimiter ?? ",";
|
|
1702
|
+
let text = value === null || value === void 0 ? "" : String(value);
|
|
1703
|
+
if (typeof value === "string" && opts.escapeFormulas !== false && text.length > 0 && FORMULA_LEADERS.has(text[0])) text = `'${text}`;
|
|
1704
|
+
return text.includes(delimiter) || text.includes("\"") || text.includes("\n") || text.includes("\r") ? `"${text.replaceAll("\"", "\"\"")}"` : text;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Serialise a header row + data rows to an RFC 4180 CSV string (`\r\n` row
|
|
1708
|
+
* separators, quoted fields where required, optional UTF-8 BOM).
|
|
1709
|
+
*
|
|
1710
|
+
* Pure — no DOM, no framework. See {@link csvCell} for the per-field rules.
|
|
1711
|
+
*/
|
|
1712
|
+
function toCsv(header, rows, opts = {}) {
|
|
1713
|
+
const delimiter = opts.delimiter ?? ",";
|
|
1714
|
+
const lines = [header.map((h) => csvCell(h, opts)).join(delimiter)];
|
|
1715
|
+
for (const row of rows) lines.push(row.map((v) => csvCell(v, opts)).join(delimiter));
|
|
1716
|
+
const body = lines.join(CRLF) + CRLF;
|
|
1717
|
+
return opts.bom ? `${body}` : body;
|
|
1718
|
+
}
|
|
1719
|
+
//#endregion
|
|
1720
|
+
//#region src/export/export-paging.ts
|
|
1721
|
+
/** Default page size used by the export pager when the caller gives none. */
|
|
1722
|
+
const DEFAULT_EXPORT_PAGE_SIZE = 500;
|
|
1723
|
+
/** Rejection raised when an export is cancelled through its `AbortSignal`. */
|
|
1724
|
+
var ExportAbortError = class extends Error {
|
|
1725
|
+
/** Matches the DOM convention so `err.name === "AbortError"` checks work. */
|
|
1726
|
+
name = "AbortError";
|
|
1727
|
+
constructor(message = "Export aborted") {
|
|
1728
|
+
super(message);
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
/**
|
|
1732
|
+
* Append the primary key(s) to a query's `$sort` as a tiebreaker so paging is
|
|
1733
|
+
* stable: two rows equal on every user sorter keep a deterministic relative
|
|
1734
|
+
* order, and no row is skipped or duplicated across page boundaries.
|
|
1735
|
+
*
|
|
1736
|
+
* Existing sort fields are preserved in order and never replaced — a pk the
|
|
1737
|
+
* user already sorts by is left where it is. Returns the input query when
|
|
1738
|
+
* there is nothing to add.
|
|
1739
|
+
*/
|
|
1740
|
+
function withStableOrder(query, primaryKeys) {
|
|
1741
|
+
if (primaryKeys.length === 0) return query;
|
|
1742
|
+
const current = query.controls?.$sort ?? {};
|
|
1743
|
+
const missing = primaryKeys.filter((pk) => !(pk in current));
|
|
1744
|
+
if (missing.length === 0) return query;
|
|
1745
|
+
const $sort = { ...current };
|
|
1746
|
+
for (const pk of missing) $sort[pk] = 1;
|
|
1747
|
+
return {
|
|
1748
|
+
...query,
|
|
1749
|
+
controls: {
|
|
1750
|
+
...query.controls,
|
|
1751
|
+
$sort
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Page through `query` until the endpoint runs out of rows, collecting every
|
|
1757
|
+
* row into one array.
|
|
1758
|
+
*
|
|
1759
|
+
* Stops when a page comes back short (fewer rows than `pageSize`), when the
|
|
1760
|
+
* reported `count` is reached, or when `maxRows` is hit. Checks `signal`
|
|
1761
|
+
* before and after every request so a cancel lands between pages rather than
|
|
1762
|
+
* mid-flight.
|
|
1763
|
+
*/
|
|
1764
|
+
async function collectExportRows(opts) {
|
|
1765
|
+
const pageSize = opts.pageSize && opts.pageSize > 0 ? opts.pageSize : 500;
|
|
1766
|
+
const mapRow = opts.mapRow ?? ((row) => row);
|
|
1767
|
+
const rows = [];
|
|
1768
|
+
let total;
|
|
1769
|
+
let page = 1;
|
|
1770
|
+
for (;;) {
|
|
1771
|
+
throwIfAborted(opts.signal);
|
|
1772
|
+
const result = await opts.fetchPage(opts.query, page, pageSize);
|
|
1773
|
+
throwIfAborted(opts.signal);
|
|
1774
|
+
if (page === 1 && typeof result.count === "number") total = result.count;
|
|
1775
|
+
const data = result.data ?? [];
|
|
1776
|
+
const take = opts.maxRows === void 0 ? data.length : Math.min(data.length, opts.maxRows - rows.length);
|
|
1777
|
+
for (let i = 0; i < take; i++) rows.push(mapRow(data[i]));
|
|
1778
|
+
opts.onProgress?.(rows.length, total);
|
|
1779
|
+
if (data.length < pageSize || opts.maxRows !== void 0 && rows.length >= opts.maxRows || total !== void 0 && rows.length >= total) break;
|
|
1780
|
+
page++;
|
|
1781
|
+
}
|
|
1782
|
+
return {
|
|
1783
|
+
rows,
|
|
1784
|
+
total
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
function throwIfAborted(signal) {
|
|
1788
|
+
if (signal?.aborted) throw new ExportAbortError();
|
|
1789
|
+
}
|
|
1790
|
+
//#endregion
|
|
1791
|
+
//#region src/export/export-value.ts
|
|
1792
|
+
/**
|
|
1793
|
+
* Turn a raw cell value into the scalar an export carries, using the same
|
|
1794
|
+
* column metadata the cell renderers read:
|
|
1795
|
+
*
|
|
1796
|
+
* - `null` / `undefined` → `null` (an empty CSV field).
|
|
1797
|
+
* - union / enum columns (`column.options`) → the option's **label**, so the
|
|
1798
|
+
* file reads like the screen; an unknown key falls back to the raw value.
|
|
1799
|
+
* - `Date` → ISO 8601 (spreadsheet- and re-import-friendly, unlike a
|
|
1800
|
+
* locale-formatted date).
|
|
1801
|
+
* - arrays → comma-joined scalars; other objects → JSON.
|
|
1802
|
+
* - numbers / booleans pass through untouched so a spreadsheet keeps them
|
|
1803
|
+
* numeric.
|
|
1804
|
+
*
|
|
1805
|
+
* Callers override per column (`formatters`) or globally (`formatCell`) when
|
|
1806
|
+
* they want different text.
|
|
1807
|
+
*/
|
|
1808
|
+
function resolveExportValue(value, column) {
|
|
1809
|
+
if (value === null || value === void 0) return null;
|
|
1810
|
+
const options = column?.options;
|
|
1811
|
+
if (options?.length) {
|
|
1812
|
+
const key = typeof value === "string" || typeof value === "number" ? String(value) : void 0;
|
|
1813
|
+
if (key !== void 0) {
|
|
1814
|
+
const hit = options.find((o) => o.key === key);
|
|
1815
|
+
if (hit) return hit.label;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
1819
|
+
if (typeof value === "string") return value;
|
|
1820
|
+
if (value instanceof Date) return value.toISOString();
|
|
1821
|
+
if (Array.isArray(value)) return value.map((v) => scalarText(v)).join(", ");
|
|
1822
|
+
return scalarText(value);
|
|
1823
|
+
}
|
|
1824
|
+
function scalarText(value) {
|
|
1825
|
+
if (value === null || value === void 0 || typeof value === "function") return "";
|
|
1826
|
+
try {
|
|
1827
|
+
return (0, _atscript_ui.str)(value);
|
|
1828
|
+
} catch {
|
|
1829
|
+
return "";
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
//#endregion
|
|
1833
|
+
//#region src/columns/display-columns.ts
|
|
1834
|
+
/** Columns with no explicit `order` land after every server column. */
|
|
1835
|
+
const TRAILING_ORDER = Number.MAX_SAFE_INTEGER;
|
|
1836
|
+
/** Turn a {@link DisplayColumnDef} into the `ColumnDef` the table renders. */
|
|
1837
|
+
function displayColumnToDef(def) {
|
|
1838
|
+
return {
|
|
1839
|
+
path: def.key,
|
|
1840
|
+
label: def.label,
|
|
1841
|
+
type: def.type ?? "text",
|
|
1842
|
+
component: def.component,
|
|
1843
|
+
sortable: def.sortable === "local",
|
|
1844
|
+
filterable: false,
|
|
1845
|
+
nullable: true,
|
|
1846
|
+
order: def.order ?? TRAILING_ORDER,
|
|
1847
|
+
width: def.width,
|
|
1848
|
+
local: true
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Merge client-owned columns into the server's column list, ordered by each
|
|
1853
|
+
* column's `order`. `Array#sort` is stable, so server columns that share an
|
|
1854
|
+
* `order` keep their `/meta` sequence. Returns `base` untouched when there is
|
|
1855
|
+
* nothing to merge.
|
|
1856
|
+
*/
|
|
1857
|
+
function mergeDisplayColumns(base, display) {
|
|
1858
|
+
if (display.length === 0) return base;
|
|
1859
|
+
return [...base, ...display.map(displayColumnToDef)].toSorted((a, b) => a.order - b.order);
|
|
1860
|
+
}
|
|
1861
|
+
//#endregion
|
|
1862
|
+
//#region src/utils/sort-rows.ts
|
|
1863
|
+
/**
|
|
1864
|
+
* Coerce a primitive cell value to the text used for comparing and matching
|
|
1865
|
+
* cells — the string compare in {@link sortRowsLocally} and the substring
|
|
1866
|
+
* search of an in-memory table. Objects and functions collapse to `""` so
|
|
1867
|
+
* `'[object Object]'` never drives an ordering or matches a search.
|
|
1868
|
+
*/
|
|
1869
|
+
function cellAsString(v) {
|
|
1870
|
+
if (v == null) return "";
|
|
1871
|
+
if (typeof v === "string") return v;
|
|
1872
|
+
if (typeof v === "number" || typeof v === "boolean") return v.toString();
|
|
1873
|
+
return "";
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Sort rows in memory by `sorters`, numerically when both sides are numbers
|
|
1877
|
+
* and locale-aware otherwise. Returns a NEW array; the input is untouched. A
|
|
1878
|
+
* no-op (returns the input reference) when there is nothing to sort.
|
|
1879
|
+
*
|
|
1880
|
+
* `getValue` reads a sorter's field off a row — pass a dot-path reader for
|
|
1881
|
+
* nested fields; the default is a flat property read.
|
|
1882
|
+
*/
|
|
1883
|
+
function sortRowsLocally(rows, sorters, getValue = (row, field) => row[field]) {
|
|
1884
|
+
if (sorters.length === 0 || rows.length < 2) return rows;
|
|
1885
|
+
return rows.toSorted((a, b) => {
|
|
1886
|
+
for (const s of sorters) {
|
|
1887
|
+
const dir = s.direction === "desc" ? -1 : 1;
|
|
1888
|
+
const av = getValue(a, s.field);
|
|
1889
|
+
const bv = getValue(b, s.field);
|
|
1890
|
+
if (typeof av === "number" && typeof bv === "number") {
|
|
1891
|
+
if (av < bv) return -dir;
|
|
1892
|
+
if (av > bv) return dir;
|
|
1893
|
+
} else {
|
|
1894
|
+
const cmp = cellAsString(av).localeCompare(cellAsString(bv));
|
|
1895
|
+
if (cmp !== 0) return cmp * dir;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return 0;
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
//#endregion
|
|
1631
1902
|
exports.APP_CONF_PREFIX = APP_CONF_PREFIX;
|
|
1632
1903
|
exports.AppPrefsClient = AppPrefsClient;
|
|
1904
|
+
exports.DEFAULT_EXPORT_PAGE_SIZE = DEFAULT_EXPORT_PAGE_SIZE;
|
|
1633
1905
|
exports.DEFAULT_ROW_HEIGHT_PX = DEFAULT_ROW_HEIGHT_PX;
|
|
1634
1906
|
exports.DRAFT_PERSISTED_ASPECTS = DRAFT_PERSISTED_ASPECTS;
|
|
1907
|
+
exports.ExportAbortError = ExportAbortError;
|
|
1635
1908
|
exports.MAX_DEFAULT_COLUMN_WIDTH_PX = MAX_DEFAULT_COLUMN_WIDTH_PX;
|
|
1636
1909
|
exports.NULL_OPS = NULL_OPS;
|
|
1637
1910
|
exports.PRESET_ASPECTS = PRESET_ASPECTS;
|
|
@@ -1645,11 +1918,14 @@ exports.appConfId = appConfId;
|
|
|
1645
1918
|
exports.arraysEqual = arraysEqual;
|
|
1646
1919
|
exports.blockStartFor = blockStartFor;
|
|
1647
1920
|
exports.buildTableQuery = buildTableQuery;
|
|
1921
|
+
exports.cellAsString = cellAsString;
|
|
1648
1922
|
exports.clampTopIndex = clampTopIndex;
|
|
1923
|
+
exports.collectExportRows = collectExportRows;
|
|
1649
1924
|
exports.columnFilterType = columnFilterType;
|
|
1650
1925
|
exports.computeDefaultColumnWidth = computeDefaultColumnWidth;
|
|
1651
1926
|
exports.conditionLabel = conditionLabel;
|
|
1652
1927
|
exports.conditionsForType = conditionsForType;
|
|
1928
|
+
exports.csvCell = csvCell;
|
|
1653
1929
|
exports.dateShortcuts = dateShortcuts;
|
|
1654
1930
|
exports.debounce = debounce;
|
|
1655
1931
|
exports.defaultCondition = defaultCondition;
|
|
@@ -1669,6 +1945,8 @@ exports.isEmptyDraft = isEmptyDraft;
|
|
|
1669
1945
|
exports.isFilled = isFilled;
|
|
1670
1946
|
exports.isSimpleEq = isSimpleEq;
|
|
1671
1947
|
exports.isSystemPresetId = isSystemPresetId;
|
|
1948
|
+
exports.isUnavailableError = isUnavailableError;
|
|
1949
|
+
exports.mergeDisplayColumns = mergeDisplayColumns;
|
|
1672
1950
|
exports.mergeFilters = mergeFilters;
|
|
1673
1951
|
exports.mergeSorters = mergeSorters;
|
|
1674
1952
|
exports.normaliseSystemPresetId = normaliseSystemPresetId;
|
|
@@ -1678,20 +1956,25 @@ exports.planFetch = planFetch;
|
|
|
1678
1956
|
exports.reconcileColumnWidthDefaults = reconcileColumnWidthDefaults;
|
|
1679
1957
|
exports.reorderColumnNames = reorderColumnNames;
|
|
1680
1958
|
exports.resolveAspectGate = resolveAspectGate;
|
|
1959
|
+
exports.resolveExportValue = resolveExportValue;
|
|
1681
1960
|
exports.resolveSystemPresets = resolveSystemPresets;
|
|
1682
1961
|
exports.rowsToPks = rowsToPks;
|
|
1683
1962
|
exports.sameColumnSet = sameColumnSet;
|
|
1684
1963
|
exports.serializeDraft = serializeDraft;
|
|
1685
1964
|
exports.setsEqual = setsEqual;
|
|
1965
|
+
exports.sortRowsLocally = sortRowsLocally;
|
|
1686
1966
|
exports.sortersEqual = sortersEqual;
|
|
1687
1967
|
exports.stableStringify = stableStringify;
|
|
1688
1968
|
exports.stateToUrlQueryString = stateToUrlQueryString;
|
|
1969
|
+
exports.toCsv = toCsv;
|
|
1689
1970
|
exports.toWireSnapshot = toWireSnapshot;
|
|
1690
1971
|
exports.togglePk = togglePk;
|
|
1691
1972
|
exports.trimSelection = trimSelection;
|
|
1692
1973
|
exports.unescapeRegex = unescapeRegex;
|
|
1693
1974
|
exports.uniqueryFilterToFieldFilters = uniqueryFilterToFieldFilters;
|
|
1975
|
+
exports.urlQueryConsumesKey = urlQueryConsumesKey;
|
|
1694
1976
|
exports.urlQueryStringToState = urlQueryStringToState;
|
|
1695
1977
|
exports.userConfId = userConfId;
|
|
1696
1978
|
exports.walkBackwardAbsorb = walkBackwardAbsorb;
|
|
1697
1979
|
exports.walkForwardAbsorb = walkForwardAbsorb;
|
|
1980
|
+
exports.withStableOrder = withStableOrder;
|