@atscript/vue-table 0.1.139 → 0.1.140

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-DXuyDZ_X.cjs → as-action-menu-content-B5RFt7Hc.cjs} +1 -1
  4. package/dist/{as-action-menu-content-D7NIwc9X.mjs → as-action-menu-content-KNJr1EkO.mjs} +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-DKQNY5gh.d.cts} +1 -1
  13. package/dist/{as-column-menu.vue-Dw2m8ZqD.d.mts → as-column-menu.vue-hOmuVcKf.d.mts} +1 -1
  14. package/dist/{as-config-dialog-j_-oUmN7.cjs → as-config-dialog-B1KJJSHE.cjs} +2 -2
  15. package/dist/{as-config-dialog-DrcYlrOh.mjs → as-config-dialog-JXNkCCrh.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-CC6U8nLZ.mjs} +1 -1
  20. package/dist/{as-confirm-dialog-CStjIWN0.cjs → as-confirm-dialog-a736BLOm.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-C3wltqrN.mjs} +8 -8
  25. package/dist/{as-filter-dialog-B6_NvvlM.cjs → as-filter-dialog-IflKkOd8.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-3DJh_ICs.mjs → as-filter-field-D_6DlFcp.mjs} +3 -3
  30. package/dist/{as-filter-field-DCUMuu0M.cjs → as-filter-field-pygBjA-K.cjs} +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-D0DWDyiv.mjs → as-filters-BDzQ2SM_.mjs} +19 -4
  36. package/dist/{as-filters-CHLPYHNP.cjs → as-filters-BIoEsPD0.cjs} +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-RLW0gZDj.cjs → as-preset-dialog-BgZB8xyk.cjs} +2 -2
  44. package/dist/{as-preset-dialog-H98DeOMM.mjs → as-preset-dialog-D_w4cxKS.mjs} +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-B8d6l3U1.mjs} +13 -8
  49. package/dist/{as-preset-picker-yWXEhWEq.cjs → as-preset-picker-XnGrqHyF.cjs} +13 -8
  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-C65QsWxr.mjs +49 -0
  54. package/dist/as-residual-filter-ihvXCtpH.cjs +54 -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-C6N0lcZb.cjs} +2 -2
  62. package/dist/{as-row-actions-BvJWHf3j.mjs → as-row-actions-xQ9M1PrO.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-B3XAxDAL.d.cts} +1 -1
  68. package/dist/{as-row-actions.vue-WRrzvdbJ.d.cts → as-row-actions.vue-C8vslg0n.d.mts} +1 -1
  69. package/dist/{as-table-D5mMZKKg.mjs → as-table-B49x0hDM.mjs} +3 -3
  70. package/dist/{as-table-actions-D91sNgel.cjs → as-table-actions-B-jY3s6f.cjs} +2 -2
  71. package/dist/{as-table-actions-C6moFlxt.mjs → as-table-actions-B2Hy3h7v.mjs} +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-BgTJuXEH.d.cts} +2 -2
  77. package/dist/{as-table-actions.vue-DB3LW-eI.d.cts → as-table-actions.vue-BnLH2NqO.d.mts} +2 -2
  78. package/dist/{as-table-base-CEpvNFBd.mjs → as-table-base-DErJfFYz.mjs} +2 -2
  79. package/dist/{as-table-base-CyBOmUK9.cjs → as-table-base-ceywcVRq.cjs} +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-header-cell-CUs-sSBm.mjs → as-table-header-cell-Ccf06b2_.mjs} +1 -1
  85. package/dist/{as-table-header-cell-D7pW2sV3.cjs → as-table-header-cell-ZCk3XD_d.cjs} +1 -1
  86. package/dist/as-table-header-cell.cjs +1 -1
  87. package/dist/as-table-header-cell.d.cts +1 -1
  88. package/dist/as-table-header-cell.d.mts +1 -1
  89. package/dist/as-table-header-cell.mjs +1 -1
  90. package/dist/{as-table-header-cell.vue-DFaRLZZA.d.cts → as-table-header-cell.vue-C7jNmdDh.d.mts} +2 -2
  91. package/dist/{as-table-header-cell.vue-BDhs3PNi.d.mts → as-table-header-cell.vue-DkClJBAT.d.cts} +2 -2
  92. package/dist/{as-table-CVD1zLva.cjs → as-table-ouPxssqv.cjs} +3 -3
  93. package/dist/{as-table-root-C61wrFd9.mjs → as-table-root-CG7bLzcQ.mjs} +7 -4
  94. package/dist/{as-table-root-C7Tr_9W-.cjs → as-table-root-_58hdHAi.cjs} +7 -4
  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-D5Xyux8I.d.cts → as-table-root.vue-D8hH1xGK.d.cts} +10 -3
  100. package/dist/{as-table-root.vue-Bza3D5F1.d.mts → as-table-root.vue-DpPRMy3v.d.mts} +10 -3
  101. package/dist/{as-table-status-D08b0cqn.mjs → as-table-status-2rA91OFT.mjs} +5 -5
  102. package/dist/{as-table-status-B-MjDXNZ.cjs → as-table-status-UWTPJJUL.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-Bv6sKnL5.d.cts} +2 -2
  108. package/dist/{as-table.vue-CPfERNtx.d.mts → as-table.vue-CudFoZTF.d.mts} +2 -2
  109. package/dist/{as-window-table-BfSud7yB.cjs → as-window-table-C9YtzepV.cjs} +3 -3
  110. package/dist/{as-window-table-CjYMF0aX.mjs → as-window-table-KyS6YUpa.mjs} +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-COVl3die.d.mts} +2 -2
  116. package/dist/{as-window-table.vue-3egLvwAy.d.mts → as-window-table.vue-CRWdhOgC.d.cts} +2 -2
  117. package/dist/index.cjs +23 -48
  118. package/dist/index.d.cts +10 -10
  119. package/dist/index.d.mts +22 -22
  120. package/dist/index.mjs +23 -48
  121. package/dist/{types-B6N5NDx_.d.cts → types-BCixOa8x.d.cts} +20 -1
  122. package/dist/{types-CgdpcJdd.d.mts → types-D7qVxU_y.d.mts} +20 -1
  123. package/dist/{use-table-DUwUfjSZ.mjs → use-table-2xPxoNRX.mjs} +1 -1
  124. package/dist/{use-table-c4ku22Mx.cjs → use-table-B45WZLwg.cjs} +1 -1
  125. package/dist/{use-table-column-handlers-eKHCfUm_.mjs → use-table-column-handlers-p_xK-l0e.mjs} +1 -1
  126. package/dist/{use-table-column-handlers-Do0Z-R9D.cjs → use-table-column-handlers-rqFo9HLz.cjs} +1 -1
  127. package/dist/{use-table-component-wfU13P3L.mjs → use-table-component-BYO0zMQj.mjs} +1 -1
  128. package/dist/{use-table-component-DbQDINoa.cjs → use-table-component-XQ-Qt_sp.cjs} +1 -1
  129. package/dist/{use-table-nav-bridge-DRrvQxXq.mjs → use-table-nav-bridge-BDBnRc4C.mjs} +1 -1
  130. package/dist/{use-table-nav-bridge-BLLVGR1r.cjs → use-table-nav-bridge-DvrsPanF.cjs} +1 -1
  131. package/dist/{use-table-state-BEwXQayO.mjs → use-table-state-BzjbnpBA.mjs} +50 -8
  132. package/dist/{use-table-state-Cnxv0w_E.cjs → use-table-state-pF_9gnTG.cjs} +49 -7
  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
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { O as ROW_ACTIONS_TYPE, a as useTableContext, n as createTableState, o as useTableContextOptional, r as provideTableContext, t as createStaticTableState, x as extractIdentifier } from "./use-table-state-BEwXQayO.mjs";
1
+ import { O as ROW_ACTIONS_TYPE, a as useTableContext, n as createTableState, o as useTableContextOptional, r as provideTableContext, t as createStaticTableState, x as extractIdentifier } from "./use-table-state-BzjbnpBA.mjs";
2
2
  import { t as getCellValue } from "./get-cell-value-Cb3X8nUn.mjs";
