@atscript/vue-table 0.1.139 → 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.
Files changed (151) hide show
  1. package/dist/as-action-form-dialog.cjs +1 -1
  2. package/dist/as-action-form-dialog.mjs +1 -1
  3. package/dist/{as-action-menu-content-D7NIwc9X.mjs → as-action-menu-content-6dR5q8bs.mjs} +1 -1
  4. package/dist/{as-action-menu-content-DXuyDZ_X.cjs → as-action-menu-content-AKbIp4Ni.cjs} +1 -1
  5. package/dist/as-cell-array.d.mts +1 -1
  6. package/dist/as-cell-date.d.mts +1 -1
  7. package/dist/as-cell-json.d.mts +1 -1
  8. package/dist/as-cell-number.d.mts +1 -1
  9. package/dist/as-cell-union.d.mts +1 -1
  10. package/dist/as-column-menu.d.cts +1 -1
  11. package/dist/as-column-menu.d.mts +1 -1
  12. package/dist/{as-column-menu.vue-Cul3eIz_.d.cts → as-column-menu.vue-DapdB_qF.d.cts} +1 -1
  13. package/dist/{as-column-menu.vue-Dw2m8ZqD.d.mts → as-column-menu.vue-Ys_sx0VE.d.mts} +1 -1
  14. package/dist/{as-config-dialog-j_-oUmN7.cjs → as-config-dialog-Chpw_2tu.cjs} +2 -2
  15. package/dist/{as-config-dialog-DrcYlrOh.mjs → as-config-dialog-UeMU7jTP.mjs} +2 -2
  16. package/dist/as-config-dialog.cjs +1 -1
  17. package/dist/as-config-dialog.d.mts +1 -1
  18. package/dist/as-config-dialog.mjs +1 -1
  19. package/dist/{as-confirm-dialog-Pnt60ALy.mjs → as-confirm-dialog-SIqCOw2p.mjs} +1 -1
  20. package/dist/{as-confirm-dialog-CStjIWN0.cjs → as-confirm-dialog-hHYVEX9E.cjs} +1 -1
  21. package/dist/as-confirm-dialog.cjs +1 -1
  22. package/dist/as-confirm-dialog.d.mts +1 -1
  23. package/dist/as-confirm-dialog.mjs +1 -1
  24. package/dist/{as-filter-dialog-CUzahpd_.mjs → as-filter-dialog-DVRhCx6O.mjs} +8 -8
  25. package/dist/{as-filter-dialog-B6_NvvlM.cjs → as-filter-dialog-Xvj5q09A.cjs} +8 -8
  26. package/dist/as-filter-dialog.cjs +1 -1
  27. package/dist/as-filter-dialog.d.mts +1 -1
  28. package/dist/as-filter-dialog.mjs +1 -1
  29. package/dist/{as-filter-field-DCUMuu0M.cjs → as-filter-field-B7b2sEG0.cjs} +3 -3
  30. package/dist/{as-filter-field-3DJh_ICs.mjs → as-filter-field-D4vQCOEU.mjs} +3 -3
  31. package/dist/as-filter-field.cjs +1 -1
  32. package/dist/as-filter-field.d.mts +1 -1
  33. package/dist/as-filter-field.mjs +1 -1
  34. package/dist/as-filter-input.d.mts +1 -1
  35. package/dist/{as-filters-CHLPYHNP.cjs → as-filters-CWQgQBCH.cjs} +19 -4
  36. package/dist/{as-filters-D0DWDyiv.mjs → as-filters-CudeInPr.mjs} +19 -4
  37. package/dist/as-filters.cjs +1 -1
  38. package/dist/as-filters.d.cts +1 -1
  39. package/dist/as-filters.d.mts +1 -1
  40. package/dist/as-filters.mjs +1 -1
  41. package/dist/{as-filters.vue-BG_ysCkC.d.mts → as-filters.vue-BSgUEh4D.d.mts} +23 -1
  42. package/dist/{as-filters.vue-DcxDnWAU.d.cts → as-filters.vue-DMXZhQW3.d.cts} +23 -1
  43. package/dist/{as-preset-dialog-H98DeOMM.mjs → as-preset-dialog-BsWSuY9N.mjs} +2 -2
  44. package/dist/{as-preset-dialog-RLW0gZDj.cjs → as-preset-dialog-l81WXBmf.cjs} +2 -2
  45. package/dist/as-preset-dialog.cjs +1 -1
  46. package/dist/as-preset-dialog.d.mts +1 -1
  47. package/dist/as-preset-dialog.mjs +1 -1
  48. package/dist/{as-preset-picker-Cw7LB7o1.mjs → as-preset-picker-BTkMqSxH.mjs} +31 -20
  49. package/dist/{as-preset-picker-yWXEhWEq.cjs → as-preset-picker-Bp4xMTua.cjs} +31 -20
  50. package/dist/as-preset-picker.cjs +1 -1
  51. package/dist/as-preset-picker.d.mts +1 -1
  52. package/dist/as-preset-picker.mjs +1 -1
  53. package/dist/as-residual-filter-BJ4cwFP4.cjs +54 -0
  54. package/dist/as-residual-filter-D1SGFQ1p.mjs +49 -0
  55. package/dist/as-residual-filter.cjs +2 -0
  56. package/dist/as-residual-filter.d.cts +2 -0
  57. package/dist/as-residual-filter.d.mts +2 -0
  58. package/dist/as-residual-filter.mjs +2 -0
  59. package/dist/as-residual-filter.vue-Dfw1l2wS.d.cts +21 -0
  60. package/dist/as-residual-filter.vue-N89YqlCz.d.mts +21 -0
  61. package/dist/{as-row-actions-D_TqnP3S.cjs → as-row-actions-9p_BJPga.cjs} +2 -2
  62. package/dist/{as-row-actions-BvJWHf3j.mjs → as-row-actions-CH8WZ5kN.mjs} +2 -2
  63. package/dist/as-row-actions.cjs +1 -1
  64. package/dist/as-row-actions.d.cts +1 -1
  65. package/dist/as-row-actions.d.mts +1 -1
  66. package/dist/as-row-actions.mjs +1 -1
  67. package/dist/{as-row-actions.vue-DxXodLGq.d.mts → as-row-actions.vue-BOURuyjv.d.mts} +1 -1
  68. package/dist/{as-row-actions.vue-WRrzvdbJ.d.cts → as-row-actions.vue-BezMFOr6.d.cts} +1 -1
  69. package/dist/{as-table-D5mMZKKg.mjs → as-table-DmmXWq94.mjs} +3 -3
  70. package/dist/{as-table-actions-C6moFlxt.mjs → as-table-actions-CFBAxKIS.mjs} +2 -2
  71. package/dist/{as-table-actions-D91sNgel.cjs → as-table-actions-oxVqSH-G.cjs} +2 -2
  72. package/dist/as-table-actions.cjs +1 -1
  73. package/dist/as-table-actions.d.cts +1 -1
  74. package/dist/as-table-actions.d.mts +1 -1
  75. package/dist/as-table-actions.mjs +1 -1
  76. package/dist/{as-table-actions.vue-CPVhj2ZC.d.mts → as-table-actions.vue-CrZtcxAY.d.mts} +2 -2
  77. package/dist/{as-table-actions.vue-DB3LW-eI.d.cts → as-table-actions.vue-Dls_1Vze.d.cts} +2 -2
  78. package/dist/{as-table-base-CyBOmUK9.cjs → as-table-base--MfU8LVN.cjs} +2 -2
  79. package/dist/{as-table-base-CEpvNFBd.mjs → as-table-base-Cl1XJu4H.mjs} +2 -2
  80. package/dist/as-table-cell-value.cjs +1 -1
  81. package/dist/as-table-cell-value.d.cts +1 -1
  82. package/dist/as-table-cell-value.d.mts +1 -1
  83. package/dist/as-table-cell-value.mjs +1 -1
  84. package/dist/{as-table-CVD1zLva.cjs → as-table-fOBpTDrI.cjs} +3 -3
  85. package/dist/{as-table-header-cell-D7pW2sV3.cjs → as-table-header-cell-10DQEs00.cjs} +1 -1
  86. package/dist/{as-table-header-cell-CUs-sSBm.mjs → as-table-header-cell-M8g9X9dR.mjs} +1 -1
  87. package/dist/as-table-header-cell.cjs +1 -1
  88. package/dist/as-table-header-cell.d.cts +1 -1
  89. package/dist/as-table-header-cell.d.mts +1 -1
  90. package/dist/as-table-header-cell.mjs +1 -1
  91. package/dist/{as-table-header-cell.vue-DFaRLZZA.d.cts → as-table-header-cell.vue-D-iI_EJY.d.cts} +1 -1
  92. package/dist/{as-table-header-cell.vue-BDhs3PNi.d.mts → as-table-header-cell.vue-SMBpzqU2.d.mts} +1 -1
  93. package/dist/{as-table-root-C61wrFd9.mjs → as-table-root-Bl423S7X.mjs} +11 -5
  94. package/dist/{as-table-root-C7Tr_9W-.cjs → as-table-root-CGXKfCIk.cjs} +11 -5
  95. package/dist/as-table-root.cjs +1 -1
  96. package/dist/as-table-root.d.cts +1 -1
  97. package/dist/as-table-root.d.mts +1 -1
  98. package/dist/as-table-root.mjs +1 -1
  99. package/dist/{as-table-root.vue-Bza3D5F1.d.mts → as-table-root.vue-BROdsTqv.d.mts} +41 -8
  100. package/dist/{as-table-root.vue-D5Xyux8I.d.cts → as-table-root.vue-D5H8uXFK.d.cts} +41 -8
  101. package/dist/{as-table-status-D08b0cqn.mjs → as-table-status-Bl9XmWqc.mjs} +5 -5
  102. package/dist/{as-table-status-B-MjDXNZ.cjs → as-table-status-bbuFDaaK.cjs} +5 -5
  103. package/dist/as-table.cjs +1 -1
  104. package/dist/as-table.d.cts +1 -1
  105. package/dist/as-table.d.mts +1 -1
  106. package/dist/as-table.mjs +1 -1
  107. package/dist/{as-table.vue-BuZGgKuM.d.cts → as-table.vue-B533pYs6.d.cts} +5 -5
  108. package/dist/{as-table.vue-CPfERNtx.d.mts → as-table.vue-B_7Y82yW.d.mts} +5 -5
  109. package/dist/{as-window-table-CjYMF0aX.mjs → as-window-table-CkTJOvu9.mjs} +3 -3
  110. package/dist/{as-window-table-BfSud7yB.cjs → as-window-table-Dpbezh9D.cjs} +3 -3
  111. package/dist/as-window-table.cjs +1 -1
  112. package/dist/as-window-table.d.cts +1 -1
  113. package/dist/as-window-table.d.mts +1 -1
  114. package/dist/as-window-table.mjs +1 -1
  115. package/dist/{as-window-table.vue-BIjQuSie.d.cts → as-window-table.vue-Bar8mWRL.d.cts} +5 -5
  116. package/dist/{as-window-table.vue-3egLvwAy.d.mts → as-window-table.vue-OL4KC7Y-.d.mts} +5 -5
  117. package/dist/index.cjs +23 -48
  118. package/dist/index.d.cts +11 -11
  119. package/dist/index.d.mts +23 -23
  120. package/dist/index.mjs +23 -48
  121. package/dist/{types-B6N5NDx_.d.cts → types-Cdk5OH1-.d.cts} +61 -5
  122. package/dist/{types-CgdpcJdd.d.mts → types-Ev-A5h_D.d.mts} +61 -5
  123. package/dist/{use-table-DUwUfjSZ.mjs → use-table-BG3IZyCO.mjs} +3 -2
  124. package/dist/{use-table-column-handlers-Do0Z-R9D.cjs → use-table-column-handlers-CWHRcQbE.cjs} +1 -1
  125. package/dist/{use-table-column-handlers-eKHCfUm_.mjs → use-table-column-handlers-CaT7dYnI.mjs} +1 -1
  126. package/dist/{use-table-component-wfU13P3L.mjs → use-table-component-OTdgA202.mjs} +1 -1
  127. package/dist/{use-table-component-DbQDINoa.cjs → use-table-component-Tc_ZvZua.cjs} +1 -1
  128. package/dist/{use-table-nav-bridge-DRrvQxXq.mjs → use-table-nav-bridge-BNjwR3qy.mjs} +1 -1
  129. package/dist/{use-table-nav-bridge-BLLVGR1r.cjs → use-table-nav-bridge-ByQo00IH.cjs} +1 -1
  130. package/dist/{use-table-c4ku22Mx.cjs → use-table-rc6_sYU6.cjs} +3 -2
  131. package/dist/{use-table-state-BEwXQayO.mjs → use-table-state-BgLIOO31.mjs} +208 -25
  132. package/dist/{use-table-state-Cnxv0w_E.cjs → use-table-state-CgXY-ruN.cjs} +212 -23
  133. package/package.json +14 -7
  134. /package/dist/{as-cell-array.vue-D4vS7C_D.d.mts → as-cell-array.vue-ow34clSF.d.mts} +0 -0
  135. /package/dist/{as-cell-date.vue-Chs7WTZ9.d.mts → as-cell-date.vue-B4JCr1qP.d.mts} +0 -0
  136. /package/dist/{as-cell-json.vue-C0_LDvQw.d.mts → as-cell-json.vue-B-T3TuFj.d.mts} +0 -0
  137. /package/dist/{as-cell-number.vue-BBF0OuU8.d.mts → as-cell-number.vue-CNH2fbCB.d.mts} +0 -0
  138. /package/dist/{as-cell-union.vue-CL-Lf-Rm.d.mts → as-cell-union.vue-CZwN81H4.d.mts} +0 -0
  139. /package/dist/{as-config-dialog.vue-BvFZZRuY.d.mts → as-config-dialog.vue-BKo2GpzN.d.mts} +0 -0
  140. /package/dist/{as-confirm-dialog.vue-Cz52-v6c.d.mts → as-confirm-dialog.vue-BN_yzu3h.d.mts} +0 -0
  141. /package/dist/{as-filter-dialog.vue-likNLqzF.d.mts → as-filter-dialog.vue-Bl8OIKOf.d.mts} +0 -0
  142. /package/dist/{as-filter-field.vue-BbXLhd6O.d.mts → as-filter-field.vue-DD0IZXS1.d.mts} +0 -0
  143. /package/dist/{as-filter-input.vue-B9ma2l9j.d.mts → as-filter-input.vue-CFZ33B--.d.mts} +0 -0
  144. /package/dist/{as-preset-dialog.vue-BRjjfPty.d.mts → as-preset-dialog.vue-ChM7H7B4.d.mts} +0 -0
  145. /package/dist/{as-preset-picker.vue-BPjsiaVt.d.mts → as-preset-picker.vue-OaAV2v9z.d.mts} +0 -0
  146. /package/dist/{as-table-cell-value-Ct0KnyKc.cjs → as-table-cell-value-COyZnwUj.cjs} +0 -0
  147. /package/dist/{as-table-cell-value-D1eLiQKJ.mjs → as-table-cell-value-DoH8Mieo.mjs} +0 -0
  148. /package/dist/{as-table-cell-value.vue-BgFDv2JQ.d.cts → as-table-cell-value.vue-C0VR9GRK.d.cts} +0 -0
  149. /package/dist/{as-table-cell-value.vue-kfNgVhzn.d.mts → as-table-cell-value.vue-DphckGq-.d.mts} +0 -0
  150. /package/dist/{error-message-CglODWl4.cjs → error-message-CfKpn_5w.cjs} +0 -0
  151. /package/dist/{error-message-hPSAu8Ug.mjs → error-message-gNEXnnbb.mjs} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { ColumnDef, PaginationControl, SortControl, TableDef } from "@atscript/ui";
