@toolbox-web/grid 1.21.1 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +6 -8
  2. package/all.js +2 -6850
  3. package/all.js.map +1 -1
  4. package/index.js +1 -4352
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +75 -8
  7. package/lib/core/grid.d.ts.map +1 -1
  8. package/lib/core/internal/rows.d.ts.map +1 -1
  9. package/lib/core/plugin/base-plugin.d.ts +8 -2
  10. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  11. package/lib/core/styles/index.d.ts.map +1 -1
  12. package/lib/core/types.d.ts +84 -2
  13. package/lib/core/types.d.ts.map +1 -1
  14. package/lib/plugins/clipboard/index.js +1 -733
  15. package/lib/plugins/clipboard/index.js.map +1 -1
  16. package/lib/plugins/clipboard/types.d.ts +5 -0
  17. package/lib/plugins/clipboard/types.d.ts.map +1 -1
  18. package/lib/plugins/column-virtualization/index.js +1 -560
  19. package/lib/plugins/column-virtualization/index.js.map +1 -1
  20. package/lib/plugins/column-virtualization/types.d.ts +5 -0
  21. package/lib/plugins/column-virtualization/types.d.ts.map +1 -1
  22. package/lib/plugins/context-menu/index.js +1 -754
  23. package/lib/plugins/context-menu/index.js.map +1 -1
  24. package/lib/plugins/context-menu/types.d.ts +5 -0
  25. package/lib/plugins/context-menu/types.d.ts.map +1 -1
  26. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  27. package/lib/plugins/editing/index.d.ts +1 -1
  28. package/lib/plugins/editing/index.d.ts.map +1 -1
  29. package/lib/plugins/editing/index.js +1 -1539
  30. package/lib/plugins/editing/index.js.map +1 -1
  31. package/lib/plugins/editing/types.d.ts +49 -0
  32. package/lib/plugins/editing/types.d.ts.map +1 -1
  33. package/lib/plugins/export/index.js +1 -589
  34. package/lib/plugins/export/index.js.map +1 -1
  35. package/lib/plugins/export/types.d.ts +5 -0
  36. package/lib/plugins/export/types.d.ts.map +1 -1
  37. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  38. package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
  39. package/lib/plugins/filtering/index.js +1 -1283
  40. package/lib/plugins/filtering/index.js.map +1 -1
  41. package/lib/plugins/filtering/types.d.ts +7 -2
  42. package/lib/plugins/filtering/types.d.ts.map +1 -1
  43. package/lib/plugins/grouping-columns/index.js +1 -726
  44. package/lib/plugins/grouping-columns/index.js.map +1 -1
  45. package/lib/plugins/grouping-columns/types.d.ts +3 -0
  46. package/lib/plugins/grouping-columns/types.d.ts.map +1 -1
  47. package/lib/plugins/grouping-rows/index.js +2 -905
  48. package/lib/plugins/grouping-rows/index.js.map +1 -1
  49. package/lib/plugins/grouping-rows/types.d.ts +5 -0
  50. package/lib/plugins/grouping-rows/types.d.ts.map +1 -1
  51. package/lib/plugins/master-detail/index.js +1 -950
  52. package/lib/plugins/master-detail/index.js.map +1 -1
  53. package/lib/plugins/master-detail/types.d.ts +5 -0
  54. package/lib/plugins/master-detail/types.d.ts.map +1 -1
  55. package/lib/plugins/multi-sort/index.js +1 -553
  56. package/lib/plugins/multi-sort/index.js.map +1 -1
  57. package/lib/plugins/multi-sort/types.d.ts +5 -0
  58. package/lib/plugins/multi-sort/types.d.ts.map +1 -1
  59. package/lib/plugins/pinned-columns/index.js +1 -688
  60. package/lib/plugins/pinned-columns/index.js.map +1 -1
  61. package/lib/plugins/pinned-columns/types.d.ts +3 -0
  62. package/lib/plugins/pinned-columns/types.d.ts.map +1 -1
  63. package/lib/plugins/pinned-rows/index.js +1 -704
  64. package/lib/plugins/pinned-rows/index.js.map +1 -1
  65. package/lib/plugins/pinned-rows/types.d.ts +5 -0
  66. package/lib/plugins/pinned-rows/types.d.ts.map +1 -1
  67. package/lib/plugins/pivot/index.js +1 -1191
  68. package/lib/plugins/pivot/index.js.map +1 -1
  69. package/lib/plugins/pivot/types.d.ts +5 -0
  70. package/lib/plugins/pivot/types.d.ts.map +1 -1
  71. package/lib/plugins/print/index.js +1 -691
  72. package/lib/plugins/print/index.js.map +1 -1
  73. package/lib/plugins/print/types.d.ts +3 -0
  74. package/lib/plugins/print/types.d.ts.map +1 -1
  75. package/lib/plugins/reorder/index.js +1 -703
  76. package/lib/plugins/reorder/index.js.map +1 -1
  77. package/lib/plugins/reorder/types.d.ts +5 -0
  78. package/lib/plugins/reorder/types.d.ts.map +1 -1
  79. package/lib/plugins/responsive/index.js +1 -971
  80. package/lib/plugins/responsive/index.js.map +1 -1
  81. package/lib/plugins/responsive/types.d.ts +5 -0
  82. package/lib/plugins/responsive/types.d.ts.map +1 -1
  83. package/lib/plugins/row-reorder/index.js +1 -728
  84. package/lib/plugins/row-reorder/index.js.map +1 -1
  85. package/lib/plugins/row-reorder/types.d.ts +5 -0
  86. package/lib/plugins/row-reorder/types.d.ts.map +1 -1
  87. package/lib/plugins/selection/index.js +1 -1071
  88. package/lib/plugins/selection/index.js.map +1 -1
  89. package/lib/plugins/selection/types.d.ts +3 -0
  90. package/lib/plugins/selection/types.d.ts.map +1 -1
  91. package/lib/plugins/server-side/index.js +1 -521
  92. package/lib/plugins/server-side/index.js.map +1 -1
  93. package/lib/plugins/server-side/types.d.ts +5 -0
  94. package/lib/plugins/server-side/types.d.ts.map +1 -1
  95. package/lib/plugins/tree/index.js +1 -686
  96. package/lib/plugins/tree/index.js.map +1 -1
  97. package/lib/plugins/tree/types.d.ts +5 -0
  98. package/lib/plugins/tree/types.d.ts.map +1 -1
  99. package/lib/plugins/undo-redo/index.js +1 -584
  100. package/lib/plugins/undo-redo/index.js.map +1 -1
  101. package/lib/plugins/undo-redo/types.d.ts +5 -0
  102. package/lib/plugins/undo-redo/types.d.ts.map +1 -1
  103. package/lib/plugins/visibility/index.js +1 -792
  104. package/lib/plugins/visibility/index.js.map +1 -1
  105. package/lib/plugins/visibility/types.d.ts +5 -0
  106. package/lib/plugins/visibility/types.d.ts.map +1 -1
  107. package/package.json +1 -1
  108. package/umd/grid.all.umd.js +1 -186
  109. package/umd/grid.all.umd.js.map +1 -1
  110. package/umd/grid.umd.js +1 -90
  111. package/umd/grid.umd.js.map +1 -1
  112. package/umd/plugins/clipboard.umd.js +1 -6
  113. package/umd/plugins/clipboard.umd.js.map +1 -1
  114. package/umd/plugins/column-virtualization.umd.js +1 -1
  115. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  116. package/umd/plugins/context-menu.umd.js +1 -1
  117. package/umd/plugins/context-menu.umd.js.map +1 -1
  118. package/umd/plugins/editing.umd.js +1 -1
  119. package/umd/plugins/editing.umd.js.map +1 -1
  120. package/umd/plugins/export.umd.js +1 -13
  121. package/umd/plugins/export.umd.js.map +1 -1
  122. package/umd/plugins/filtering.umd.js +1 -1
  123. package/umd/plugins/filtering.umd.js.map +1 -1
  124. package/umd/plugins/grouping-columns.umd.js +1 -1
  125. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  126. package/umd/plugins/grouping-rows.umd.js +1 -4
  127. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  128. package/umd/plugins/master-detail.umd.js +1 -1
  129. package/umd/plugins/master-detail.umd.js.map +1 -1
  130. package/umd/plugins/multi-sort.umd.js +1 -1
  131. package/umd/plugins/multi-sort.umd.js.map +1 -1
  132. package/umd/plugins/pinned-columns.umd.js +1 -1
  133. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  134. package/umd/plugins/pinned-rows.umd.js +1 -1
  135. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  136. package/umd/plugins/pivot.umd.js +1 -1
  137. package/umd/plugins/pivot.umd.js.map +1 -1
  138. package/umd/plugins/print.umd.js +1 -75
  139. package/umd/plugins/print.umd.js.map +1 -1
  140. package/umd/plugins/reorder.umd.js +1 -1
  141. package/umd/plugins/reorder.umd.js.map +1 -1
  142. package/umd/plugins/responsive.umd.js +1 -1
  143. package/umd/plugins/responsive.umd.js.map +1 -1
  144. package/umd/plugins/row-reorder.umd.js +1 -1
  145. package/umd/plugins/row-reorder.umd.js.map +1 -1
  146. package/umd/plugins/selection.umd.js +1 -3
  147. package/umd/plugins/selection.umd.js.map +1 -1
  148. package/umd/plugins/server-side.umd.js +1 -1
  149. package/umd/plugins/server-side.umd.js.map +1 -1
  150. package/umd/plugins/tree.umd.js +1 -1
  151. package/umd/plugins/tree.umd.js.map +1 -1
  152. package/umd/plugins/undo-redo.umd.js +1 -1
  153. package/umd/plugins/undo-redo.umd.js.map +1 -1
  154. package/umd/plugins/visibility.umd.js +1 -1
  155. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,1284 +1,2 @@