3
3
  import { n as useTableSelection } from "./use-table-selection-ULbKT2xW.mjs";
4
- import { a as useLocalDraft, i as usePresets, o as AS_PRESETS_APP, r as useTable, s as injectPresetsApp, t as clearTableCache } from "./use-table-DUwUfjSZ.mjs";
5
- import { t as useTableNavBridge } from "./use-table-nav-bridge-DRrvQxXq.mjs";
6
- import { t as useTableComponent } from "./use-table-component-wfU13P3L.mjs";
4
+ import { a as useLocalDraft, i as usePresets, o as AS_PRESETS_APP, r as useTable, s as injectPresetsApp, t as clearTableCache } from "./use-table-2xPxoNRX.mjs";
5
+ import { t as useTableNavBridge } from "./use-table-nav-bridge-BDBnRc4C.mjs";
6
+ import { t as useTableComponent } from "./use-table-component-BYO0zMQj.mjs";
7
7
  import { t as as_cell_array_default } from "./as-cell-array-Bgc4GYZf.mjs";
8
8
  import { t as as_cell_date_default } from "./as-cell-date-B9mu0OOD.mjs";
9
9
  import { n as useCellLocale, t as provideCellLocale } from "./use-cell-locale-MQXoONh5.mjs";
@@ -12,24 +12,26 @@ import { t as as_cell_number_default } from "./as-cell-number-CNDT6gfw.mjs";
12
12
  import { t as formatCellValue } from "./format-cell-D1Ziv96Q.mjs";
13
13
  import { t as as_cell_union_default } from "./as-cell-union-DMnoa7RM.mjs";
14
14
  import { t as as_column_menu_default } from "./as-column-menu-CBtUfYEE.mjs";
15
- import { t as as_config_dialog_default } from "./as-config-dialog-DrcYlrOh.mjs";
16
- import { t as as_confirm_dialog_default } from "./as-confirm-dialog-Pnt60ALy.mjs";
15
+ import { t as as_config_dialog_default } from "./as-config-dialog-JXNkCCrh.mjs";
16
+ import { t as as_confirm_dialog_default } from "./as-confirm-dialog-CC6U8nLZ.mjs";
17
17
  import { t as as_filter_input_default } from "./as-filter-input-BM23TNeh.mjs";