2
2
  import { Component, ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
3
- import { AsPresetEntryRow, AspectMask, ColumnWidthsMap, ConfigTab, FieldFilters, PresetAspect, PresetCapabilities, PresetSnapshot, SelectionMode, SystemPreset, SystemPresetInput, TableStateMethods } from "@atscript/ui-table";
3
+ import { AsPresetEntryRow, AspectMask, ColumnWidthsMap, ConfigTab, DroppedFields, FieldFilters, PresetAspect, PresetCapabilities, PresetSnapshot, SelectionMode, SystemPreset, SystemPresetInput, TableStateMethods } from "@atscript/ui-table";
4
4
  import { ClientError, PageResult, TDbActionInfo, TDbActionProcessor, TDbDeleteResult } from "@atscript/db-client";
5
- import { Uniquery } from "@uniqu/core";
5
+ import { FilterExpr, Uniquery } from "@uniqu/core";
6
6
 
7
7
  //#region src/composables/state/intent-scope.d.ts
8
8
  interface ActionBuckets {
@@ -369,6 +369,11 @@ interface TAsTableControls {
369
369
  filterDialog?: Component;
370
370
  filterField?: Component;
371
371
  filterValueHelp?: Component;
372
+ /**
373
+ * One residual filter condition's chip in `<AsFilters>` — receives
374
+ * `expr` and `index`. Default `AsResidualFilter`. Since 0.1.140.
375
+ */
376
+ residualFilter?: Component;
372
377
  configDialog?: Component;
373
378
  fieldsSelector?: Component;
374
379
  sortersConfig?: Component;
@@ -408,6 +413,28 @@ type TAsCellTypeComponents = {
408
413
  ref: Component; /** Synthesised row-actions pseudo-column (`:rowActionsColumn` opt-in). */
409
414
  __actions?: Component;
410
415
  } & Record<string, Component>;
416
+ /**
417
+ * What the table left out because it names a field outside the columns this
418
+ * caller can use — a field hidden from their role, or gone from the schema.
419
+ * Sent to `onFieldsDropped` / `@fields-dropped` once per apply, only when
420
+ * something was dropped. `fields` lists the unavailable paths; the other
421
+ * lists hold the dropped entries themselves.
422
+ *
423
+ * - `"preset"` — a preset applied (picker switch, reset, raw snapshot, and
424
+ * the bootstrap default with the local draft laid over it).
425
+ * - `"url"` — a restored URL (deep link, shared link, Back/Forward). Its
426
+ * dropped filter pieces are listed in `residual`, as whole expressions.
427
+ *
428
+ * State written straight to the model is not reported: the query leaves such
429
+ * fields out (a dev-mode warning names them once).
430
+ *
431
+ * @since 0.1.141
432
+ */
433
+ interface DroppedFieldsReport extends DroppedFields {
434
+ source: "preset" | "url";
435
+ /** The preset whose apply dropped the entries (`"preset"`). */
436
+ presetId?: string;
437
+ }
411
438
  /**
412
439
  * How `applyUrlQuery` treats filters and sorters the URL does not mention.
413
440
  * @since 0.1.137
@@ -442,6 +469,15 @@ interface ReactiveTableState extends TableStateMethods {
442
469
  columnWidths: Ref<ColumnWidthsMap>;
443
470
  filterFields: ShallowRef<string[]>;
444
471
  filters: ShallowRef<FieldFilters>;
472
+ /**
473
+ * Residual filter conditions — AND-ed Uniquery conjuncts the field-filter
474
+ * model cannot hold (a cross-field `$or`, a second range on one field, …).
475
+ * AND'd after `filters` into every query, carried through the URL, shown
476
+ * as "custom filter" chips by `<AsFilters>`. Replace wholesale (or use
477
+ * `setResidualFilters` / `removeResidualFilter`); `resetFilters()`
478
+ * clears them. Not saved in presets. Since 0.1.140.
479
+ */
480
+ residualFilters: ShallowRef<FilterExpr[]>;
445
481
  sorters: ShallowRef<SortControl[]>;
446
482
  results: ShallowRef<Record<string, unknown>[]>;
447
483
  /** Absolute index where `results[0]` sits. */
@@ -686,6 +722,11 @@ interface ReactiveTableState extends TableStateMethods {
686
722
  * the baseline.
687
723
  *
688
724
  * `opts.mode` overrides the marker either way. Since 0.1.139.
725
+ *
726
+ * Filter pieces the field-filter model cannot hold are restored as
727
+ * `residualFilters` (since 0.1.140). A URL owns every path it mentions —
728
+ * in a field filter or inside such a condition — so on merge the
729
+ * baseline's filters and residual conditions on those paths give way.
689
730
  */
690
731
  applyUrlQuery: (urlString: string, opts?: ApplyUrlQueryOptions) => void;
691
732
  /**
@@ -784,8 +825,19 @@ interface PresetSurface {
784
825
  available: ComputedRef<boolean>;
785
826
  /** Currently active preset id (system `'sys:*'` or stored). null = no active. */
786
827
  activeId: Ref<string | null>;
787
- /** Active preset's snapshot (system presets are aspect-expanded). */
828
+ /**
829
+ * Active preset's snapshot (system presets are aspect-expanded), pruned of
830
+ * fields this caller cannot use — what applying it wrote. The stored row
831
+ * is never rewritten.
832
+ */
788
833
  activeSnapshot: ComputedRef<PresetSnapshot>;
834
+ /**
835
+ * What the active preset names that this caller cannot use — left out
836
+ * when it was applied — or `null`. Derived from the stored preset, so it
837
+ * follows the active id. `<AsPresetPicker>` shows a quiet note while set.
838
+ * Since 0.1.141.
839
+ */
840
+ droppedFields: ComputedRef<DroppedFieldsReport | null>;
789
841
  /** True when the current snapshot ≠ active preset's claimed aspects. */
790
842
  isDirty: ComputedRef<boolean>;
791
843
  /** True iff the active preset is owned by the current user (gates Save). */
@@ -811,7 +863,11 @@ interface PresetSurface {
811
863
  * if it still references a known preset, else `STANDARD_PRESET_ID`.
812
864
  */
813
865
  resolveDefaultId: () => string;
814
- /** Re-capture the active preset's existing aspect mask. Never widens. */
866
+ /**
867
+ * Re-capture the active preset's existing aspect mask. Never widens.
868
+ * Entries on fields this caller cannot use (`droppedFields`) are appended
869
+ * back after the visible ones, so they survive the overwrite.
870
+ */
815
871
  saveActive: () => Promise<void>;
816
872
  /** Create a new preset and switch the active id to it. */
817
873
  saveAs: (label: string, opts?: {
@@ -849,4 +905,4 @@ interface PresetSurface {
849
905
  lastError: Ref<Error | null>;
850
906
  }
851
907
  //#endregion
852
- export { TAsTableControls as C, extractIdentifier as D, TableNavBridge as E, TAsCellTypeComponents as S, TableActionsState as T, RowClassHook as _, ConfirmOptions as a, RowSelectableHook as b, LocalRowAction as c, PresetConfig as d, QueryErrorKind as f, RowAttrsHook as g, RowActionsConfig as h, ConfigTab as i, MainActionRequest as l, RowActionsColumnPlacement as m, ApplyUrlQueryOptions as n, EnterAction as o, ReactiveTableState as p, ColumnMenuConfig as r, InvokeOpts as s, ActionResult as t, NavKeyOptions as u, RowDeleteOpt as v, TVueTableActionInfo as w, RowSelectableVerdict as x, RowHookContext as y };
908
+ export { TAsCellTypeComponents as C, TableNavBridge as D, TableActionsState as E, extractIdentifier as O, RowSelectableVerdict as S, TVueTableActionInfo as T, RowAttrsHook as _, ConfirmOptions as a, RowHookContext as b, InvokeOpts as c, NavKeyOptions as d, PresetConfig as f, RowActionsConfig as g, RowActionsColumnPlacement as h, ConfigTab as i, LocalRowAction as l, ReactiveTableState as m, ApplyUrlQueryOptions as n, DroppedFieldsReport as o, QueryErrorKind as p, ColumnMenuConfig as r, EnterAction as s, ActionResult as t, MainActionRequest as u, RowClassHook as v, TAsTableControls as w, RowSelectableHook as x, RowDeleteOpt as y };
@@ -1,4 +1,4 @@
1
- import { c as DEFAULT_AVAILABLE_ASPECTS, n as createTableState, r as provideTableContext } from "./use-table-state-BEwXQayO.mjs";
1
+ import { l as DEFAULT_AVAILABLE_ASPECTS, n as createTableState, r as provideTableContext } from "./use-table-state-BgLIOO31.mjs";
2
2
  import { n as useTableSelection } from "./use-table-selection-ULbKT2xW.mjs";
3
3
  import { createTableDef, getMetaEntry, resetMetaCache } from "@atscript/ui";
4
4
  import { computed, inject, isRef, ref, shallowRef, toValue, watch } from "vue";
@@ -435,7 +435,8 @@ function useTable(url, opts) {
435
435
  urlQueryReady: opts?.urlQueryReady,
436
436
  onUrlQueryChange: opts?.onUrlQueryChange,
437
437
  urlQuerySync: opts?.urlQuerySync,
438
- onUnsupportedFilter: opts?.onUnsupportedFilter
438
+ onUnsupportedFilter: opts?.onUnsupportedFilter,
439
+ onFieldsDropped: opts?.onFieldsDropped
439
440
  },
440
441
  window: {
441
442
  blockSize: opts?.blockSize,
@@ -1,4 +1,4 @@
1
- const require_use_table_state = require("./use-table-state-Cnxv0w_E.cjs");
1
+ const require_use_table_state = require("./use-table-state-CgXY-ruN.cjs");
2
2
  let vue = require("vue");
3
3
  let _atscript_ui_table = require("@atscript/ui-table");
4
4
  //#region src/composables/use-row-actions-column.ts
@@ -1,4 +1,4 @@
1
- import { D as ROW_ACTIONS_PATH, O as ROW_ACTIONS_TYPE } from "./use-table-state-BEwXQayO.mjs";
1
+ import { O as ROW_ACTIONS_PATH, k as ROW_ACTIONS_TYPE } from "./use-table-state-BgLIOO31.mjs";
2
2
  import { computed, watch } from "vue";
3
3
  import { reorderColumnNames } from "@atscript/ui-table";
4
4
  //#region src/composables/use-row-actions-column.ts
@@ -1,4 +1,4 @@
1
- import { o as useTableContextOptional } from "./use-table-state-BEwXQayO.mjs";
1
+ import { o as useTableContextOptional } from "./use-table-state-BgLIOO31.mjs";
2
2
  //#region src/composables/use-table-component.ts
3
3
  /**
4
4
  * Resolve a single skin-slot component from the injected `controls` map,
@@ -1,4 +1,4 @@
1
- const require_use_table_state = require("./use-table-state-Cnxv0w_E.cjs");
1
+ const require_use_table_state = require("./use-table-state-CgXY-ruN.cjs");
2
2
  //#region src/composables/use-table-component.ts
3
3
  /**
4
4
  * Resolve a single skin-slot component from the injected `controls` map,
@@ -1,4 +1,4 @@
1
- import { o as useTableContextOptional } from "./use-table-state-BEwXQayO.mjs";
1
+ import { o as useTableContextOptional } from "./use-table-state-BgLIOO31.mjs";
2
2
  //#region src/composables/use-table-nav-bridge.ts
3
3
  function isPassthroughKey(event) {
4
4
  const key = event.key;
@@ -1,4 +1,4 @@
1
- const require_use_table_state = require("./use-table-state-Cnxv0w_E.cjs");
1
+ const require_use_table_state = require("./use-table-state-CgXY-ruN.cjs");
2
2
  //#region src/composables/use-table-nav-bridge.ts
3
3
  function isPassthroughKey(event) {
4
4
  const key = event.key;
@@ -1,4 +1,4 @@
1
- const require_use_table_state = require("./use-table-state-Cnxv0w_E.cjs");
1
+ const require_use_table_state = require("./use-table-state-CgXY-ruN.cjs");
2
2
  const require_use_table_selection = require("./use-table-selection-DtCV3rhu.cjs");
3
3
  let _atscript_ui = require("@atscript/ui");
4
4
  let vue = require("vue");
@@ -435,7 +435,8 @@ function useTable(url, opts) {
435
435
  urlQueryReady: opts?.urlQueryReady,
436
436
  onUrlQueryChange: opts?.onUrlQueryChange,
437
437
  urlQuerySync: opts?.urlQuerySync,
438
- onUnsupportedFilter: opts?.onUnsupportedFilter
438
+ onUnsupportedFilter: opts?.onUnsupportedFilter,
439
+ onFieldsDropped: opts?.onFieldsDropped
439
440
  },
440
441
  window: {
441
442
  blockSize: opts?.blockSize,
@@ -1,7 +1,7 @@
1
1
  import { t as getCellValue } from "./get-cell-value-Cb3X8nUn.mjs";
2
2
  import { navigateHrefFor } from "@atscript/ui";
3
3
  import { computed, inject, nextTick, onBeforeUnmount, onScopeDispose, provide, ref, shallowRef, toValue, watch } from "vue";
4
- import { DEV, STANDARD_PRESET_ID, blockStartFor, buildTableQuery, cellAsString, debounce, derivePresetAspects, fromWireSnapshot, gateOwns, isDirtyAgainst, isFilled, isSystemPresetId, mergeDisplayColumns, mergeSorters, pageAlignedBlocksFor, planFetch, reconcileColumnWidthDefaults, resolveAspectGate, resolveSystemPresets, rowsToPks, sameColumnSet, sortRowsLocally, sortersEqual, stateToUrlQueryString, togglePk, urlQueryStringToState, walkBackwardAbsorb, walkForwardAbsorb } from "@atscript/ui-table";
4
+ import { DEV, STANDARD_PRESET_ID, arraysEqual, blockStartFor, buildTableQuery, cellAsString, debounce, derivePresetAspects, filterExprFields, filterExprKey, fromWireSnapshot, gateOwns, isDirtyAgainst, isFilled, isSystemPresetId, mergeDisplayColumns, mergeSorters, normalizeResidualFilters, pageAlignedBlocksFor, planFetch, prunePresetSnapshot, pruneResidualFilters, reconcileColumnWidthDefaults, residualGateOwns, resolveAspectGate, resolveSystemPresets, restoreDroppedEntries, rowsToPks, sameColumnSet, sortRowsLocally, sortersEqual, stateToUrlQueryString, togglePk, urlQueryStringToState, walkBackwardAbsorb, walkForwardAbsorb } from "@atscript/ui-table";
5
5
  import { formatIdentifier } from "@atscript/db-client";
6
6
  //#region src/types.ts
7
7
  /** UI-side sentinel for the synthesised row-delete processor. */
@@ -1023,6 +1023,7 @@ function createPresetState(opts) {
1023
1023
  apply(o, value, isSystem) {
1024
1024
  if (!value && !isSystem) return;
1025
1025
  o.filters.value = value ? { ...value } : {};
1026
+ if (o.residualFilters?.value.length) o.residualFilters.value = [];
1026
1027
  },
1027
1028
  expandDefault: () => ({})
1028
1029
  },
@@ -1100,8 +1101,25 @@ function createPresetState(opts) {
1100
1101
  nextActiveId: id
1101
1102
  };
1102
1103
  }
1104
+ /**
1105
+ * The one write-side prune: drop what names a field this table cannot use,
1106
+ * and report it. A no-op before the table definition loads — bootstrap
1107
+ * waits for it, and the query gate covers any other early writer.
1108
+ */
1109
+ function pruneForApply(snapshot, presetId) {
1110
+ const known = opts.knownFields?.value;
1111
+ if (!known) return snapshot;
1112
+ const { snapshot: pruned, dropped } = prunePresetSnapshot(snapshot, known);
1113
+ if (dropped) opts.onFieldsDropped?.({
1114
+ ...dropped,
1115
+ source: "preset",
1116
+ presetId: presetId ?? void 0
1117
+ });
1118
+ return pruned;
1119
+ }
1103
1120
  function apply(idOrSnapshot) {
1104
- const { snapshot, nextActiveId } = resolveSnapshot(idOrSnapshot);
1121
+ const { snapshot: resolved, nextActiveId } = resolveSnapshot(idOrSnapshot);
1122
+ const snapshot = pruneForApply(resolved, nextActiveId);
1105
1123
  const isSystem = typeof idOrSnapshot === "string" && nextActiveId !== null && isSystemPresetId(nextActiveId);
1106
1124
  for (const aspect of ownedAspects(nextActiveId)) {
1107
1125
  const value = snapshot[aspect];
@@ -1142,7 +1160,8 @@ function createPresetState(opts) {
1142
1160
  }
1143
1161
  return out;
1144
1162
  }
1145
- const activeSnapshot = computed(() => {
1163
+ /** The active preset's snapshot as stored — the base `saveActive` merges back into. */
1164
+ const storedActiveSnapshot = computed(() => {
1146
1165
  const id = activeId.value;
1147
1166
  if (!id) return {};
1148
1167
  if (isSystemPresetId(id)) return expandSystemSnapshot(systemPresetsById.value.get(id)?.content ?? {}, id);
@@ -1151,7 +1170,25 @@ function createPresetState(opts) {
1151
1170
  const wire = row.data?.content;
1152
1171
  return wire ? fromWireSnapshot(wire) : {};
1153
1172
  });
1154
- const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()));
1173
+ const prunedActive = computed(() => {
1174
+ const known = opts.knownFields?.value;
1175
+ const stored = storedActiveSnapshot.value;
1176
+ return known ? prunePresetSnapshot(stored, known) : {
1177
+ snapshot: stored,
1178
+ dropped: null
1179
+ };
1180
+ });
1181
+ const activeSnapshot = computed(() => prunedActive.value.snapshot);
1182
+ const droppedFields = computed(() => {
1183
+ const dropped = prunedActive.value.dropped;
1184
+ const presetId = activeId.value;
1185
+ return dropped && presetId ? {
1186
+ ...dropped,
1187
+ source: "preset",
1188
+ presetId
1189
+ } : null;
1190
+ });
1191
+ const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()) || activeSnapshot.value.filterOps !== void 0 && !!opts.residualFilters?.value.length);
1155
1192
  function requirePresets() {
1156
1193
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
1157
1194
  return opts.presetsHandle;
@@ -1162,10 +1199,12 @@ function createPresetState(opts) {
1162
1199
  const id = activeId.value;
1163
1200
  if (!id) throw new Error("[vue-table] saveActive: no active preset");
1164
1201
  if (isSystemPresetId(id)) throw new Error("[vue-table] saveActive: system presets cannot be overwritten");
1165
- const existing = activeSnapshot.value;
1202
+ const existing = storedActiveSnapshot.value;
1166
1203
  const mask = {};
1167
1204
  for (const aspect of availableAspects) if (existing[aspect] !== void 0) mask[aspect] = true;
1168
- await handle.savePreset(captureSnapshot(mask));
1205
+ const captured = captureSnapshot(mask);
1206
+ const dropped = prunedActive.value.dropped;
1207
+ await handle.savePreset(dropped ? restoreDroppedEntries(captured, dropped) : captured);
1169
1208
  clearLocalDraft();
1170
1209
  });
1171
1210
  }
@@ -1224,20 +1263,25 @@ function createPresetState(opts) {
1224
1263
  function bootstrap() {
1225
1264
  if (!opts.presetsHandle) return;
1226
1265
  const handle = opts.presetsHandle;
1227
- const stop = watch(() => handle.loading.value, (loading) => {
1228
- if (loading) return;
1229
- stop();
1266
+ const knownReady = () => !opts.knownFields || opts.knownFields.value !== null;
1267
+ let done = false;
1268
+ let stop;
1269
+ stop = watch(() => !handle.loading.value && knownReady(), (ready) => {
1270
+ if (!ready || done) return;
1271
+ done = true;
1272
+ stop?.();
1230
1273
  const id = resolveDefaultId();
1231
1274
  const { snapshot, nextActiveId } = resolveSnapshot(id);
1232
1275
  const base = isSystemPresetId(id) ? expandSystemSnapshot(snapshot, id) : snapshot;
1233
1276
  const draftEnabled = !!(opts.draftHandle && opts.persistDrafts);
1234
- apply(draftEnabled ? opts.draftHandle.hydrate(base) : base);
1277
+ apply(pruneForApply(draftEnabled ? opts.draftHandle.hydrate(base) : base, nextActiveId));
1235
1278
  activeId.value = nextActiveId;
1236
1279
  if (draftEnabled) opts.draftHandle.watchAndPersist(() => captureSnapshot(), () => activeSnapshot.value);
1237
1280
  nextTick(() => {
1238
1281
  gate.value = true;
1239
1282
  });
1240
1283
  }, { immediate: true });
1284
+ if (done) stop();
1241
1285
  }
1242
1286
  return {
1243
1287
  slice: {
@@ -1253,6 +1297,7 @@ function createPresetState(opts) {
1253
1297
  available,
1254
1298
  activeId,
1255
1299
  activeSnapshot,
1300
+ droppedFields,
1256
1301
  isDirty,
1257
1302
  canSaveActive,
1258
1303
  currentUser,
@@ -1594,6 +1639,10 @@ const cloneSorter = (s) => ({
1594
1639
  field: s.field,
1595
1640
  direction: s.direction
1596
1641
  });
1642
+ /** Same residual conditions, in the same (canonical) order. */
1643
+ function sameResidualFilters(a, b) {
1644
+ return arraysEqual(a.map(filterExprKey), b.map(filterExprKey));
1645
+ }
1597
1646
  /**
1598
1647
  * The report for a restored URL's filter piece when nobody handles it
1599
1648
  * (`onUnsupportedFilter` / `@unsupported-filter`): a dev-mode warning.
@@ -1602,6 +1651,15 @@ function warnUnsupportedFilter(issue) {
1602
1651
  if (!DEV) return;
1603
1652
  console.warn(`[vue-table] URL filter left out (${issue.reason}): ${JSON.stringify(issue.expr)}. Field filters cannot express it, so the table shows more rows than the URL described.`);
1604
1653
  }
1654
+ /**
1655
+ * The report for fields the table left out when nobody handles it
1656
+ * (`onFieldsDropped` / `@fields-dropped`): a dev-mode warning.
1657
+ */
1658
+ function warnFieldsDropped(report) {
1659
+ if (!DEV) return;
1660
+ const from = report.presetId ? `${report.source} "${report.presetId}"` : report.source;
1661
+ console.warn(`[vue-table] Left out fields this table cannot use (${from}): ${report.fields.join(", ")}.`);
1662
+ }
1605
1663
  function createTableState(opts) {
1606
1664
  const client = opts.client;
1607
1665
  const modelOpts = opts.model;
@@ -1638,6 +1696,7 @@ function createTableState(opts) {
1638
1696
  */
1639
1697
  const rowActionsPolicy = computed(() => compileRowActionsConfig(rowActions.value));
1640
1698
  const filters = shallowRef({});
1699
+ const residualFilters = shallowRef([]);
1641
1700
  const results = shallowRef([]);
1642
1701
  const resultsStart = ref(0);
1643
1702
  const querying = ref(false);
@@ -1675,11 +1734,34 @@ function createTableState(opts) {
1675
1734
  const configDialogOpen = ref(false);
1676
1735
  const configTab = ref("columns");
1677
1736
  const filterDialogColumn = ref(null);
1737
+ /**
1738
+ * The paths the table can use, from its column list: every column, and the
1739
+ * server-backed ones. `null` until the table definition loads. Presets,
1740
+ * drafts, URLs and the query itself are all pruned against it.
1741
+ */
1742
+ const knownFields = computed(() => {
1743
+ const all = allColumns.value;
1744
+ if (all.length === 0) return null;
1745
+ const columns = /* @__PURE__ */ new Set();
1746
+ const server = /* @__PURE__ */ new Set();
1747
+ for (const c of all) {
1748
+ columns.add(c.path);
1749
+ if (!c.local) server.add(c.path);
1750
+ }
1751
+ return {
1752
+ columns,
1753
+ server
1754
+ };
1755
+ });
1756
+ function reportFieldsDropped(report) {
1757
+ (queryOpts?.onFieldsDropped ?? warnFieldsDropped)(report);
1758
+ }
1678
1759
  const { slice: presetSlice, internals: presetInternals } = createPresetState({
1679
1760
  columnNames,
1680
1761
  columnWidths,
1681
1762
  filterFields,
1682
1763
  filters,
1764
+ residualFilters,
1683
1765
  sorters,
1684
1766
  pagination,
1685
1767
  allColumns,
@@ -1688,7 +1770,9 @@ function createTableState(opts) {
1688
1770
  availableAspects: opts.preset?.availableAspects,
1689
1771
  systemAspects: opts.preset?.systemAspects,
1690
1772
  persistDrafts: opts.preset?.persistDrafts,
1691
- fallbackSystemPresets: opts.preset?.fallbackSystemPresets
1773
+ fallbackSystemPresets: opts.preset?.fallbackSystemPresets,
1774
+ knownFields,
1775
+ onFieldsDropped: reportFieldsDropped
1692
1776
  });
1693
1777
  presetInternals.bootstrap();
1694
1778
  const promptSlot = createRequestSlot(false);
@@ -1730,7 +1814,10 @@ function createTableState(opts) {
1730
1814
  */
1731
1815
  const localColumnPaths = computed(() => {
1732
1816
  const out = /* @__PURE__ */ new Set();
1733
- for (const c of allColumns.value) if (c.local) out.add(c.path);
1817
+ const known = knownFields.value;
1818
+ if (known) {
1819
+ for (const p of known.columns) if (!known.server.has(p)) out.add(p);
1820
+ }
1734
1821
  return out;
1735
1822
  });
1736
1823
  /**
@@ -1763,21 +1850,67 @@ function createTableState(opts) {
1763
1850
  if (splitSorters.value.local.length === 0 || sortersIgnored()) return rows;
1764
1851
  return sortRowsLocally(rows, queryOpts?.forceSorters?.length ? mergeSorters(queryOpts.forceSorters, sorters.value) : sorters.value, sortValueOf);
1765
1852
  }
1853
+ /**
1854
+ * The query's view of the model, gated to the fields this caller can use.
1855
+ * This is the guarantee that no table state sends a field the server does
1856
+ * not expose: presets and URLs are pruned on apply, but `setFieldFilter`,
1857
+ * `addFilterField`, `v-model` writes, `setResidualFilters` and the like
1858
+ * write straight to the model. `$select` is gated by the column list
1859
+ * (`knownFields.columns`, then client-owned columns are stripped); filters
1860
+ * and residual conditions by the server-backed columns. `forceFilters` /
1861
+ * `forceSorters` are app-authored and never gated. Memoized: pages,
1862
+ * window blocks and exports reuse it.
1863
+ */
1864
+ const queryModel = computed(() => {
1865
+ const model = {
1866
+ columnNames: columnNames.value,
1867
+ filters: filters.value,
1868
+ sorters: splitSorters.value.server,
1869
+ residual: residualFilters.value,
1870
+ stripped: []
1871
+ };
1872
+ const known = knownFields.value;
1873
+ if (!known) return model;
1874
+ const { snapshot, dropped } = prunePresetSnapshot({
1875
+ columns: { columnNames: model.columnNames },
1876
+ filterOps: model.filters,
1877
+ sorters: model.sorters
1878
+ }, known);
1879
+ const residual = pruneResidualFilters(model.residual, known.server);
1880
+ return {
1881
+ columnNames: model.columnNames,
1882
+ filters: snapshot.filterOps,
1883
+ sorters: snapshot.sorters,
1884
+ residual: residual.kept,
1885
+ stripped: [...new Set([...dropped?.fields ?? [], ...residual.dropped?.fields ?? []])]
1886
+ };
1887
+ });
1888
+ /** Stripped path sets already warned about — one dev warning per set. */
1889
+ const warnedStripped = /* @__PURE__ */ new Set();
1766
1890
  function buildCurrentQuery(buildOpts) {
1767
- const available = tableDef.value?.fetchableFields ?? new Set(allColumns.value.map((c) => c.path));
1891
+ const available = tableDef.value?.fetchableFields ?? knownFields.value?.columns ?? /* @__PURE__ */ new Set();
1768
1892
  const extra = /* @__PURE__ */ new Set();
1769
1893
  for (const c of columns.value) for (const p of c.selectWith ?? []) if (available.has(p)) extra.add(p);
1770
1894
  if (queryOpts?.alwaysSelected) {
1771
1895
  for (const p of queryOpts.alwaysSelected) if (available.has(p)) extra.add(p);
1772
1896
  }
1897
+ const model = queryModel.value;
1898
+ if (DEV && model.stripped.length > 0) {
1899
+ const key = model.stripped.join(",");
1900
+ if (!warnedStripped.has(key)) {
1901
+ warnedStripped.add(key);
1902
+ console.warn(`[vue-table] Query leaves out fields this table cannot use: ${model.stripped.join(", ")}.`);
1903
+ }
1904
+ }
1905
+ const known = knownFields.value;
1773
1906
  const localPaths = localColumnPaths.value;
1774
- const requested = buildOpts?.columnPaths ?? columnNames.value;
1775
1907
  return buildTableQuery({
1776
- visibleColumnPaths: localPaths.size ? requested.filter((p) => !localPaths.has(p)) : requested,
1908
+ visibleColumnPaths: (buildOpts?.columnPaths ?? model.columnNames).filter((p) => !localPaths.has(p) && (!known || known.columns.has(p))),
1777
1909
  extraSelect: extra.size ? [...extra] : void 0,
1778
- sorters: splitSorters.value.server,
1910
+ sorters: model.sorters,
1779
1911
  forceSorters: queryOpts?.forceSorters,
1780
- filters: filters.value,
1912
+ filters: model.filters,
1913
+ residualFilters: model.residual,
1781
1914
  forceFilters: queryOpts?.forceFilters,
1782
1915
  search: searchTerm.value || void 0,
1783
1916
  ignoreSorters: sortersIgnored(),
@@ -1964,6 +2097,10 @@ function createTableState(opts) {
1964
2097
  }
1965
2098
  };
1966
2099
  }
2100
+ function setResidualFilters(exprs) {
2101
+ const next = normalizeResidualFilters(exprs);
2102
+ if (!sameResidualFilters(next, residualFilters.value)) residualFilters.value = next;
2103
+ }
1967
2104
  const state = {
1968
2105
  tableDef,
1969
2106
  loadingMetadata,
@@ -1973,6 +2110,7 @@ function createTableState(opts) {
1973
2110
  columnWidths,
1974
2111
  filterFields,
1975
2112
  filters,
2113
+ residualFilters,
1976
2114
  sorters,
1977
2115
  results,
1978
2116
  resultsStart,
@@ -2047,8 +2185,14 @@ function createTableState(opts) {
2047
2185
  loadingAt,
2048
2186
  errorAt,
2049
2187
  resetFilters() {
2050
- if (Object.keys(filters.value).length === 0) return;
2051
- filters.value = {};
2188
+ if (Object.keys(filters.value).length > 0) filters.value = {};
2189
+ if (residualFilters.value.length > 0) residualFilters.value = [];
2190
+ },
2191
+ setResidualFilters,
2192
+ removeResidualFilter(index) {
2193
+ const list = residualFilters.value;
2194
+ if (index < 0 || index >= list.length) return;
2195
+ residualFilters.value = list.filter((_, i) => i !== index);
2052
2196
  },
2053
2197
  showConfigDialog(tab) {
2054
2198
  configTab.value = tab ?? "columns";
@@ -2099,6 +2243,7 @@ function createTableState(opts) {
2099
2243
  function serializeStateForUrl() {
2100
2244
  return stateToUrlQueryString({
2101
2245
  filters: filters.value,
2246
+ residualFilters: residualFilters.value,
2102
2247
  sorters: sorters.value,
2103
2248
  page: pagination.value.page,
2104
2249
  itemsPerPage: pagination.value.itemsPerPage,
@@ -2129,6 +2274,8 @@ function createTableState(opts) {
2129
2274
  }
2130
2275
  const filtersGate = resolveAspectGate(urlQuerySync?.filters);
2131
2276
  const sortersGate = resolveAspectGate(urlQuerySync?.sorters);
2277
+ const residualSync = urlQuerySync?.residual !== false;
2278
+ const urlOwnsResidual = (expr) => residualSync && residualGateOwns(filtersGate, expr);
2132
2279
  let urlBaseline = null;
2133
2280
  function captureUrlBaseline() {
2134
2281
  if (urlBaseline) return;
@@ -2139,6 +2286,7 @@ function createTableState(opts) {
2139
2286
  }
2140
2287
  urlBaseline = {
2141
2288
  filters: owned,
2289
+ residual: residualFilters.value.filter(urlOwnsResidual),
2142
2290
  sorters: sorters.value.filter((s) => gateOwns(sortersGate, s.field)).map(cloneSorter)
2143
2291
  };
2144
2292
  }
@@ -2156,12 +2304,30 @@ function createTableState(opts) {
2156
2304
  function applyUrlQuery(urlString, opts) {
2157
2305
  captureUrlBaseline();
2158
2306
  if (urlsEquivalent(urlString, lastEmittedUrl)) return;
2159
- const cols = allColumns.value;
2307
+ const known = knownFields.value;
2160
2308
  const parsed = urlQueryStringToState(urlString, {
2161
- knownFields: cols.length > 0 ? cols.map((c) => c.path) : void 0,
2309
+ knownFields: known?.server,
2310
+ localFields: known ? localColumnPaths.value : void 0,
2162
2311
  sync: urlQuerySync
2163
2312
  });
2164
2313
  for (const issue of parsed.unsupported ?? []) (queryOpts?.onUnsupportedFilter ?? warnUnsupportedFilter)(issue);
2314
+ if (parsed.unknown || parsed.unknownSorters) {
2315
+ const residual = parsed.unknown?.map((u) => u.expr) ?? [];
2316
+ const sorters = parsed.unknownSorters ?? [];
2317
+ const fields = new Set(parsed.unknown?.flatMap((u) => u.fields));
2318
+ for (const s of sorters) fields.add(s.field);
2319
+ reportFieldsDropped({
2320
+ source: "url",
2321
+ fields: [...fields],
2322
+ columns: [],
2323
+ filterFields: [],
2324
+ filters: {},
2325
+ residual,
2326
+ sorters
2327
+ });
2328
+ }
2329
+ const urlResidual = parsed.residual ?? [];
2330
+ const residualPaths = new Set(urlResidual.flatMap((expr) => filterExprFields(expr)));
2165
2331
  const base = (opts?.mode ? opts.mode === "replace" : parsed.snapshot === true) ? null : urlBaseline;
2166
2332
  const currentItemsPerPage = pagination.value.itemsPerPage;
2167
2333
  const skip = parsed.skip ?? 0;
@@ -2170,12 +2336,24 @@ function createTableState(opts) {
2170
2336
  hydratingFromUrl = true;
2171
2337
  if (filtersGate !== "none") {
2172
2338
  const next = {};
2339
+ const fromBase = (path) => !!base?.filters[path] && !residualPaths.has(path);
2173
2340
  for (const path in filters.value) if (!gateOwns(filtersGate, path)) next[path] = filters.value[path];
2174
- else if (base?.filters[path]) next[path] = cloneConditions(base.filters[path]);
2175
- if (base) for (const path in base.filters) next[path] ??= cloneConditions(base.filters[path]);
2341
+ else if (fromBase(path)) next[path] = cloneConditions(base.filters[path]);
2342
+ if (base) {
2343
+ for (const path in base.filters) if (fromBase(path)) next[path] ??= cloneConditions(base.filters[path]);
2344
+ }
2176
2345
  for (const path in parsed.filters) next[path] = parsed.filters[path];
2177
2346
  filters.value = next;
2178
2347
  }
2348
+ if (residualSync) {
2349
+ const mentioned = new Set([...Object.keys(parsed.filters), ...residualPaths]);
2350
+ const next = residualFilters.value.filter((expr) => !urlOwnsResidual(expr));
2351
+ if (base) {
2352
+ for (const expr of base.residual) if (!filterExprFields(expr).some((f) => mentioned.has(f))) next.push(expr);
2353
+ }
2354
+ next.push(...urlResidual);
2355
+ setResidualFilters(next);
2356
+ }
2179
2357
  let urlSortersChanged = false;
2180
2358
  if (sortersGate !== "none") {
2181
2359
  const urlSortFields = new Set(parsed.sorters.map((s) => s.field));
@@ -2220,7 +2398,11 @@ function createTableState(opts) {
2220
2398
  const debouncedFilterQuery = debounce(() => {
2221
2399
  if (queryDetected) scheduleQuery();
2222
2400
  }, FILTER_DEBOUNCE_MS);
2223
- watch([() => filters.value, () => searchTerm.value], () => {
2401
+ watch([
2402
+ () => filters.value,
2403
+ () => residualFilters.value,
2404
+ () => searchTerm.value
2405
+ ], () => {
2224
2406
  if (hydratingFromUrl) return;
2225
2407
  if (!queryDetected) return;
2226
2408
  mustRefresh.value = true;
@@ -2267,6 +2449,7 @@ function createTableState(opts) {
2267
2449
  });
2268
2450
  watch([
2269
2451
  () => filters.value,
2452
+ () => residualFilters.value,
2270
2453
  () => sorters.value,
2271
2454
  () => searchTerm.value,
2272
2455
  () => pagination.value,
@@ -2451,4 +2634,4 @@ function useRegisterMainActionListener(state, listener, enabled) {
2451
2634
  });
2452
2635
  }
2453
2636
  //#endregion
2454
- export { intentToScope as C, ROW_ACTIONS_PATH as D, triggerAction as E, ROW_ACTIONS_TYPE as O, intentClass as S, isPlainLeftClick as T, applyRowsGate as _, useTableContext as a, createNavigateGestures as b, DEFAULT_AVAILABLE_ASPECTS as c, ownerNameOf as d, readPresetLabel as f, applyRowGate as g, rowActionHref as h, useRegisterMainActionListener as i, ASPECT_ICONS as l, onSelectControlKeydown as m, createTableState as n, useTableContextOptional as o, INTERACTIVE_SELECTOR as p, provideTableContext as r, warnUnsupportedFilter as s, createStaticTableState as t, ASPECT_LABELS as u, ariaLabelFor as v, isModClick as w, extractIdentifier as x, collectIdentifiers as y };
2637
+ export { intentClass as C, triggerAction as D, isPlainLeftClick as E, ROW_ACTIONS_PATH as O, extractIdentifier as S, isModClick as T, applyRowGate as _, useTableContext as a, collectIdentifiers as b, warnUnsupportedFilter as c, ASPECT_LABELS as d, ownerNameOf as f, rowActionHref as g, onSelectControlKeydown as h, useRegisterMainActionListener as i, ROW_ACTIONS_TYPE as k, DEFAULT_AVAILABLE_ASPECTS as l, INTERACTIVE_SELECTOR as m, createTableState as n, useTableContextOptional as o, readPresetLabel as p, provideTableContext as r, warnFieldsDropped as s, createStaticTableState as t, ASPECT_ICONS as u, applyRowsGate as v, intentToScope as w, createNavigateGestures as x, ariaLabelFor as y };