1
- const B = "(Blank)";
2
- function M(w) {
3
- if (w instanceof Date) return w.getTime();
4
- const e = Number(w);
5
- return isNaN(e) ? new Date(w).getTime() : e;
6
- }
7
- function $(w, e, t = !1, n) {
8
- const r = w[e.field];
9
- if (e.operator === "blank")
10
- return r == null || r === "";
11
- if (e.operator === "notBlank")
12
- return r != null && r !== "";
13
- if (n && (e.operator === "notIn" || e.operator === "in")) {
14
- const s = n(r, w), u = Array.isArray(s) ? s : s != null ? [s] : [];
15
- if (e.operator === "notIn") {
16
- const m = e.value;
17
- return Array.isArray(m) ? u.length === 0 ? !m.includes(B) : !u.some((S) => m.includes(S)) : !0;
18
- }
19
- if (e.operator === "in") {
20
- const m = e.value;
21
- return Array.isArray(m) ? u.length === 0 ? m.includes(B) : u.some((S) => m.includes(S)) : !1;
22
- }
23
- }
24
- if (e.operator === "notIn")
25
- return r == null ? !0 : Array.isArray(e.value) && !e.value.includes(r);
26
- if (e.operator === "in")
27
- return Array.isArray(e.value) && e.value.includes(r);
28
- if (r == null) return !1;
29
- const i = String(r), a = t ? i : i.toLowerCase(), l = t ? String(e.value) : String(e.value).toLowerCase();
30
- switch (e.operator) {
31
- // Text operators
32
- case "contains":
33
- return a.includes(l);
34
- case "notContains":
35
- return !a.includes(l);
36
- case "equals":
37
- return a === l;
38
- case "notEquals":
39
- return a !== l;
40
- case "startsWith":
41
- return a.startsWith(l);
42
- case "endsWith":
43
- return a.endsWith(l);
44
- // Number/Date operators (use toNumeric for Date objects and date strings)
45
- case "lessThan":
46
- return M(r) < M(e.value);
47
- case "lessThanOrEqual":
48
- return M(r) <= M(e.value);
49
- case "greaterThan":
50
- return M(r) > M(e.value);
51
- case "greaterThanOrEqual":
52
- return M(r) >= M(e.value);
53
- case "between":
54
- return M(r) >= M(e.value) && M(r) <= M(e.valueTo);
55
- default:
56
- return !0;
57
- }
58
- }
59
- function K(w, e, t = !1, n) {
60
- return e.length ? w.filter(
61
- (r) => e.every(
62
- (i) => $(
63
- r,
64
- i,
65
- t,
66
- n?.get(i.field)
67
- )
68
- )
69
- ) : w;
70
- }
71
- function U(w) {
72
- return JSON.stringify(
73
- w.map((e) => ({
74
- field: e.field,
75
- operator: e.operator,
76
- value: e.value,
77
- valueTo: e.valueTo
78
- }))
79
- );
80
- }
81
- function O(w, e, t) {
82
- const n = /* @__PURE__ */ new Set();
83
- let r = !1;
84
- for (const i of w) {
85
- const a = i[e];
86
- if (t) {
87
- const l = t(a, i);
88
- if (Array.isArray(l)) {
89
- l.length === 0 && (r = !0);
90
- for (const s of l)
91
- s != null && n.add(s);
92
- } else l != null ? n.add(l) : r = !0;
93
- } else
94
- a != null && n.add(a);
95
- }
96
- return t && r && n.add(B), [...n].sort((i, a) => typeof i == "number" && typeof a == "number" ? i - a : String(i).localeCompare(String(a)));
97
- }
98
- function W(w, e) {
99
- const t = /* @__PURE__ */ new Map();
100
- for (const { field: r, filterValue: i } of e)
101
- t.set(r, { values: /* @__PURE__ */ new Set(), hasBlank: !1, hasExtractor: !!i });
102
- for (const r of w)
103
- for (const { field: i, filterValue: a } of e) {
104
- const l = t.get(i), s = r[i];
105
- if (a) {
106
- const u = a(s, r);
107
- if (Array.isArray(u)) {
108
- u.length === 0 && (l.hasBlank = !0);
109
- for (const m of u)
110
- m != null && l.values.add(m);
111
- } else u != null ? l.values.add(u) : l.hasBlank = !0;
112
- } else
113
- s != null && l.values.add(s);
114
- }
115
- const n = /* @__PURE__ */ new Map();
116
- for (const [r, { values: i, hasBlank: a, hasExtractor: l }] of t)
117
- l && a && i.add(B), n.set(
118
- r,
119
- [...i].sort((s, u) => typeof s == "number" && typeof u == "number" ? s - u : String(s).localeCompare(String(u)))
120
- );
121
- return n;
122
- }
123
- function j(w) {
124
- const { totalRows: e, viewportHeight: t, scrollTop: n, rowHeight: r, overscan: i } = w, a = Math.ceil(t / r);
125
- let l = Math.floor(n / r) - i;
126
- l < 0 && (l = 0);
127
- let s = l + a + i * 2;
128
- return s > e && (s = e), s === e && l > 0 && (l = Math.max(0, s - a - i * 2)), {
129
- start: l,
130
- end: s,
131
- offsetY: l * r,
132
- totalHeight: e * r
133
- };
134
- }
135
- function J(w, e) {
136
- return w <= e;
137
- }
138
- const Y = '<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>', Q = {
139
- expand: "▶",
140
- collapse: "▼",
141
- sortAsc: "▲",
142
- sortDesc: "▼",
143
- sortNone: "⇅",
144
- submenuArrow: "▶",
145
- dragHandle: "⋮⋮",
146
- toolPanel: "☰",
147
- filter: Y,
148
- filterActive: Y,
149
- print: "🖨️"
150
- };
151
- class X {
152
- /**
153
- * Plugin dependencies - declare other plugins this one requires.
154
- *
155
- * Dependencies are validated when the plugin is attached.
156
- * Required dependencies throw an error if missing.
157
- * Optional dependencies log an info message if missing.
158
- *
159
- * @example
160
- * ```typescript
161
- * static readonly dependencies: PluginDependency[] = [
162
- * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
163
- * { name: 'selection', required: false, reason: 'Enables selection-based undo' },
164
- * ];
165
- * ```
166
- */
167
- static dependencies;
168
- /**
169
- * Plugin manifest - declares owned properties, config rules, and hook priorities.
170
- *
171
- * This is read by the configuration validator to:
172
- * - Validate that required plugins are loaded when their properties are used
173
- * - Execute configRules to detect invalid/conflicting settings
174
- * - Order hook execution based on priority
175
- *
176
- * @example
177
- * ```typescript
178
- * static override readonly manifest: PluginManifest<MyConfig> = {
179
- * ownedProperties: [
180
- * { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
181
- * ],
182
- * configRules: [
183
- * { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
184
- * ],
185
- * };
186
- * ```
187
- */
188
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
- static manifest;
190
- /**
191
- * Plugin version - defaults to grid version for built-in plugins.
192
- * Third-party plugins can override with their own semver.
193
- */
194
- version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
195
- /** CSS styles to inject into the grid's shadow DOM */
196
- styles;
197
- /** Custom cell renderers keyed by type name */
198
- cellRenderers;
199
- /** Custom header renderers keyed by type name */
200
- headerRenderers;
201
- /** Custom cell editors keyed by type name */
202
- cellEditors;
203
- /** The grid instance this plugin is attached to */
204
- grid;
205
- /** Plugin configuration - merged with defaults in attach() */
206
- config;
207
- /** User-provided configuration from constructor */
208
- userConfig;
209
- /**
210
- * Plugin-level AbortController for event listener cleanup.
211
- * Created fresh in attach(), aborted in detach().
212
- * This ensures event listeners are properly cleaned up when plugins are re-attached.
213
- */
214
- #e;
215
- /**
216
- * Default configuration - subclasses should override this getter.
217
- * Note: This must be a getter (not property initializer) for proper inheritance
218
- * since property initializers run after parent constructor.
219
- */
220
- get defaultConfig() {
221
- return {};
222
- }
223
- constructor(e = {}) {
224
- this.userConfig = e;
225
- }
226
- /**
227
- * Called when the plugin is attached to a grid.
228
- * Override to set up event listeners, initialize state, etc.
229
- *
230
- * @example
231
- * ```ts
232
- * attach(grid: GridElement): void {
233
- * super.attach(grid);
234
- * // Set up document-level listeners with auto-cleanup
235
- * document.addEventListener('keydown', this.handleEscape, {
236
- * signal: this.disconnectSignal
237
- * });
238
- * }
239
- * ```
240
- */
241
- attach(e) {
242
- this.#e?.abort(), this.#e = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
243
- }
244
- /**
245
- * Called when the plugin is detached from a grid.
246
- * Override to clean up event listeners, timers, etc.
247
- *
248
- * @example
249
- * ```ts
250
- * detach(): void {
251
- * // Clean up any state not handled by disconnectSignal
252
- * this.selectedRows.clear();
253
- * this.cache = null;
254
- * }
255
- * ```
256
- */
257
- detach() {
258
- this.#e?.abort(), this.#e = void 0;
259
- }
260
- /**
261
- * Get another plugin instance from the same grid.
262
- * Use for inter-plugin communication.
263
- *
264
- * @example
265
- * ```ts
266
- * const selection = this.getPlugin(SelectionPlugin);
267
- * if (selection) {
268
- * const selectedRows = selection.getSelectedRows();
269
- * }
270
- * ```
271
- */
272
- getPlugin(e) {
273
- return this.grid?.getPlugin(e);
274
- }
275
- /**
276
- * Emit a custom event from the grid.
277
- */
278
- emit(e, t) {
279
- this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
280
- }
281
- /**
282
- * Emit a cancelable custom event from the grid.
283
- * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
284
- */
285
- emitCancelable(e, t) {
286
- const n = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
287
- return this.grid?.dispatchEvent?.(n), n.defaultPrevented;
288
- }
289
- // =========================================================================
290
- // Event Bus - Plugin-to-Plugin Communication
291
- // =========================================================================
292
- /**
293
- * Subscribe to an event from another plugin.
294
- * The subscription is automatically cleaned up when this plugin is detached.
295
- *
296
- * @category Plugin Development
297
- * @param eventType - The event type to listen for (e.g., 'filter-change')
298
- * @param callback - The callback to invoke when the event is emitted
299
- *
300
- * @example
301
- * ```typescript
302
- * // In attach() or other initialization
303
- * this.on('filter-change', (detail) => {
304
- * console.log('Filter changed:', detail);
305
- * });
306
- * ```
307
- */
308
- on(e, t) {
309
- this.grid?._pluginManager?.subscribe(this, e, t);
310
- }
311
- /**
312
- * Unsubscribe from a plugin event.
313
- *
314
- * @category Plugin Development
315
- * @param eventType - The event type to stop listening for
316
- *
317
- * @example
318
- * ```typescript
319
- * this.off('filter-change');
320
- * ```
321
- */
322
- off(e) {
323
- this.grid?._pluginManager?.unsubscribe(this, e);
324
- }
325
- /**
326
- * Emit an event to other plugins via the Event Bus.
327
- * This is for inter-plugin communication only; it does NOT dispatch DOM events.
328
- * Use `emit()` to dispatch DOM events that external consumers can listen to.
329
- *
330
- * @category Plugin Development
331
- * @param eventType - The event type to emit (should be declared in manifest.events)
332
- * @param detail - The event payload
333
- *
334
- * @example
335
- * ```typescript
336
- * // Emit to other plugins (not DOM)
337
- * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });
338
- *
339
- * // For DOM events that consumers can addEventListener to:
340
- * this.emit('filter-change', { field: 'name', value: 'Alice' });
341
- * ```
342
- */
343
- emitPluginEvent(e, t) {
344
- this.grid?._pluginManager?.emitPluginEvent(e, t);
345
- }
346
- /**
347
- * Request a re-render of the grid.
348
- * Uses ROWS phase - does NOT trigger processColumns hooks.
349
- */
350
- requestRender() {
351
- this.grid?.requestRender?.();
352
- }
353
- /**
354
- * Request a columns re-render of the grid.
355
- * Uses COLUMNS phase - triggers processColumns hooks.
356
- * Use this when your plugin needs to reprocess column configuration.
357
- */
358
- requestColumnsRender() {
359
- this.grid?.requestColumnsRender?.();
360
- }
361
- /**
362
- * Request a re-render and restore focus styling afterward.
363
- * Use this when a plugin action (like expand/collapse) triggers a render
364
- * but needs to maintain keyboard navigation focus.
365
- */
366
- requestRenderWithFocus() {
367
- this.grid?.requestRenderWithFocus?.();
368
- }
369
- /**
370
- * Request a lightweight style update without rebuilding DOM.
371
- * Use this instead of requestRender() when only CSS classes need updating.
372
- */
373
- requestAfterRender() {
374
- this.grid?.requestAfterRender?.();
375
- }
376
- /**
377
- * Get the current rows from the grid.
378
- */
379
- get rows() {
380
- return this.grid?.rows ?? [];
381
- }
382
- /**
383
- * Get the original unfiltered/unprocessed rows from the grid.
384
- * Use this when you need all source data regardless of active filters.
385
- */
386
- get sourceRows() {
387
- return this.grid?.sourceRows ?? [];
388
- }
389
- /**
390
- * Get the current columns from the grid.
391
- */
392
- get columns() {
393
- return this.grid?.columns ?? [];
394
- }
395
- /**
396
- * Get only visible columns from the grid (excludes hidden).
397
- * Use this for rendering that needs to match the grid template.
398
- */
399
- get visibleColumns() {
400
- return this.grid?._visibleColumns ?? [];
401
- }
402
- /**
403
- * Get the grid as an HTMLElement for direct DOM operations.
404
- * Use sparingly - prefer the typed GridElementRef API when possible.
405
- *
406
- * @example
407
- * ```ts
408
- * const width = this.gridElement.clientWidth;
409
- * this.gridElement.classList.add('my-plugin-active');
410
- * ```
411
- */
412
- get gridElement() {
413
- return this.grid;
414
- }
415
- /**
416
- * Get the disconnect signal for event listener cleanup.
417
- * This signal is aborted when the grid disconnects from the DOM.
418
- * Use this when adding event listeners that should be cleaned up automatically.
419
- *
420
- * Best for:
421
- * - Document/window-level listeners added in attach()
422
- * - Listeners on the grid element itself
423
- * - Any listener that should persist across renders
424
- *
425
- * Not needed for:
426
- * - Listeners on elements created in afterRender() (removed with element)
427
- *
428
- * @example
429
- * element.addEventListener('click', handler, { signal: this.disconnectSignal });
430
- * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
431
- */
432
- get disconnectSignal() {
433
- return this.#e?.signal ?? this.grid?.disconnectSignal;
434
- }
435
- /**
436
- * Get the grid-level icons configuration.
437
- * Returns merged icons (user config + defaults).
438
- */
439
- get gridIcons() {
440
- const e = this.grid?.gridConfig?.icons ?? {};
441
- return { ...Q, ...e };
442
- }
443
- // #region Animation Helpers
444
- /**
445
- * Check if animations are enabled at the grid level.
446
- * Respects gridConfig.animation.mode and the CSS variable set by the grid.
447
- *
448
- * Plugins should use this to skip animations when:
449
- * - Animation mode is 'off' or `false`
450
- * - User prefers reduced motion and mode is 'reduced-motion' (default)
451
- *
452
- * @example
453
- * ```ts
454
- * private get animationStyle(): 'slide' | 'fade' | false {
455
- * if (!this.isAnimationEnabled) return false;
456
- * return this.config.animation ?? 'slide';
457
- * }
458
- * ```
459
- */
460
- get isAnimationEnabled() {
461
- const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
462
- if (e === !1 || e === "off") return !1;
463
- if (e === !0 || e === "on") return !0;
464
- const t = this.gridElement;
465
- return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
466
- }
467
- /**
468
- * Get the animation duration in milliseconds from CSS variable.
469
- * Falls back to 200ms if not set.
470
- *
471
- * Plugins can use this for their animation timing to stay consistent
472
- * with the grid-level animation.duration setting.
473
- *
474
- * @example
475
- * ```ts
476
- * element.animate(keyframes, { duration: this.animationDuration });
477
- * ```
478
- */
479
- get animationDuration() {
480
- const e = this.gridElement;
481
- if (e) {
482
- const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), n = parseInt(t, 10);
483
- if (!isNaN(n)) return n;
484
- }
485
- return 200;
486
- }
487
- // #endregion
488
- /**
489
- * Resolve an icon value to string or HTMLElement.
490
- * Checks plugin config first, then grid-level icons, then defaults.
491
- *
492
- * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
493
- * @param pluginOverride - Optional plugin-level override
494
- * @returns The resolved icon value
495
- */
496
- resolveIcon(e, t) {
497
- return t !== void 0 ? t : this.gridIcons[e];
498
- }
499
- /**
500
- * Set an icon value on an element.
501
- * Handles both string (text/HTML) and HTMLElement values.
502
- *
503
- * @param element - The element to set the icon on
504
- * @param icon - The icon value (string or HTMLElement)
505
- */
506
- setIcon(e, t) {
507
- typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
508
- }
509
- /**
510
- * Log a warning message.
511
- */
512
- warn(e) {
513
- console.warn(`[tbw-grid:${this.name}] ${e}`);
514
- }
515
- // #endregion
516
- }
517
- function Z(w) {
518
- return w.meta?.utility === !0;
519
- }
520
- const ee = '@layer tbw-plugins{tbw-grid .tbw-quick-filter-input{flex:1;max-width:300px;height:var(--tbw-input-height, 1.75rem);padding:var(--tbw-input-padding, 0 .5rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);color:var(--tbw-color-fg);font-size:var(--tbw-font-size-sm, .8125rem)}tbw-grid .tbw-quick-filter-input:focus{outline:none;border-color:var(--tbw-color-accent)}tbw-grid .header-cell.filtered:before{content:"";position:absolute;top:var(--tbw-spacing-xs, .25rem);right:var(--tbw-spacing-xs, .25rem);width:var(--tbw-indicator-size, .375rem);height:var(--tbw-indicator-size, .375rem);background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:50%}tbw-grid .tbw-filter-btn{display:var(--tbw-filter-btn-display, inline-flex);visibility:var(--tbw-filter-btn-visibility, visible);align-items:center;justify-content:center;background:transparent;border:none;cursor:pointer;padding:2px;margin-left:var(--tbw-spacing-xs, .25rem);opacity:.4;transition:opacity .15s,visibility 0s,display 0s allow-discrete;color:inherit;vertical-align:middle;transition-behavior:allow-discrete}tbw-grid .tbw-filter-btn:hover,tbw-grid .tbw-filter-btn.active{opacity:1;visibility:visible;display:inline-flex}tbw-grid .tbw-filter-btn.active{color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}tbw-grid .header-row .cell:hover .tbw-filter-btn,tbw-grid .header-row .cell.filtered .tbw-filter-btn{display:inline-flex;visibility:visible}}', te = "@layer tbw-plugins{.tbw-filter-panel{position:fixed;background:var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));color:var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));border:1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-panel-radius, var(--tbw-border-radius, .25rem));box-shadow:0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0, 0, 0, .1), rgba(0, 0, 0, .3))));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));z-index:10000;min-width:200px;max-width:280px;max-height:350px;display:flex;flex-direction:column;font-family:var(--tbw-font-family, system-ui, sans-serif);font-size:var(--tbw-font-size, .8125rem);transform-origin:top center}.tbw-filter-panel.tbw-filter-panel-above{transform-origin:bottom center}.tbw-filter-panel.tbw-filter-panel-animated{animation:tbw-filter-panel-enter var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}.tbw-filter-panel.tbw-filter-panel-above.tbw-filter-panel-animated{animation:tbw-filter-panel-enter-above var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}@keyframes tbw-filter-panel-enter{0%{opacity:0;transform:scaleY(.3) translateY(-10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@keyframes tbw-filter-panel-enter-above{0%{opacity:0;transform:scaleY(.3) translateY(10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@supports (anchor-name: --test){.tbw-filter-panel{position-anchor:--tbw-filter-anchor;top:anchor(bottom);left:anchor(left);margin-top:4px;position-try-fallbacks:flip-inline,flip-block,flip-block flip-inline}}.tbw-filter-search{margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));min-height:var(--tbw-filter-item-height, 28px)}.tbw-filter-search-input{height:var(--tbw-filter-item-height, 28px);width:100%;padding:var(--tbw-filter-search-padding, var(--tbw-spacing-sm, .375rem) var(--tbw-spacing-md, .5rem));background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, var(--tbw-border-radius, .25rem));font-size:inherit;box-sizing:border-box}.tbw-filter-search-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-actions{display:flex;padding:var(--tbw-button-padding-sm, .25rem .125rem);margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-bottom:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));min-height:var(--tbw-filter-item-height, 28px)}.tbw-filter-actions .tbw-filter-value-item{flex:1}.tbw-filter-values{flex:1;overflow-y:auto;margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));max-height:180px;position:relative}.tbw-filter-values-spacer{width:1px}.tbw-filter-values-content{position:absolute;top:0;left:0;right:0}.tbw-filter-value-item{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding-sm, .25rem .125rem);cursor:pointer;border-radius:3px;height:var(--tbw-filter-item-height, 28px)}.tbw-filter-value-item:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-checkbox{margin:0;cursor:pointer;accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}.tbw-filter-no-match{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem)) 0;text-align:center;font-style:italic}.tbw-filter-buttons{display:flex;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding-top:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-top:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-apply-btn{flex:1;padding:var(--tbw-filter-btn-padding, var(--tbw-button-padding, .375rem .75rem));background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));color:var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));border:none;border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);font-weight:var(--tbw-filter-btn-font-weight, 500);min-height:var(--tbw-filter-btn-min-height, auto)}.tbw-filter-apply-btn:hover{filter:brightness(.9)}.tbw-filter-clear-btn{flex:1;padding:var(--tbw-filter-btn-padding, var(--tbw-button-padding, .375rem .75rem));background:transparent;color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);font-weight:var(--tbw-filter-btn-font-weight, 500);min-height:var(--tbw-filter-btn-min-height, auto)}.tbw-filter-clear-btn:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-range-inputs,.tbw-filter-date-range{display:flex;align-items:flex-end;gap:var(--tbw-spacing-sm, .375rem);margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-filter-range-group,.tbw-filter-date-group{display:flex;flex-direction:column;gap:var(--tbw-spacing-xs, .25rem);flex:1}.tbw-filter-range-label{font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)))}.tbw-filter-range-input,.tbw-filter-date-input{width:100%;height:var(--tbw-filter-item-height, 28px);padding:var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-sm, .375rem);background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, var(--tbw-border-radius, .25rem));font-size:inherit;box-sizing:border-box}.tbw-filter-range-input:focus,.tbw-filter-date-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-range-separator{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding-bottom:var(--tbw-spacing-xs, .25rem)}.tbw-filter-blank-option{display:flex;align-items:center;gap:var(--tbw-spacing-sm, .375rem);padding:var(--tbw-spacing-xs, .25rem) 0;margin-bottom:var(--tbw-spacing-xs, .25rem);font-size:var(--tbw-font-size-sm, .8125rem);cursor:pointer;-webkit-user-select:none;user-select:none}.tbw-filter-blank-checkbox{accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));margin:0;cursor:pointer}.tbw-filter-date-range.tbw-filter-disabled{opacity:.4;pointer-events:none}.tbw-filter-range-slider{position:relative;height:24px;margin:var(--tbw-spacing-md, .5rem) 0 var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-filter-range-track{position:absolute;top:50%;left:0;right:0;height:4px;background:var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:2px;transform:translateY(-50%)}.tbw-filter-range-fill{position:absolute;top:50%;height:4px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:2px;transform:translateY(-50%)}.tbw-filter-range-thumb{position:absolute;top:0;width:100%;height:100%;background:none;pointer-events:none;-webkit-appearance:none;appearance:none}.tbw-filter-range-thumb::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border:2px solid var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));border-radius:50%;cursor:pointer;pointer-events:all;box-shadow:0 1px 3px #0003}.tbw-filter-range-thumb::-moz-range-thumb{width:16px;height:16px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border:2px solid var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));border-radius:50%;cursor:pointer;pointer-events:all;box-shadow:0 1px 3px #0003}.tbw-filter-range-thumb::-webkit-slider-thumb:hover{transform:scale(1.1)}.tbw-filter-range-thumb::-moz-range-thumb:hover{transform:scale(1.1)}}";
521
- class _ extends X {
522
- /**
523
- * Plugin manifest - declares events emitted by this plugin.
524
- * @internal
525
- */
526
- static manifest = {
527
- events: [
528
- {
529
- type: "filter-applied",
530
- description: "Emitted when filter criteria change. Subscribers can react to row visibility changes."
531
- }
532
- ],
533
- queries: [
534
- {
535
- type: "getContextMenuItems",
536
- description: "Contributes filter-related items to the header context menu"
537
- }
538
- ]
539
- };
540
- /** @internal */
541
- name = "filtering";
542
- /** @internal */
543
- styles = ee;
544
- /** @internal */
545
- get defaultConfig() {
546
- return {
547
- debounceMs: 300,
548
- caseSensitive: !1,
549
- trimInput: !0,
550
- useWorker: !0
551
- };
552
- }
553
- // #region Helpers
554
- /**
555
- * Check if filtering is enabled at the grid level.
556
- * Grid-wide `filterable: false` disables filtering for all columns.
557
- */
558
- isFilteringEnabled() {
559
- return this.grid.effectiveConfig?.filterable !== !1;
560
- }
561
- /**
562
- * Check if a specific column is filterable, respecting both grid-level and column-level settings.
563
- */
564
- isColumnFilterable(e) {
565
- return this.isFilteringEnabled() ? e.filterable !== !1 : !1;
566
- }
567
- /**
568
- * Build a map of field → filterValue extractor for columns that have one.
569
- * Used to pass array-aware value extraction to the pure filter functions.
570
- */
571
- getFilterValues() {
572
- const e = this.grid.effectiveConfig?.columns;
573
- if (!e) return;
574
- let t;
575
- for (const n of e)
576
- n.field && n.filterValue && (t || (t = /* @__PURE__ */ new Map()), t.set(n.field, n.filterValue));
577
- return t;
578
- }
579
- // #endregion
580
- // #region Internal State
581
- filters = /* @__PURE__ */ new Map();
582
- cachedResult = null;
583
- cacheKey = null;
584
- /** Spot-check of input rows for cache invalidation when upstream plugins (e.g. sort) change row order */
585
- cachedInputSpot = null;
586
- openPanelField = null;
587
- panelElement = null;
588
- panelAnchorElement = null;
589
- // For CSS anchor positioning cleanup
590
- searchText = /* @__PURE__ */ new Map();
591
- excludedValues = /* @__PURE__ */ new Map();
592
- panelAbortController = null;
593
- // For panel-scoped listeners
594
- globalStylesInjected = !1;
595
- // Virtualization constants for filter value list
596
- static DEFAULT_LIST_ITEM_HEIGHT = 28;
597
- static LIST_OVERSCAN = 3;
598
- static LIST_BYPASS_THRESHOLD = 50;
599
- // Don't virtualize if < 50 items
600
- /**
601
- * Get the item height from CSS variable or fallback to default.
602
- * Reads --tbw-filter-item-height from the panel element.
603
- */
604
- getListItemHeight() {
605
- if (this.panelElement) {
606
- const e = getComputedStyle(this.panelElement).getPropertyValue("--tbw-filter-item-height");
607
- if (e && e.trim()) {
608
- const t = parseFloat(e);
609
- if (!isNaN(t) && t > 0)
610
- return t;
611
- }
612
- }
613
- return _.DEFAULT_LIST_ITEM_HEIGHT;
614
- }
615
- /**
616
- * Compute the inclusion (selected) map from the current filters and excluded values.
617
- * For set filters this is: uniqueValues \ excludedValues.
618
- * Only includes entries for fields that have a set filter.
619
- * Uses a single-pass batch extraction to avoid iterating sourceRows per field.
620
- */
621
- computeSelected() {
622
- const e = [];
623
- for (const [r, i] of this.filters) {
624
- if (i.type !== "set" || i.operator !== "notIn") continue;
625
- const a = this.grid.effectiveConfig?.columns?.find((l) => l.field === r);
626
- e.push({ field: r, filterValue: a?.filterValue });
627
- }
628
- if (e.length === 0) return {};
629
- const t = W(this.sourceRows, e), n = {};
630
- for (const { field: r } of e) {
631
- const i = this.excludedValues.get(r), a = t.get(r) ?? [];
632
- n[r] = i ? a.filter((l) => !i.has(l)) : a;
633
- }
634
- return n;
635
- }
636
- /**
637
- * Sync excludedValues map from a filter model (for set filters).
638
- */
639
- syncExcludedValues(e, t) {
640
- t ? t.type === "set" && t.operator === "notIn" && Array.isArray(t.value) ? this.excludedValues.set(e, new Set(t.value)) : t.type === "set" && this.excludedValues.delete(e) : this.excludedValues.delete(e);
641
- }
642
- // #endregion
643
- // #region Lifecycle
644
- /** @internal */
645
- attach(e) {
646
- super.attach(e), this.injectGlobalStyles();
647
- }
648
- /** @internal */
649
- detach() {
650
- this.filters.clear(), this.cachedResult = null, this.cacheKey = null, this.cachedInputSpot = null, this.openPanelField = null, this.panelElement && (this.panelElement.remove(), this.panelElement = null), this.searchText.clear(), this.excludedValues.clear(), this.panelAbortController?.abort(), this.panelAbortController = null;
651
- }
652
- // #endregion
653
- // #region Query Handlers
654
- /**
655
- * Handle inter-plugin queries.
656
- * Contributes filter-related items to the header context menu.
657
- * @internal
658
- */
659
- handleQuery(e) {
660
- if (e.type === "getContextMenuItems") {
661
- const t = e.context;
662
- if (!t.isHeader) return;
663
- const n = t.column;
664
- if (!n?.field || !this.isFilteringEnabled() || !this.isColumnFilterable(n)) return;
665
- const r = [], i = this.isFieldFiltered(n.field), a = this.filters.size > 0;
666
- return i && r.push({
667
- id: "filtering/clear-column-filter",
668
- label: "Clear Filter",
669
- icon: "✕",
670
- order: 20,
671
- action: () => this.clearFieldFilter(n.field)
672
- }), a && r.push({
673
- id: "filtering/clear-all-filters",
674
- label: "Clear All Filters",
675
- icon: "✕",
676
- order: 21,
677
- disabled: !a,
678
- action: () => this.clearAllFilters()
679
- }), r.length > 0 ? r : void 0;
680
- }
681
- }
682
- // #endregion
683
- // #region Hooks
684
- /** @internal */
685
- processRows(e) {
686
- const t = [...this.filters.values()];
687
- if (!t.length) return [...e];
688
- if (this.config.filterHandler)
689
- return this.cachedResult ? this.cachedResult : [...e];
690
- const n = U(t), r = {
691
- len: e.length,
692
- first: e[0],
693
- mid: e[Math.floor(e.length / 2)],
694
- last: e[e.length - 1]
695
- }, i = this.cachedInputSpot != null && r.len === this.cachedInputSpot.len && r.first === this.cachedInputSpot.first && r.mid === this.cachedInputSpot.mid && r.last === this.cachedInputSpot.last;
696
- if (this.cacheKey === n && this.cachedResult && i)
697
- return this.cachedResult;
698
- const a = K(
699
- [...e],
700
- t,
701
- this.config.caseSensitive,
702
- this.getFilterValues()
703
- );
704
- return this.cachedResult = a, this.cacheKey = n, this.cachedInputSpot = r, a;
705
- }
706
- /** @internal */
707
- afterRender() {
708
- const e = this.gridElement;
709
- if (!e) return;
710
- e.querySelectorAll('[part~="header-cell"]').forEach((n) => {
711
- const r = n.getAttribute("data-col");
712
- if (r === null) return;
713
- const i = this.visibleColumns[parseInt(r, 10)];
714
- if (!i || !this.isColumnFilterable(i) || Z(i)) return;
715
- const a = i.field;
716
- if (!a) return;
717
- const l = this.filters.has(a);
718
- let s = n.querySelector(".tbw-filter-btn");
719
- if (s) {
720
- const S = s.classList.contains("active");
721
- if (s.classList.toggle("active", l), n.classList.toggle("filtered", l), S !== l) {
722
- const f = l ? "filterActive" : "filter";
723
- this.setIcon(s, this.resolveIcon(f));
724
- }
725
- return;
726
- }
727
- s = document.createElement("button"), s.className = "tbw-filter-btn", s.setAttribute("aria-label", `Filter ${i.header ?? a}`);
728
- const u = l ? "filterActive" : "filter";
729
- this.setIcon(s, this.resolveIcon(u)), l && (s.classList.add("active"), n.classList.add("filtered")), s.addEventListener("click", (S) => {
730
- S.stopPropagation(), this.toggleFilterPanel(a, i, s);
731
- });
732
- const m = n.querySelector(".resize-handle");
733
- m ? n.insertBefore(s, m) : n.appendChild(s);
734
- });
735
- }
736
- // #endregion
737
- // #region Public API
738
- /**
739
- * Set a filter on a specific field.
740
- * Pass null to remove the filter.
741
- */
742
- setFilter(e, t) {
743
- if (t === null)
744
- this.filters.delete(e), this.syncExcludedValues(e, null);
745
- else {
746
- const n = { ...t, field: e };
747
- this.filters.set(e, n), this.syncExcludedValues(e, n);
748
- }
749
- this.cachedResult = null, this.cacheKey = null, this.cachedInputSpot = null, this.emit("filter-change", {
750
- filters: [...this.filters.values()],
751
- filteredRowCount: 0,
752
- // Will be accurate after processRows
753
- selected: this.computeSelected()
754
- }), this.emitPluginEvent("filter-applied", { filters: [...this.filters.values()] }), this.requestRender();
755
- }
756
- /**
757
- * Get the current filter for a field.
758
- */
759
- getFilter(e) {
760
- return this.filters.get(e);
761
- }
762
- /**
763
- * Get all active filters.
764
- */
765
- getFilters() {
766
- return [...this.filters.values()];
767
- }
768
- /**
769
- * Alias for getFilters() to match functional API naming.
770
- */
771
- getFilterModel() {
772
- return this.getFilters();
773
- }
774
- /**
775
- * Set filters from an array (replaces all existing filters).
776
- */
777
- setFilterModel(e) {
778
- this.filters.clear(), this.excludedValues.clear();
779
- for (const t of e)
780
- this.filters.set(t.field, t), this.syncExcludedValues(t.field, t);
781
- this.cachedResult = null, this.cacheKey = null, this.cachedInputSpot = null, this.emit("filter-change", {
782
- filters: [...this.filters.values()],
783
- filteredRowCount: 0,
784
- selected: this.computeSelected()
785
- }), this.emitPluginEvent("filter-applied", { filters: [...this.filters.values()] }), this.requestRender();
786
- }
787
- /**
788
- * Clear all filters.
789
- */
790
- clearAllFilters() {
791
- this.filters.clear(), this.excludedValues.clear(), this.searchText.clear(), this.applyFiltersInternal();
792
- }
793
- /**
794
- * Clear filter for a specific field.
795
- */
796
- clearFieldFilter(e) {
797
- this.filters.delete(e), this.excludedValues.delete(e), this.searchText.delete(e), this.applyFiltersInternal();
798
- }
799
- /**
800
- * Check if a field has an active filter.
801
- */
802
- isFieldFiltered(e) {
803
- return this.filters.has(e);
804
- }
805
- /**
806
- * Get the count of filtered rows (from cache).
807
- */
808
- getFilteredRowCount() {
809
- return this.cachedResult?.length ?? this.rows.length;
810
- }
811
- /**
812
- * Get all active filters (alias for getFilters).
813
- */
814
- getActiveFilters() {
815
- return this.getFilters();
816
- }
817
- /**
818
- * Get unique values for a field (for set filter dropdowns).
819
- * Uses sourceRows to include all values regardless of current filter.
820
- * When a column has `filterValue`, individual extracted values are returned.
821
- */
822
- getUniqueValues(e) {
823
- const n = this.grid.effectiveConfig?.columns?.find((r) => r.field === e)?.filterValue;
824
- return O(this.sourceRows, e, n);
825
- }
826
- // #endregion
827
- // #region Private Methods
828
- /**
829
- * Copy CSS classes and data attributes from grid to filter panel.
830
- * This ensures theme classes (e.g., .eds-theme) cascade to the panel.
831
- */
832
- copyGridThemeContext(e) {
833
- const t = this.gridElement;
834
- if (!t) return;
835
- for (const r of t.classList)
836
- r.startsWith("tbw-") || r === "selecting" || e.classList.add(r);
837
- const n = t.dataset.theme;
838
- n && (e.dataset.theme = n);
839
- }
840
- /**
841
- * Inject global styles for filter panel (rendered in document.body)
842
- */
843
- injectGlobalStyles() {
844
- if (this.globalStylesInjected) return;
845
- if (document.getElementById("tbw-filter-panel-styles")) {
846
- this.globalStylesInjected = !0;
847
- return;
848
- }
849
- const e = document.createElement("style");
850
- e.id = "tbw-filter-panel-styles", e.textContent = te, document.head.appendChild(e), this.globalStylesInjected = !0;
851
- }
852
- /**
853
- * Toggle the filter panel for a field
854
- */
855
- toggleFilterPanel(e, t, n) {
856
- if (this.openPanelField === e) {
857
- this.closeFilterPanel();
858
- return;
859
- }
860
- this.closeFilterPanel();
861
- const r = document.createElement("div");
862
- if (r.className = "tbw-filter-panel", this.copyGridThemeContext(r), this.isAnimationEnabled && r.classList.add("tbw-filter-panel-animated"), this.panelElement = r, this.openPanelField = e, this.config.valuesHandler) {
863
- r.innerHTML = '<div class="tbw-filter-loading">Loading...</div>', document.body.appendChild(r), this.positionPanel(r, n), this.setupPanelCloseHandler(r, n), this.config.valuesHandler(e, t).then((a) => {
864
- this.openPanelField !== e || !this.panelElement || (r.innerHTML = "", this.renderPanelContent(e, t, r, a));
865
- });
866
- return;
867
- }
868
- const i = O(this.sourceRows, e, t.filterValue);
869
- document.body.appendChild(r), this.positionPanel(r, n), this.renderPanelContent(e, t, r, i), this.setupPanelCloseHandler(r, n);
870
- }
871
- /**
872
- * Render filter panel content with given values
873
- */
874
- renderPanelContent(e, t, n, r) {
875
- let i = this.excludedValues.get(e);
876
- i || (i = /* @__PURE__ */ new Set(), this.excludedValues.set(e, i));
877
- const a = this.searchText.get(e) ?? "", l = {
878
- field: e,
879
- column: t,
880
- uniqueValues: r,
881
- excludedValues: i,
882
- searchText: a,
883
- applySetFilter: (u) => {
884
- this.applySetFilter(e, u), this.closeFilterPanel();
885
- },
886
- applyTextFilter: (u, m, S) => {
887
- this.applyTextFilter(e, u, m, S), this.closeFilterPanel();
888
- },
889
- clearFilter: () => {
890
- this.clearFieldFilter(e), this.closeFilterPanel();
891
- },
892
- closePanel: () => this.closeFilterPanel()
893
- };
894
- let s = !1;
895
- if (this.config.filterPanelRenderer && (this.config.filterPanelRenderer(n, l), s = n.children.length > 0), !s && t.type) {
896
- const u = this.grid.effectiveConfig.typeDefaults?.[t.type];
897
- u?.filterPanelRenderer && (u.filterPanelRenderer(n, l), s = n.children.length > 0);
898
- }
899
- if (!s) {
900
- const u = t.type;
901
- u === "number" ? this.renderNumberFilterPanel(n, l, r) : u === "date" ? this.renderDateFilterPanel(n, l, r) : this.renderDefaultFilterPanel(n, l, r, i);
902
- }
903
- }
904
- /**
905
- * Setup click-outside handler to close the panel
906
- */
907
- setupPanelCloseHandler(e, t) {
908
- this.panelAbortController = new AbortController(), setTimeout(() => {
909
- document.addEventListener(
910
- "click",
911
- (n) => {
912
- !e.contains(n.target) && n.target !== t && this.closeFilterPanel();
913
- },
914
- { signal: this.panelAbortController?.signal }
915
- );
916
- }, 0);
917
- }
918
- /**
919
- * Close the filter panel
920
- */
921
- closeFilterPanel() {
922
- const e = this.panelElement;
923
- e && (e.remove(), this.panelElement = null), this.panelAnchorElement && (this.panelAnchorElement.style.anchorName = "", this.panelAnchorElement = null), this.openPanelField = null, this.panelAbortController?.abort(), this.panelAbortController = null;
924
- }
925
- /** Cache for CSS anchor positioning support check */
926
- static supportsAnchorPositioning = null;
927
- /**
928
- * Check if browser supports CSS Anchor Positioning
929
- */
930
- static checkAnchorPositioningSupport() {
931
- return _.supportsAnchorPositioning === null && (_.supportsAnchorPositioning = CSS.supports("anchor-name", "--test")), _.supportsAnchorPositioning;
932
- }
933
- /**
934
- * Position the panel below the header cell
935
- * Uses CSS Anchor Positioning if supported, falls back to JS positioning
936
- */
937
- positionPanel(e, t) {
938
- const r = t.closest(".cell") ?? t;
939
- if (r.style.anchorName = "--tbw-filter-anchor", this.panelAnchorElement = r, _.checkAnchorPositioningSupport()) {
940
- requestAnimationFrame(() => {
941
- const a = e.getBoundingClientRect(), l = r.getBoundingClientRect();
942
- a.top < l.top && e.classList.add("tbw-filter-panel-above");
943
- });
944
- return;
945
- }
946
- const i = r.getBoundingClientRect();
947
- e.style.position = "fixed", e.style.top = `${i.bottom + 4}px`, e.style.left = `${i.left}px`, requestAnimationFrame(() => {
948
- const a = e.getBoundingClientRect();
949
- a.right > window.innerWidth - 8 && (e.style.left = `${i.right - a.width}px`), a.bottom > window.innerHeight - 8 && (e.style.top = `${i.top - a.height - 4}px`, e.classList.add("tbw-filter-panel-above"));
950
- });
951
- }
952
- /**
953
- * Render the default filter panel content
954
- */
955
- renderDefaultFilterPanel(e, t, n, r) {
956
- const { field: i, column: a } = t, l = this.getListItemHeight(), s = (o) => {
957
- if (o == null) return "(Blank)";
958
- if (a.format && !a.filterValue) {
959
- const d = a.format(o, void 0);
960
- if (d) return d;
961
- }
962
- return String(o);
963
- };
964
- n = n.slice().sort((o, d) => s(o).localeCompare(s(d)));
965
- const u = document.createElement("div");
966
- u.className = "tbw-filter-search";
967
- const m = document.createElement("input");
968
- m.type = "text", m.placeholder = "Search...", m.className = "tbw-filter-search-input", m.value = this.searchText.get(i) ?? "", u.appendChild(m), e.appendChild(u);
969
- const S = document.createElement("div");
970
- S.className = "tbw-filter-actions";
971
- const f = document.createElement("label");
972
- f.className = "tbw-filter-value-item", f.style.padding = "0", f.style.margin = "0";
973
- const v = document.createElement("input");
974
- v.type = "checkbox", v.className = "tbw-filter-checkbox";
975
- const L = document.createElement("span");
976
- L.textContent = "Select All", f.appendChild(v), f.appendChild(L), S.appendChild(f);
977
- const C = () => {
978
- const o = [...k.values()], d = o.every((p) => p), g = o.every((p) => !p);
979
- v.checked = d, v.indeterminate = !d && !g;
980
- };
981
- v.addEventListener("change", () => {
982
- const o = v.checked;
983
- for (const d of k.keys())
984
- k.set(d, o);
985
- C(), A();
986
- }), e.appendChild(S);
987
- const T = document.createElement("div");
988
- T.className = "tbw-filter-values";
989
- const R = document.createElement("div");
990
- R.className = "tbw-filter-values-spacer", T.appendChild(R);
991
- const F = document.createElement("div");
992
- F.className = "tbw-filter-values-content", T.appendChild(F);
993
- const k = /* @__PURE__ */ new Map();
994
- n.forEach((o) => {
995
- const d = o == null ? "__null__" : String(o);
996
- k.set(d, !r.has(o));
997
- }), C();
998
- let I = [];
999
- const N = (o, d) => {
1000
- const g = s(o), p = o == null ? "__null__" : String(o), h = document.createElement("label");
1001
- h.className = "tbw-filter-value-item", h.style.position = "absolute", h.style.top = `calc(var(--tbw-filter-item-height, 28px) * ${d})`, h.style.left = "0", h.style.right = "0", h.style.boxSizing = "border-box";
1002
- const y = document.createElement("input");
1003
- y.type = "checkbox", y.className = "tbw-filter-checkbox", y.checked = k.get(p) ?? !0, y.dataset.value = p, y.addEventListener("change", () => {
1004
- k.set(p, y.checked), C();
1005
- });
1006
- const c = document.createElement("span");
1007
- return c.textContent = g, h.appendChild(y), h.appendChild(c), h;
1008
- }, A = () => {
1009
- const o = I.length, d = T.clientHeight, g = T.scrollTop;
1010
- if (R.style.height = `${o * l}px`, J(o, _.LIST_BYPASS_THRESHOLD / 3)) {
1011
- F.innerHTML = "", F.style.transform = "translateY(0px)", I.forEach((h, y) => {
1012
- F.appendChild(N(h, y));
1013
- });
1014
- return;
1015
- }
1016
- const p = j({
1017
- totalRows: o,
1018
- viewportHeight: d,
1019
- scrollTop: g,
1020
- rowHeight: l,
1021
- overscan: _.LIST_OVERSCAN
1022
- });
1023
- F.style.transform = `translateY(${p.offsetY}px)`, F.innerHTML = "";
1024
- for (let h = p.start; h < p.end; h++)
1025
- F.appendChild(N(I[h], h - p.start));
1026
- }, V = (o) => {
1027
- const d = this.config.caseSensitive ?? !1, g = d ? o : o.toLowerCase();
1028
- if (I = n.filter((p) => {
1029
- const h = s(p), y = d ? h : h.toLowerCase();
1030
- return !o || y.includes(g);
1031
- }), I.length === 0) {
1032
- R.style.height = "0px", F.innerHTML = "";
1033
- const p = document.createElement("div");
1034
- p.className = "tbw-filter-no-match", p.textContent = "No matching values", F.appendChild(p);
1035
- return;
1036
- }
1037
- A();
1038
- };
1039
- T.addEventListener(
1040
- "scroll",
1041
- () => {
1042
- I.length > 0 && A();
1043
- },
1044
- { passive: !0 }
1045
- ), V(m.value), e.appendChild(T);
1046
- let H;
1047
- m.addEventListener("input", () => {
1048
- clearTimeout(H), H = setTimeout(() => {
1049
- this.searchText.set(i, m.value), V(m.value);
1050
- }, this.config.debounceMs ?? 150);
1051
- });
1052
- const x = document.createElement("div");
1053
- x.className = "tbw-filter-buttons";
1054
- const E = document.createElement("button");
1055
- E.className = "tbw-filter-apply-btn", E.textContent = "Apply", E.addEventListener("click", () => {
1056
- const o = [];
1057
- for (const [d, g] of k)
1058
- if (!g)
1059
- if (d === "__null__")
1060
- o.push(null);
1061
- else {
1062
- const p = n.find((h) => String(h) === d);
1063
- o.push(p !== void 0 ? p : d);
1064
- }
1065
- t.applySetFilter(o);
1066
- }), x.appendChild(E);
1067
- const P = document.createElement("button");
1068
- P.className = "tbw-filter-clear-btn", P.textContent = "Clear Filter", P.addEventListener("click", () => {
1069
- t.clearFilter();
1070
- }), x.appendChild(P), e.appendChild(x);
1071
- }
1072
- /**
1073
- * Render a number range filter panel with min/max inputs and slider
1074
- */
1075
- renderNumberFilterPanel(e, t, n) {
1076
- const { field: r, column: i } = t, a = i.filterParams, l = i.editorParams, s = (b, D) => {
1077
- if (typeof b == "number") return b;
1078
- if (typeof b == "string") {
1079
- const z = parseFloat(b);
1080
- return isNaN(z) ? D : z;
1081
- }
1082
- return D;
1083
- }, u = n.filter((b) => typeof b == "number" && !isNaN(b)), m = u.length > 0 ? Math.min(...u) : 0, S = u.length > 0 ? Math.max(...u) : 100, f = s(a?.min ?? l?.min, m), v = s(a?.max ?? l?.max, S), L = a?.step ?? l?.step ?? 1, C = this.filters.get(r);
1084
- let T = f, R = v;
1085
- C?.operator === "between" ? (T = s(C.value, f), R = s(C.valueTo, v)) : C?.operator === "greaterThanOrEqual" ? T = s(C.value, f) : C?.operator === "lessThanOrEqual" && (R = s(C.value, v));
1086
- const F = document.createElement("div");
1087
- F.className = "tbw-filter-range-inputs";
1088
- const k = document.createElement("div");
1089
- k.className = "tbw-filter-range-group";
1090
- const I = document.createElement("label");
1091
- I.textContent = "Min", I.className = "tbw-filter-range-label";
1092
- const N = document.createElement("input");
1093
- N.type = "number", N.className = "tbw-filter-range-input", N.min = String(f), N.max = String(v), N.step = String(L), N.value = String(T), k.appendChild(I), k.appendChild(N), F.appendChild(k);
1094
- const A = document.createElement("span");
1095
- A.className = "tbw-filter-range-separator", A.textContent = "–", F.appendChild(A);
1096
- const V = document.createElement("div");
1097
- V.className = "tbw-filter-range-group";
1098
- const H = document.createElement("label");
1099
- H.textContent = "Max", H.className = "tbw-filter-range-label";
1100
- const x = document.createElement("input");
1101
- x.type = "number", x.className = "tbw-filter-range-input", x.min = String(f), x.max = String(v), x.step = String(L), x.value = String(R), V.appendChild(H), V.appendChild(x), F.appendChild(V), e.appendChild(F);
1102
- const E = document.createElement("div");
1103
- E.className = "tbw-filter-range-slider";
1104
- const P = document.createElement("div");
1105
- P.className = "tbw-filter-range-track";
1106
- const o = document.createElement("div");
1107
- o.className = "tbw-filter-range-fill";
1108
- const d = document.createElement("input");
1109
- d.type = "range", d.className = "tbw-filter-range-thumb tbw-filter-range-thumb-min", d.min = String(f), d.max = String(v), d.step = String(L), d.value = String(T);
1110
- const g = document.createElement("input");
1111
- g.type = "range", g.className = "tbw-filter-range-thumb tbw-filter-range-thumb-max", g.min = String(f), g.max = String(v), g.step = String(L), g.value = String(R), E.appendChild(P), E.appendChild(o), E.appendChild(d), E.appendChild(g), e.appendChild(E);
1112
- const p = () => {
1113
- const b = parseFloat(d.value), D = parseFloat(g.value), z = v - f, q = (b - f) / z * 100, G = (D - f) / z * 100;
1114
- o.style.left = `${q}%`, o.style.width = `${G - q}%`;
1115
- };
1116
- d.addEventListener("input", () => {
1117
- const b = Math.min(parseFloat(d.value), parseFloat(g.value));
1118
- d.value = String(b), N.value = String(b), p();
1119
- }), g.addEventListener("input", () => {
1120
- const b = Math.max(parseFloat(g.value), parseFloat(d.value));
1121
- g.value = String(b), x.value = String(b), p();
1122
- }), N.addEventListener("input", () => {
1123
- let b = parseFloat(N.value) || f;
1124
- b = Math.max(f, Math.min(b, parseFloat(x.value))), d.value = String(b), p();
1125
- }), x.addEventListener("input", () => {
1126
- let b = parseFloat(x.value) || v;
1127
- b = Math.min(v, Math.max(b, parseFloat(N.value))), g.value = String(b), p();
1128
- }), p();
1129
- const h = document.createElement("div");
1130
- h.className = "tbw-filter-buttons";
1131
- const y = document.createElement("button");
1132
- y.className = "tbw-filter-apply-btn", y.textContent = "Apply", y.addEventListener("click", () => {
1133
- const b = parseFloat(N.value), D = parseFloat(x.value);
1134
- t.applyTextFilter("between", b, D);
1135
- }), h.appendChild(y);
1136
- const c = document.createElement("button");
1137
- c.className = "tbw-filter-clear-btn", c.textContent = "Clear Filter", c.addEventListener("click", () => {
1138
- t.clearFilter();
1139
- }), h.appendChild(c), e.appendChild(h);
1140
- }
1141
- /**
1142
- * Render a date range filter panel with from/to date inputs
1143
- */
1144
- renderDateFilterPanel(e, t, n) {
1145
- const { field: r, column: i } = t, a = i.filterParams, l = i.editorParams, s = n.filter((c) => c instanceof Date || typeof c == "string" && !isNaN(Date.parse(c))).map((c) => c instanceof Date ? c : new Date(c)).filter((c) => !isNaN(c.getTime())), u = s.length > 0 ? new Date(Math.min(...s.map((c) => c.getTime()))) : null, m = s.length > 0 ? new Date(Math.max(...s.map((c) => c.getTime()))) : null, S = (c) => c ? c.toISOString().split("T")[0] : "", f = (c) => c ? typeof c == "string" ? c : typeof c == "number" ? S(new Date(c)) : "" : "", v = f(a?.min) || f(l?.min) || S(u), L = f(a?.max) || f(l?.max) || S(m), C = this.filters.get(r);
1146
- let T = "", R = "";
1147
- const F = C?.operator === "blank";
1148
- C?.operator === "between" ? (T = f(C.value) || "", R = f(C.valueTo) || "") : C?.operator === "greaterThanOrEqual" ? T = f(C.value) || "" : C?.operator === "lessThanOrEqual" && (R = f(C.value) || "");
1149
- const k = document.createElement("div");
1150
- k.className = "tbw-filter-date-range";
1151
- const I = document.createElement("div");
1152
- I.className = "tbw-filter-date-group";
1153
- const N = document.createElement("label");
1154
- N.textContent = "From", N.className = "tbw-filter-range-label";
1155
- const A = document.createElement("input");
1156
- A.type = "date", A.className = "tbw-filter-date-input", v && (A.min = v), L && (A.max = L), A.value = T, I.appendChild(N), I.appendChild(A), k.appendChild(I);
1157
- const V = document.createElement("span");
1158
- V.className = "tbw-filter-range-separator", V.textContent = "–", k.appendChild(V);
1159
- const H = document.createElement("div");
1160
- H.className = "tbw-filter-date-group";
1161
- const x = document.createElement("label");
1162
- x.textContent = "To", x.className = "tbw-filter-range-label";
1163
- const E = document.createElement("input");
1164
- E.type = "date", E.className = "tbw-filter-date-input", v && (E.min = v), L && (E.max = L), E.value = R, H.appendChild(x), H.appendChild(E), k.appendChild(H), e.appendChild(k);
1165
- const P = document.createElement("label");
1166
- P.className = "tbw-filter-blank-option";
1167
- const o = document.createElement("input");
1168
- o.type = "checkbox", o.className = "tbw-filter-blank-checkbox", o.checked = F;
1169
- const d = document.createTextNode("Show only blank");
1170
- P.appendChild(o), P.appendChild(d);
1171
- const g = (c) => {
1172
- A.disabled = c, E.disabled = c, k.classList.toggle("tbw-filter-disabled", c);
1173
- };
1174
- g(F), o.addEventListener("change", () => {
1175
- g(o.checked);
1176
- }), e.appendChild(P);
1177
- const p = document.createElement("div");
1178
- p.className = "tbw-filter-buttons";
1179
- const h = document.createElement("button");
1180
- h.className = "tbw-filter-apply-btn", h.textContent = "Apply", h.addEventListener("click", () => {
1181
- if (o.checked) {
1182
- t.applyTextFilter("blank", "");
1183
- return;
1184
- }
1185
- const c = A.value, b = E.value;
1186
- c && b ? t.applyTextFilter("between", c, b) : c ? t.applyTextFilter("greaterThanOrEqual", c) : b ? t.applyTextFilter("lessThanOrEqual", b) : t.clearFilter();
1187
- }), p.appendChild(h);
1188
- const y = document.createElement("button");
1189
- y.className = "tbw-filter-clear-btn", y.textContent = "Clear Filter", y.addEventListener("click", () => {
1190
- t.clearFilter();
1191
- }), p.appendChild(y), e.appendChild(p);
1192
- }
1193
- /**
1194
- * Apply a set filter (exclude values)
1195
- */
1196
- applySetFilter(e, t) {
1197
- this.excludedValues.set(e, new Set(t)), t.length === 0 ? this.filters.delete(e) : this.filters.set(e, {
1198
- field: e,
1199
- type: "set",
1200
- operator: "notIn",
1201
- value: t
1202
- }), this.applyFiltersInternal();
1203
- }
1204
- /**
1205
- * Apply a text/number/date filter
1206
- */
1207
- applyTextFilter(e, t, n, r) {
1208
- this.filters.set(e, {
1209
- field: e,
1210
- type: "text",
1211
- operator: t,
1212
- value: n,
1213
- valueTo: r
1214
- }), this.applyFiltersInternal();
1215
- }
1216
- /**
1217
- * Internal method to apply filters (sync or async based on config)
1218
- */
1219
- applyFiltersInternal() {
1220
- this.cachedResult = null, this.cacheKey = null, this.cachedInputSpot = null;
1221
- const e = [...this.filters.values()];
1222
- if (this.config.filterHandler) {
1223
- const t = this.grid;
1224
- t.setAttribute("aria-busy", "true");
1225
- const n = this.config.filterHandler(e, this.sourceRows), r = (i) => {
1226
- t.removeAttribute("aria-busy"), this.cachedResult = i, this.grid.rows = i, this.emit("filter-change", {
1227
- filters: e,
1228
- filteredRowCount: i.length,
1229
- selected: this.computeSelected()
1230
- }), this.emitPluginEvent("filter-applied", { filters: e }), this.requestRender();
1231
- };
1232
- n && typeof n.then == "function" ? n.then(r) : r(n);
1233
- return;
1234
- }
1235
- this.emit("filter-change", {
1236
- filters: e,
1237
- filteredRowCount: 0,
1238
- selected: this.computeSelected()
1239
- }), this.emitPluginEvent("filter-applied", { filters: e }), this.requestRender();
1240
- }
1241
- // #endregion
1242
- // #region Column State Hooks
1243
- /**
1244
- * Return filter state for a column if it has an active filter.
1245
- * @internal
1246
- */
1247
- getColumnState(e) {
1248
- const t = this.filters.get(e);
1249
- if (t)
1250
- return {
1251
- filter: {
1252
- type: t.type,
1253
- operator: t.operator,
1254
- value: t.value,
1255
- valueTo: t.valueTo
1256
- }
1257
- };
1258
- }
1259
- /**
1260
- * Apply filter state from column state.
1261
- * @internal
1262
- */
1263
- applyColumnState(e, t) {
1264
- if (!t.filter) {
1265
- this.filters.delete(e);
1266
- return;
1267
- }
1268
- const n = {
1269
- field: e,
1270
- type: t.filter.type,
1271
- operator: t.filter.operator,
1272
- value: t.filter.value,
1273
- valueTo: t.filter.valueTo
1274
- };
1275
- this.filters.set(e, n), this.cachedResult = null, this.cacheKey = null, this.cachedInputSpot = null;
1276
- }
1277
- // #endregion
1278
- }
1279
- export {
1280
- B as BLANK_FILTER_VALUE,
1281
- _ as FilteringPlugin,
1282
- W as getUniqueValuesBatch
1283
- };
1
+ const e="(Blank)";function t(e){if(e instanceof Date)return e.getTime();const t=Number(e);if(!isNaN(t))return t;return new Date(e).getTime()}function r(r,i,n=!1,l){return i.length?r.filter(r=>i.every(i=>function(r,i,n=!1,l){const a=r[i.field];if("blank"===i.operator)return null==a||""===a;if("notBlank"===i.operator)return null!=a&&""!==a;if(l&&("notIn"===i.operator||"in"===i.operator)){const t=l(a,r),n=Array.isArray(t)?t:null!=t?[t]:[];if("notIn"===i.operator){const t=i.value;return!Array.isArray(t)||(0===n.length?!t.includes(e):!n.some(e=>t.includes(e)))}if("in"===i.operator){const t=i.value;return!!Array.isArray(t)&&(0===n.length?t.includes(e):n.some(e=>t.includes(e)))}}if("notIn"===i.operator)return null==a||""===a?!Array.isArray(i.value)||!i.value.includes(e):Array.isArray(i.value)&&!i.value.includes(a);if("in"===i.operator)return null==a||""===a?Array.isArray(i.value)&&i.value.includes(e):Array.isArray(i.value)&&i.value.includes(a);if(null==a)return!1;const s=String(a),o=n?s:s.toLowerCase(),c=n?String(i.value):String(i.value).toLowerCase();switch(i.operator){case"contains":return o.includes(c);case"notContains":return!o.includes(c);case"equals":return o===c;case"notEquals":return o!==c;case"startsWith":return o.startsWith(c);case"endsWith":return o.endsWith(c);case"lessThan":return t(a)<t(i.value);case"lessThanOrEqual":return t(a)<=t(i.value);case"greaterThan":return t(a)>t(i.value);case"greaterThanOrEqual":return t(a)>=t(i.value);case"between":return t(a)>=t(i.value)&&t(a)<=t(i.valueTo);default:return!0}}(r,i,n,l?.get(i.field)))):r}function i(t,r,i){const n=/* @__PURE__ */new Set;let l=!1;for(const e of t){const t=e[r];if(i){const r=i(t,e);if(Array.isArray(r)){0===r.length&&(l=!0);for(const e of r)null!=e&&n.add(e)}else null!=r?n.add(r):l=!0}else null!=t&&""!==t?n.add(t):l=!0}return l&&n.add(e),[...n].sort((e,t)=>"number"==typeof e&&"number"==typeof t?e-t:String(e).localeCompare(String(t)))}function n(t,r){const i=/* @__PURE__ */new Map;for(const{field:e,filterValue:l}of r)i.set(e,{values:/* @__PURE__ */new Set,hasBlank:!1,hasExtractor:!!l});for(const e of t)for(const{field:t,filterValue:n}of r){const r=i.get(t),l=e[t];if(n){const t=n(l,e);if(Array.isArray(t)){0===t.length&&(r.hasBlank=!0);for(const e of t)null!=e&&r.values.add(e)}else null!=t?r.values.add(t):r.hasBlank=!0}else null!=l&&""!==l?r.values.add(l):r.hasBlank=!0}const n=/* @__PURE__ */new Map;for(const[l,{values:a,hasBlank:s}]of i)s&&a.add(e),n.set(l,[...a].sort((e,t)=>"number"==typeof e&&"number"==typeof t?e-t:String(e).localeCompare(String(t))));return n}const l='<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>',a={expand:"▶",collapse:"▼",sortAsc:"▲",sortDesc:"▼",sortNone:"⇅",submenuArrow:"▶",dragHandle:"⋮⋮",toolPanel:"☰",filter:l,filterActive:l,print:"🖨️"};class s{static dependencies;static manifest;version="undefined"!=typeof __GRID_VERSION__?__GRID_VERSION__:"dev";styles;cellRenderers;headerRenderers;cellEditors;grid;config;userConfig;#e;get defaultConfig(){return{}}constructor(e={}){this.userConfig=e}attach(e){this.#e?.abort(),this.#e=new AbortController,this.grid=e,this.config={...this.defaultConfig,...this.userConfig}}detach(){this.#e?.abort(),this.#e=void 0}getPlugin(e){return this.grid?.getPlugin(e)}emit(e,t){this.grid?.dispatchEvent?.(new CustomEvent(e,{detail:t,bubbles:!0}))}emitCancelable(e,t){const r=new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0});return this.grid?.dispatchEvent?.(r),r.defaultPrevented}on(e,t){this.grid?._pluginManager?.subscribe(this,e,t)}off(e){this.grid?._pluginManager?.unsubscribe(this,e)}emitPluginEvent(e,t){this.grid?._pluginManager?.emitPluginEvent(e,t)}requestRender(){this.grid?.requestRender?.()}requestColumnsRender(){this.grid?.requestColumnsRender?.()}requestRenderWithFocus(){this.grid?.requestRenderWithFocus?.()}requestAfterRender(){this.grid?.requestAfterRender?.()}get rows(){return this.grid?.rows??[]}get sourceRows(){return this.grid?.sourceRows??[]}get columns(){return this.grid?.columns??[]}get visibleColumns(){return this.grid?._visibleColumns??[]}get gridElement(){return this.grid}get disconnectSignal(){return this.#e?.signal??this.grid?.disconnectSignal}get gridIcons(){const e=this.grid?.gridConfig?.icons??{};return{...a,...e}}get isAnimationEnabled(){const e=this.grid?.effectiveConfig?.animation?.mode??"reduced-motion";if(!1===e||"off"===e)return!1;if(!0===e||"on"===e)return!0;const t=this.gridElement;if(t){return"0"!==getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim()}return!0}get animationDuration(){const e=this.gridElement;if(e){const t=getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(),r=parseInt(t,10);if(!isNaN(r))return r}return 200}resolveIcon(e,t){return void 0!==t?t:this.gridIcons[e]}setIcon(e,t){"string"==typeof t?e.innerHTML=t:t instanceof HTMLElement&&(e.innerHTML="",e.appendChild(t.cloneNode(!0)))}warn(e){console.warn(`[tbw-grid:${this.name}] ${e}`)}}class o extends s{static manifest={events:[{type:"filter-applied",description:"Emitted when filter criteria change. Subscribers can react to row visibility changes."}],queries:[{type:"getContextMenuItems",description:"Contributes filter-related items to the header context menu"}]};name="filtering";styles='@layer tbw-plugins{tbw-grid .tbw-quick-filter-input{flex:1;max-width:300px;height:var(--tbw-input-height, 1.75rem);padding:var(--tbw-input-padding, 0 .5rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);color:var(--tbw-color-fg);font-size:var(--tbw-font-size-sm, .8125rem)}tbw-grid .tbw-quick-filter-input:focus{outline:none;border-color:var(--tbw-color-accent)}tbw-grid .header-cell.filtered:before{content:"";position:absolute;top:var(--tbw-spacing-xs, .25rem);right:var(--tbw-spacing-xs, .25rem);width:var(--tbw-indicator-size, .375rem);height:var(--tbw-indicator-size, .375rem);background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:50%}tbw-grid .tbw-filter-btn{display:var(--tbw-filter-btn-display, inline-flex);visibility:var(--tbw-filter-btn-visibility, visible);align-items:center;justify-content:center;background:transparent;border:none;cursor:pointer;padding:2px;margin-left:var(--tbw-spacing-xs, .25rem);opacity:.4;transition:opacity .15s,visibility 0s,display 0s allow-discrete;color:inherit;vertical-align:middle;transition-behavior:allow-discrete}tbw-grid .tbw-filter-btn:hover,tbw-grid .tbw-filter-btn.active{opacity:1;visibility:visible;display:inline-flex}tbw-grid .tbw-filter-btn.active{color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}tbw-grid .header-row .cell:hover .tbw-filter-btn,tbw-grid .header-row .cell.filtered .tbw-filter-btn{display:inline-flex;visibility:visible}}';get defaultConfig(){return{debounceMs:300,caseSensitive:!1,trimInput:!0,useWorker:!0}}isFilteringEnabled(){return!1!==this.grid.effectiveConfig?.filterable}isColumnFilterable(e){return!!this.isFilteringEnabled()&&!1!==e.filterable}getFilterValues(){const e=this.grid.effectiveConfig?.columns;if(!e)return;let t;for(const r of e)r.field&&r.filterValue&&(t||(t=/* @__PURE__ */new Map),t.set(r.field,r.filterValue));return t}filters=/* @__PURE__ */new Map;cachedResult=null;cacheKey=null;cachedInputSpot=null;openPanelField=null;panelElement=null;panelAnchorElement=null;searchText=/* @__PURE__ */new Map;excludedValues=/* @__PURE__ */new Map;panelAbortController=null;globalStylesInjected=!1;static DEFAULT_LIST_ITEM_HEIGHT=28;static LIST_OVERSCAN=3;static LIST_BYPASS_THRESHOLD=50;getListItemHeight(){if(this.panelElement){const e=getComputedStyle(this.panelElement).getPropertyValue("--tbw-filter-item-height");if(e&&e.trim()){const t=parseFloat(e);if(!isNaN(t)&&t>0)return t}}return o.DEFAULT_LIST_ITEM_HEIGHT}computeSelected(){const e=[];for(const[i,n]of this.filters){if("set"!==n.type||"notIn"!==n.operator)continue;const t=this.grid.effectiveConfig?.columns?.find(e=>e.field===i);e.push({field:i,filterValue:t?.filterValue})}if(0===e.length)return{};const t=n(this.sourceRows,e),r={};for(const{field:i}of e){const e=this.excludedValues.get(i),n=t.get(i)??[];r[i]=e?n.filter(t=>!e.has(t)):n}return r}syncExcludedValues(e,t){t?"set"===t.type&&"notIn"===t.operator&&Array.isArray(t.value)?this.excludedValues.set(e,new Set(t.value)):"set"===t.type&&this.excludedValues.delete(e):this.excludedValues.delete(e)}attach(e){super.attach(e),this.injectGlobalStyles()}detach(){this.filters.clear(),this.cachedResult=null,this.cacheKey=null,this.cachedInputSpot=null,this.openPanelField=null,this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.searchText.clear(),this.excludedValues.clear(),this.panelAbortController?.abort(),this.panelAbortController=null}handleQuery(e){if("getContextMenuItems"===e.type){const t=e.context;if(!t.isHeader)return;const r=t.column;if(!r?.field)return;if(!this.isFilteringEnabled())return;if(!this.isColumnFilterable(r))return;const i=[],n=this.isFieldFiltered(r.field),l=this.filters.size>0;return n&&i.push({id:"filtering/clear-column-filter",label:"Clear Filter",icon:"✕",order:20,action:()=>this.clearFieldFilter(r.field)}),l&&i.push({id:"filtering/clear-all-filters",label:"Clear All Filters",icon:"✕",order:21,disabled:!l,action:()=>this.clearAllFilters()}),i.length>0?i:void 0}}processRows(e){const t=[...this.filters.values()];if(!t.length)return[...e];if(this.config.filterHandler)return this.cachedResult?this.cachedResult:[...e];const i=(n=t,JSON.stringify(n.map(e=>({field:e.field,operator:e.operator,value:e.value,valueTo:e.valueTo}))));var n;const l={len:e.length,first:e[0],mid:e[Math.floor(e.length/2)],last:e[e.length-1]},a=null!=this.cachedInputSpot&&l.len===this.cachedInputSpot.len&&l.first===this.cachedInputSpot.first&&l.mid===this.cachedInputSpot.mid&&l.last===this.cachedInputSpot.last;if(this.cacheKey===i&&this.cachedResult&&a)return this.cachedResult;const s=r([...e],t,this.config.caseSensitive,this.getFilterValues());return this.cachedResult=s,this.cacheKey=i,this.cachedInputSpot=l,s}afterRender(){const e=this.gridElement;if(!e)return;e.querySelectorAll('[part~="header-cell"]').forEach(e=>{const t=e.getAttribute("data-col");if(null===t)return;const r=this.visibleColumns[parseInt(t,10)];if(!r||!this.isColumnFilterable(r))return;if(i=r,!0===i.meta?.utility)return;var i;const n=r.field;if(!n)return;const l=this.filters.has(n);let a=e.querySelector(".tbw-filter-btn");if(a){const t=a.classList.contains("active");if(a.classList.toggle("active",l),e.classList.toggle("filtered",l),t!==l){const e=l?"filterActive":"filter";this.setIcon(a,this.resolveIcon(e))}return}a=document.createElement("button"),a.className="tbw-filter-btn",a.setAttribute("aria-label",`Filter ${r.header??n}`);const s=l?"filterActive":"filter";this.setIcon(a,this.resolveIcon(s)),l&&(a.classList.add("active"),e.classList.add("filtered")),a.addEventListener("click",e=>{e.stopPropagation(),this.toggleFilterPanel(n,r,a)});const o=e.querySelector(".resize-handle");o?e.insertBefore(a,o):e.appendChild(a)})}setFilter(e,t){if(null===t)this.filters.delete(e),this.syncExcludedValues(e,null);else{const r={...t,field:e};this.filters.set(e,r),this.syncExcludedValues(e,r)}this.cachedResult=null,this.cacheKey=null,this.cachedInputSpot=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0,selected:this.computeSelected()}),this.emitPluginEvent("filter-applied",{filters:[...this.filters.values()]}),this.requestRender()}getFilter(e){return this.filters.get(e)}getFilters(){return[...this.filters.values()]}getFilterModel(){return this.getFilters()}setFilterModel(e){this.filters.clear(),this.excludedValues.clear();for(const t of e)this.filters.set(t.field,t),this.syncExcludedValues(t.field,t);this.cachedResult=null,this.cacheKey=null,this.cachedInputSpot=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0,selected:this.computeSelected()}),this.emitPluginEvent("filter-applied",{filters:[...this.filters.values()]}),this.requestRender()}clearAllFilters(){this.filters.clear(),this.excludedValues.clear(),this.searchText.clear(),this.applyFiltersInternal()}clearFieldFilter(e){this.filters.delete(e),this.excludedValues.delete(e),this.searchText.delete(e),this.applyFiltersInternal()}isFieldFiltered(e){return this.filters.has(e)}getFilteredRowCount(){return this.cachedResult?.length??this.rows.length}getActiveFilters(){return this.getFilters()}getUniqueValues(e){const t=this.grid.effectiveConfig?.columns?.find(t=>t.field===e),r=t?.filterValue;return i(this.sourceRows,e,r)}copyGridThemeContext(e){const t=this.gridElement;if(!t)return;for(const i of t.classList)i.startsWith("tbw-")||"selecting"===i||e.classList.add(i);const r=t.dataset.theme;r&&(e.dataset.theme=r)}injectGlobalStyles(){if(this.globalStylesInjected)return;if(document.getElementById("tbw-filter-panel-styles"))return void(this.globalStylesInjected=!0);const e=document.createElement("style");e.id="tbw-filter-panel-styles",e.textContent="@layer tbw-plugins{.tbw-filter-panel{position:fixed;background:var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));color:var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));border:1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-panel-radius, var(--tbw-border-radius, .25rem));box-shadow:0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0, 0, 0, .1), rgba(0, 0, 0, .3))));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));z-index:10000;min-width:200px;max-width:280px;max-height:350px;display:flex;flex-direction:column;font-family:var(--tbw-font-family, system-ui, sans-serif);font-size:var(--tbw-font-size, .8125rem);transform-origin:top center}.tbw-filter-panel.tbw-filter-panel-above{transform-origin:bottom center}.tbw-filter-panel.tbw-filter-panel-animated{animation:tbw-filter-panel-enter var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}.tbw-filter-panel.tbw-filter-panel-above.tbw-filter-panel-animated{animation:tbw-filter-panel-enter-above var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}@keyframes tbw-filter-panel-enter{0%{opacity:0;transform:scaleY(.3) translateY(-10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@keyframes tbw-filter-panel-enter-above{0%{opacity:0;transform:scaleY(.3) translateY(10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@supports (anchor-name: --test){.tbw-filter-panel{position-anchor:--tbw-filter-anchor;top:anchor(bottom);left:anchor(left);margin-top:4px;position-try-fallbacks:flip-inline,flip-block,flip-block flip-inline}}.tbw-filter-search{margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));min-height:var(--tbw-filter-item-height, 28px)}.tbw-filter-search-input{height:var(--tbw-filter-item-height, 28px);width:100%;padding:var(--tbw-filter-search-padding, var(--tbw-spacing-sm, .375rem) var(--tbw-spacing-md, .5rem));background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, var(--tbw-border-radius, .25rem));font-size:inherit;box-sizing:border-box}.tbw-filter-search-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-actions{display:flex;padding:var(--tbw-button-padding-sm, .25rem .125rem);margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-bottom:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));min-height:var(--tbw-filter-item-height, 28px)}.tbw-filter-actions .tbw-filter-value-item{flex:1}.tbw-filter-values{flex:1;overflow-y:auto;margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));max-height:180px;position:relative}.tbw-filter-values-spacer{width:1px}.tbw-filter-values-content{position:absolute;top:0;left:0;right:0}.tbw-filter-value-item{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding-sm, .25rem .125rem);cursor:pointer;border-radius:3px;height:var(--tbw-filter-item-height, 28px)}.tbw-filter-value-item:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-checkbox{margin:0;cursor:pointer;accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}.tbw-filter-no-match{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem)) 0;text-align:center;font-style:italic}.tbw-filter-buttons{display:flex;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding-top:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-top:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-apply-btn{flex:1;padding:var(--tbw-filter-btn-padding, var(--tbw-button-padding, .375rem .75rem));background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));color:var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));border:none;border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);font-weight:var(--tbw-filter-btn-font-weight, 500);min-height:var(--tbw-filter-btn-min-height, auto)}.tbw-filter-apply-btn:hover{filter:brightness(.9)}.tbw-filter-clear-btn{flex:1;padding:var(--tbw-filter-btn-padding, var(--tbw-button-padding, .375rem .75rem));background:transparent;color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);font-weight:var(--tbw-filter-btn-font-weight, 500);min-height:var(--tbw-filter-btn-min-height, auto)}.tbw-filter-clear-btn:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-range-inputs,.tbw-filter-date-range{display:flex;align-items:flex-end;gap:var(--tbw-spacing-sm, .375rem);margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-filter-range-group,.tbw-filter-date-group{display:flex;flex-direction:column;gap:var(--tbw-spacing-xs, .25rem);flex:1}.tbw-filter-range-label{font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)))}.tbw-filter-range-input,.tbw-filter-date-input{width:100%;height:var(--tbw-filter-item-height, 28px);padding:var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-sm, .375rem);background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, var(--tbw-border-radius, .25rem));font-size:inherit;box-sizing:border-box}.tbw-filter-range-input:focus,.tbw-filter-date-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-range-separator{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding-bottom:var(--tbw-spacing-xs, .25rem)}.tbw-filter-blank-option{display:flex;align-items:center;gap:var(--tbw-spacing-sm, .375rem);padding:var(--tbw-spacing-xs, .25rem) 0;margin-bottom:var(--tbw-spacing-xs, .25rem);font-size:var(--tbw-font-size-sm, .8125rem);cursor:pointer;-webkit-user-select:none;user-select:none}.tbw-filter-blank-checkbox{accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));margin:0;cursor:pointer}.tbw-filter-date-range.tbw-filter-disabled,.tbw-filter-range-inputs.tbw-filter-disabled,.tbw-filter-range-slider.tbw-filter-disabled{opacity:.4;pointer-events:none}.tbw-filter-range-slider{position:relative;height:24px;margin:var(--tbw-spacing-md, .5rem) 0 var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-filter-range-track{position:absolute;top:50%;left:0;right:0;height:4px;background:var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:2px;transform:translateY(-50%)}.tbw-filter-range-fill{position:absolute;top:50%;height:4px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:2px;transform:translateY(-50%)}.tbw-filter-range-thumb{position:absolute;top:0;width:100%;height:100%;background:none;pointer-events:none;-webkit-appearance:none;appearance:none}.tbw-filter-range-thumb::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border:2px solid var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));border-radius:50%;cursor:pointer;pointer-events:all;box-shadow:0 1px 3px #0003}.tbw-filter-range-thumb::-moz-range-thumb{width:16px;height:16px;background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border:2px solid var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));border-radius:50%;cursor:pointer;pointer-events:all;box-shadow:0 1px 3px #0003}.tbw-filter-range-thumb::-webkit-slider-thumb:hover{transform:scale(1.1)}.tbw-filter-range-thumb::-moz-range-thumb:hover{transform:scale(1.1)}}",document.head.appendChild(e),this.globalStylesInjected=!0}toggleFilterPanel(e,t,r){if(this.openPanelField===e)return void this.closeFilterPanel();this.closeFilterPanel();const n=document.createElement("div");if(n.className="tbw-filter-panel",this.copyGridThemeContext(n),this.isAnimationEnabled&&n.classList.add("tbw-filter-panel-animated"),this.panelElement=n,this.openPanelField=e,this.config.valuesHandler)return n.innerHTML='<div class="tbw-filter-loading">Loading...</div>',document.body.appendChild(n),this.positionPanel(n,r),this.setupPanelCloseHandler(n,r),void this.config.valuesHandler(e,t).then(r=>{this.openPanelField===e&&this.panelElement&&(n.innerHTML="",this.renderPanelContent(e,t,n,r))});const l=i(this.sourceRows,e,t.filterValue);document.body.appendChild(n),this.positionPanel(n,r),this.renderPanelContent(e,t,n,l),this.setupPanelCloseHandler(n,r)}renderPanelContent(e,t,r,i){let n=this.excludedValues.get(e);n||(n=/* @__PURE__ */new Set,this.excludedValues.set(e,n));const l=this.searchText.get(e)??"",a={field:e,column:t,uniqueValues:i,excludedValues:n,searchText:l,currentFilter:this.filters.get(e),applySetFilter:(t,r)=>{this.applySetFilter(e,t,r),this.closeFilterPanel()},applyTextFilter:(t,r,i)=>{this.applyTextFilter(e,t,r,i),this.closeFilterPanel()},clearFilter:()=>{this.clearFieldFilter(e),this.closeFilterPanel()},closePanel:()=>this.closeFilterPanel()};let s=!1;if(this.config.filterPanelRenderer&&(this.config.filterPanelRenderer(r,a),s=r.children.length>0),!s&&t.type){const e=this.grid.effectiveConfig.typeDefaults?.[t.type];e?.filterPanelRenderer&&(e.filterPanelRenderer(r,a),s=r.children.length>0)}if(!s){const e=t.type;"number"===e?this.renderNumberFilterPanel(r,a,i):"date"===e?this.renderDateFilterPanel(r,a,i):this.renderDefaultFilterPanel(r,a,i,n)}}setupPanelCloseHandler(e,t){this.panelAbortController=new AbortController,setTimeout(()=>{document.addEventListener("click",r=>{e.contains(r.target)||r.target===t||this.closeFilterPanel()},{signal:this.panelAbortController?.signal})},0)}closeFilterPanel(){const e=this.panelElement;e&&(e.remove(),this.panelElement=null),this.panelAnchorElement&&(this.panelAnchorElement.style.anchorName="",this.panelAnchorElement=null),this.openPanelField=null,this.panelAbortController?.abort(),this.panelAbortController=null}static supportsAnchorPositioning=null;static checkAnchorPositioningSupport(){return null===o.supportsAnchorPositioning&&(o.supportsAnchorPositioning=CSS.supports("anchor-name","--test")),o.supportsAnchorPositioning}positionPanel(e,t){const r=t.closest(".cell")??t;if(r.style.anchorName="--tbw-filter-anchor",this.panelAnchorElement=r,o.checkAnchorPositioningSupport())return void requestAnimationFrame(()=>{const t=e.getBoundingClientRect(),i=r.getBoundingClientRect();t.top<i.top&&e.classList.add("tbw-filter-panel-above")});const i=r.getBoundingClientRect();e.style.position="fixed",e.style.top=`${i.bottom+4}px`,e.style.left=`${i.left}px`,requestAnimationFrame(()=>{const t=e.getBoundingClientRect();t.right>window.innerWidth-8&&(e.style.left=i.right-t.width+"px"),t.bottom>window.innerHeight-8&&(e.style.top=i.top-t.height-4+"px",e.classList.add("tbw-filter-panel-above"))})}renderDefaultFilterPanel(e,t,r,i){const{field:n,column:l}=t,a=this.getListItemHeight(),s=e=>{if(null==e)return"(Blank)";if(l.format&&!l.filterValue){const t=l.format(e,void 0);if(t)return t}return String(e)};r=r.slice().sort((e,t)=>s(e).localeCompare(s(t)));const c=document.createElement("div");c.className="tbw-filter-search";const d=document.createElement("input");d.type="text",d.placeholder="Search...",d.className="tbw-filter-search-input",d.value=this.searchText.get(n)??"",c.appendChild(d),e.appendChild(c);const u=document.createElement("div");u.className="tbw-filter-actions";const p=document.createElement("label");p.className="tbw-filter-value-item",p.style.padding="0",p.style.margin="0";const h=document.createElement("input");h.type="checkbox",h.className="tbw-filter-checkbox";const b=document.createElement("span");b.textContent="Select All",p.appendChild(h),p.appendChild(b),u.appendChild(p);const f=()=>{const e=[...w.values()],t=e.every(e=>e),r=e.every(e=>!e);h.checked=t,h.indeterminate=!t&&!r};h.addEventListener("change",()=>{const e=h.checked;for(const t of w.keys())w.set(t,e);f(),C()}),e.appendChild(u);const g=document.createElement("div");g.className="tbw-filter-values";const m=document.createElement("div");m.className="tbw-filter-values-spacer",g.appendChild(m);const v=document.createElement("div");v.className="tbw-filter-values-content",g.appendChild(v);const w=/* @__PURE__ */new Map;r.forEach(e=>{const t=null==e?"__null__":String(e);w.set(t,!i.has(e))}),f();let x=[];const y=(e,t)=>{const r=s(e),i=null==e?"__null__":String(e),n=document.createElement("label");n.className="tbw-filter-value-item",n.style.position="absolute",n.style.top=`calc(var(--tbw-filter-item-height, 28px) * ${t})`,n.style.left="0",n.style.right="0",n.style.boxSizing="border-box";const l=document.createElement("input");l.type="checkbox",l.className="tbw-filter-checkbox",l.checked=w.get(i)??!0,l.dataset.value=i,l.addEventListener("change",()=>{w.set(i,l.checked),f()});const a=document.createElement("span");return a.textContent=r,n.appendChild(l),n.appendChild(a),n},C=()=>{const e=x.length,t=g.clientHeight,r=g.scrollTop;if(m.style.height=e*a+"px",i=e,n=o.LIST_BYPASS_THRESHOLD/3,i<=n)return v.innerHTML="",v.style.transform="translateY(0px)",void x.forEach((e,t)=>{v.appendChild(y(e,t))});var i,n;const l=function(e){const{totalRows:t,viewportHeight:r,scrollTop:i,rowHeight:n,overscan:l}=e,a=Math.ceil(r/n);let s=Math.floor(i/n)-l;s<0&&(s=0);let o=s+a+2*l;return o>t&&(o=t),o===t&&s>0&&(s=Math.max(0,o-a-2*l)),{start:s,end:o,offsetY:s*n,totalHeight:t*n}}({totalRows:e,viewportHeight:t,scrollTop:r,rowHeight:a,overscan:o.LIST_OVERSCAN});v.style.transform=`translateY(${l.offsetY}px)`,v.innerHTML="";for(let a=l.start;a<l.end;a++)v.appendChild(y(x[a],a-l.start))},E=e=>{const t=this.config.caseSensitive??!1,i=t?e:e.toLowerCase();if(x=r.filter(r=>{const n=s(r),l=t?n:n.toLowerCase();return!e||l.includes(i)}),0===x.length){m.style.height="0px",v.innerHTML="";const e=document.createElement("div");return e.className="tbw-filter-no-match",e.textContent="No matching values",void v.appendChild(e)}C()};let S;g.addEventListener("scroll",()=>{x.length>0&&C()},{passive:!0}),E(d.value),e.appendChild(g),d.addEventListener("input",()=>{clearTimeout(S),S=setTimeout(()=>{this.searchText.set(n,d.value),E(d.value)},this.config.debounceMs??150)});const F=document.createElement("div");F.className="tbw-filter-buttons";const k=document.createElement("button");k.className="tbw-filter-apply-btn",k.textContent="Apply",k.addEventListener("click",()=>{const e=[];for(const[t,i]of w)if(!i)if("__null__"===t)e.push(null);else{const i=r.find(e=>String(e)===t);e.push(void 0!==i?i:t)}t.applySetFilter(e)}),F.appendChild(k);const T=document.createElement("button");T.className="tbw-filter-clear-btn",T.textContent="Clear Filter",T.addEventListener("click",()=>{t.clearFilter()}),F.appendChild(T),e.appendChild(F)}renderNumberFilterPanel(e,t,r){const{field:i,column:n}=t,l=n.filterParams,a=n.editorParams,s=(e,t)=>{if("number"==typeof e)return e;if("string"==typeof e){const r=parseFloat(e);return isNaN(r)?t:r}return t},o=r.filter(e=>"number"==typeof e&&!isNaN(e)),c=o.length>0?Math.min(...o):0,d=o.length>0?Math.max(...o):100,u=s(l?.min??a?.min,c),p=s(l?.max??a?.max,d),h=l?.step??a?.step??1,b=this.filters.get(i);let f=u,g=p;const m="blank"===b?.operator;"between"===b?.operator?(f=s(b.value,u),g=s(b.valueTo,p)):"greaterThanOrEqual"===b?.operator?f=s(b.value,u):"lessThanOrEqual"===b?.operator&&(g=s(b.value,p));const v=document.createElement("div");v.className="tbw-filter-range-inputs";const w=document.createElement("div");w.className="tbw-filter-range-group";const x=document.createElement("label");x.textContent="Min",x.className="tbw-filter-range-label";const y=document.createElement("input");y.type="number",y.className="tbw-filter-range-input",y.min=String(u),y.max=String(p),y.step=String(h),y.value=String(f),w.appendChild(x),w.appendChild(y),v.appendChild(w);const C=document.createElement("span");C.className="tbw-filter-range-separator",C.textContent="–",v.appendChild(C);const E=document.createElement("div");E.className="tbw-filter-range-group";const S=document.createElement("label");S.textContent="Max",S.className="tbw-filter-range-label";const F=document.createElement("input");F.type="number",F.className="tbw-filter-range-input",F.min=String(u),F.max=String(p),F.step=String(h),F.value=String(g),E.appendChild(S),E.appendChild(F),v.appendChild(E),e.appendChild(v);const k=document.createElement("div");k.className="tbw-filter-range-slider";const T=document.createElement("div");T.className="tbw-filter-range-track";const N=document.createElement("div");N.className="tbw-filter-range-fill";const A=document.createElement("input");A.type="range",A.className="tbw-filter-range-thumb tbw-filter-range-thumb-min",A.min=String(u),A.max=String(p),A.step=String(h),A.value=String(f);const P=document.createElement("input");P.type="range",P.className="tbw-filter-range-thumb tbw-filter-range-thumb-max",P.min=String(u),P.max=String(p),P.step=String(h),P.value=String(g),k.appendChild(T),k.appendChild(N),k.appendChild(A),k.appendChild(P),e.appendChild(k);const I=document.createElement("label");I.className="tbw-filter-blank-option";const R=document.createElement("input");R.type="checkbox",R.className="tbw-filter-blank-checkbox",R.checked=m;const L=document.createTextNode("Blank");I.appendChild(R),I.appendChild(L);const M=e=>{y.disabled=e,F.disabled=e,A.disabled=e,P.disabled=e,v.classList.toggle("tbw-filter-disabled",e),k.classList.toggle("tbw-filter-disabled",e)};M(m),R.addEventListener("change",()=>{M(R.checked)}),e.appendChild(I);const _=()=>{const e=parseFloat(A.value),t=parseFloat(P.value),r=p-u,i=(e-u)/r*100,n=(t-u)/r*100;N.style.left=`${i}%`,N.style.width=n-i+"%"};A.addEventListener("input",()=>{const e=Math.min(parseFloat(A.value),parseFloat(P.value));A.value=String(e),y.value=String(e),_()}),P.addEventListener("input",()=>{const e=Math.max(parseFloat(P.value),parseFloat(A.value));P.value=String(e),F.value=String(e),_()}),y.addEventListener("input",()=>{let e=parseFloat(y.value)||u;e=Math.max(u,Math.min(e,parseFloat(F.value))),A.value=String(e),_()}),F.addEventListener("input",()=>{let e=parseFloat(F.value)||p;e=Math.min(p,Math.max(e,parseFloat(y.value))),P.value=String(e),_()}),_();const V=document.createElement("div");V.className="tbw-filter-buttons";const H=document.createElement("button");H.className="tbw-filter-apply-btn",H.textContent="Apply",H.addEventListener("click",()=>{if(R.checked)return void t.applyTextFilter("blank","");const e=parseFloat(y.value),r=parseFloat(F.value);t.applyTextFilter("between",e,r)}),V.appendChild(H);const q=document.createElement("button");q.className="tbw-filter-clear-btn",q.textContent="Clear Filter",q.addEventListener("click",()=>{t.clearFilter()}),V.appendChild(q),e.appendChild(V)}renderDateFilterPanel(e,t,r){const{field:i,column:n}=t,l=n.filterParams,a=n.editorParams,s=r.filter(e=>e instanceof Date||"string"==typeof e&&!isNaN(Date.parse(e))).map(e=>e instanceof Date?e:new Date(e)).filter(e=>!isNaN(e.getTime())),o=s.length>0?new Date(Math.min(...s.map(e=>e.getTime()))):null,c=s.length>0?new Date(Math.max(...s.map(e=>e.getTime()))):null,d=e=>e?e.toISOString().split("T")[0]:"",u=e=>e?"string"==typeof e?e:"number"==typeof e?d(new Date(e)):"":"",p=u(l?.min)||u(a?.min)||d(o),h=u(l?.max)||u(a?.max)||d(c),b=this.filters.get(i);let f="",g="";const m="blank"===b?.operator;"between"===b?.operator?(f=u(b.value)||"",g=u(b.valueTo)||""):"greaterThanOrEqual"===b?.operator?f=u(b.value)||"":"lessThanOrEqual"===b?.operator&&(g=u(b.value)||"");const v=document.createElement("div");v.className="tbw-filter-date-range";const w=document.createElement("div");w.className="tbw-filter-date-group";const x=document.createElement("label");x.textContent="From",x.className="tbw-filter-range-label";const y=document.createElement("input");y.type="date",y.className="tbw-filter-date-input",p&&(y.min=p),h&&(y.max=h),y.value=f,w.appendChild(x),w.appendChild(y),v.appendChild(w);const C=document.createElement("span");C.className="tbw-filter-range-separator",C.textContent="–",v.appendChild(C);const E=document.createElement("div");E.className="tbw-filter-date-group";const S=document.createElement("label");S.textContent="To",S.className="tbw-filter-range-label";const F=document.createElement("input");F.type="date",F.className="tbw-filter-date-input",p&&(F.min=p),h&&(F.max=h),F.value=g,E.appendChild(S),E.appendChild(F),v.appendChild(E),e.appendChild(v);const k=document.createElement("label");k.className="tbw-filter-blank-option";const T=document.createElement("input");T.type="checkbox",T.className="tbw-filter-blank-checkbox",T.checked=m;const N=document.createTextNode("Show only blank");k.appendChild(T),k.appendChild(N);const A=e=>{y.disabled=e,F.disabled=e,v.classList.toggle("tbw-filter-disabled",e)};A(m),T.addEventListener("change",()=>{A(T.checked)}),e.appendChild(k);const P=document.createElement("div");P.className="tbw-filter-buttons";const I=document.createElement("button");I.className="tbw-filter-apply-btn",I.textContent="Apply",I.addEventListener("click",()=>{if(T.checked)return void t.applyTextFilter("blank","");const e=y.value,r=F.value;e&&r?t.applyTextFilter("between",e,r):e?t.applyTextFilter("greaterThanOrEqual",e):r?t.applyTextFilter("lessThanOrEqual",r):t.clearFilter()}),P.appendChild(I);const R=document.createElement("button");R.className="tbw-filter-clear-btn",R.textContent="Clear Filter",R.addEventListener("click",()=>{t.clearFilter()}),P.appendChild(R),e.appendChild(P)}applySetFilter(e,t,r){this.excludedValues.set(e,new Set(t)),0===t.length?this.filters.delete(e):this.filters.set(e,{field:e,type:"set",operator:"notIn",value:t,...void 0!==r&&{valueTo:r}}),this.applyFiltersInternal()}applyTextFilter(e,t,r,i){this.filters.set(e,{field:e,type:"text",operator:t,value:r,valueTo:i}),this.applyFiltersInternal()}applyFiltersInternal(){this.cachedResult=null,this.cacheKey=null,this.cachedInputSpot=null;const e=[...this.filters.values()];if(this.config.filterHandler){const t=this.grid;t.setAttribute("aria-busy","true");const r=this.config.filterHandler(e,this.sourceRows),i=r=>{t.removeAttribute("aria-busy"),this.cachedResult=r,this.grid.rows=r,this.emit("filter-change",{filters:e,filteredRowCount:r.length,selected:this.computeSelected()}),this.emitPluginEvent("filter-applied",{filters:e}),this.requestRender()};return void(r&&"function"==typeof r.then?r.then(i):i(r))}this.emit("filter-change",{filters:e,filteredRowCount:0,selected:this.computeSelected()}),this.emitPluginEvent("filter-applied",{filters:e}),this.requestRender()}getColumnState(e){const t=this.filters.get(e);if(t)return{filter:{type:t.type,operator:t.operator,value:t.value,valueTo:t.valueTo}}}applyColumnState(e,t){if(!t.filter)return void this.filters.delete(e);const r={field:e,type:t.filter.type,operator:t.filter.operator,value:t.filter.value,valueTo:t.filter.valueTo};this.filters.set(e,r),this.cachedResult=null,this.cacheKey=null,this.cachedInputSpot=null}}export{e as BLANK_FILTER_VALUE,o as FilteringPlugin,n as getUniqueValuesBatch};
1284
2
  //# sourceMappingURL=index.js.map