18
- import { t as as_filter_dialog_default } from "./as-filter-dialog-CUzahpd_.mjs";
19
- import { t as as_table_header_cell_default } from "./as-table-header-cell-CUs-sSBm.mjs";
20
- import { t as as_window_table_default } from "./as-window-table-CjYMF0aX.mjs";
21
- import { t as as_filter_field_default } from "./as-filter-field-3DJh_ICs.mjs";
22
- import { t as as_filters_default } from "./as-filters-D0DWDyiv.mjs";
23
- import { t as as_preset_dialog_default } from "./as-preset-dialog-H98DeOMM.mjs";
24
- import { t as as_row_actions_default } from "./as-row-actions-BvJWHf3j.mjs";
25
- import { t as as_table_cell_value_default } from "./as-table-cell-value-D1eLiQKJ.mjs";
26
- import { t as as_table_root_default } from "./as-table-root-C61wrFd9.mjs";
27
- import { t as as_table_default } from "./as-table-D5mMZKKg.mjs";
28
- import { t as as_table_actions_default } from "./as-table-actions-C6moFlxt.mjs";
29
- import { t as as_preset_picker_default } from "./as-preset-picker-Cw7LB7o1.mjs";
18
+ import { t as as_filter_dialog_default } from "./as-filter-dialog-C3wltqrN.mjs";
19
+ import { t as as_table_header_cell_default } from "./as-table-header-cell-Ccf06b2_.mjs";
20
+ import { t as as_window_table_default } from "./as-window-table-KyS6YUpa.mjs";
21
+ import { t as as_filter_field_default } from "./as-filter-field-D_6DlFcp.mjs";
22
+ import { t as as_residual_filter_default } from "./as-residual-filter-C65QsWxr.mjs";
23
+ import { t as as_filters_default } from "./as-filters-BDzQ2SM_.mjs";
24
+ import { t as as_preset_dialog_default } from "./as-preset-dialog-D_w4cxKS.mjs";
25
+ import { t as as_row_actions_default } from "./as-row-actions-xQ9M1PrO.mjs";
26
+ import { t as as_table_cell_value_default } from "./as-table-cell-value-DoH8Mieo.mjs";
27
+ import { t as as_table_root_default } from "./as-table-root-CG7bLzcQ.mjs";
28
+ import { t as as_table_default } from "./as-table-B49x0hDM.mjs";
29
+ import { t as as_table_actions_default } from "./as-table-actions-B2Hy3h7v.mjs";
30
+ import { t as as_preset_picker_default } from "./as-preset-picker-B8d6l3U1.mjs";
30
31
  import { getDefaultClientFactory, resetDefaultClientFactory, setDefaultClientFactory } from "@atscript/ui";
31
32
  import { computed, effectScope, ref, shallowRef } from "vue";
32
33
  import { AppPrefsClient, ExportAbortError, PRESET_ASPECTS, STANDARD_PRESET_ID, SYSTEM_PRESET_PREFIX, collectExportRows, columnDefaultCondition, columnFilterConditions, columnFilterType, isFilled, isSystemPresetId, isUnavailableError, resolveExportValue, resolveSystemPresets, toCsv, urlQueryConsumesKey, withStableOrder } from "@atscript/ui-table";
34
+ import { splitUrlSegments } from "@uniqu/url";
33
35
  import { StorageSerializers, useEventBus, useStorage } from "@vueuse/core";
34
36
  //#region src/composables/use-table-export.ts
35
37
  /**
@@ -292,35 +294,6 @@ function useTableActions() {
292
294
  }
293
295
  //#endregion
294
296
  //#region src/composables/use-table-url-query.ts
295
- /**
296
- * Split a uniqu URL string on top-level `&` separators, respecting
297
- * single-quoted string literals (`name='foo&bar'` is one segment, not two).
298
- * Within a quote, `\'` escapes a literal apostrophe per @uniqu/url's encoder.
299
- * Empty segments (from leading/trailing/double `&`) are dropped.
300
- */
301
- function splitSegments(urlString) {
302
- if (!urlString) return [];
303
- const out = [];
304
- let start = 0;
305
- let inQuote = false;
306
- const push = (end) => {
307
- if (end > start) out.push(urlString.slice(start, end));
308
- };
309
- for (let i = 0; i < urlString.length; i++) {
310
- const c = urlString[i];
311
- if (c === "\\" && inQuote) {
312
- i++;
313
- continue;
314
- }
315
- if (c === "'") inQuote = !inQuote;
316
- else if (c === "&" && !inQuote) {
317
- push(i);
318
- start = i + 1;
319
- }
320
- }
321
- push(urlString.length);
322
- return out;
323
- }
324
297
  const KEY_CHAR = /[A-Za-z0-9_.$-]/;
325
298
  /**
326
299
  * Index of `=` if every char before it is URL-key-safe. Returns -1 when any
@@ -396,7 +369,8 @@ function useTableUrlQuery(route, router, opts = {}) {
396
369
  },
397
370
  set: (urlString) => {
398
371
  const serialized = /* @__PURE__ */ new Map();
