@atscript/ui-table 0.1.140 → 0.1.141

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 CHANGED
@@ -528,6 +528,7 @@ function fieldsOf(expr, out) {
528
528
  else if (!out.includes(key)) out.push(key);
529
529
  return out;
530
530
  }
531
+ /** @internal Module-shared with the URL parser; not a package export. */
531
532
  function unsupported(reason, expr) {
532
533
  const fields = fieldsOf(expr, []);
533
534
  return {
@@ -753,10 +754,10 @@ function warnUnsupported(issue) {
753
754
  /**
754
755
  * Split a Uniquery `FilterExpr` into what the table's field-filter model
755
756
  * holds exactly (`filters`), what it cannot hold but carries as residual
756
- * conditions (`residual`, with `carry`), and what it leaves out
757
- * (`unsupported`). Nothing is approximated: `filters AND residual` selects
758
- * `expr` minus the `unsupported` pieces and pieces on fields outside
759
- * `knownFields`.
757
+ * conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
758
+ * and what names a field outside `knownFields` (`unknown`). Nothing is
759
+ * approximated: `filters AND residual` selects `expr` minus the
760
+ * `unsupported` and `unknown` pieces.
760
761
  *
761
762
  * Never throws, never warns — the caller decides how to report.
762
763
  *
@@ -767,7 +768,8 @@ function decomposeUniqueryFilter(expr, opts = {}) {
767
768
  const result = {
768
769
  filters,
769
770
  residual: [],
770
- unsupported: []
771
+ unsupported: [],
772
+ unknown: []
771
773
  };
772
774
  if (!expr) return result;
773
775
  const knownFields = opts.knownFields;
@@ -788,8 +790,12 @@ function decomposeUniqueryFilter(expr, opts = {}) {
788
790
  }];
789
791
  }
790
792
  const positives = /* @__PURE__ */ new Map();
793
+ const unknown = /* @__PURE__ */ new Map();
791
794
  for (const term of out.terms) {
792
- if (!isKnown(term.field)) continue;
795
+ if (!isKnown(term.field)) {
796
+ unknown.set(term.expr, [term.field]);
797
+ continue;
798
+ }
793
799
  const list = filters[term.field] ??= [];
794
800
  if (isNegative(term)) {
795
801
  list.push(...term.conds);
@@ -819,11 +825,16 @@ function decomposeUniqueryFilter(expr, opts = {}) {
819
825
  }
820
826
  for (const issue of out.issues) {
821
827
  const fields = issue.fields;
822
- if (fields.length > 0 && !fields.some(isKnown)) continue;
823
- if (opts.carry && fields.length > 0 && fields.every(isKnown)) carried.push(issue.expr);
828
+ const outside = fields.filter((f) => !isKnown(f));
829
+ if (outside.length > 0) unknown.set(issue.expr, outside);
830
+ else if (opts.carry && fields.length > 0) carried.push(issue.expr);
824
831
  else result.unsupported.push(issue);
825
832
  }
826
833
  result.residual = normalizeResidualFilters(carried);
834
+ result.unknown = [...unknown].map(([expr, fields]) => ({
835
+ expr,
836
+ fields
837
+ }));
827
838
  return result;
828
839
  }
829
840
  /**
@@ -874,9 +885,10 @@ function normalizeResidualFilters(exprs) {
874
885
  * to a dev-mode `console.warn` when no handler is given. To keep those
875
886
  * pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
876
887
  *
877
- * Conditions on fields outside `knownFields` (when provided) are ignored
878
- * silently: they are not this table's (a host page flag, a stale column). A
879
- * piece that mixes known and unknown fields is reported.
888
+ * Pieces that name a field outside `knownFields` (when provided) are ignored
889
+ * silently: they are not this table's (a host page flag, a hidden or stale
890
+ * column). Since 0.1.141 that includes a piece mixing known and unknown
891
+ * fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
880
892
  *
881
893
  * Returns `{}` for an empty/missing expression. Never throws.
882
894
  */
@@ -1146,6 +1158,137 @@ function resolveSystemPresets(input) {
1146
1158
  }, ...named];
1147
1159
  }
1148
1160
  //#endregion
1161
+ //#region src/presets/prune-preset-snapshot.ts
1162
+ /** `[kept, dropped]` in one pass. */
1163
+ function partition(list, keep) {
1164
+ const kept = [];
1165
+ const dropped = [];
1166
+ for (const x of list) (keep(x) ? kept : dropped).push(x);
1167
+ return [kept, dropped];
1168
+ }
1169
+ /** The columns aspect, an empty width map spelled by omission — the way capture writes it. */
1170
+ function columnsAspect(columnNames, columnWidths) {
1171
+ return columnWidths && Object.keys(columnWidths).length > 0 ? {
1172
+ columnNames,
1173
+ columnWidths
1174
+ } : { columnNames };
1175
+ }
1176
+ /**
1177
+ * Remove every entry of `snapshot` that names a field outside `known`, per
1178
+ * aspect:
1179
+ *
1180
+ * - `columns.columnNames` — unknown names go, the rest keep their order. When
1181
+ * none survives, it falls back to every column, never an empty grid.
1182
+ * - `columns.columnWidths` — unknown keys go.
1183
+ * - `filters` (displayed inputs) and `filterOps` — entries on a field outside
1184
+ * `known.server` go. A field's conditions are one AND-ed conjunct, so
1185
+ * dropping one only broadens the result.
1186
+ * - `sorters` — unknown entries go, the rest keep their priority.
1187
+ * - `itemsPerPage` — untouched.
1188
+ *
1189
+ * `dropped` is `null` when nothing user-visible went (a width-only drop is
1190
+ * hygiene). The input is never mutated.
1191
+ *
1192
+ * @since 0.1.141
1193
+ */
1194
+ function prunePresetSnapshot(snapshot, known) {
1195
+ const isColumn = (p) => known.columns.has(p);
1196
+ const isServer = (p) => known.server.has(p);
1197
+ const out = { ...snapshot };
1198
+ const dropped = {
1199
+ fields: [],
1200
+ columns: [],
1201
+ filterFields: [],
1202
+ filters: {},
1203
+ residual: [],
1204
+ sorters: []
1205
+ };
1206
+ if (snapshot.columns) {
1207
+ const [names, gone] = partition(snapshot.columns.columnNames, isColumn);
1208
+ const [widths] = partition(Object.entries(snapshot.columns.columnWidths ?? {}), ([p]) => isColumn(p));
1209
+ dropped.columns = gone;
1210
+ out.columns = columnsAspect(names.length > 0 || gone.length === 0 ? names : [...known.columns], Object.fromEntries(widths));
1211
+ }
1212
+ if (snapshot.filters) [out.filters, dropped.filterFields] = partition(snapshot.filters, isServer);
1213
+ if (snapshot.filterOps) {
1214
+ const [kept, gone] = partition(Object.entries(snapshot.filterOps), ([p]) => isServer(p));
1215
+ out.filterOps = Object.fromEntries(kept);
1216
+ dropped.filters = Object.fromEntries(gone);
1217
+ }
1218
+ if (snapshot.sorters) [out.sorters, dropped.sorters] = partition(snapshot.sorters, (s) => isColumn(s.field));
1219
+ dropped.fields = [...new Set([
1220
+ ...dropped.columns,
1221
+ ...dropped.filterFields,
1222
+ ...Object.keys(dropped.filters),
1223
+ ...dropped.sorters.map((s) => s.field)
1224
+ ])];
1225
+ return {
1226
+ snapshot: out,
1227
+ dropped: dropped.fields.length > 0 ? dropped : null
1228
+ };
1229
+ }
1230
+ /**
1231
+ * Split residual filter conditions, in one pass, into the ones that name
1232
+ * only `known` fields and the ones that do not. A condition naming any
1233
+ * unknown field is dropped WHOLE — never pruned inside an `$or` (that would
1234
+ * narrow the result) or a `$not` (that would invert it). Each condition is an
1235
+ * AND-ed conjunct, so dropping one only broadens the result.
1236
+ *
1237
+ * `dropped` holds the conditions in `residual` and the unknown paths in
1238
+ * `fields`; `null` when every condition is kept.
1239
+ *
1240
+ * @since 0.1.141
1241
+ */
1242
+ function pruneResidualFilters(exprs, known) {
1243
+ const kept = [];
1244
+ const residual = [];
1245
+ const fields = /* @__PURE__ */ new Set();
1246
+ for (const expr of exprs) {
1247
+ const unknown = filterExprFields(expr).filter((p) => !known.has(p));
1248
+ if (unknown.length === 0) {
1249
+ kept.push(expr);
1250
+ continue;
1251
+ }
1252
+ residual.push(expr);
1253
+ for (const p of unknown) fields.add(p);
1254
+ }
1255
+ if (residual.length === 0) return {
1256
+ kept,
1257
+ dropped: null
1258
+ };
1259
+ return {
1260
+ kept,
1261
+ dropped: {
1262
+ fields: [...fields],
1263
+ columns: [],
1264
+ filterFields: [],
1265
+ filters: {},
1266
+ residual,
1267
+ sorters: []
1268
+ }
1269
+ };
1270
+ }
1271
+ /**
1272
+ * Append what a prune dropped back onto a snapshot captured from (pruned)
1273
+ * table state, so overwriting a preset with a narrower view does not destroy
1274
+ * the parts the saver cannot see. Column names, filter inputs and sorters go
1275
+ * after the captured ones; field filters are added back. Only aspects
1276
+ * `captured` carries are touched; widths of hidden columns are not kept.
1277
+ *
1278
+ * @since 0.1.141
1279
+ */
1280
+ function restoreDroppedEntries(captured, dropped) {
1281
+ const out = { ...captured };
1282
+ if (captured.columns) out.columns = columnsAspect([...captured.columns.columnNames, ...dropped.columns], captured.columns.columnWidths);
1283
+ if (captured.filters) out.filters = [...captured.filters, ...dropped.filterFields];
1284
+ if (captured.filterOps) out.filterOps = {
1285
+ ...captured.filterOps,
1286
+ ...dropped.filters
1287
+ };
1288
+ if (captured.sorters) out.sorters = [...captured.sorters, ...dropped.sorters];
1289
+ return out;
1290
+ }
1291
+ //#endregion
1149
1292
  //#region src/presets/preset-dirty.ts
1150
1293
  /**
1151
1294
  * JSON-stringify with object keys sorted alphabetically at every depth so
@@ -1749,6 +1892,20 @@ const CONSUMED_CONTROLS = new Set([
1749
1892
  "$skip",
1750
1893
  URL_SNAPSHOT_KEY
1751
1894
  ]);
1895
+ function toSet(paths) {
1896
+ if (!paths) return null;
1897
+ return paths instanceof Set ? paths : new Set(paths);
1898
+ }
1899
+ /**
1900
+ * `field=value` (or `field=null`) — the one filter shape a host page flag
1901
+ * shares, so a bare one on an unknown field is not reported as dropped.
1902
+ */
1903
+ function isBareEquality(expr) {
1904
+ const keys = Object.keys(expr);
1905
+ if (keys.length !== 1 || keys[0].startsWith("$")) return false;
1906
+ const v = expr[keys[0]];
1907
+ return v === null || typeof v !== "object";
1908
+ }
1752
1909
  /**
1753
1910
  * Characters that can only appear in a uniqu filter key, never in a page flag:
1754
1911
  * comparison operators, `^` (OR), `( )` (groups) and `{ }` (`$in` lists).
@@ -1775,9 +1932,10 @@ function urlQueryConsumesKey(key) {
1775
1932
  /**
1776
1933
  * Parse a URL query string back into the table state subset.
1777
1934
  *
1778
- * Robust by design — schema drift and copy-paste errors must not break the
1779
- * recipient's view:
1780
- * - unknown fields (not in `knownFields`) → silently dropped
1935
+ * Robust by design — schema drift, fields hidden from the recipient and
1936
+ * copy-paste errors must not break the recipient's view:
1937
+ * - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
1938
+ * `unknownSorters` (the parser does not warn — the caller decides)
1781
1939
  * - filter pieces field filters cannot express (cross-field OR, unknown
1782
1940
  * operator, …) → left out of `filters` and listed in `unsupported`, never
1783
1941
  * approximated (the parser does not warn — the caller decides). Unless
@@ -1810,12 +1968,15 @@ function urlQueryStringToState(urlString, opts = {}) {
1810
1968
  const sortersGate = resolveAspectGate(opts.sync?.sorters);
1811
1969
  const searchOff = opts.sync?.search === false;
1812
1970
  const paginationOff = opts.sync?.pagination === false;
1813
- const knownSet = opts.knownFields ? new Set(opts.knownFields) : null;
1971
+ const knownSet = toSet(opts.knownFields);
1972
+ const localSet = toSet(opts.localFields);
1973
+ const isHidden = (f) => !!knownSet && !knownSet.has(f) && !localSet?.has(f);
1814
1974
  let filterKnown;
1815
1975
  if (filtersGate === "all") filterKnown = knownSet ?? void 0;
1816
1976
  else if (filtersGate !== "none") if (knownSet) {
1817
- filterKnown = /* @__PURE__ */ new Set();
1818
- for (const path of filtersGate) if (knownSet.has(path)) filterKnown.add(path);
1977
+ const owned = /* @__PURE__ */ new Set();
1978
+ for (const path of filtersGate) if (knownSet.has(path)) owned.add(path);
1979
+ filterKnown = owned;
1819
1980
  } else filterKnown = filtersGate;
1820
1981
  const decomposed = filtersGate === "none" ? null : decomposeUniqueryFilter(parsed.filter, {
1821
1982
  knownFields: filterKnown,
@@ -1823,20 +1984,19 @@ function urlQueryStringToState(urlString, opts = {}) {
1823
1984
  });
1824
1985
  const filters = decomposed?.filters ?? {};
1825
1986
  const sorters = [];
1987
+ const unknownSorters = [];
1826
1988
  if (sortersGate !== "none") {
1827
1989
  const $sort = parsed.controls?.$sort;
1828
1990
  if ($sort && typeof $sort === "object") for (const field in $sort) {
1829
- if (knownSet && !knownSet.has(field)) continue;
1830
1991
  if (!gateOwns(sortersGate, field)) continue;
1831
1992
  const dir = $sort[field];
1832
- if (dir === 1) sorters.push({
1833
- field,
1834
- direction: "asc"
1835
- });
1836
- else if (dir === -1) sorters.push({
1993
+ if (dir !== 1 && dir !== -1) continue;
1994
+ const sorter = {
1837
1995
  field,
1838
- direction: "desc"
1839
- });
1996
+ direction: dir === 1 ? "asc" : "desc"
1997
+ };
1998
+ if (!knownSet || knownSet.has(field)) sorters.push(sorter);
1999
+ else if (isHidden(field)) unknownSorters.push(sorter);
1840
2000
  }
1841
2001
  }
1842
2002
  const $search = parsed.controls?.$search;
@@ -1845,8 +2005,24 @@ function urlQueryStringToState(urlString, opts = {}) {
1845
2005
  sorters,
1846
2006
  searchTerm: !searchOff && typeof $search === "string" ? $search : ""
1847
2007
  };
1848
- if (decomposed?.unsupported.length) out.unsupported = decomposed.unsupported;
2008
+ const unsupported$1 = decomposed?.unsupported ?? [];
2009
+ const unknown = [];
2010
+ for (const piece of decomposed?.unknown ?? []) {
2011
+ const hidden = piece.fields.filter(isHidden);
2012
+ if (hidden.length > 0) {
2013
+ if (!isBareEquality(piece.expr)) unknown.push({
2014
+ expr: piece.expr,
2015
+ fields: hidden
2016
+ });
2017
+ continue;
2018
+ }
2019
+ const issue = unsupported("cross-field", piece.expr);
2020
+ if (issue.fields.some((f) => filterKnown?.has(f))) unsupported$1.push(issue);
2021
+ }
2022
+ if (unsupported$1.length) out.unsupported = unsupported$1;
1849
2023
  if (decomposed?.residual.length) out.residual = decomposed.residual;
2024
+ if (unknown.length) out.unknown = unknown;
2025
+ if (unknownSorters.length) out.unknownSorters = unknownSorters;
1850
2026
  if (opts.sync?.snapshot !== false && parsed.controls && "$snapshot" in parsed.controls) out.snapshot = true;
1851
2027
  if (!searchOff) {
1852
2028
  const $relevance = parsed.controls?.$relevance;
@@ -2464,12 +2640,15 @@ exports.pageAlignedBlocksFor = pageAlignedBlocksFor;
2464
2640
  exports.parseColumnFilterInput = parseColumnFilterInput;
2465
2641
  exports.parseFilterInput = parseFilterInput;
2466
2642
  exports.planFetch = planFetch;
2643
+ exports.prunePresetSnapshot = prunePresetSnapshot;
2644
+ exports.pruneResidualFilters = pruneResidualFilters;
2467
2645
  exports.reconcileColumnWidthDefaults = reconcileColumnWidthDefaults;
2468
2646
  exports.reorderColumnNames = reorderColumnNames;
2469
2647
  exports.residualGateOwns = residualGateOwns;
2470
2648
  exports.resolveAspectGate = resolveAspectGate;
2471
2649
  exports.resolveExportValue = resolveExportValue;
2472
2650
  exports.resolveSystemPresets = resolveSystemPresets;
2651
+ exports.restoreDroppedEntries = restoreDroppedEntries;
2473
2652
  exports.rowsToPks = rowsToPks;
2474
2653
  exports.sameColumnSet = sameColumnSet;
2475
2654
  exports.serializeDraft = serializeDraft;
package/dist/index.d.cts CHANGED
@@ -175,6 +175,18 @@ interface UnsupportedFilter {
175
175
  /** Field paths the sub-expression references, in order of appearance. */
176
176
  fields: string[];
177
177
  }
178
+ /**
179
+ * One AND-ed piece of a Uniquery filter left out because it names a field
180
+ * outside `knownFields` — alone or next to known ones.
181
+ *
182
+ * @since 0.1.141
183
+ */
184
+ interface UnknownFilter {
185
+ /** The left-out sub-expression, as it appeared in the input. */
186
+ expr: FilterExpr;
187
+ /** The paths it names that are outside `knownFields`. */
188
+ fields: string[];
189
+ }
178
190
  /**
179
191
  * Field paths a Uniquery filter expression references, deduped, in order of
180
192
  * appearance (logical operators are walked, operator keys skipped).
@@ -185,9 +197,10 @@ declare function filterExprFields(expr: unknown): string[];
185
197
  /** Options for {@link decomposeUniqueryFilter}. @since 0.1.140 */
186
198
  interface DecomposeUniqueryFilterOptions {
187
199
  /**
188
- * Field paths the table knows. Pieces on fields outside it are ignored
189
- * silently; a piece mixing known and unknown fields is reported, never
190
- * carried. Omit to accept every field.
200
+ * Field paths the table knows. A piece that names a field outside it —
201
+ * alone or mixed with known ones — is left out whole and listed in
202
+ * `unknown`, never carried or reported as `unsupported`. Omit to accept
203
+ * every field.
191
204
  */
192
205
  knownFields?: Iterable<string>;
193
206
  /**
@@ -205,14 +218,22 @@ interface DecomposedUniqueryFilter {
205
218
  residual: FilterExpr[];
206
219
  /** Pieces left out and lost — the result is broader by exactly these. */
207
220
  unsupported: UnsupportedFilter[];
221
+ /**
222
+ * Pieces left out because they name a field outside `knownFields` (hidden
223
+ * from the caller, or not in the schema). Each is a whole AND-ed piece, so
224
+ * the result is broader by exactly these too. `[]` without `knownFields`.
225
+ * Since 0.1.141 — before, pieces on unknown fields only were dropped
226
+ * silently and mixed known/unknown pieces went to `unsupported`.
227
+ */
228
+ unknown: UnknownFilter[];
208
229
  }
209
230
  /**
210
231
  * Split a Uniquery `FilterExpr` into what the table's field-filter model
211
232
  * holds exactly (`filters`), what it cannot hold but carries as residual
212
- * conditions (`residual`, with `carry`), and what it leaves out
213
- * (`unsupported`). Nothing is approximated: `filters AND residual` selects
214
- * `expr` minus the `unsupported` pieces and pieces on fields outside
215
- * `knownFields`.
233
+ * conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
234
+ * and what names a field outside `knownFields` (`unknown`). Nothing is
235
+ * approximated: `filters AND residual` selects `expr` minus the
236
+ * `unsupported` and `unknown` pieces.
216
237
  *
217
238
  * Never throws, never warns — the caller decides how to report.
218
239
  *
@@ -253,9 +274,10 @@ declare function normalizeResidualFilters(exprs: readonly FilterExpr[]): FilterE
253
274
  * to a dev-mode `console.warn` when no handler is given. To keep those
254
275
  * pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
255
276
  *
256
- * Conditions on fields outside `knownFields` (when provided) are ignored
257
- * silently: they are not this table's (a host page flag, a stale column). A
258
- * piece that mixes known and unknown fields is reported.
277
+ * Pieces that name a field outside `knownFields` (when provided) are ignored
278
+ * silently: they are not this table's (a host page flag, a hidden or stale
279
+ * column). Since 0.1.141 that includes a piece mixing known and unknown
280
+ * fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
259
281
  *
260
282
  * Returns `{}` for an empty/missing expression. Never throws.
261
283
  */
@@ -530,6 +552,96 @@ interface SystemPresetInput {
530
552
  */
531
553
  declare function resolveSystemPresets(input?: SystemPresetInput[]): SystemPreset[];
532
554
  //#endregion
555
+ //#region src/presets/prune-preset-snapshot.d.ts
556
+ /**
557
+ * The field paths a table can use, derived from its column list. A path
558
+ * outside these sets is one the server does not expose to the current caller
559
+ * (hidden by role, removed from the schema) — naming it in a query is rejected.
560
+ *
561
+ * @since 0.1.141
562
+ */
563
+ interface KnownFields {
564
+ /**
565
+ * Every column path, client-owned (display) columns included, in column
566
+ * order. Gates `columnNames`, column widths and sorters (a sorter on a
567
+ * client-owned column is applied in memory).
568
+ */
569
+ columns: ReadonlySet<string>;
570
+ /**
571
+ * Server-backed column paths. Gates displayed filter inputs, field filters
572
+ * and residual filter conditions.
573
+ */
574
+ server: ReadonlySet<string>;
575
+ }
576
+ /**
577
+ * What pruning left out of a preset snapshot or of residual conditions
578
+ * because it names a field outside {@link KnownFields}.
579
+ *
580
+ * @since 0.1.141
581
+ */
582
+ interface DroppedFields {
583
+ /** The unavailable field paths behind the drops, deduped, in order of appearance. */
584
+ fields: string[];
585
+ /** Dropped `columnNames` entries. */
586
+ columns: string[];
587
+ /** Dropped displayed filter inputs. */
588
+ filterFields: string[];
589
+ /** Dropped field filters — whole entries, one per field. */
590
+ filters: FieldFilters;
591
+ /** Dropped filter conditions — each a whole AND-ed conjunct. */
592
+ residual: FilterExpr[];
593
+ /** Dropped sorters. */
594
+ sorters: SortControl[];
595
+ }
596
+ /**
597
+ * Remove every entry of `snapshot` that names a field outside `known`, per
598
+ * aspect:
599
+ *
600
+ * - `columns.columnNames` — unknown names go, the rest keep their order. When
601
+ * none survives, it falls back to every column, never an empty grid.
602
+ * - `columns.columnWidths` — unknown keys go.
603
+ * - `filters` (displayed inputs) and `filterOps` — entries on a field outside
604
+ * `known.server` go. A field's conditions are one AND-ed conjunct, so
605
+ * dropping one only broadens the result.
606
+ * - `sorters` — unknown entries go, the rest keep their priority.
607
+ * - `itemsPerPage` — untouched.
608
+ *
609
+ * `dropped` is `null` when nothing user-visible went (a width-only drop is
610
+ * hygiene). The input is never mutated.
611
+ *
612
+ * @since 0.1.141
613
+ */
614
+ declare function prunePresetSnapshot(snapshot: PresetSnapshot, known: KnownFields): {
615
+ snapshot: PresetSnapshot;
616
+ dropped: DroppedFields | null;
617
+ };
618
+ /**
619
+ * Split residual filter conditions, in one pass, into the ones that name
620
+ * only `known` fields and the ones that do not. A condition naming any
621
+ * unknown field is dropped WHOLE — never pruned inside an `$or` (that would
622
+ * narrow the result) or a `$not` (that would invert it). Each condition is an
623
+ * AND-ed conjunct, so dropping one only broadens the result.
624
+ *
625
+ * `dropped` holds the conditions in `residual` and the unknown paths in
626
+ * `fields`; `null` when every condition is kept.
627
+ *
628
+ * @since 0.1.141
629
+ */
630
+ declare function pruneResidualFilters(exprs: FilterExpr[], known: ReadonlySet<string>): {
631
+ kept: FilterExpr[];
632
+ dropped: DroppedFields | null;
633
+ };
634
+ /**
635
+ * Append what a prune dropped back onto a snapshot captured from (pruned)
636
+ * table state, so overwriting a preset with a narrower view does not destroy
637
+ * the parts the saver cannot see. Column names, filter inputs and sorters go
638
+ * after the captured ones; field filters are added back. Only aspects
639
+ * `captured` carries are touched; widths of hidden columns are not kept.
640
+ *
641
+ * @since 0.1.141
642
+ */
643
+ declare function restoreDroppedEntries(captured: PresetSnapshot, dropped: DroppedFields): PresetSnapshot;
644
+ //#endregion
533
645
  //#region src/presets/preset-dirty.d.ts
534
646
  /**
535
647
  * JSON-stringify with object keys sorted alphabetically at every depth so
@@ -888,6 +1000,21 @@ interface UrlQueryStateSnapshot {
888
1000
  * or `sync.residual` is `false`. Since 0.1.140.
889
1001
  */
890
1002
  residual?: FilterExpr[];
1003
+ /**
1004
+ * Filter pieces left out because they name a field outside `knownFields`
1005
+ * and `localFields` — a column hidden from this caller, or gone from the
1006
+ * schema — with those paths in `fields`. Each is a whole AND-ed piece (a
1007
+ * mixed known/unknown one included), so the result is broader by exactly
1008
+ * these. A bare `field=value` piece is not listed: without the table
1009
+ * definition it reads the same as a flag of the host page. Omitted when
1010
+ * none. Since 0.1.141 — before, a mixed piece was listed in `unsupported`.
1011
+ */
1012
+ unknown?: UnknownFilter[];
1013
+ /**
1014
+ * `$sort` entries left out because their field is outside `knownFields`
1015
+ * and `localFields`. Omitted when none. Since 0.1.141.
1016
+ */
1017
+ unknownSorters?: SortControl[];
891
1018
  /**
892
1019
  * Raw record offset from `$skip` (omitted when no `$skip` in URL). The
893
1020
  * decoder does NOT compute a page index — that requires `itemsPerPage`,
@@ -1013,20 +1140,29 @@ declare function stateToUrlQueryString(state: UrlQueryStateLike, defaults: UrlQu
1013
1140
  declare function urlQueryConsumesKey(key: string): boolean;
1014
1141
  interface UrlQueryParseOptions {
1015
1142
  /**
1016
- * Field paths the table knows about. Conditions on fields outside this set
1017
- * are silently dropped. Omit to accept any field (useful when the table
1018
- * definition isn't loaded yet).
1143
+ * Field paths the table knows about. Conditions and sorters on fields
1144
+ * outside this set are dropped and listed in `unknown` / `unknownSorters`.
1145
+ * Omit to accept any field (useful when the table definition isn't loaded
1146
+ * yet).
1019
1147
  */
1020
1148
  knownFields?: Iterable<string>;
1149
+ /**
1150
+ * Client-owned column paths. The URL never restores them (no server query
1151
+ * can use them), but they are not unknown either: a sorter on one is
1152
+ * ignored, and a filter piece correlating one with a known field is
1153
+ * reported as `unsupported` (`"cross-field"`). Since 0.1.141.
1154
+ */
1155
+ localFields?: Iterable<string>;
1021
1156
  /** Per-aspect sync gates — must match the encoder's config to keep the round-trip symmetric. */
1022
1157
  sync?: UrlQuerySync;
1023
1158
  }
1024
1159
  /**
1025
1160
  * Parse a URL query string back into the table state subset.
1026
1161
  *
1027
- * Robust by design — schema drift and copy-paste errors must not break the
1028
- * recipient's view:
1029
- * - unknown fields (not in `knownFields`) → silently dropped
1162
+ * Robust by design — schema drift, fields hidden from the recipient and
1163
+ * copy-paste errors must not break the recipient's view:
1164
+ * - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
1165
+ * `unknownSorters` (the parser does not warn — the caller decides)
1030
1166
  * - filter pieces field filters cannot express (cross-field OR, unknown
1031
1167
  * operator, …) → left out of `filters` and listed in `unsupported`, never
1032
1168
  * approximated (the parser does not warn — the caller decides). Unless
@@ -1561,4 +1697,4 @@ declare function sortRowsLocally<T extends Record<string, unknown>>(rows: T[], s
1561
1697
  //#region src/utils/dev.d.ts
1562
1698
  declare const DEV: boolean;
1563
1699
  //#endregion
1564
- export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
1700
+ export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, type DroppedFields, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, type KnownFields, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnknownFilter, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
package/dist/index.d.mts CHANGED
@@ -175,6 +175,18 @@ interface UnsupportedFilter {
175
175
  /** Field paths the sub-expression references, in order of appearance. */
176
176
  fields: string[];
177
177
  }
178
+ /**
179
+ * One AND-ed piece of a Uniquery filter left out because it names a field
180
+ * outside `knownFields` — alone or next to known ones.
181
+ *
182
+ * @since 0.1.141
183
+ */
184
+ interface UnknownFilter {
185
+ /** The left-out sub-expression, as it appeared in the input. */
186
+ expr: FilterExpr;
187
+ /** The paths it names that are outside `knownFields`. */
188
+ fields: string[];
189
+ }
178
190
  /**
179
191
  * Field paths a Uniquery filter expression references, deduped, in order of
180
192
  * appearance (logical operators are walked, operator keys skipped).
@@ -185,9 +197,10 @@ declare function filterExprFields(expr: unknown): string[];
185
197
  /** Options for {@link decomposeUniqueryFilter}. @since 0.1.140 */
186
198
  interface DecomposeUniqueryFilterOptions {
187
199
  /**
188
- * Field paths the table knows. Pieces on fields outside it are ignored
189
- * silently; a piece mixing known and unknown fields is reported, never
190
- * carried. Omit to accept every field.
200
+ * Field paths the table knows. A piece that names a field outside it —
201
+ * alone or mixed with known ones — is left out whole and listed in
202
+ * `unknown`, never carried or reported as `unsupported`. Omit to accept
203
+ * every field.
191
204
  */
192
205
  knownFields?: Iterable<string>;
193
206
  /**
@@ -205,14 +218,22 @@ interface DecomposedUniqueryFilter {
205
218
  residual: FilterExpr[];
206
219
  /** Pieces left out and lost — the result is broader by exactly these. */
207
220
  unsupported: UnsupportedFilter[];
221
+ /**
222
+ * Pieces left out because they name a field outside `knownFields` (hidden
223
+ * from the caller, or not in the schema). Each is a whole AND-ed piece, so
224
+ * the result is broader by exactly these too. `[]` without `knownFields`.
225
+ * Since 0.1.141 — before, pieces on unknown fields only were dropped
226
+ * silently and mixed known/unknown pieces went to `unsupported`.
227
+ */
228
+ unknown: UnknownFilter[];
208
229
  }
209
230
  /**
210
231
  * Split a Uniquery `FilterExpr` into what the table's field-filter model
211
232
  * holds exactly (`filters`), what it cannot hold but carries as residual
212
- * conditions (`residual`, with `carry`), and what it leaves out
213
- * (`unsupported`). Nothing is approximated: `filters AND residual` selects
214
- * `expr` minus the `unsupported` pieces and pieces on fields outside
215
- * `knownFields`.
233
+ * conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
234
+ * and what names a field outside `knownFields` (`unknown`). Nothing is
235
+ * approximated: `filters AND residual` selects `expr` minus the
236
+ * `unsupported` and `unknown` pieces.
216
237
  *
217
238
  * Never throws, never warns — the caller decides how to report.
218
239
  *
@@ -253,9 +274,10 @@ declare function normalizeResidualFilters(exprs: readonly FilterExpr[]): FilterE
253
274
  * to a dev-mode `console.warn` when no handler is given. To keep those
254
275
  * pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
255
276
  *
256
- * Conditions on fields outside `knownFields` (when provided) are ignored
257
- * silently: they are not this table's (a host page flag, a stale column). A
258
- * piece that mixes known and unknown fields is reported.
277
+ * Pieces that name a field outside `knownFields` (when provided) are ignored
278
+ * silently: they are not this table's (a host page flag, a hidden or stale
279
+ * column). Since 0.1.141 that includes a piece mixing known and unknown
280
+ * fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
259
281
  *
260
282
  * Returns `{}` for an empty/missing expression. Never throws.
261
283
  */
@@ -530,6 +552,96 @@ interface SystemPresetInput {
530
552
  */
531
553
  declare function resolveSystemPresets(input?: SystemPresetInput[]): SystemPreset[];
532
554
  //#endregion
555
+ //#region src/presets/prune-preset-snapshot.d.ts
556
+ /**
557
+ * The field paths a table can use, derived from its column list. A path
558
+ * outside these sets is one the server does not expose to the current caller
559
+ * (hidden by role, removed from the schema) — naming it in a query is rejected.
560
+ *
561
+ * @since 0.1.141
562
+ */
563
+ interface KnownFields {
564
+ /**
565
+ * Every column path, client-owned (display) columns included, in column
566
+ * order. Gates `columnNames`, column widths and sorters (a sorter on a
567
+ * client-owned column is applied in memory).
568
+ */
569
+ columns: ReadonlySet<string>;
570
+ /**
571
+ * Server-backed column paths. Gates displayed filter inputs, field filters
572
+ * and residual filter conditions.
573
+ */
574
+ server: ReadonlySet<string>;
575
+ }
576
+ /**
577
+ * What pruning left out of a preset snapshot or of residual conditions
578
+ * because it names a field outside {@link KnownFields}.
579
+ *
580
+ * @since 0.1.141
581
+ */
582
+ interface DroppedFields {
583
+ /** The unavailable field paths behind the drops, deduped, in order of appearance. */
584
+ fields: string[];
585
+ /** Dropped `columnNames` entries. */
586
+ columns: string[];
587
+ /** Dropped displayed filter inputs. */
588
+ filterFields: string[];
589
+ /** Dropped field filters — whole entries, one per field. */
590
+ filters: FieldFilters;
591
+ /** Dropped filter conditions — each a whole AND-ed conjunct. */
592
+ residual: FilterExpr[];
593
+ /** Dropped sorters. */
594
+ sorters: SortControl[];
595
+ }
596
+ /**
597
+ * Remove every entry of `snapshot` that names a field outside `known`, per
598
+ * aspect:
599
+ *
600
+ * - `columns.columnNames` — unknown names go, the rest keep their order. When
601
+ * none survives, it falls back to every column, never an empty grid.
602
+ * - `columns.columnWidths` — unknown keys go.
603
+ * - `filters` (displayed inputs) and `filterOps` — entries on a field outside
604
+ * `known.server` go. A field's conditions are one AND-ed conjunct, so
605
+ * dropping one only broadens the result.
606
+ * - `sorters` — unknown entries go, the rest keep their priority.
607
+ * - `itemsPerPage` — untouched.
608
+ *
609
+ * `dropped` is `null` when nothing user-visible went (a width-only drop is
610
+ * hygiene). The input is never mutated.
611
+ *
612
+ * @since 0.1.141
613
+ */
614
+ declare function prunePresetSnapshot(snapshot: PresetSnapshot, known: KnownFields): {
615
+ snapshot: PresetSnapshot;
616
+ dropped: DroppedFields | null;
617
+ };
618
+ /**
619
+ * Split residual filter conditions, in one pass, into the ones that name
620
+ * only `known` fields and the ones that do not. A condition naming any
621
+ * unknown field is dropped WHOLE — never pruned inside an `$or` (that would
622
+ * narrow the result) or a `$not` (that would invert it). Each condition is an
623
+ * AND-ed conjunct, so dropping one only broadens the result.
624
+ *
625
+ * `dropped` holds the conditions in `residual` and the unknown paths in
626
+ * `fields`; `null` when every condition is kept.
627
+ *
628
+ * @since 0.1.141
629
+ */
630
+ declare function pruneResidualFilters(exprs: FilterExpr[], known: ReadonlySet<string>): {
631
+ kept: FilterExpr[];
632
+ dropped: DroppedFields | null;
633
+ };
634
+ /**
635
+ * Append what a prune dropped back onto a snapshot captured from (pruned)
636
+ * table state, so overwriting a preset with a narrower view does not destroy
637
+ * the parts the saver cannot see. Column names, filter inputs and sorters go
638
+ * after the captured ones; field filters are added back. Only aspects
639
+ * `captured` carries are touched; widths of hidden columns are not kept.
640
+ *
641
+ * @since 0.1.141
642
+ */
643
+ declare function restoreDroppedEntries(captured: PresetSnapshot, dropped: DroppedFields): PresetSnapshot;
644
+ //#endregion
533
645
  //#region src/presets/preset-dirty.d.ts
534
646
  /**
535
647
  * JSON-stringify with object keys sorted alphabetically at every depth so
@@ -888,6 +1000,21 @@ interface UrlQueryStateSnapshot {
888
1000
  * or `sync.residual` is `false`. Since 0.1.140.
889
1001
  */
890
1002
  residual?: FilterExpr[];
1003
+ /**
1004
+ * Filter pieces left out because they name a field outside `knownFields`
1005
+ * and `localFields` — a column hidden from this caller, or gone from the
1006
+ * schema — with those paths in `fields`. Each is a whole AND-ed piece (a
1007
+ * mixed known/unknown one included), so the result is broader by exactly
1008
+ * these. A bare `field=value` piece is not listed: without the table
1009
+ * definition it reads the same as a flag of the host page. Omitted when
1010
+ * none. Since 0.1.141 — before, a mixed piece was listed in `unsupported`.
1011
+ */
1012
+ unknown?: UnknownFilter[];
1013
+ /**
1014
+ * `$sort` entries left out because their field is outside `knownFields`
1015
+ * and `localFields`. Omitted when none. Since 0.1.141.
1016
+ */
1017
+ unknownSorters?: SortControl[];
891
1018
  /**
892
1019
  * Raw record offset from `$skip` (omitted when no `$skip` in URL). The
893
1020
  * decoder does NOT compute a page index — that requires `itemsPerPage`,
@@ -1013,20 +1140,29 @@ declare function stateToUrlQueryString(state: UrlQueryStateLike, defaults: UrlQu
1013
1140
  declare function urlQueryConsumesKey(key: string): boolean;
1014
1141
  interface UrlQueryParseOptions {
1015
1142
  /**
1016
- * Field paths the table knows about. Conditions on fields outside this set
1017
- * are silently dropped. Omit to accept any field (useful when the table
1018
- * definition isn't loaded yet).
1143
+ * Field paths the table knows about. Conditions and sorters on fields
1144
+ * outside this set are dropped and listed in `unknown` / `unknownSorters`.
1145
+ * Omit to accept any field (useful when the table definition isn't loaded
1146
+ * yet).
1019
1147
  */
1020
1148
  knownFields?: Iterable<string>;
1149
+ /**
1150
+ * Client-owned column paths. The URL never restores them (no server query
1151
+ * can use them), but they are not unknown either: a sorter on one is
1152
+ * ignored, and a filter piece correlating one with a known field is
1153
+ * reported as `unsupported` (`"cross-field"`). Since 0.1.141.
1154
+ */
1155
+ localFields?: Iterable<string>;
1021
1156
  /** Per-aspect sync gates — must match the encoder's config to keep the round-trip symmetric. */
1022
1157
  sync?: UrlQuerySync;
1023
1158
  }
1024
1159
  /**
1025
1160
  * Parse a URL query string back into the table state subset.
1026
1161
  *
1027
- * Robust by design — schema drift and copy-paste errors must not break the
1028
- * recipient's view:
1029
- * - unknown fields (not in `knownFields`) → silently dropped
1162
+ * Robust by design — schema drift, fields hidden from the recipient and
1163
+ * copy-paste errors must not break the recipient's view:
1164
+ * - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
1165
+ * `unknownSorters` (the parser does not warn — the caller decides)
1030
1166
  * - filter pieces field filters cannot express (cross-field OR, unknown
1031
1167
  * operator, …) → left out of `filters` and listed in `unsupported`, never
1032
1168
  * approximated (the parser does not warn — the caller decides). Unless
@@ -1561,4 +1697,4 @@ declare function sortRowsLocally<T extends Record<string, unknown>>(rows: T[], s
1561
1697
  //#region src/utils/dev.d.ts
1562
1698
  declare const DEV: boolean;
1563
1699
  //#endregion
1564
- export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
1700
+ export { APP_CONF_PREFIX, type AppConfData, AppPrefsClient, type AppPrefsClientConfig, type AppPrefsLoadResult, type AsPresetEntryData, type AsPresetEntryRow, type AsPresetsErrorCode, type AspectGate, type AspectMask, type BuildTableQueryOptions, type ColumnFilterType, type ColumnReorderPosition, type ColumnWidthEntry, type ColumnWidthsMap, type ConfigTab, type CsvOptions, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, type DateShortcut, type DecomposeUniqueryFilterOptions, type DecomposedUniqueryFilter, type DisplayColumnDef, type DraftPersistedAspect, type DroppedFields, ExportAbortError, type ExportPage, type ExportPageFetcher, type ExportScalar, type FetchPlan, type FetchPlanMode, type FieldFilters, type FilterCondition, type FilterConditionType, type FilterableColumn, type KnownFields, MAX_DEFAULT_COLUMN_WIDTH_PX, type MergeResult, NULL_OPS, PRESET_ASPECTS, type PageAlignedBlock, type PlanFetchArgs, type PresetAspect, type PresetCapabilities, type PresetColumnWidthEntry, type PresetData, type PresetDraft, type PresetFilterOpEntry, type PresetLimitReachedBody, type PresetSnapshot, type PresetSnapshotWire, type PresetSorterEntry, PresetsClient, type PresetsClientConfig, PresetsHttpError, type PresetsListResult, type PresetsSaveAsOptions, type PresetsSaveResult, type QueryOptions, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, type SelectionMode, type SystemPreset, type SystemPresetInput, type TableStateData, type TableStateMethods, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, type UnknownFilter, type UnsupportedFilter, type UnsupportedFilterReason, type UrlQueryDefaults, type UrlQueryParseOptions, type UrlQueryStateLike, type UrlQueryStateSnapshot, type UrlQuerySync, type UserConfData, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
package/dist/index.mjs CHANGED
@@ -527,6 +527,7 @@ function fieldsOf(expr, out) {
527
527
  else if (!out.includes(key)) out.push(key);
528
528
  return out;
529
529
  }
530
+ /** @internal Module-shared with the URL parser; not a package export. */
530
531
  function unsupported(reason, expr) {
531
532
  const fields = fieldsOf(expr, []);
532
533
  return {
@@ -752,10 +753,10 @@ function warnUnsupported(issue) {
752
753
  /**
753
754
  * Split a Uniquery `FilterExpr` into what the table's field-filter model
754
755
  * holds exactly (`filters`), what it cannot hold but carries as residual
755
- * conditions (`residual`, with `carry`), and what it leaves out
756
- * (`unsupported`). Nothing is approximated: `filters AND residual` selects
757
- * `expr` minus the `unsupported` pieces and pieces on fields outside
758
- * `knownFields`.
756
+ * conditions (`residual`, with `carry`), what it leaves out (`unsupported`)
757
+ * and what names a field outside `knownFields` (`unknown`). Nothing is
758
+ * approximated: `filters AND residual` selects `expr` minus the
759
+ * `unsupported` and `unknown` pieces.
759
760
  *
760
761
  * Never throws, never warns — the caller decides how to report.
761
762
  *
@@ -766,7 +767,8 @@ function decomposeUniqueryFilter(expr, opts = {}) {
766
767
  const result = {
767
768
  filters,
768
769
  residual: [],
769
- unsupported: []
770
+ unsupported: [],
771
+ unknown: []
770
772
  };
771
773
  if (!expr) return result;
772
774
  const knownFields = opts.knownFields;
@@ -787,8 +789,12 @@ function decomposeUniqueryFilter(expr, opts = {}) {
787
789
  }];
788
790
  }
789
791
  const positives = /* @__PURE__ */ new Map();
792
+ const unknown = /* @__PURE__ */ new Map();
790
793
  for (const term of out.terms) {
791
- if (!isKnown(term.field)) continue;
794
+ if (!isKnown(term.field)) {
795
+ unknown.set(term.expr, [term.field]);
796
+ continue;
797
+ }
792
798
  const list = filters[term.field] ??= [];
793
799
  if (isNegative(term)) {
794
800
  list.push(...term.conds);
@@ -818,11 +824,16 @@ function decomposeUniqueryFilter(expr, opts = {}) {
818
824
  }
819
825
  for (const issue of out.issues) {
820
826
  const fields = issue.fields;
821
- if (fields.length > 0 && !fields.some(isKnown)) continue;
822
- if (opts.carry && fields.length > 0 && fields.every(isKnown)) carried.push(issue.expr);
827
+ const outside = fields.filter((f) => !isKnown(f));
828
+ if (outside.length > 0) unknown.set(issue.expr, outside);
829
+ else if (opts.carry && fields.length > 0) carried.push(issue.expr);
823
830
  else result.unsupported.push(issue);
824
831
  }
825
832
  result.residual = normalizeResidualFilters(carried);
833
+ result.unknown = [...unknown].map(([expr, fields]) => ({
834
+ expr,
835
+ fields
836
+ }));
826
837
  return result;
827
838
  }
828
839
  /**
@@ -873,9 +884,10 @@ function normalizeResidualFilters(exprs) {
873
884
  * to a dev-mode `console.warn` when no handler is given. To keep those
874
885
  * pieces instead, use {@link decomposeUniqueryFilter} with `carry`.
875
886
  *
876
- * Conditions on fields outside `knownFields` (when provided) are ignored
877
- * silently: they are not this table's (a host page flag, a stale column). A
878
- * piece that mixes known and unknown fields is reported.
887
+ * Pieces that name a field outside `knownFields` (when provided) are ignored
888
+ * silently: they are not this table's (a host page flag, a hidden or stale
889
+ * column). Since 0.1.141 that includes a piece mixing known and unknown
890
+ * fields — {@link decomposeUniqueryFilter} lists them as `unknown`.
879
891
  *
880
892
  * Returns `{}` for an empty/missing expression. Never throws.
881
893
  */
@@ -1145,6 +1157,137 @@ function resolveSystemPresets(input) {
1145
1157
  }, ...named];
1146
1158
  }
1147
1159
  //#endregion
1160
+ //#region src/presets/prune-preset-snapshot.ts
1161
+ /** `[kept, dropped]` in one pass. */
1162
+ function partition(list, keep) {
1163
+ const kept = [];
1164
+ const dropped = [];
1165
+ for (const x of list) (keep(x) ? kept : dropped).push(x);
1166
+ return [kept, dropped];
1167
+ }
1168
+ /** The columns aspect, an empty width map spelled by omission — the way capture writes it. */
1169
+ function columnsAspect(columnNames, columnWidths) {
1170
+ return columnWidths && Object.keys(columnWidths).length > 0 ? {
1171
+ columnNames,
1172
+ columnWidths
1173
+ } : { columnNames };
1174
+ }
1175
+ /**
1176
+ * Remove every entry of `snapshot` that names a field outside `known`, per
1177
+ * aspect:
1178
+ *
1179
+ * - `columns.columnNames` — unknown names go, the rest keep their order. When
1180
+ * none survives, it falls back to every column, never an empty grid.
1181
+ * - `columns.columnWidths` — unknown keys go.
1182
+ * - `filters` (displayed inputs) and `filterOps` — entries on a field outside
1183
+ * `known.server` go. A field's conditions are one AND-ed conjunct, so
1184
+ * dropping one only broadens the result.
1185
+ * - `sorters` — unknown entries go, the rest keep their priority.
1186
+ * - `itemsPerPage` — untouched.
1187
+ *
1188
+ * `dropped` is `null` when nothing user-visible went (a width-only drop is
1189
+ * hygiene). The input is never mutated.
1190
+ *
1191
+ * @since 0.1.141
1192
+ */
1193
+ function prunePresetSnapshot(snapshot, known) {
1194
+ const isColumn = (p) => known.columns.has(p);
1195
+ const isServer = (p) => known.server.has(p);
1196
+ const out = { ...snapshot };
1197
+ const dropped = {
1198
+ fields: [],
1199
+ columns: [],
1200
+ filterFields: [],
1201
+ filters: {},
1202
+ residual: [],
1203
+ sorters: []
1204
+ };
1205
+ if (snapshot.columns) {
1206
+ const [names, gone] = partition(snapshot.columns.columnNames, isColumn);
1207
+ const [widths] = partition(Object.entries(snapshot.columns.columnWidths ?? {}), ([p]) => isColumn(p));
1208
+ dropped.columns = gone;
1209
+ out.columns = columnsAspect(names.length > 0 || gone.length === 0 ? names : [...known.columns], Object.fromEntries(widths));
1210
+ }
1211
+ if (snapshot.filters) [out.filters, dropped.filterFields] = partition(snapshot.filters, isServer);
1212
+ if (snapshot.filterOps) {
1213
+ const [kept, gone] = partition(Object.entries(snapshot.filterOps), ([p]) => isServer(p));
1214
+ out.filterOps = Object.fromEntries(kept);
1215
+ dropped.filters = Object.fromEntries(gone);
1216
+ }
1217
+ if (snapshot.sorters) [out.sorters, dropped.sorters] = partition(snapshot.sorters, (s) => isColumn(s.field));
1218
+ dropped.fields = [...new Set([
1219
+ ...dropped.columns,
1220
+ ...dropped.filterFields,
1221
+ ...Object.keys(dropped.filters),
1222
+ ...dropped.sorters.map((s) => s.field)
1223
+ ])];
1224
+ return {
1225
+ snapshot: out,
1226
+ dropped: dropped.fields.length > 0 ? dropped : null
1227
+ };
1228
+ }
1229
+ /**
1230
+ * Split residual filter conditions, in one pass, into the ones that name
1231
+ * only `known` fields and the ones that do not. A condition naming any
1232
+ * unknown field is dropped WHOLE — never pruned inside an `$or` (that would
1233
+ * narrow the result) or a `$not` (that would invert it). Each condition is an
1234
+ * AND-ed conjunct, so dropping one only broadens the result.
1235
+ *
1236
+ * `dropped` holds the conditions in `residual` and the unknown paths in
1237
+ * `fields`; `null` when every condition is kept.
1238
+ *
1239
+ * @since 0.1.141
1240
+ */
1241
+ function pruneResidualFilters(exprs, known) {
1242
+ const kept = [];
1243
+ const residual = [];
1244
+ const fields = /* @__PURE__ */ new Set();
1245
+ for (const expr of exprs) {
1246
+ const unknown = filterExprFields(expr).filter((p) => !known.has(p));
1247
+ if (unknown.length === 0) {
1248
+ kept.push(expr);
1249
+ continue;
1250
+ }
1251
+ residual.push(expr);
1252
+ for (const p of unknown) fields.add(p);
1253
+ }
1254
+ if (residual.length === 0) return {
1255
+ kept,
1256
+ dropped: null
1257
+ };
1258
+ return {
1259
+ kept,
1260
+ dropped: {
1261
+ fields: [...fields],
1262
+ columns: [],
1263
+ filterFields: [],
1264
+ filters: {},
1265
+ residual,
1266
+ sorters: []
1267
+ }
1268
+ };
1269
+ }
1270
+ /**
1271
+ * Append what a prune dropped back onto a snapshot captured from (pruned)
1272
+ * table state, so overwriting a preset with a narrower view does not destroy
1273
+ * the parts the saver cannot see. Column names, filter inputs and sorters go
1274
+ * after the captured ones; field filters are added back. Only aspects
1275
+ * `captured` carries are touched; widths of hidden columns are not kept.
1276
+ *
1277
+ * @since 0.1.141
1278
+ */
1279
+ function restoreDroppedEntries(captured, dropped) {
1280
+ const out = { ...captured };
1281
+ if (captured.columns) out.columns = columnsAspect([...captured.columns.columnNames, ...dropped.columns], captured.columns.columnWidths);
1282
+ if (captured.filters) out.filters = [...captured.filters, ...dropped.filterFields];
1283
+ if (captured.filterOps) out.filterOps = {
1284
+ ...captured.filterOps,
1285
+ ...dropped.filters
1286
+ };
1287
+ if (captured.sorters) out.sorters = [...captured.sorters, ...dropped.sorters];
1288
+ return out;
1289
+ }
1290
+ //#endregion
1148
1291
  //#region src/presets/preset-dirty.ts
1149
1292
  /**
1150
1293
  * JSON-stringify with object keys sorted alphabetically at every depth so
@@ -1748,6 +1891,20 @@ const CONSUMED_CONTROLS = new Set([
1748
1891
  "$skip",
1749
1892
  URL_SNAPSHOT_KEY
1750
1893
  ]);
1894
+ function toSet(paths) {
1895
+ if (!paths) return null;
1896
+ return paths instanceof Set ? paths : new Set(paths);
1897
+ }
1898
+ /**
1899
+ * `field=value` (or `field=null`) — the one filter shape a host page flag
1900
+ * shares, so a bare one on an unknown field is not reported as dropped.
1901
+ */
1902
+ function isBareEquality(expr) {
1903
+ const keys = Object.keys(expr);
1904
+ if (keys.length !== 1 || keys[0].startsWith("$")) return false;
1905
+ const v = expr[keys[0]];
1906
+ return v === null || typeof v !== "object";
1907
+ }
1751
1908
  /**
1752
1909
  * Characters that can only appear in a uniqu filter key, never in a page flag:
1753
1910
  * comparison operators, `^` (OR), `( )` (groups) and `{ }` (`$in` lists).
@@ -1774,9 +1931,10 @@ function urlQueryConsumesKey(key) {
1774
1931
  /**
1775
1932
  * Parse a URL query string back into the table state subset.
1776
1933
  *
1777
- * Robust by design — schema drift and copy-paste errors must not break the
1778
- * recipient's view:
1779
- * - unknown fields (not in `knownFields`) → silently dropped
1934
+ * Robust by design — schema drift, fields hidden from the recipient and
1935
+ * copy-paste errors must not break the recipient's view:
1936
+ * - unknown fields (not in `knownFields`) → dropped, listed in `unknown` /
1937
+ * `unknownSorters` (the parser does not warn — the caller decides)
1780
1938
  * - filter pieces field filters cannot express (cross-field OR, unknown
1781
1939
  * operator, …) → left out of `filters` and listed in `unsupported`, never
1782
1940
  * approximated (the parser does not warn — the caller decides). Unless
@@ -1809,12 +1967,15 @@ function urlQueryStringToState(urlString, opts = {}) {
1809
1967
  const sortersGate = resolveAspectGate(opts.sync?.sorters);
1810
1968
  const searchOff = opts.sync?.search === false;
1811
1969
  const paginationOff = opts.sync?.pagination === false;
1812
- const knownSet = opts.knownFields ? new Set(opts.knownFields) : null;
1970
+ const knownSet = toSet(opts.knownFields);
1971
+ const localSet = toSet(opts.localFields);
1972
+ const isHidden = (f) => !!knownSet && !knownSet.has(f) && !localSet?.has(f);
1813
1973
  let filterKnown;
1814
1974
  if (filtersGate === "all") filterKnown = knownSet ?? void 0;
1815
1975
  else if (filtersGate !== "none") if (knownSet) {
1816
- filterKnown = /* @__PURE__ */ new Set();
1817
- for (const path of filtersGate) if (knownSet.has(path)) filterKnown.add(path);
1976
+ const owned = /* @__PURE__ */ new Set();
1977
+ for (const path of filtersGate) if (knownSet.has(path)) owned.add(path);
1978
+ filterKnown = owned;
1818
1979
  } else filterKnown = filtersGate;
1819
1980
  const decomposed = filtersGate === "none" ? null : decomposeUniqueryFilter(parsed.filter, {
1820
1981
  knownFields: filterKnown,
@@ -1822,20 +1983,19 @@ function urlQueryStringToState(urlString, opts = {}) {
1822
1983
  });
1823
1984
  const filters = decomposed?.filters ?? {};
1824
1985
  const sorters = [];
1986
+ const unknownSorters = [];
1825
1987
  if (sortersGate !== "none") {
1826
1988
  const $sort = parsed.controls?.$sort;
1827
1989
  if ($sort && typeof $sort === "object") for (const field in $sort) {
1828
- if (knownSet && !knownSet.has(field)) continue;
1829
1990
  if (!gateOwns(sortersGate, field)) continue;
1830
1991
  const dir = $sort[field];
1831
- if (dir === 1) sorters.push({
1832
- field,
1833
- direction: "asc"
1834
- });
1835
- else if (dir === -1) sorters.push({
1992
+ if (dir !== 1 && dir !== -1) continue;
1993
+ const sorter = {
1836
1994
  field,
1837
- direction: "desc"
1838
- });
1995
+ direction: dir === 1 ? "asc" : "desc"
1996
+ };
1997
+ if (!knownSet || knownSet.has(field)) sorters.push(sorter);
1998
+ else if (isHidden(field)) unknownSorters.push(sorter);
1839
1999
  }
1840
2000
  }
1841
2001
  const $search = parsed.controls?.$search;
@@ -1844,8 +2004,24 @@ function urlQueryStringToState(urlString, opts = {}) {
1844
2004
  sorters,
1845
2005
  searchTerm: !searchOff && typeof $search === "string" ? $search : ""
1846
2006
  };
1847
- if (decomposed?.unsupported.length) out.unsupported = decomposed.unsupported;
2007
+ const unsupported$1 = decomposed?.unsupported ?? [];
2008
+ const unknown = [];
2009
+ for (const piece of decomposed?.unknown ?? []) {
2010
+ const hidden = piece.fields.filter(isHidden);
2011
+ if (hidden.length > 0) {
2012
+ if (!isBareEquality(piece.expr)) unknown.push({
2013
+ expr: piece.expr,
2014
+ fields: hidden
2015
+ });
2016
+ continue;
2017
+ }
2018
+ const issue = unsupported("cross-field", piece.expr);
2019
+ if (issue.fields.some((f) => filterKnown?.has(f))) unsupported$1.push(issue);
2020
+ }
2021
+ if (unsupported$1.length) out.unsupported = unsupported$1;
1848
2022
  if (decomposed?.residual.length) out.residual = decomposed.residual;
2023
+ if (unknown.length) out.unknown = unknown;
2024
+ if (unknownSorters.length) out.unknownSorters = unknownSorters;
1849
2025
  if (opts.sync?.snapshot !== false && parsed.controls && "$snapshot" in parsed.controls) out.snapshot = true;
1850
2026
  if (!searchOff) {
1851
2027
  const $relevance = parsed.controls?.$relevance;
@@ -2397,4 +2573,4 @@ function sortRowsLocally(rows, sorters, getValue = (row, field) => row[field]) {
2397
2573
  });
2398
2574
  }
2399
2575
  //#endregion
2400
- export { APP_CONF_PREFIX, AppPrefsClient, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, ExportAbortError, MAX_DEFAULT_COLUMN_WIDTH_PX, NULL_OPS, PRESET_ASPECTS, PresetsClient, PresetsHttpError, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
2576
+ export { APP_CONF_PREFIX, AppPrefsClient, DEFAULT_EXPORT_PAGE_SIZE, DEFAULT_ROW_HEIGHT_PX, DEV, DRAFT_PERSISTED_ASPECTS, ExportAbortError, MAX_DEFAULT_COLUMN_WIDTH_PX, NULL_OPS, PRESET_ASPECTS, PresetsClient, PresetsHttpError, RESERVED_ID_PREFIXES, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, URL_SNAPSHOT_KEY, USER_CONF_PREFIX, appConfId, arraysEqual, blockStartFor, buildTableQuery, cellAsString, clampTopIndex, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, computeDefaultColumnWidth, conditionLabel, conditionsForType, csvCell, dateShortcuts, debounce, decomposeUniqueryFilter, defaultCondition, derivePresetAspects, deserializeDraft, draftMatchesPreset, escapeRegex, filledFilterCount, filterExprFields, filterExprKey, filterTokenLabel, filtersToUniqueryFilter, formatFilterCondition, formatFilterExpr, fromWireSnapshot, gateOwns, hasSecondValue, isAuthError, isColumnFilterable, isDirtyAgainst, isEmptyDraft, isFilled, isSimpleEq, isSystemPresetId, isUnavailableError, mergeDisplayColumns, mergeFilters, mergeSorters, normaliseSystemPresetId, normalizeResidualFilters, pageAlignedBlocksFor, parseColumnFilterInput, parseFilterInput, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, reorderColumnNames, residualGateOwns, resolveAspectGate, resolveExportValue, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, serializeDraft, setsEqual, sortRowsLocally, sortersEqual, stableStringify, stateToUrlQueryString, toCsv, toWireSnapshot, togglePk, trimSelection, unescapeRegex, uniqueryFilterToFieldFilters, urlQueryConsumesKey, urlQueryStringToState, userConfId, walkBackwardAbsorb, walkForwardAbsorb, withStableOrder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/ui-table",
3
- "version": "0.1.140",
3
+ "version": "0.1.141",
4
4
  "description": "Framework-agnostic filter model, filter-to-Uniquery conversion, and preset serialization for atscript tables",
5
5
  "keywords": [
6
6
  "atscript",
@@ -42,7 +42,7 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "@atscript/ui": "0.1.140"
45
+ "@atscript/ui": "0.1.141"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@atscript/db-client": "^0.1.133",