399
- for (const segment of splitSegments(urlString)) {
372
+ for (const segment of splitUrlSegments(urlString)) {
373
+ if (!segment) continue;
400
374
  const eqIdx = findCleanEq(segment);
401
375
  const key = eqIdx > 0 ? `${prefix}${segment.slice(0, eqIdx)}` : `${prefix}${segment}`;
402
376
  serialized.set(key, eqIdx > 0 ? segment.slice(eqIdx + 1) : null);
@@ -637,6 +611,7 @@ function createDefaultControls() {
637
611
  filterDialog: as_filter_dialog_default,
638
612
  filterInput: as_filter_input_default,
639
613
  filterField: as_filter_field_default,
614
+ residualFilter: as_residual_filter_default,
640
615
  configDialog: as_config_dialog_default,
641
616
  confirmDialog: as_confirm_dialog_default
642
617
  };
@@ -1,6 +1,6 @@
1
1
  import { Component, ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
2
2
  import { ClientError, PageResult, TDbActionInfo, TDbActionProcessor, TDbDeleteResult } from "@atscript/db-client";
3
- import { Uniquery } from "@uniqu/core";
3
+ import { FilterExpr, Uniquery } from "@uniqu/core";
4
4
  import { ColumnDef, PaginationControl, SortControl, TableDef } from "@atscript/ui";
5
5
  import { AsPresetEntryRow, AspectMask, ColumnWidthsMap, ConfigTab, FieldFilters, PresetAspect, PresetCapabilities, PresetSnapshot, SelectionMode, SystemPreset, SystemPresetInput, TableStateMethods } from "@atscript/ui-table";
6
6
 
@@ -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;
@@ -442,6 +447,15 @@ interface ReactiveTableState extends TableStateMethods {
442
447
  columnWidths: Ref<ColumnWidthsMap>;
443
448
  filterFields: ShallowRef<string[]>;
444
449
  filters: ShallowRef<FieldFilters>;
450
+ /**
451
+ * Residual filter conditions — AND-ed Uniquery conjuncts the field-filter
452
+ * model cannot hold (a cross-field `$or`, a second range on one field, …).
453
+ * AND'd after `filters` into every query, carried through the URL, shown
454
+ * as "custom filter" chips by `<AsFilters>`. Replace wholesale (or use
455
+ * `setResidualFilters` / `removeResidualFilter`); `resetFilters()`
456
+ * clears them. Not saved in presets. Since 0.1.140.
457
+ */
458
+ residualFilters: ShallowRef<FilterExpr[]>;
445
459
  sorters: ShallowRef<SortControl[]>;
446
460
  results: ShallowRef<Record<string, unknown>[]>;
447
461
  /** Absolute index where `results[0]` sits. */
@@ -686,6 +700,11 @@ interface ReactiveTableState extends TableStateMethods {
686
700
  * the baseline.
687
701
  *
688
702
  * `opts.mode` overrides the marker either way. Since 0.1.139.
703
+ *
704
+ * Filter pieces the field-filter model cannot hold are restored as
705
+ * `residualFilters` (since 0.1.140). A URL owns every path it mentions —
706
+ * in a field filter or inside such a condition — so on merge the
707
+ * baseline's filters and residual conditions on those paths give way.
689
708
  */
690
709
  applyUrlQuery: (urlString: string, opts?: ApplyUrlQueryOptions) => void;
691
710
  /**
@@ -2,7 +2,7 @@ import { ColumnDef, PaginationControl, SortControl, TableDef } from "@atscript/u
2
2
  import { Component, ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from "vue";
3
3
  import { AsPresetEntryRow, AspectMask, ColumnWidthsMap, ConfigTab, 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;
@@ -442,6 +447,15 @@ interface ReactiveTableState extends TableStateMethods {
442
447
  columnWidths: Ref<ColumnWidthsMap>;
443
448
  filterFields: ShallowRef<string[]>;
444
449
  filters: ShallowRef<FieldFilters>;
450
+ /**
451
+ * Residual filter conditions — AND-ed Uniquery conjuncts the field-filter
452
+ * model cannot hold (a cross-field `$or`, a second range on one field, …).
453
+ * AND'd after `filters` into every query, carried through the URL, shown
454
+ * as "custom filter" chips by `<AsFilters>`. Replace wholesale (or use
455
+ * `setResidualFilters` / `removeResidualFilter`); `resetFilters()`
456
+ * clears them. Not saved in presets. Since 0.1.140.
457
+ */
458
+ residualFilters: ShallowRef<FilterExpr[]>;
445
459
  sorters: ShallowRef<SortControl[]>;
446
460
  results: ShallowRef<Record<string, unknown>[]>;
447
461
  /** Absolute index where `results[0]` sits. */
@@ -686,6 +700,11 @@ interface ReactiveTableState extends TableStateMethods {
686
700
  * the baseline.
687
701
  *
688
702
  * `opts.mode` overrides the marker either way. Since 0.1.139.
703
+ *
704
+ * Filter pieces the field-filter model cannot hold are restored as
705
+ * `residualFilters` (since 0.1.140). A URL owns every path it mentions —
706
+ * in a field filter or inside such a condition — so on merge the
707
+ * baseline's filters and residual conditions on those paths give way.
689
708
  */
690
709
  applyUrlQuery: (urlString: string, opts?: ApplyUrlQueryOptions) => void;
691
710
  /**
@@ -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 { c as DEFAULT_AVAILABLE_ASPECTS, n as createTableState, r as provideTableContext } from "./use-table-state-BzjbnpBA.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";
@@ -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-pF_9gnTG.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");
@@ -1,4 +1,4 @@
1
- import { D as ROW_ACTIONS_PATH, O as ROW_ACTIONS_TYPE } from "./use-table-state-BEwXQayO.mjs";
1
+ import { D as ROW_ACTIONS_PATH, O as ROW_ACTIONS_TYPE } from "./use-table-state-BzjbnpBA.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
- const require_use_table_state = require("./use-table-state-Cnxv0w_E.cjs");
1
+ const require_use_table_state = require("./use-table-state-pF_9gnTG.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 { o as useTableContextOptional } from "./use-table-state-BEwXQayO.mjs";
1
+ import { o as useTableContextOptional } from "./use-table-state-BzjbnpBA.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-pF_9gnTG.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-BzjbnpBA.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-pF_9gnTG.cjs");
2
2
  //#region src/composables/use-table-nav-bridge.ts
3
3
  function isPassthroughKey(event) {
4
4
  const key = event.key;
@@ -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, reconcileColumnWidthDefaults, residualGateOwns, resolveAspectGate, resolveSystemPresets, 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
  },
@@ -1151,7 +1152,7 @@ function createPresetState(opts) {
1151
1152
  const wire = row.data?.content;
1152
1153
  return wire ? fromWireSnapshot(wire) : {};
1153
1154
  });
1154
- const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()));
1155
+ const isDirty = computed(() => isDirtyAgainst(activeSnapshot.value, captureSnapshot()) || activeSnapshot.value.filterOps !== void 0 && !!opts.residualFilters?.value.length);
1155
1156
  function requirePresets() {
1156
1157
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
1157
1158
  return opts.presetsHandle;
@@ -1594,6 +1595,10 @@ const cloneSorter = (s) => ({
1594
1595
  field: s.field,
1595
1596
  direction: s.direction
1596
1597
  });
1598
+ /** Same residual conditions, in the same (canonical) order. */
1599
+ function sameResidualFilters(a, b) {
1600
+ return arraysEqual(a.map(filterExprKey), b.map(filterExprKey));
1601
+ }
1597
1602
  /**
1598
1603
  * The report for a restored URL's filter piece when nobody handles it
1599
1604
  * (`onUnsupportedFilter` / `@unsupported-filter`): a dev-mode warning.
@@ -1638,6 +1643,7 @@ function createTableState(opts) {
1638
1643
  */
1639
1644
  const rowActionsPolicy = computed(() => compileRowActionsConfig(rowActions.value));
1640
1645
  const filters = shallowRef({});
1646
+ const residualFilters = shallowRef([]);
1641
1647
  const results = shallowRef([]);
1642
1648
  const resultsStart = ref(0);
1643
1649
  const querying = ref(false);
@@ -1680,6 +1686,7 @@ function createTableState(opts) {
1680
1686
  columnWidths,
1681
1687
  filterFields,
1682
1688
  filters,
1689
+ residualFilters,
1683
1690
  sorters,
1684
1691
  pagination,
1685
1692
  allColumns,
@@ -1778,6 +1785,7 @@ function createTableState(opts) {
1778
1785
  sorters: splitSorters.value.server,
1779
1786
  forceSorters: queryOpts?.forceSorters,
1780
1787
  filters: filters.value,
1788
+ residualFilters: residualFilters.value,
1781
1789
  forceFilters: queryOpts?.forceFilters,
1782
1790
  search: searchTerm.value || void 0,
1783
1791
  ignoreSorters: sortersIgnored(),
@@ -1964,6 +1972,10 @@ function createTableState(opts) {
1964
1972
  }
1965
1973
  };
1966
1974
  }
1975
+ function setResidualFilters(exprs) {
1976
+ const next = normalizeResidualFilters(exprs);
1977
+ if (!sameResidualFilters(next, residualFilters.value)) residualFilters.value = next;
1978
+ }
1967
1979
  const state = {
1968
1980
  tableDef,
1969
1981
  loadingMetadata,
@@ -1973,6 +1985,7 @@ function createTableState(opts) {
1973
1985
  columnWidths,
1974
1986
  filterFields,
1975
1987
  filters,
1988
+ residualFilters,
1976
1989
  sorters,
1977
1990
  results,
1978
1991
  resultsStart,
@@ -2047,8 +2060,14 @@ function createTableState(opts) {
2047
2060
  loadingAt,
2048
2061
  errorAt,
2049
2062
  resetFilters() {
2050
- if (Object.keys(filters.value).length === 0) return;
2051
- filters.value = {};
2063
+ if (Object.keys(filters.value).length > 0) filters.value = {};
2064
+ if (residualFilters.value.length > 0) residualFilters.value = [];
2065
+ },
2066
+ setResidualFilters,
2067
+ removeResidualFilter(index) {
2068
+ const list = residualFilters.value;
2069
+ if (index < 0 || index >= list.length) return;
2070
+ residualFilters.value = list.filter((_, i) => i !== index);
2052
2071
  },
2053
2072
  showConfigDialog(tab) {
2054
2073
  configTab.value = tab ?? "columns";
@@ -2099,6 +2118,7 @@ function createTableState(opts) {
2099
2118
  function serializeStateForUrl() {
2100
2119
  return stateToUrlQueryString({
2101
2120
  filters: filters.value,
2121
+ residualFilters: residualFilters.value,
2102
2122
  sorters: sorters.value,
2103
2123
  page: pagination.value.page,
2104
2124
  itemsPerPage: pagination.value.itemsPerPage,
@@ -2129,6 +2149,8 @@ function createTableState(opts) {
2129
2149
  }
2130
2150
  const filtersGate = resolveAspectGate(urlQuerySync?.filters);
2131
2151
  const sortersGate = resolveAspectGate(urlQuerySync?.sorters);
2152
+ const residualSync = urlQuerySync?.residual !== false;
2153
+ const urlOwnsResidual = (expr) => residualSync && residualGateOwns(filtersGate, expr);
2132
2154
  let urlBaseline = null;
2133
2155
  function captureUrlBaseline() {
2134
2156
  if (urlBaseline) return;
@@ -2139,6 +2161,7 @@ function createTableState(opts) {
2139
2161
  }
2140
2162
  urlBaseline = {
2141
2163
  filters: owned,
2164
+ residual: residualFilters.value.filter(urlOwnsResidual),
2142
2165
  sorters: sorters.value.filter((s) => gateOwns(sortersGate, s.field)).map(cloneSorter)
2143
2166
  };
2144
2167
  }
@@ -2158,10 +2181,12 @@ function createTableState(opts) {
2158
2181
  if (urlsEquivalent(urlString, lastEmittedUrl)) return;
2159
2182
  const cols = allColumns.value;
2160
2183
  const parsed = urlQueryStringToState(urlString, {
2161
- knownFields: cols.length > 0 ? cols.map((c) => c.path) : void 0,
2184
+ knownFields: cols.length > 0 ? cols.filter((c) => !c.local).map((c) => c.path) : void 0,
2162
2185
  sync: urlQuerySync
2163
2186
  });
2164
2187
  for (const issue of parsed.unsupported ?? []) (queryOpts?.onUnsupportedFilter ?? warnUnsupportedFilter)(issue);
2188
+ const urlResidual = parsed.residual ?? [];
2189
+ const residualPaths = new Set(urlResidual.flatMap((expr) => filterExprFields(expr)));
2165
2190
  const base = (opts?.mode ? opts.mode === "replace" : parsed.snapshot === true) ? null : urlBaseline;
2166
2191
  const currentItemsPerPage = pagination.value.itemsPerPage;
2167
2192
  const skip = parsed.skip ?? 0;
@@ -2170,12 +2195,24 @@ function createTableState(opts) {
2170
2195
  hydratingFromUrl = true;
2171
2196
  if (filtersGate !== "none") {
2172
2197
  const next = {};
2198
+ const fromBase = (path) => !!base?.filters[path] && !residualPaths.has(path);
2173
2199
  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]);
2200
+ else if (fromBase(path)) next[path] = cloneConditions(base.filters[path]);
2201
+ if (base) {
2202
+ for (const path in base.filters) if (fromBase(path)) next[path] ??= cloneConditions(base.filters[path]);
2203
+ }
2176
2204
  for (const path in parsed.filters) next[path] = parsed.filters[path];
2177
2205
  filters.value = next;
2178
2206
  }
2207
+ if (residualSync) {
2208
+ const mentioned = new Set([...Object.keys(parsed.filters), ...residualPaths]);
2209
+ const next = residualFilters.value.filter((expr) => !urlOwnsResidual(expr));
2210
+ if (base) {
2211
+ for (const expr of base.residual) if (!filterExprFields(expr).some((f) => mentioned.has(f))) next.push(expr);
2212
+ }
2213
+ next.push(...urlResidual);
2214
+ setResidualFilters(next);
2215
+ }
2179
2216
  let urlSortersChanged = false;
2180
2217
  if (sortersGate !== "none") {
2181
2218
  const urlSortFields = new Set(parsed.sorters.map((s) => s.field));
@@ -2220,7 +2257,11 @@ function createTableState(opts) {
2220
2257
  const debouncedFilterQuery = debounce(() => {
2221
2258
  if (queryDetected) scheduleQuery();
2222
2259
  }, FILTER_DEBOUNCE_MS);
2223
- watch([() => filters.value, () => searchTerm.value], () => {
2260
+ watch([
2261
+ () => filters.value,
2262
+ () => residualFilters.value,
2263
+ () => searchTerm.value
2264
+ ], () => {
2224
2265
  if (hydratingFromUrl) return;
2225
2266
  if (!queryDetected) return;
2226
2267
  mustRefresh.value = true;
@@ -2267,6 +2308,7 @@ function createTableState(opts) {
2267
2308
  });
2268
2309
  watch([
2269
2310
  () => filters.value,
2311
+ () => residualFilters.value,
2270
2312
  () => sorters.value,
2271
2313
  () => searchTerm.value,
2272
2314
  () => pagination.value,
@@ -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
  },
@@ -1151,7 +1152,7 @@ function createPresetState(opts) {
1151
1152
  const wire = row.data?.content;
1152
1153
  return wire ? (0, _atscript_ui_table.fromWireSnapshot)(wire) : {};
1153
1154
  });
1154
- const isDirty = (0, vue.computed)(() => (0, _atscript_ui_table.isDirtyAgainst)(activeSnapshot.value, captureSnapshot()));
1155
+ const isDirty = (0, vue.computed)(() => (0, _atscript_ui_table.isDirtyAgainst)(activeSnapshot.value, captureSnapshot()) || activeSnapshot.value.filterOps !== void 0 && !!opts.residualFilters?.value.length);
1155
1156
  function requirePresets() {
1156
1157
  if (!opts.presetsHandle) throw new Error("[vue-table] presets feature not configured (preset.url missing)");
1157
1158
  return opts.presetsHandle;
@@ -1594,6 +1595,10 @@ const cloneSorter = (s) => ({
1594
1595
  field: s.field,
1595
1596
  direction: s.direction
1596
1597
  });
1598
+ /** Same residual conditions, in the same (canonical) order. */
1599
+ function sameResidualFilters(a, b) {
1600
+ return (0, _atscript_ui_table.arraysEqual)(a.map(_atscript_ui_table.filterExprKey), b.map(_atscript_ui_table.filterExprKey));
1601
+ }
1597
1602
  /**
1598
1603
  * The report for a restored URL's filter piece when nobody handles it
1599
1604
  * (`onUnsupportedFilter` / `@unsupported-filter`): a dev-mode warning.
@@ -1638,6 +1643,7 @@ function createTableState(opts) {
1638
1643
  */
1639
1644
  const rowActionsPolicy = (0, vue.computed)(() => compileRowActionsConfig(rowActions.value));
1640
1645
  const filters = (0, vue.shallowRef)({});
1646
+ const residualFilters = (0, vue.shallowRef)([]);
1641
1647
  const results = (0, vue.shallowRef)([]);
1642
1648
  const resultsStart = (0, vue.ref)(0);
1643
1649
  const querying = (0, vue.ref)(false);
@@ -1680,6 +1686,7 @@ function createTableState(opts) {
1680
1686
  columnWidths,
1681
1687
  filterFields,
1682
1688
  filters,
1689
+ residualFilters,
1683
1690
  sorters,
1684
1691
  pagination,
1685
1692
  allColumns,
@@ -1778,6 +1785,7 @@ function createTableState(opts) {
1778
1785
  sorters: splitSorters.value.server,
1779
1786
  forceSorters: queryOpts?.forceSorters,
1780
1787
  filters: filters.value,
1788
+ residualFilters: residualFilters.value,
1781
1789
  forceFilters: queryOpts?.forceFilters,
1782
1790
  search: searchTerm.value || void 0,
1783
1791
  ignoreSorters: sortersIgnored(),
@@ -1964,6 +1972,10 @@ function createTableState(opts) {
1964
1972
  }
1965
1973
  };
1966
1974
  }
1975
+ function setResidualFilters(exprs) {
1976
+ const next = (0, _atscript_ui_table.normalizeResidualFilters)(exprs);
1977
+ if (!sameResidualFilters(next, residualFilters.value)) residualFilters.value = next;
1978
+ }
1967
1979
  const state = {
1968
1980
  tableDef,
1969
1981
  loadingMetadata,
@@ -1973,6 +1985,7 @@ function createTableState(opts) {
1973
1985
  columnWidths,
1974
1986
  filterFields,
1975
1987
  filters,
1988
+ residualFilters,
1976
1989
  sorters,
1977
1990
  results,
1978
1991
  resultsStart,
@@ -2047,8 +2060,14 @@ function createTableState(opts) {
2047
2060
  loadingAt,
2048
2061
  errorAt,
2049
2062
  resetFilters() {
2050
- if (Object.keys(filters.value).length === 0) return;
2051
- filters.value = {};
2063
+ if (Object.keys(filters.value).length > 0) filters.value = {};
2064
+ if (residualFilters.value.length > 0) residualFilters.value = [];
2065
+ },
2066
+ setResidualFilters,
2067
+ removeResidualFilter(index) {
2068
+ const list = residualFilters.value;
2069
+ if (index < 0 || index >= list.length) return;
2070
+ residualFilters.value = list.filter((_, i) => i !== index);
2052
2071
  },
2053
2072
  showConfigDialog(tab) {
2054
2073
  configTab.value = tab ?? "columns";
@@ -2099,6 +2118,7 @@ function createTableState(opts) {
2099
2118
  function serializeStateForUrl() {
2100
2119
  return (0, _atscript_ui_table.stateToUrlQueryString)({
2101
2120
  filters: filters.value,
2121
+ residualFilters: residualFilters.value,
2102
2122
  sorters: sorters.value,
2103
2123
  page: pagination.value.page,
2104
2124
  itemsPerPage: pagination.value.itemsPerPage,
@@ -2129,6 +2149,8 @@ function createTableState(opts) {
2129
2149
  }
2130
2150
  const filtersGate = (0, _atscript_ui_table.resolveAspectGate)(urlQuerySync?.filters);
2131
2151
  const sortersGate = (0, _atscript_ui_table.resolveAspectGate)(urlQuerySync?.sorters);
2152
+ const residualSync = urlQuerySync?.residual !== false;
2153
+ const urlOwnsResidual = (expr) => residualSync && (0, _atscript_ui_table.residualGateOwns)(filtersGate, expr);
2132
2154
  let urlBaseline = null;
2133
2155
  function captureUrlBaseline() {
2134
2156
  if (urlBaseline) return;
@@ -2139,6 +2161,7 @@ function createTableState(opts) {
2139
2161
  }
2140
2162
  urlBaseline = {
2141
2163
  filters: owned,
2164
+ residual: residualFilters.value.filter(urlOwnsResidual),
2142
2165
  sorters: sorters.value.filter((s) => (0, _atscript_ui_table.gateOwns)(sortersGate, s.field)).map(cloneSorter)
2143
2166
  };
2144
2167
  }
@@ -2158,10 +2181,12 @@ function createTableState(opts) {
2158
2181
  if (urlsEquivalent(urlString, lastEmittedUrl)) return;
2159
2182
  const cols = allColumns.value;
2160
2183
  const parsed = (0, _atscript_ui_table.urlQueryStringToState)(urlString, {
2161
- knownFields: cols.length > 0 ? cols.map((c) => c.path) : void 0,
2184
+ knownFields: cols.length > 0 ? cols.filter((c) => !c.local).map((c) => c.path) : void 0,
2162
2185
  sync: urlQuerySync
2163
2186
  });
2164
2187
  for (const issue of parsed.unsupported ?? []) (queryOpts?.onUnsupportedFilter ?? warnUnsupportedFilter)(issue);
2188
+ const urlResidual = parsed.residual ?? [];
2189
+ const residualPaths = new Set(urlResidual.flatMap((expr) => (0, _atscript_ui_table.filterExprFields)(expr)));
2165
2190
  const base = (opts?.mode ? opts.mode === "replace" : parsed.snapshot === true) ? null : urlBaseline;
2166
2191
  const currentItemsPerPage = pagination.value.itemsPerPage;
2167
2192
  const skip = parsed.skip ?? 0;
@@ -2170,12 +2195,24 @@ function createTableState(opts) {
2170
2195
  hydratingFromUrl = true;
2171
2196
  if (filtersGate !== "none") {
2172
2197
  const next = {};
2198
+ const fromBase = (path) => !!base?.filters[path] && !residualPaths.has(path);
2173
2199
  for (const path in filters.value) if (!(0, _atscript_ui_table.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]);
2200
+ else if (fromBase(path)) next[path] = cloneConditions(base.filters[path]);
2201
+ if (base) {
2202
+ for (const path in base.filters) if (fromBase(path)) next[path] ??= cloneConditions(base.filters[path]);
2203
+ }
2176
2204
  for (const path in parsed.filters) next[path] = parsed.filters[path];
2177
2205
  filters.value = next;
2178
2206
  }
2207
+ if (residualSync) {
2208
+ const mentioned = new Set([...Object.keys(parsed.filters), ...residualPaths]);
2209
+ const next = residualFilters.value.filter((expr) => !urlOwnsResidual(expr));
2210
+ if (base) {
2211
+ for (const expr of base.residual) if (!(0, _atscript_ui_table.filterExprFields)(expr).some((f) => mentioned.has(f))) next.push(expr);
2212
+ }
2213
+ next.push(...urlResidual);
2214
+ setResidualFilters(next);
2215
+ }
2179
2216
  let urlSortersChanged = false;
2180
2217
  if (sortersGate !== "none") {
2181
2218
  const urlSortFields = new Set(parsed.sorters.map((s) => s.field));
@@ -2220,7 +2257,11 @@ function createTableState(opts) {
2220
2257
  const debouncedFilterQuery = (0, _atscript_ui_table.debounce)(() => {
2221
2258
  if (queryDetected) scheduleQuery();
2222
2259
  }, FILTER_DEBOUNCE_MS);
2223
- (0, vue.watch)([() => filters.value, () => searchTerm.value], () => {
2260
+ (0, vue.watch)([
2261
+ () => filters.value,
2262
+ () => residualFilters.value,
2263
+ () => searchTerm.value
2264
+ ], () => {
2224
2265
  if (hydratingFromUrl) return;
2225
2266
  if (!queryDetected) return;
2226
2267
  mustRefresh.value = true;
@@ -2267,6 +2308,7 @@ function createTableState(opts) {
2267
2308
  });
2268
2309
  (0, vue.watch)([
2269
2310
  () => filters.value,
2311
+ () => residualFilters.value,
2270
2312
  () => sorters.value,
2271
2313
  () => searchTerm.value,
2272
2314
  () => pagination.value,