advanced-filter-system 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/filter.js DELETED
@@ -1,493 +0,0 @@
1
- (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Filter = {}));
5
- })(this, (function (exports) { 'use strict';
6
-
7
- /**
8
- * @fileoverview Advanced Filter System for DOM elements
9
- * @version 1.0.0
10
- *
11
- * A flexible and customizable filtering system that supports:
12
- * - Multiple filtering modes (OR/AND)
13
- * - Text search with debouncing
14
- * - Multiple sorting criteria
15
- * - Range filtering
16
- * - URL state management
17
- * - Animation and transitions
18
- * - Results counter
19
- */
20
-
21
- /**
22
- * Utility function for debouncing
23
- * @param {Function} func - Function to debounce
24
- * @param {number} wait - Delay in milliseconds
25
- * @returns {Function} Debounced function
26
- */
27
- function debounce(func, wait) {
28
- let timeout;
29
- return function executedFunction(...args) {
30
- const later = () => {
31
- clearTimeout(timeout);
32
- func(...args);
33
- };
34
- clearTimeout(timeout);
35
- timeout = setTimeout(later, wait);
36
- };
37
- }
38
- class Filter {
39
- /**
40
- * @typedef {Object} FilterOptions
41
- * @property {string} containerSelector - Main container selector
42
- * @property {string} itemSelector - Items to filter selector
43
- * @property {string} filterButtonSelector - Filter buttons selector
44
- * @property {string} [searchInputSelector] - Search input selector
45
- * @property {string} [counterSelector] - Results counter selector
46
- * @property {string} [activeClass='active'] - Active state class
47
- * @property {string} [hiddenClass='hidden'] - Hidden state class
48
- * @property {number} [animationDuration=300] - Animation duration in ms
49
- * @property {string} [filterMode='OR'] - Filter mode ('OR' or 'AND')
50
- * @property {string[]} [searchKeys=['title']] - Data attributes to search in
51
- * @property {number} [debounceTime=300] - Search debounce delay in ms
52
- */
53
-
54
- /**
55
- * @param {FilterOptions} options - Filter configuration options
56
- */
57
- constructor(options = {}) {
58
- this.options = {
59
- containerSelector: '.filter-container',
60
- itemSelector: '.filter-item',
61
- filterButtonSelector: '.btn-filter',
62
- searchInputSelector: '.filter-search',
63
- counterSelector: '.filter-counter',
64
- activeClass: 'active',
65
- hiddenClass: 'hidden',
66
- animationDuration: 300,
67
- filterMode: 'OR',
68
- searchKeys: ['title'],
69
- debounceTime: 300,
70
- ...options
71
- };
72
-
73
- // Initialize elements
74
- this.container = document.querySelector(this.options.containerSelector);
75
- this.items = document.querySelectorAll(this.options.itemSelector);
76
- this.filterButtons = document.querySelectorAll(this.options.filterButtonSelector);
77
- this.searchInput = document.querySelector(this.options.searchInputSelector);
78
- this.counter = document.querySelector(this.options.counterSelector);
79
-
80
- // Initialize state
81
- this.currentFilters = new Set(['*']);
82
- this.currentSearch = '';
83
- this.visibleItems = new Set(this.items);
84
- this.init();
85
- }
86
-
87
- /**
88
- * Initialize the filter system
89
- * @private
90
- */
91
- init() {
92
- this.addStyles();
93
- this.bindEvents();
94
- this.loadFromURL();
95
- this.updateCounter();
96
- }
97
-
98
- /**
99
- * Add required styles to document
100
- * @private
101
- */
102
- addStyles() {
103
- const styles = `
104
- .${this.options.hiddenClass} {
105
- display: none !important;
106
- }
107
-
108
- ${this.options.itemSelector} {
109
- opacity: 1;
110
- transform: scale(1);
111
- transition: opacity ${this.options.animationDuration}ms ease-out,
112
- transform ${this.options.animationDuration}ms ease-out;
113
- }
114
-
115
- ${this.options.filterButtonSelector} {
116
- opacity: 0.5;
117
- transition: opacity ${this.options.animationDuration}ms ease;
118
- }
119
-
120
- ${this.options.filterButtonSelector}.${this.options.activeClass} {
121
- opacity: 1;
122
- }
123
- `;
124
- const styleSheet = document.createElement('style');
125
- styleSheet.textContent = styles;
126
- document.head.appendChild(styleSheet);
127
- }
128
-
129
- /**
130
- * Bind all event listeners
131
- * @private
132
- */
133
- bindEvents() {
134
- this.filterButtons.forEach(button => {
135
- button.addEventListener('click', () => this.handleFilterClick(button));
136
- });
137
- if (this.searchInput) {
138
- this.searchInput.addEventListener('input', debounce(e => {
139
- this.search(e.target.value);
140
- }, this.options.debounceTime));
141
- }
142
- window.addEventListener('popstate', () => this.loadFromURL());
143
- }
144
-
145
- /**
146
- * Handle filter button clicks
147
- * @private
148
- * @param {HTMLElement} button - Clicked filter button
149
- */
150
- handleFilterClick(button) {
151
- const filterValue = button.dataset.filter;
152
- if (filterValue === '*') {
153
- this.resetFilters();
154
- } else {
155
- this.toggleFilter(filterValue, button);
156
- }
157
- this.filter();
158
- this.updateURL();
159
- }
160
-
161
- /**
162
- * Reset all filters to default state
163
- * @private
164
- */
165
- resetFilters() {
166
- this.filterButtons.forEach(btn => btn.classList.remove(this.options.activeClass));
167
- this.currentFilters.clear();
168
- this.currentFilters.add('*');
169
- this.filterButtons[0].classList.add(this.options.activeClass);
170
- this.resetCounter();
171
- }
172
-
173
- /**
174
- * Reset visible items counter
175
- * @private
176
- */
177
- resetCounter() {
178
- this.visibleItems = new Set(this.items);
179
- this.updateCounter();
180
- }
181
-
182
- /**
183
- * Toggle individual filter state
184
- * @private
185
- * @param {string} filterValue - Filter value to toggle
186
- * @param {HTMLElement} button - Filter button element
187
- */
188
- toggleFilter(filterValue, button) {
189
- this.currentFilters.delete('*');
190
- this.filterButtons[0].classList.remove(this.options.activeClass);
191
- if (button.classList.contains(this.options.activeClass)) {
192
- button.classList.remove(this.options.activeClass);
193
- this.currentFilters.delete(filterValue);
194
- if (this.currentFilters.size === 0) {
195
- this.resetFilters();
196
- }
197
- } else {
198
- button.classList.add(this.options.activeClass);
199
- this.currentFilters.add(filterValue);
200
- }
201
- }
202
-
203
- /**
204
- * Apply current filters to items
205
- * @public
206
- */
207
- filter() {
208
- this.items.forEach(item => {
209
- if (this.currentFilters.has('*')) {
210
- this.showItem(item);
211
- return;
212
- }
213
- const itemCategories = new Set(item.dataset.categories?.split(' ') || []);
214
- const matchesFilter = this.options.filterMode === 'OR' ? this.matchesAnyFilter(itemCategories) : this.matchesAllFilters(itemCategories);
215
- if (matchesFilter) {
216
- this.showItem(item);
217
- } else {
218
- this.hideItem(item);
219
- }
220
- });
221
- setTimeout(() => {
222
- this.updateCounter();
223
- }, this.options.animationDuration);
224
- }
225
-
226
- /**
227
- * Check if item matches any active filter (OR mode)
228
- * @private
229
- * @param {Set} itemCategories - Item's categories
230
- * @returns {boolean} Whether item matches any filter
231
- */
232
- matchesAnyFilter(itemCategories) {
233
- return [...this.currentFilters].some(filter => {
234
- const [type, value] = filter.split(':');
235
- return itemCategories.has(`${type}:${value}`);
236
- });
237
- }
238
-
239
- /**
240
- * Check if item matches all active filters (AND mode)
241
- * @private
242
- * @param {Set} itemCategories - Item's categories
243
- * @returns {boolean} Whether item matches all filters
244
- */
245
- matchesAllFilters(itemCategories) {
246
- return [...this.currentFilters].every(filter => {
247
- const [type, value] = filter.split(':');
248
- return itemCategories.has(`${type}:${value}`);
249
- });
250
- }
251
-
252
- /**
253
- * Show an item with animation
254
- * @private
255
- * @param {HTMLElement} item - Item to show
256
- */
257
- showItem(item) {
258
- this.visibleItems.add(item);
259
- item.classList.remove(this.options.hiddenClass);
260
- item.style.opacity = '0';
261
- item.style.transform = 'scale(0.95)';
262
- item.offsetHeight;
263
- requestAnimationFrame(() => {
264
- item.style.opacity = '1';
265
- item.style.transform = 'scale(1)';
266
- });
267
- }
268
-
269
- /**
270
- * Hide an item with animation
271
- * @private
272
- * @param {HTMLElement} item - Item to hide
273
- */
274
- hideItem(item) {
275
- item.style.opacity = '0';
276
- item.style.transform = 'scale(0.95)';
277
- setTimeout(() => {
278
- if (item.style.opacity === '0') {
279
- item.classList.add(this.options.hiddenClass);
280
- this.visibleItems.delete(item);
281
- }
282
- }, this.options.animationDuration);
283
- }
284
-
285
- /**
286
- * Search items by text
287
- * @public
288
- * @param {string} query - Search query
289
- */
290
- search(query) {
291
- this.currentSearch = query.toLowerCase().trim();
292
- this.items.forEach(item => {
293
- const searchText = this.options.searchKeys.map(key => item.dataset[key] || '').join(' ').toLowerCase();
294
- const matchesSearch = this.currentSearch === '' || searchText.includes(this.currentSearch);
295
- if (matchesSearch) {
296
- this.showItem(item);
297
- } else {
298
- this.hideItem(item);
299
- }
300
- });
301
- this.updateURL();
302
- setTimeout(() => {
303
- this.updateCounter();
304
- }, this.options.animationDuration);
305
- }
306
-
307
- /**
308
- * Sort items by multiple criteria
309
- * @public
310
- * @param {Array<{key: string, direction: string}>} criteria - Sort criteria
311
- */
312
- sortMultiple(criteria) {
313
- const items = [...this.items];
314
- items.sort((a, b) => {
315
- for (const criterion of criteria) {
316
- const valueA = a.dataset[criterion.key];
317
- const valueB = b.dataset[criterion.key];
318
- const comparison = criterion.direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
319
- if (comparison !== 0) return comparison;
320
- }
321
- return 0;
322
- });
323
- items.forEach(item => this.container.appendChild(item));
324
- }
325
-
326
- /**
327
- * Filter items by numeric range
328
- * @public
329
- * @param {string} key - Data attribute key
330
- * @param {number} min - Minimum value
331
- * @param {number} max - Maximum value
332
- */
333
- addRangeFilter(key, min, max) {
334
- this.items.forEach(item => {
335
- const value = parseFloat(item.dataset[key]);
336
- const inRange = value >= min && value <= max;
337
- if (inRange) {
338
- this.showItem(item);
339
- } else {
340
- this.hideItem(item);
341
- }
342
- });
343
- setTimeout(() => {
344
- this.updateCounter();
345
- }, this.options.animationDuration);
346
- }
347
-
348
- /**
349
- * Update URL with current filter state
350
- * @private
351
- */
352
- updateURL() {
353
- const params = new URLSearchParams();
354
-
355
- // Séparer les filtres par type
356
- const filtersByType = {};
357
- for (const filter of this.currentFilters) {
358
- if (filter !== '*') {
359
- const [type, value] = filter.split(':');
360
- if (!filtersByType[type]) {
361
- filtersByType[type] = new Set();
362
- }
363
- filtersByType[type].add(value);
364
- }
365
- }
366
-
367
- // Ajouter chaque type de filtre à l'URL
368
- Object.entries(filtersByType).forEach(([type, values]) => {
369
- params.set(type, Array.from(values).join(','));
370
- });
371
- if (this.currentSearch) {
372
- params.set('search', this.currentSearch);
373
- }
374
- const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`;
375
- window.history.pushState({}, '', newURL);
376
- }
377
-
378
- /**
379
- * Load filter state from URL
380
- * @private
381
- */
382
- loadFromURL() {
383
- const params = new URLSearchParams(window.location.search);
384
- this.currentFilters.clear();
385
-
386
- // Si aucun filtre n'est présent, utiliser '*'
387
- let hasFilters = false;
388
-
389
- // Parcourir tous les paramètres
390
- for (const [type, values] of params.entries()) {
391
- if (type !== 'search') {
392
- hasFilters = true;
393
- values.split(',').forEach(value => {
394
- this.currentFilters.add(`${type}:${value}`);
395
- });
396
- }
397
- }
398
- if (!hasFilters) {
399
- this.currentFilters.add('*');
400
- }
401
-
402
- // Update active buttons
403
- this.filterButtons.forEach(button => {
404
- const filterValue = button.dataset.filter;
405
- if (this.currentFilters.has(filterValue) || filterValue === '*' && this.currentFilters.has('*')) {
406
- button.classList.add(this.options.activeClass);
407
- } else {
408
- button.classList.remove(this.options.activeClass);
409
- }
410
- });
411
-
412
- // Load search
413
- const search = params.get('search') || '';
414
- if (this.searchInput) {
415
- this.searchInput.value = search;
416
- }
417
- this.filter();
418
- if (search) {
419
- this.search(search);
420
- }
421
- }
422
-
423
- /**
424
- * Update results counter
425
- * @private
426
- * @returns {{total: number, visible: number}}
427
- */
428
- updateCounter() {
429
- const total = this.items.length;
430
- const visible = this.visibleItems.size;
431
- if (this.counter) {
432
- this.counter.textContent = `Showing ${visible} of ${total}`;
433
- }
434
- return {
435
- total,
436
- visible
437
- };
438
- }
439
-
440
- /**
441
- * Change filter mode
442
- * @public
443
- * @param {string} mode - New filter mode ('OR' or 'AND')
444
- */
445
- setFilterMode(mode) {
446
- if (['OR', 'AND'].includes(mode.toUpperCase())) {
447
- this.options.filterMode = mode.toUpperCase();
448
- this.filter();
449
- }
450
- }
451
-
452
- /**
453
- * Add filter by type and value
454
- * @public
455
- * @param {string} type - Filter type
456
- * @param {string} value - Filter value
457
- */
458
- addFilter(type, value) {
459
- this.currentFilters.add(`${type}:${value}`);
460
- this.filter();
461
- this.updateURL();
462
- }
463
-
464
- /**
465
- * Remove filter by type and value
466
- * @public
467
- * @param {string} type - Filter type
468
- */
469
- removeFilter(type, value) {
470
- this.currentFilters.delete(`${type}:${value}`);
471
- if (this.currentFilters.size === 0) {
472
- this.currentFilters.add('*');
473
- }
474
- this.filter();
475
- this.updateURL();
476
- }
477
-
478
- /**
479
- * Get active filters by type
480
- * @public
481
- * @param {string} type - Filter type
482
- */
483
- getActiveFiltersByType(type) {
484
- return [...this.currentFilters].filter(filter => filter.startsWith(`${type}:`)).map(filter => filter.split(':')[1]);
485
- }
486
- }
487
-
488
- exports.Filter = Filter;
489
-
490
- Object.defineProperty(exports, '__esModule', { value: true });
491
-
492
- }));
493
- //# sourceMappingURL=filter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"filter.js","sources":["../src/index.js"],"sourcesContent":["/**\n * @fileoverview Advanced Filter System for DOM elements\n * @version 1.0.0\n * \n * A flexible and customizable filtering system that supports:\n * - Multiple filtering modes (OR/AND)\n * - Text search with debouncing\n * - Multiple sorting criteria\n * - Range filtering\n * - URL state management\n * - Animation and transitions\n * - Results counter\n */\n\n/**\n * Utility function for debouncing\n * @param {Function} func - Function to debounce\n * @param {number} wait - Delay in milliseconds\n * @returns {Function} Debounced function\n */\nfunction debounce(func, wait) {\n let timeout;\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n}\n\nclass Filter {\n /**\n * @typedef {Object} FilterOptions\n * @property {string} containerSelector - Main container selector\n * @property {string} itemSelector - Items to filter selector\n * @property {string} filterButtonSelector - Filter buttons selector\n * @property {string} [searchInputSelector] - Search input selector\n * @property {string} [counterSelector] - Results counter selector\n * @property {string} [activeClass='active'] - Active state class\n * @property {string} [hiddenClass='hidden'] - Hidden state class\n * @property {number} [animationDuration=300] - Animation duration in ms\n * @property {string} [filterMode='OR'] - Filter mode ('OR' or 'AND')\n * @property {string[]} [searchKeys=['title']] - Data attributes to search in\n * @property {number} [debounceTime=300] - Search debounce delay in ms\n */\n\n /**\n * @param {FilterOptions} options - Filter configuration options\n */\n constructor(options = {}) {\n this.options = {\n containerSelector: '.filter-container',\n itemSelector: '.filter-item',\n filterButtonSelector: '.btn-filter',\n searchInputSelector: '.filter-search',\n counterSelector: '.filter-counter',\n activeClass: 'active',\n hiddenClass: 'hidden',\n animationDuration: 300,\n filterMode: 'OR',\n searchKeys: ['title'],\n debounceTime: 300,\n ...options,\n };\n\n // Initialize elements\n this.container = document.querySelector(this.options.containerSelector);\n this.items = document.querySelectorAll(this.options.itemSelector);\n this.filterButtons = document.querySelectorAll(this.options.filterButtonSelector);\n this.searchInput = document.querySelector(this.options.searchInputSelector);\n this.counter = document.querySelector(this.options.counterSelector);\n\n // Initialize state\n this.currentFilters = new Set(['*']);\n this.currentSearch = '';\n this.visibleItems = new Set(this.items);\n\n this.init();\n }\n\n /**\n * Initialize the filter system\n * @private\n */\n init() {\n this.addStyles();\n this.bindEvents();\n this.loadFromURL();\n this.updateCounter();\n }\n\n /**\n * Add required styles to document\n * @private\n */\n addStyles() {\n const styles = `\n .${this.options.hiddenClass} {\n display: none !important;\n }\n\n ${this.options.itemSelector} {\n opacity: 1;\n transform: scale(1);\n transition: opacity ${this.options.animationDuration}ms ease-out,\n transform ${this.options.animationDuration}ms ease-out;\n }\n\n ${this.options.filterButtonSelector} {\n opacity: 0.5;\n transition: opacity ${this.options.animationDuration}ms ease;\n }\n\n ${this.options.filterButtonSelector}.${this.options.activeClass} {\n opacity: 1;\n }\n `;\n\n const styleSheet = document.createElement('style');\n styleSheet.textContent = styles;\n document.head.appendChild(styleSheet);\n }\n\n /**\n * Bind all event listeners\n * @private\n */\n bindEvents() {\n this.filterButtons.forEach(button => {\n button.addEventListener('click', () => this.handleFilterClick(button));\n });\n\n if (this.searchInput) {\n this.searchInput.addEventListener('input', debounce((e) => {\n this.search(e.target.value);\n }, this.options.debounceTime));\n }\n\n window.addEventListener('popstate', () => this.loadFromURL());\n }\n\n /**\n * Handle filter button clicks\n * @private\n * @param {HTMLElement} button - Clicked filter button\n */\n handleFilterClick(button) {\n const filterValue = button.dataset.filter;\n\n if (filterValue === '*') {\n this.resetFilters();\n } else {\n this.toggleFilter(filterValue, button);\n }\n\n this.filter();\n this.updateURL();\n }\n\n /**\n * Reset all filters to default state\n * @private\n */\n resetFilters() {\n this.filterButtons.forEach(btn => btn.classList.remove(this.options.activeClass));\n this.currentFilters.clear();\n this.currentFilters.add('*');\n this.filterButtons[0].classList.add(this.options.activeClass);\n this.resetCounter();\n }\n\n /**\n * Reset visible items counter\n * @private\n */\n resetCounter() {\n this.visibleItems = new Set(this.items);\n this.updateCounter();\n }\n\n /**\n * Toggle individual filter state\n * @private\n * @param {string} filterValue - Filter value to toggle\n * @param {HTMLElement} button - Filter button element\n */\n toggleFilter(filterValue, button) {\n this.currentFilters.delete('*');\n this.filterButtons[0].classList.remove(this.options.activeClass);\n\n if (button.classList.contains(this.options.activeClass)) {\n button.classList.remove(this.options.activeClass);\n this.currentFilters.delete(filterValue);\n\n if (this.currentFilters.size === 0) {\n this.resetFilters();\n }\n } else {\n button.classList.add(this.options.activeClass);\n this.currentFilters.add(filterValue);\n }\n }\n\n /**\n * Apply current filters to items\n * @public\n */\n filter() {\n this.items.forEach(item => {\n if (this.currentFilters.has('*')) {\n this.showItem(item);\n return;\n }\n\n const itemCategories = new Set(item.dataset.categories?.split(' ') || []);\n const matchesFilter = this.options.filterMode === 'OR'\n ? this.matchesAnyFilter(itemCategories)\n : this.matchesAllFilters(itemCategories);\n\n if (matchesFilter) {\n this.showItem(item);\n } else {\n this.hideItem(item);\n }\n });\n\n setTimeout(() => {\n this.updateCounter();\n }, this.options.animationDuration);\n }\n\n /**\n * Check if item matches any active filter (OR mode)\n * @private\n * @param {Set} itemCategories - Item's categories\n * @returns {boolean} Whether item matches any filter\n */\n matchesAnyFilter(itemCategories) {\n return [...this.currentFilters].some(filter => {\n const [type, value] = filter.split(':');\n return itemCategories.has(`${type}:${value}`);\n });\n }\n\n /**\n * Check if item matches all active filters (AND mode)\n * @private\n * @param {Set} itemCategories - Item's categories\n * @returns {boolean} Whether item matches all filters\n */\n matchesAllFilters(itemCategories) {\n return [...this.currentFilters].every(filter => {\n const [type, value] = filter.split(':');\n return itemCategories.has(`${type}:${value}`);\n });\n }\n\n /**\n * Show an item with animation\n * @private\n * @param {HTMLElement} item - Item to show\n */\n showItem(item) {\n this.visibleItems.add(item);\n item.classList.remove(this.options.hiddenClass);\n item.style.opacity = '0';\n item.style.transform = 'scale(0.95)';\n \n item.offsetHeight;\n \n requestAnimationFrame(() => {\n item.style.opacity = '1';\n item.style.transform = 'scale(1)';\n });\n }\n\n /**\n * Hide an item with animation\n * @private\n * @param {HTMLElement} item - Item to hide\n */\n hideItem(item) {\n item.style.opacity = '0';\n item.style.transform = 'scale(0.95)';\n \n setTimeout(() => {\n if (item.style.opacity === '0') {\n item.classList.add(this.options.hiddenClass);\n this.visibleItems.delete(item);\n }\n }, this.options.animationDuration);\n }\n\n /**\n * Search items by text\n * @public\n * @param {string} query - Search query\n */\n search(query) {\n this.currentSearch = query.toLowerCase().trim();\n let matches = 0;\n \n this.items.forEach(item => {\n const searchText = this.options.searchKeys\n .map(key => item.dataset[key] || '')\n .join(' ')\n .toLowerCase();\n\n const matchesSearch = this.currentSearch === '' || \n searchText.includes(this.currentSearch);\n\n if (matchesSearch) {\n this.showItem(item);\n matches++;\n } else {\n this.hideItem(item);\n }\n });\n\n this.updateURL();\n \n setTimeout(() => {\n this.updateCounter();\n }, this.options.animationDuration);\n }\n\n /**\n * Sort items by multiple criteria\n * @public\n * @param {Array<{key: string, direction: string}>} criteria - Sort criteria\n */\n sortMultiple(criteria) {\n const items = [...this.items];\n \n items.sort((a, b) => {\n for (const criterion of criteria) {\n const valueA = a.dataset[criterion.key];\n const valueB = b.dataset[criterion.key];\n \n const comparison = criterion.direction === 'asc'\n ? valueA.localeCompare(valueB)\n : valueB.localeCompare(valueA);\n \n if (comparison !== 0) return comparison;\n }\n return 0;\n });\n\n items.forEach(item => this.container.appendChild(item));\n }\n\n /**\n * Filter items by numeric range\n * @public\n * @param {string} key - Data attribute key\n * @param {number} min - Minimum value\n * @param {number} max - Maximum value\n */\n addRangeFilter(key, min, max) {\n this.items.forEach(item => {\n const value = parseFloat(item.dataset[key]);\n const inRange = value >= min && value <= max;\n \n if (inRange) {\n this.showItem(item);\n } else {\n this.hideItem(item);\n }\n });\n\n setTimeout(() => {\n this.updateCounter();\n }, this.options.animationDuration);\n }\n\n /**\n * Update URL with current filter state\n * @private\n */\n updateURL() {\n const params = new URLSearchParams();\n \n // Séparer les filtres par type\n const filtersByType = {};\n for (const filter of this.currentFilters) {\n if (filter !== '*') {\n const [type, value] = filter.split(':');\n if (!filtersByType[type]) {\n filtersByType[type] = new Set();\n }\n filtersByType[type].add(value);\n }\n }\n \n // Ajouter chaque type de filtre à l'URL\n Object.entries(filtersByType).forEach(([type, values]) => {\n params.set(type, Array.from(values).join(','));\n });\n \n if (this.currentSearch) {\n params.set('search', this.currentSearch);\n }\n \n const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`;\n window.history.pushState({}, '', newURL);\n }\n\n /**\n * Load filter state from URL\n * @private\n */\n loadFromURL() {\n const params = new URLSearchParams(window.location.search);\n this.currentFilters.clear();\n \n // Si aucun filtre n'est présent, utiliser '*'\n let hasFilters = false;\n \n // Parcourir tous les paramètres\n for (const [type, values] of params.entries()) {\n if (type !== 'search') {\n hasFilters = true;\n values.split(',').forEach(value => {\n this.currentFilters.add(`${type}:${value}`);\n });\n }\n }\n \n if (!hasFilters) {\n this.currentFilters.add('*');\n }\n \n // Update active buttons\n this.filterButtons.forEach(button => {\n const filterValue = button.dataset.filter;\n if (this.currentFilters.has(filterValue) || \n (filterValue === '*' && this.currentFilters.has('*'))) {\n button.classList.add(this.options.activeClass);\n } else {\n button.classList.remove(this.options.activeClass);\n }\n });\n \n // Load search\n const search = params.get('search') || '';\n if (this.searchInput) {\n this.searchInput.value = search;\n }\n \n this.filter();\n if (search) {\n this.search(search);\n }\n }\n\n /**\n * Update results counter\n * @private\n * @returns {{total: number, visible: number}}\n */\n updateCounter() {\n const total = this.items.length;\n const visible = this.visibleItems.size;\n \n if (this.counter) {\n this.counter.textContent = `Showing ${visible} of ${total}`;\n }\n \n return { total, visible };\n }\n\n /**\n * Change filter mode\n * @public\n * @param {string} mode - New filter mode ('OR' or 'AND')\n */\n setFilterMode(mode) {\n if (['OR', 'AND'].includes(mode.toUpperCase())) {\n this.options.filterMode = mode.toUpperCase();\n this.filter();\n }\n }\n\n /**\n * Add filter by type and value\n * @public\n * @param {string} type - Filter type\n * @param {string} value - Filter value\n */\n addFilter(type, value) {\n this.currentFilters.add(`${type}:${value}`);\n this.filter();\n this.updateURL();\n }\n \n /**\n * Remove filter by type and value\n * @public\n * @param {string} type - Filter type\n */\n removeFilter(type, value) {\n this.currentFilters.delete(`${type}:${value}`);\n if (this.currentFilters.size === 0) {\n this.currentFilters.add('*');\n }\n this.filter();\n this.updateURL();\n }\n \n /**\n * Get active filters by type\n * @public\n * @param {string} type - Filter type\n */\n getActiveFiltersByType(type) {\n return [...this.currentFilters]\n .filter(filter => filter.startsWith(`${type}:`))\n .map(filter => filter.split(':')[1]);\n }\n}\n\nexport { Filter };"],"names":["debounce","func","wait","timeout","executedFunction","args","later","clearTimeout","setTimeout","Filter","constructor","options","containerSelector","itemSelector","filterButtonSelector","searchInputSelector","counterSelector","activeClass","hiddenClass","animationDuration","filterMode","searchKeys","debounceTime","container","document","querySelector","items","querySelectorAll","filterButtons","searchInput","counter","currentFilters","Set","currentSearch","visibleItems","init","addStyles","bindEvents","loadFromURL","updateCounter","styles","styleSheet","createElement","textContent","head","appendChild","forEach","button","addEventListener","handleFilterClick","e","search","target","value","window","filterValue","dataset","filter","resetFilters","toggleFilter","updateURL","btn","classList","remove","clear","add","resetCounter","delete","contains","size","item","has","showItem","itemCategories","categories","split","matchesFilter","matchesAnyFilter","matchesAllFilters","hideItem","some","type","every","style","opacity","transform","offsetHeight","requestAnimationFrame","query","toLowerCase","trim","searchText","map","key","join","matchesSearch","includes","sortMultiple","criteria","sort","a","b","criterion","valueA","valueB","comparison","direction","localeCompare","addRangeFilter","min","max","parseFloat","inRange","params","URLSearchParams","filtersByType","Object","entries","values","set","Array","from","newURL","location","pathname","toString","history","pushState","hasFilters","get","total","length","visible","setFilterMode","mode","toUpperCase","addFilter","removeFilter","getActiveFiltersByType","startsWith"],"mappings":";;;;;;IAAA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;IAEA;IACA;IACA;IACA;IACA;IACA;IACA,SAASA,QAAQA,CAACC,IAAI,EAAEC,IAAI,EAAE;IAC1B,EAAA,IAAIC,OAAO,CAAA;IACX,EAAA,OAAO,SAASC,gBAAgBA,CAAC,GAAGC,IAAI,EAAE;QACtC,MAAMC,KAAK,GAAGA,MAAM;UAChBC,YAAY,CAACJ,OAAO,CAAC,CAAA;UACrBF,IAAI,CAAC,GAAGI,IAAI,CAAC,CAAA;SAChB,CAAA;QACDE,YAAY,CAACJ,OAAO,CAAC,CAAA;IACrBA,IAAAA,OAAO,GAAGK,UAAU,CAACF,KAAK,EAAEJ,IAAI,CAAC,CAAA;OACpC,CAAA;IACL,CAAA;IAEA,MAAMO,MAAM,CAAC;IACT;IACJ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;IAEI;IACJ;IACA;IACIC,EAAAA,WAAWA,CAACC,OAAO,GAAG,EAAE,EAAE;QACtB,IAAI,CAACA,OAAO,GAAG;IACXC,MAAAA,iBAAiB,EAAE,mBAAmB;IACtCC,MAAAA,YAAY,EAAE,cAAc;IAC5BC,MAAAA,oBAAoB,EAAE,aAAa;IACnCC,MAAAA,mBAAmB,EAAE,gBAAgB;IACrCC,MAAAA,eAAe,EAAE,iBAAiB;IAClCC,MAAAA,WAAW,EAAE,QAAQ;IACrBC,MAAAA,WAAW,EAAE,QAAQ;IACrBC,MAAAA,iBAAiB,EAAE,GAAG;IACtBC,MAAAA,UAAU,EAAE,IAAI;UAChBC,UAAU,EAAE,CAAC,OAAO,CAAC;IACrBC,MAAAA,YAAY,EAAE,GAAG;UACjB,GAAGX,OAAAA;SACN,CAAA;;IAED;IACA,IAAA,IAAI,CAACY,SAAS,GAAGC,QAAQ,CAACC,aAAa,CAAC,IAAI,CAACd,OAAO,CAACC,iBAAiB,CAAC,CAAA;IACvE,IAAA,IAAI,CAACc,KAAK,GAAGF,QAAQ,CAACG,gBAAgB,CAAC,IAAI,CAAChB,OAAO,CAACE,YAAY,CAAC,CAAA;IACjE,IAAA,IAAI,CAACe,aAAa,GAAGJ,QAAQ,CAACG,gBAAgB,CAAC,IAAI,CAAChB,OAAO,CAACG,oBAAoB,CAAC,CAAA;IACjF,IAAA,IAAI,CAACe,WAAW,GAAGL,QAAQ,CAACC,aAAa,CAAC,IAAI,CAACd,OAAO,CAACI,mBAAmB,CAAC,CAAA;IAC3E,IAAA,IAAI,CAACe,OAAO,GAAGN,QAAQ,CAACC,aAAa,CAAC,IAAI,CAACd,OAAO,CAACK,eAAe,CAAC,CAAA;;IAEnE;QACA,IAAI,CAACe,cAAc,GAAG,IAAIC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACpC,IAAI,CAACC,aAAa,GAAG,EAAE,CAAA;QACvB,IAAI,CAACC,YAAY,GAAG,IAAIF,GAAG,CAAC,IAAI,CAACN,KAAK,CAAC,CAAA;QAEvC,IAAI,CAACS,IAAI,EAAE,CAAA;IACf,GAAA;;IAEA;IACJ;IACA;IACA;IACIA,EAAAA,IAAIA,GAAG;QACH,IAAI,CAACC,SAAS,EAAE,CAAA;QAChB,IAAI,CAACC,UAAU,EAAE,CAAA;QACjB,IAAI,CAACC,WAAW,EAAE,CAAA;QAClB,IAAI,CAACC,aAAa,EAAE,CAAA;IACxB,GAAA;;IAEA;IACJ;IACA;IACA;IACIH,EAAAA,SAASA,GAAG;IACR,IAAA,MAAMI,MAAM,GAAG,CAAA;AACvB,aAAA,EAAe,IAAI,CAAC7B,OAAO,CAACO,WAAW,CAAA;AACvC;AACA;AACA;AACA,YAAA,EAAc,IAAI,CAACP,OAAO,CAACE,YAAY,CAAA;AACvC;AACA;AACA,oCAAA,EAAsC,IAAI,CAACF,OAAO,CAACQ,iBAAiB,CAAA;AACpE,sCAAA,EAAwC,IAAI,CAACR,OAAO,CAACQ,iBAAiB,CAAA;AACtE;AACA;AACA,YAAA,EAAc,IAAI,CAACR,OAAO,CAACG,oBAAoB,CAAA;AAC/C;AACA,oCAAA,EAAsC,IAAI,CAACH,OAAO,CAACQ,iBAAiB,CAAA;AACpE;AACA;AACA,YAAc,EAAA,IAAI,CAACR,OAAO,CAACG,oBAAoB,IAAI,IAAI,CAACH,OAAO,CAACM,WAAW,CAAA;AAC3E;AACA;AACA,QAAS,CAAA,CAAA;IAED,IAAA,MAAMwB,UAAU,GAAGjB,QAAQ,CAACkB,aAAa,CAAC,OAAO,CAAC,CAAA;QAClDD,UAAU,CAACE,WAAW,GAAGH,MAAM,CAAA;IAC/BhB,IAAAA,QAAQ,CAACoB,IAAI,CAACC,WAAW,CAACJ,UAAU,CAAC,CAAA;IACzC,GAAA;;IAEA;IACJ;IACA;IACA;IACIJ,EAAAA,UAAUA,GAAG;IACT,IAAA,IAAI,CAACT,aAAa,CAACkB,OAAO,CAACC,MAAM,IAAI;IACjCA,MAAAA,MAAM,CAACC,gBAAgB,CAAC,OAAO,EAAE,MAAM,IAAI,CAACC,iBAAiB,CAACF,MAAM,CAAC,CAAC,CAAA;IAC1E,KAAC,CAAC,CAAA;QAEF,IAAI,IAAI,CAAClB,WAAW,EAAE;UAClB,IAAI,CAACA,WAAW,CAACmB,gBAAgB,CAAC,OAAO,EAAEhD,QAAQ,CAAEkD,CAAC,IAAK;YACvD,IAAI,CAACC,MAAM,CAACD,CAAC,CAACE,MAAM,CAACC,KAAK,CAAC,CAAA;IAC/B,OAAC,EAAE,IAAI,CAAC1C,OAAO,CAACW,YAAY,CAAC,CAAC,CAAA;IAClC,KAAA;QAEAgC,MAAM,CAACN,gBAAgB,CAAC,UAAU,EAAE,MAAM,IAAI,CAACV,WAAW,EAAE,CAAC,CAAA;IACjE,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACIW,iBAAiBA,CAACF,MAAM,EAAE;IACtB,IAAA,MAAMQ,WAAW,GAAGR,MAAM,CAACS,OAAO,CAACC,MAAM,CAAA;QAEzC,IAAIF,WAAW,KAAK,GAAG,EAAE;UACrB,IAAI,CAACG,YAAY,EAAE,CAAA;IACvB,KAAC,MAAM;IACH,MAAA,IAAI,CAACC,YAAY,CAACJ,WAAW,EAAER,MAAM,CAAC,CAAA;IAC1C,KAAA;QAEA,IAAI,CAACU,MAAM,EAAE,CAAA;QACb,IAAI,CAACG,SAAS,EAAE,CAAA;IACpB,GAAA;;IAEA;IACJ;IACA;IACA;IACIF,EAAAA,YAAYA,GAAG;IACX,IAAA,IAAI,CAAC9B,aAAa,CAACkB,OAAO,CAACe,GAAG,IAAIA,GAAG,CAACC,SAAS,CAACC,MAAM,CAAC,IAAI,CAACpD,OAAO,CAACM,WAAW,CAAC,CAAC,CAAA;IACjF,IAAA,IAAI,CAACc,cAAc,CAACiC,KAAK,EAAE,CAAA;IAC3B,IAAA,IAAI,CAACjC,cAAc,CAACkC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,IAAA,IAAI,CAACrC,aAAa,CAAC,CAAC,CAAC,CAACkC,SAAS,CAACG,GAAG,CAAC,IAAI,CAACtD,OAAO,CAACM,WAAW,CAAC,CAAA;QAC7D,IAAI,CAACiD,YAAY,EAAE,CAAA;IACvB,GAAA;;IAEA;IACJ;IACA;IACA;IACIA,EAAAA,YAAYA,GAAG;QACX,IAAI,CAAChC,YAAY,GAAG,IAAIF,GAAG,CAAC,IAAI,CAACN,KAAK,CAAC,CAAA;QACvC,IAAI,CAACa,aAAa,EAAE,CAAA;IACxB,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACA;IACIoB,EAAAA,YAAYA,CAACJ,WAAW,EAAER,MAAM,EAAE;IAC9B,IAAA,IAAI,CAAChB,cAAc,CAACoC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC/B,IAAA,IAAI,CAACvC,aAAa,CAAC,CAAC,CAAC,CAACkC,SAAS,CAACC,MAAM,CAAC,IAAI,CAACpD,OAAO,CAACM,WAAW,CAAC,CAAA;IAEhE,IAAA,IAAI8B,MAAM,CAACe,SAAS,CAACM,QAAQ,CAAC,IAAI,CAACzD,OAAO,CAACM,WAAW,CAAC,EAAE;UACrD8B,MAAM,CAACe,SAAS,CAACC,MAAM,CAAC,IAAI,CAACpD,OAAO,CAACM,WAAW,CAAC,CAAA;IACjD,MAAA,IAAI,CAACc,cAAc,CAACoC,MAAM,CAACZ,WAAW,CAAC,CAAA;IAEvC,MAAA,IAAI,IAAI,CAACxB,cAAc,CAACsC,IAAI,KAAK,CAAC,EAAE;YAChC,IAAI,CAACX,YAAY,EAAE,CAAA;IACvB,OAAA;IACJ,KAAC,MAAM;UACHX,MAAM,CAACe,SAAS,CAACG,GAAG,CAAC,IAAI,CAACtD,OAAO,CAACM,WAAW,CAAC,CAAA;IAC9C,MAAA,IAAI,CAACc,cAAc,CAACkC,GAAG,CAACV,WAAW,CAAC,CAAA;IACxC,KAAA;IACJ,GAAA;;IAEA;IACJ;IACA;IACA;IACIE,EAAAA,MAAMA,GAAG;IACL,IAAA,IAAI,CAAC/B,KAAK,CAACoB,OAAO,CAACwB,IAAI,IAAI;UACvB,IAAI,IAAI,CAACvC,cAAc,CAACwC,GAAG,CAAC,GAAG,CAAC,EAAE;IAC9B,QAAA,IAAI,CAACC,QAAQ,CAACF,IAAI,CAAC,CAAA;IACnB,QAAA,OAAA;IACJ,OAAA;IAEA,MAAA,MAAMG,cAAc,GAAG,IAAIzC,GAAG,CAACsC,IAAI,CAACd,OAAO,CAACkB,UAAU,EAAEC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;UACzE,MAAMC,aAAa,GAAG,IAAI,CAACjE,OAAO,CAACS,UAAU,KAAK,IAAI,GAChD,IAAI,CAACyD,gBAAgB,CAACJ,cAAc,CAAC,GACrC,IAAI,CAACK,iBAAiB,CAACL,cAAc,CAAC,CAAA;IAE5C,MAAA,IAAIG,aAAa,EAAE;IACf,QAAA,IAAI,CAACJ,QAAQ,CAACF,IAAI,CAAC,CAAA;IACvB,OAAC,MAAM;IACH,QAAA,IAAI,CAACS,QAAQ,CAACT,IAAI,CAAC,CAAA;IACvB,OAAA;IACJ,KAAC,CAAC,CAAA;IAEF9D,IAAAA,UAAU,CAAC,MAAM;UACb,IAAI,CAAC+B,aAAa,EAAE,CAAA;IACxB,KAAC,EAAE,IAAI,CAAC5B,OAAO,CAACQ,iBAAiB,CAAC,CAAA;IACtC,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACA;MACI0D,gBAAgBA,CAACJ,cAAc,EAAE;QAC7B,OAAO,CAAC,GAAG,IAAI,CAAC1C,cAAc,CAAC,CAACiD,IAAI,CAACvB,MAAM,IAAI;UAC3C,MAAM,CAACwB,IAAI,EAAE5B,KAAK,CAAC,GAAGI,MAAM,CAACkB,KAAK,CAAC,GAAG,CAAC,CAAA;UACvC,OAAOF,cAAc,CAACF,GAAG,CAAC,GAAGU,IAAI,CAAA,CAAA,EAAI5B,KAAK,CAAA,CAAE,CAAC,CAAA;IACjD,KAAC,CAAC,CAAA;IACN,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACA;MACIyB,iBAAiBA,CAACL,cAAc,EAAE;QAC9B,OAAO,CAAC,GAAG,IAAI,CAAC1C,cAAc,CAAC,CAACmD,KAAK,CAACzB,MAAM,IAAI;UAC5C,MAAM,CAACwB,IAAI,EAAE5B,KAAK,CAAC,GAAGI,MAAM,CAACkB,KAAK,CAAC,GAAG,CAAC,CAAA;UACvC,OAAOF,cAAc,CAACF,GAAG,CAAC,GAAGU,IAAI,CAAA,CAAA,EAAI5B,KAAK,CAAA,CAAE,CAAC,CAAA;IACjD,KAAC,CAAC,CAAA;IACN,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACImB,QAAQA,CAACF,IAAI,EAAE;IACX,IAAA,IAAI,CAACpC,YAAY,CAAC+B,GAAG,CAACK,IAAI,CAAC,CAAA;QAC3BA,IAAI,CAACR,SAAS,CAACC,MAAM,CAAC,IAAI,CAACpD,OAAO,CAACO,WAAW,CAAC,CAAA;IAC/CoD,IAAAA,IAAI,CAACa,KAAK,CAACC,OAAO,GAAG,GAAG,CAAA;IACxBd,IAAAA,IAAI,CAACa,KAAK,CAACE,SAAS,GAAG,aAAa,CAAA;IAEpCf,IAAAA,IAAI,CAACgB,YAAY,CAAA;IAEjBC,IAAAA,qBAAqB,CAAC,MAAM;IACxBjB,MAAAA,IAAI,CAACa,KAAK,CAACC,OAAO,GAAG,GAAG,CAAA;IACxBd,MAAAA,IAAI,CAACa,KAAK,CAACE,SAAS,GAAG,UAAU,CAAA;IACrC,KAAC,CAAC,CAAA;IACN,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACIN,QAAQA,CAACT,IAAI,EAAE;IACXA,IAAAA,IAAI,CAACa,KAAK,CAACC,OAAO,GAAG,GAAG,CAAA;IACxBd,IAAAA,IAAI,CAACa,KAAK,CAACE,SAAS,GAAG,aAAa,CAAA;IAEpC7E,IAAAA,UAAU,CAAC,MAAM;IACb,MAAA,IAAI8D,IAAI,CAACa,KAAK,CAACC,OAAO,KAAK,GAAG,EAAE;YAC5Bd,IAAI,CAACR,SAAS,CAACG,GAAG,CAAC,IAAI,CAACtD,OAAO,CAACO,WAAW,CAAC,CAAA;IAC5C,QAAA,IAAI,CAACgB,YAAY,CAACiC,MAAM,CAACG,IAAI,CAAC,CAAA;IAClC,OAAA;IACJ,KAAC,EAAE,IAAI,CAAC3D,OAAO,CAACQ,iBAAiB,CAAC,CAAA;IACtC,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACIgC,MAAMA,CAACqC,KAAK,EAAE;QACV,IAAI,CAACvD,aAAa,GAAGuD,KAAK,CAACC,WAAW,EAAE,CAACC,IAAI,EAAE,CAAA;IAG/C,IAAA,IAAI,CAAChE,KAAK,CAACoB,OAAO,CAACwB,IAAI,IAAI;IACvB,MAAA,MAAMqB,UAAU,GAAG,IAAI,CAAChF,OAAO,CAACU,UAAU,CACrCuE,GAAG,CAACC,GAAG,IAAIvB,IAAI,CAACd,OAAO,CAACqC,GAAG,CAAC,IAAI,EAAE,CAAC,CACnCC,IAAI,CAAC,GAAG,CAAC,CACTL,WAAW,EAAE,CAAA;IAElB,MAAA,MAAMM,aAAa,GAAG,IAAI,CAAC9D,aAAa,KAAK,EAAE,IAC3C0D,UAAU,CAACK,QAAQ,CAAC,IAAI,CAAC/D,aAAa,CAAC,CAAA;IAE3C,MAAA,IAAI8D,aAAa,EAAE;IACf,QAAA,IAAI,CAACvB,QAAQ,CAACF,IAAI,CAAC,CAAA;IAEvB,OAAC,MAAM;IACH,QAAA,IAAI,CAACS,QAAQ,CAACT,IAAI,CAAC,CAAA;IACvB,OAAA;IACJ,KAAC,CAAC,CAAA;QAEF,IAAI,CAACV,SAAS,EAAE,CAAA;IAEhBpD,IAAAA,UAAU,CAAC,MAAM;UACb,IAAI,CAAC+B,aAAa,EAAE,CAAA;IACxB,KAAC,EAAE,IAAI,CAAC5B,OAAO,CAACQ,iBAAiB,CAAC,CAAA;IACtC,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACI8E,YAAYA,CAACC,QAAQ,EAAE;IACnB,IAAA,MAAMxE,KAAK,GAAG,CAAC,GAAG,IAAI,CAACA,KAAK,CAAC,CAAA;IAE7BA,IAAAA,KAAK,CAACyE,IAAI,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAK;IACjB,MAAA,KAAK,MAAMC,SAAS,IAAIJ,QAAQ,EAAE;YAC9B,MAAMK,MAAM,GAAGH,CAAC,CAAC5C,OAAO,CAAC8C,SAAS,CAACT,GAAG,CAAC,CAAA;YACvC,MAAMW,MAAM,GAAGH,CAAC,CAAC7C,OAAO,CAAC8C,SAAS,CAACT,GAAG,CAAC,CAAA;YAEvC,MAAMY,UAAU,GAAGH,SAAS,CAACI,SAAS,KAAK,KAAK,GAC1CH,MAAM,CAACI,aAAa,CAACH,MAAM,CAAC,GAC5BA,MAAM,CAACG,aAAa,CAACJ,MAAM,CAAC,CAAA;IAElC,QAAA,IAAIE,UAAU,KAAK,CAAC,EAAE,OAAOA,UAAU,CAAA;IAC3C,OAAA;IACA,MAAA,OAAO,CAAC,CAAA;IACZ,KAAC,CAAC,CAAA;IAEF/E,IAAAA,KAAK,CAACoB,OAAO,CAACwB,IAAI,IAAI,IAAI,CAAC/C,SAAS,CAACsB,WAAW,CAACyB,IAAI,CAAC,CAAC,CAAA;IAC3D,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACA;IACA;IACIsC,EAAAA,cAAcA,CAACf,GAAG,EAAEgB,GAAG,EAAEC,GAAG,EAAE;IAC1B,IAAA,IAAI,CAACpF,KAAK,CAACoB,OAAO,CAACwB,IAAI,IAAI;UACvB,MAAMjB,KAAK,GAAG0D,UAAU,CAACzC,IAAI,CAACd,OAAO,CAACqC,GAAG,CAAC,CAAC,CAAA;UAC3C,MAAMmB,OAAO,GAAG3D,KAAK,IAAIwD,GAAG,IAAIxD,KAAK,IAAIyD,GAAG,CAAA;IAE5C,MAAA,IAAIE,OAAO,EAAE;IACT,QAAA,IAAI,CAACxC,QAAQ,CAACF,IAAI,CAAC,CAAA;IACvB,OAAC,MAAM;IACH,QAAA,IAAI,CAACS,QAAQ,CAACT,IAAI,CAAC,CAAA;IACvB,OAAA;IACJ,KAAC,CAAC,CAAA;IAEF9D,IAAAA,UAAU,CAAC,MAAM;UACb,IAAI,CAAC+B,aAAa,EAAE,CAAA;IACxB,KAAC,EAAE,IAAI,CAAC5B,OAAO,CAACQ,iBAAiB,CAAC,CAAA;IACtC,GAAA;;IAEA;IACJ;IACA;IACA;IACIyC,EAAAA,SAASA,GAAG;IACR,IAAA,MAAMqD,MAAM,GAAG,IAAIC,eAAe,EAAE,CAAA;;IAEpC;QACA,MAAMC,aAAa,GAAG,EAAE,CAAA;IACxB,IAAA,KAAK,MAAM1D,MAAM,IAAI,IAAI,CAAC1B,cAAc,EAAE;UACtC,IAAI0B,MAAM,KAAK,GAAG,EAAE;YAChB,MAAM,CAACwB,IAAI,EAAE5B,KAAK,CAAC,GAAGI,MAAM,CAACkB,KAAK,CAAC,GAAG,CAAC,CAAA;IACvC,QAAA,IAAI,CAACwC,aAAa,CAAClC,IAAI,CAAC,EAAE;IACtBkC,UAAAA,aAAa,CAAClC,IAAI,CAAC,GAAG,IAAIjD,GAAG,EAAE,CAAA;IACnC,SAAA;IACAmF,QAAAA,aAAa,CAAClC,IAAI,CAAC,CAAChB,GAAG,CAACZ,KAAK,CAAC,CAAA;IAClC,OAAA;IACJ,KAAA;;IAEA;IACA+D,IAAAA,MAAM,CAACC,OAAO,CAACF,aAAa,CAAC,CAACrE,OAAO,CAAC,CAAC,CAACmC,IAAI,EAAEqC,MAAM,CAAC,KAAK;IACtDL,MAAAA,MAAM,CAACM,GAAG,CAACtC,IAAI,EAAEuC,KAAK,CAACC,IAAI,CAACH,MAAM,CAAC,CAACxB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAClD,KAAC,CAAC,CAAA;QAEF,IAAI,IAAI,CAAC7D,aAAa,EAAE;UACpBgF,MAAM,CAACM,GAAG,CAAC,QAAQ,EAAE,IAAI,CAACtF,aAAa,CAAC,CAAA;IAC5C,KAAA;QAEA,MAAMyF,MAAM,GAAG,CAAA,EAAGpE,MAAM,CAACqE,QAAQ,CAACC,QAAQ,CAAGX,EAAAA,MAAM,CAACY,QAAQ,EAAE,GAAG,GAAG,GAAGZ,MAAM,CAACY,QAAQ,EAAE,GAAG,EAAE,CAAE,CAAA,CAAA;QAC/FvE,MAAM,CAACwE,OAAO,CAACC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAEL,MAAM,CAAC,CAAA;IAC5C,GAAA;;IAEA;IACJ;IACA;IACA;IACIpF,EAAAA,WAAWA,GAAG;QACV,MAAM2E,MAAM,GAAG,IAAIC,eAAe,CAAC5D,MAAM,CAACqE,QAAQ,CAACxE,MAAM,CAAC,CAAA;IAC1D,IAAA,IAAI,CAACpB,cAAc,CAACiC,KAAK,EAAE,CAAA;;IAE3B;QACA,IAAIgE,UAAU,GAAG,KAAK,CAAA;;IAEtB;IACA,IAAA,KAAK,MAAM,CAAC/C,IAAI,EAAEqC,MAAM,CAAC,IAAIL,MAAM,CAACI,OAAO,EAAE,EAAE;UAC3C,IAAIpC,IAAI,KAAK,QAAQ,EAAE;IACnB+C,QAAAA,UAAU,GAAG,IAAI,CAAA;YACjBV,MAAM,CAAC3C,KAAK,CAAC,GAAG,CAAC,CAAC7B,OAAO,CAACO,KAAK,IAAI;cAC/B,IAAI,CAACtB,cAAc,CAACkC,GAAG,CAAC,GAAGgB,IAAI,CAAA,CAAA,EAAI5B,KAAK,CAAA,CAAE,CAAC,CAAA;IAC/C,SAAC,CAAC,CAAA;IACN,OAAA;IACJ,KAAA;QAEA,IAAI,CAAC2E,UAAU,EAAE;IACb,MAAA,IAAI,CAACjG,cAAc,CAACkC,GAAG,CAAC,GAAG,CAAC,CAAA;IAChC,KAAA;;IAEA;IACA,IAAA,IAAI,CAACrC,aAAa,CAACkB,OAAO,CAACC,MAAM,IAAI;IACjC,MAAA,MAAMQ,WAAW,GAAGR,MAAM,CAACS,OAAO,CAACC,MAAM,CAAA;UACzC,IAAI,IAAI,CAAC1B,cAAc,CAACwC,GAAG,CAAChB,WAAW,CAAC,IACnCA,WAAW,KAAK,GAAG,IAAI,IAAI,CAACxB,cAAc,CAACwC,GAAG,CAAC,GAAG,CAAE,EAAE;YACvDxB,MAAM,CAACe,SAAS,CAACG,GAAG,CAAC,IAAI,CAACtD,OAAO,CAACM,WAAW,CAAC,CAAA;IAClD,OAAC,MAAM;YACH8B,MAAM,CAACe,SAAS,CAACC,MAAM,CAAC,IAAI,CAACpD,OAAO,CAACM,WAAW,CAAC,CAAA;IACrD,OAAA;IACJ,KAAC,CAAC,CAAA;;IAEF;QACA,MAAMkC,MAAM,GAAG8D,MAAM,CAACgB,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QACzC,IAAI,IAAI,CAACpG,WAAW,EAAE;IAClB,MAAA,IAAI,CAACA,WAAW,CAACwB,KAAK,GAAGF,MAAM,CAAA;IACnC,KAAA;QAEA,IAAI,CAACM,MAAM,EAAE,CAAA;IACb,IAAA,IAAIN,MAAM,EAAE;IACR,MAAA,IAAI,CAACA,MAAM,CAACA,MAAM,CAAC,CAAA;IACvB,KAAA;IACJ,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACIZ,EAAAA,aAAaA,GAAG;IACZ,IAAA,MAAM2F,KAAK,GAAG,IAAI,CAACxG,KAAK,CAACyG,MAAM,CAAA;IAC/B,IAAA,MAAMC,OAAO,GAAG,IAAI,CAAClG,YAAY,CAACmC,IAAI,CAAA;QAEtC,IAAI,IAAI,CAACvC,OAAO,EAAE;UACd,IAAI,CAACA,OAAO,CAACa,WAAW,GAAG,CAAWyF,QAAAA,EAAAA,OAAO,CAAOF,IAAAA,EAAAA,KAAK,CAAE,CAAA,CAAA;IAC/D,KAAA;QAEA,OAAO;UAAEA,KAAK;IAAEE,MAAAA,OAAAA;SAAS,CAAA;IAC7B,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACIC,aAAaA,CAACC,IAAI,EAAE;IAChB,IAAA,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAACtC,QAAQ,CAACsC,IAAI,CAACC,WAAW,EAAE,CAAC,EAAE;UAC5C,IAAI,CAAC5H,OAAO,CAACS,UAAU,GAAGkH,IAAI,CAACC,WAAW,EAAE,CAAA;UAC5C,IAAI,CAAC9E,MAAM,EAAE,CAAA;IACjB,KAAA;IACJ,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACA;IACI+E,EAAAA,SAASA,CAACvD,IAAI,EAAE5B,KAAK,EAAE;QACnB,IAAI,CAACtB,cAAc,CAACkC,GAAG,CAAC,GAAGgB,IAAI,CAAA,CAAA,EAAI5B,KAAK,CAAA,CAAE,CAAC,CAAA;QAC3C,IAAI,CAACI,MAAM,EAAE,CAAA;QACb,IAAI,CAACG,SAAS,EAAE,CAAA;IACpB,GAAA;;IAEA;IACJ;IACA;IACA;IACA;IACI6E,EAAAA,YAAYA,CAACxD,IAAI,EAAE5B,KAAK,EAAE;QACtB,IAAI,CAACtB,cAAc,CAACoC,MAAM,CAAC,GAAGc,IAAI,CAAA,CAAA,EAAI5B,KAAK,CAAA,CAAE,CAAC,CAAA;IAC9C,IAAA,IAAI,IAAI,CAACtB,cAAc,CAACsC,IAAI,KAAK,CAAC,EAAE;IAChC,MAAA,IAAI,CAACtC,cAAc,CAACkC,GAAG,CAAC,GAAG,CAAC,CAAA;IAChC,KAAA;QACA,IAAI,CAACR,MAAM,EAAE,CAAA;QACb,IAAI,CAACG,SAAS,EAAE,CAAA;IACpB,GAAA;;IAEA;IACJ;IACA;IACA;IACA;MACI8E,sBAAsBA,CAACzD,IAAI,EAAE;IACzB,IAAA,OAAO,CAAC,GAAG,IAAI,CAAClD,cAAc,CAAC,CAC1B0B,MAAM,CAACA,MAAM,IAAIA,MAAM,CAACkF,UAAU,CAAC,CAAG1D,EAAAA,IAAI,CAAG,CAAA,CAAA,CAAC,CAAC,CAC/CW,GAAG,CAACnC,MAAM,IAAIA,MAAM,CAACkB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5C,GAAA;IACJ;;;;;;;;;;"}
@@ -1,2 +0,0 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Filter={})}(this,(function(t){"use strict";t.Filter=class{constructor(t={}){this.options={containerSelector:".filter-container",itemSelector:".filter-item",filterButtonSelector:".btn-filter",searchInputSelector:".filter-search",counterSelector:".filter-counter",activeClass:"active",hiddenClass:"hidden",animationDuration:300,filterMode:"OR",searchKeys:["title"],debounceTime:300,...t},this.container=document.querySelector(this.options.containerSelector),this.items=document.querySelectorAll(this.options.itemSelector),this.filterButtons=document.querySelectorAll(this.options.filterButtonSelector),this.searchInput=document.querySelector(this.options.searchInputSelector),this.counter=document.querySelector(this.options.counterSelector),this.currentFilters=new Set(["*"]),this.currentSearch="",this.visibleItems=new Set(this.items),this.init()}init(){this.addStyles(),this.bindEvents(),this.loadFromURL(),this.updateCounter()}addStyles(){const t=`\n .${this.options.hiddenClass} {\n display: none !important;\n }\n\n ${this.options.itemSelector} {\n opacity: 1;\n transform: scale(1);\n transition: opacity ${this.options.animationDuration}ms ease-out,\n transform ${this.options.animationDuration}ms ease-out;\n }\n\n ${this.options.filterButtonSelector} {\n opacity: 0.5;\n transition: opacity ${this.options.animationDuration}ms ease;\n }\n\n ${this.options.filterButtonSelector}.${this.options.activeClass} {\n opacity: 1;\n }\n `,e=document.createElement("style");e.textContent=t,document.head.appendChild(e)}bindEvents(){this.filterButtons.forEach((t=>{t.addEventListener("click",(()=>this.handleFilterClick(t)))})),this.searchInput&&this.searchInput.addEventListener("input",function(t,e){let s;return function(...i){clearTimeout(s),s=setTimeout((()=>{clearTimeout(s),t(...i)}),e)}}((t=>{this.search(t.target.value)}),this.options.debounceTime)),window.addEventListener("popstate",(()=>this.loadFromURL()))}handleFilterClick(t){const e=t.dataset.filter;"*"===e?this.resetFilters():this.toggleFilter(e,t),this.filter(),this.updateURL()}resetFilters(){this.filterButtons.forEach((t=>t.classList.remove(this.options.activeClass))),this.currentFilters.clear(),this.currentFilters.add("*"),this.filterButtons[0].classList.add(this.options.activeClass),this.resetCounter()}resetCounter(){this.visibleItems=new Set(this.items),this.updateCounter()}toggleFilter(t,e){this.currentFilters.delete("*"),this.filterButtons[0].classList.remove(this.options.activeClass),e.classList.contains(this.options.activeClass)?(e.classList.remove(this.options.activeClass),this.currentFilters.delete(t),0===this.currentFilters.size&&this.resetFilters()):(e.classList.add(this.options.activeClass),this.currentFilters.add(t))}filter(){this.items.forEach((t=>{if(this.currentFilters.has("*"))return void this.showItem(t);const e=new Set(t.dataset.categories?.split(" ")||[]);("OR"===this.options.filterMode?this.matchesAnyFilter(e):this.matchesAllFilters(e))?this.showItem(t):this.hideItem(t)})),setTimeout((()=>{this.updateCounter()}),this.options.animationDuration)}matchesAnyFilter(t){return[...this.currentFilters].some((e=>{const[s,i]=e.split(":");return t.has(`${s}:${i}`)}))}matchesAllFilters(t){return[...this.currentFilters].every((e=>{const[s,i]=e.split(":");return t.has(`${s}:${i}`)}))}showItem(t){this.visibleItems.add(t),t.classList.remove(this.options.hiddenClass),t.style.opacity="0",t.style.transform="scale(0.95)",t.offsetHeight,requestAnimationFrame((()=>{t.style.opacity="1",t.style.transform="scale(1)"}))}hideItem(t){t.style.opacity="0",t.style.transform="scale(0.95)",setTimeout((()=>{"0"===t.style.opacity&&(t.classList.add(this.options.hiddenClass),this.visibleItems.delete(t))}),this.options.animationDuration)}search(t){this.currentSearch=t.toLowerCase().trim(),this.items.forEach((t=>{const e=this.options.searchKeys.map((e=>t.dataset[e]||"")).join(" ").toLowerCase();""===this.currentSearch||e.includes(this.currentSearch)?this.showItem(t):this.hideItem(t)})),this.updateURL(),setTimeout((()=>{this.updateCounter()}),this.options.animationDuration)}sortMultiple(t){const e=[...this.items];e.sort(((e,s)=>{for(const i of t){const t=e.dataset[i.key],r=s.dataset[i.key],o="asc"===i.direction?t.localeCompare(r):r.localeCompare(t);if(0!==o)return o}return 0})),e.forEach((t=>this.container.appendChild(t)))}addRangeFilter(t,e,s){this.items.forEach((i=>{const r=parseFloat(i.dataset[t]);r>=e&&r<=s?this.showItem(i):this.hideItem(i)})),setTimeout((()=>{this.updateCounter()}),this.options.animationDuration)}updateURL(){const t=new URLSearchParams,e={};for(const t of this.currentFilters)if("*"!==t){const[s,i]=t.split(":");e[s]||(e[s]=new Set),e[s].add(i)}Object.entries(e).forEach((([e,s])=>{t.set(e,Array.from(s).join(","))})),this.currentSearch&&t.set("search",this.currentSearch);const s=`${window.location.pathname}${t.toString()?"?"+t.toString():""}`;window.history.pushState({},"",s)}loadFromURL(){const t=new URLSearchParams(window.location.search);this.currentFilters.clear();let e=!1;for(const[s,i]of t.entries())"search"!==s&&(e=!0,i.split(",").forEach((t=>{this.currentFilters.add(`${s}:${t}`)})));e||this.currentFilters.add("*"),this.filterButtons.forEach((t=>{const e=t.dataset.filter;this.currentFilters.has(e)||"*"===e&&this.currentFilters.has("*")?t.classList.add(this.options.activeClass):t.classList.remove(this.options.activeClass)}));const s=t.get("search")||"";this.searchInput&&(this.searchInput.value=s),this.filter(),s&&this.search(s)}updateCounter(){const t=this.items.length,e=this.visibleItems.size;return this.counter&&(this.counter.textContent=`Showing ${e} of ${t}`),{total:t,visible:e}}setFilterMode(t){["OR","AND"].includes(t.toUpperCase())&&(this.options.filterMode=t.toUpperCase(),this.filter())}addFilter(t,e){this.currentFilters.add(`${t}:${e}`),this.filter(),this.updateURL()}removeFilter(t,e){this.currentFilters.delete(`${t}:${e}`),0===this.currentFilters.size&&this.currentFilters.add("*"),this.filter(),this.updateURL()}getActiveFiltersByType(t){return[...this.currentFilters].filter((e=>e.startsWith(`${t}:`))).map((t=>t.split(":")[1]))}},Object.defineProperty(t,"__esModule",{value:!0})}));
2
- //# sourceMappingURL=filter.min.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"filter.min.js","sources":["../src/index.js"],"sourcesContent":["/**\n * @fileoverview Advanced Filter System for DOM elements\n * @version 1.0.0\n * \n * A flexible and customizable filtering system that supports:\n * - Multiple filtering modes (OR/AND)\n * - Text search with debouncing\n * - Multiple sorting criteria\n * - Range filtering\n * - URL state management\n * - Animation and transitions\n * - Results counter\n */\n\n/**\n * Utility function for debouncing\n * @param {Function} func - Function to debounce\n * @param {number} wait - Delay in milliseconds\n * @returns {Function} Debounced function\n */\nfunction debounce(func, wait) {\n let timeout;\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n}\n\nclass Filter {\n /**\n * @typedef {Object} FilterOptions\n * @property {string} containerSelector - Main container selector\n * @property {string} itemSelector - Items to filter selector\n * @property {string} filterButtonSelector - Filter buttons selector\n * @property {string} [searchInputSelector] - Search input selector\n * @property {string} [counterSelector] - Results counter selector\n * @property {string} [activeClass='active'] - Active state class\n * @property {string} [hiddenClass='hidden'] - Hidden state class\n * @property {number} [animationDuration=300] - Animation duration in ms\n * @property {string} [filterMode='OR'] - Filter mode ('OR' or 'AND')\n * @property {string[]} [searchKeys=['title']] - Data attributes to search in\n * @property {number} [debounceTime=300] - Search debounce delay in ms\n */\n\n /**\n * @param {FilterOptions} options - Filter configuration options\n */\n constructor(options = {}) {\n this.options = {\n containerSelector: '.filter-container',\n itemSelector: '.filter-item',\n filterButtonSelector: '.btn-filter',\n searchInputSelector: '.filter-search',\n counterSelector: '.filter-counter',\n activeClass: 'active',\n hiddenClass: 'hidden',\n animationDuration: 300,\n filterMode: 'OR',\n searchKeys: ['title'],\n debounceTime: 300,\n ...options,\n };\n\n // Initialize elements\n this.container = document.querySelector(this.options.containerSelector);\n this.items = document.querySelectorAll(this.options.itemSelector);\n this.filterButtons = document.querySelectorAll(this.options.filterButtonSelector);\n this.searchInput = document.querySelector(this.options.searchInputSelector);\n this.counter = document.querySelector(this.options.counterSelector);\n\n // Initialize state\n this.currentFilters = new Set(['*']);\n this.currentSearch = '';\n this.visibleItems = new Set(this.items);\n\n this.init();\n }\n\n /**\n * Initialize the filter system\n * @private\n */\n init() {\n this.addStyles();\n this.bindEvents();\n this.loadFromURL();\n this.updateCounter();\n }\n\n /**\n * Add required styles to document\n * @private\n */\n addStyles() {\n const styles = `\n .${this.options.hiddenClass} {\n display: none !important;\n }\n\n ${this.options.itemSelector} {\n opacity: 1;\n transform: scale(1);\n transition: opacity ${this.options.animationDuration}ms ease-out,\n transform ${this.options.animationDuration}ms ease-out;\n }\n\n ${this.options.filterButtonSelector} {\n opacity: 0.5;\n transition: opacity ${this.options.animationDuration}ms ease;\n }\n\n ${this.options.filterButtonSelector}.${this.options.activeClass} {\n opacity: 1;\n }\n `;\n\n const styleSheet = document.createElement('style');\n styleSheet.textContent = styles;\n document.head.appendChild(styleSheet);\n }\n\n /**\n * Bind all event listeners\n * @private\n */\n bindEvents() {\n this.filterButtons.forEach(button => {\n button.addEventListener('click', () => this.handleFilterClick(button));\n });\n\n if (this.searchInput) {\n this.searchInput.addEventListener('input', debounce((e) => {\n this.search(e.target.value);\n }, this.options.debounceTime));\n }\n\n window.addEventListener('popstate', () => this.loadFromURL());\n }\n\n /**\n * Handle filter button clicks\n * @private\n * @param {HTMLElement} button - Clicked filter button\n */\n handleFilterClick(button) {\n const filterValue = button.dataset.filter;\n\n if (filterValue === '*') {\n this.resetFilters();\n } else {\n this.toggleFilter(filterValue, button);\n }\n\n this.filter();\n this.updateURL();\n }\n\n /**\n * Reset all filters to default state\n * @private\n */\n resetFilters() {\n this.filterButtons.forEach(btn => btn.classList.remove(this.options.activeClass));\n this.currentFilters.clear();\n this.currentFilters.add('*');\n this.filterButtons[0].classList.add(this.options.activeClass);\n this.resetCounter();\n }\n\n /**\n * Reset visible items counter\n * @private\n */\n resetCounter() {\n this.visibleItems = new Set(this.items);\n this.updateCounter();\n }\n\n /**\n * Toggle individual filter state\n * @private\n * @param {string} filterValue - Filter value to toggle\n * @param {HTMLElement} button - Filter button element\n */\n toggleFilter(filterValue, button) {\n this.currentFilters.delete('*');\n this.filterButtons[0].classList.remove(this.options.activeClass);\n\n if (button.classList.contains(this.options.activeClass)) {\n button.classList.remove(this.options.activeClass);\n this.currentFilters.delete(filterValue);\n\n if (this.currentFilters.size === 0) {\n this.resetFilters();\n }\n } else {\n button.classList.add(this.options.activeClass);\n this.currentFilters.add(filterValue);\n }\n }\n\n /**\n * Apply current filters to items\n * @public\n */\n filter() {\n this.items.forEach(item => {\n if (this.currentFilters.has('*')) {\n this.showItem(item);\n return;\n }\n\n const itemCategories = new Set(item.dataset.categories?.split(' ') || []);\n const matchesFilter = this.options.filterMode === 'OR'\n ? this.matchesAnyFilter(itemCategories)\n : this.matchesAllFilters(itemCategories);\n\n if (matchesFilter) {\n this.showItem(item);\n } else {\n this.hideItem(item);\n }\n });\n\n setTimeout(() => {\n this.updateCounter();\n }, this.options.animationDuration);\n }\n\n /**\n * Check if item matches any active filter (OR mode)\n * @private\n * @param {Set} itemCategories - Item's categories\n * @returns {boolean} Whether item matches any filter\n */\n matchesAnyFilter(itemCategories) {\n return [...this.currentFilters].some(filter => {\n const [type, value] = filter.split(':');\n return itemCategories.has(`${type}:${value}`);\n });\n }\n\n /**\n * Check if item matches all active filters (AND mode)\n * @private\n * @param {Set} itemCategories - Item's categories\n * @returns {boolean} Whether item matches all filters\n */\n matchesAllFilters(itemCategories) {\n return [...this.currentFilters].every(filter => {\n const [type, value] = filter.split(':');\n return itemCategories.has(`${type}:${value}`);\n });\n }\n\n /**\n * Show an item with animation\n * @private\n * @param {HTMLElement} item - Item to show\n */\n showItem(item) {\n this.visibleItems.add(item);\n item.classList.remove(this.options.hiddenClass);\n item.style.opacity = '0';\n item.style.transform = 'scale(0.95)';\n \n item.offsetHeight;\n \n requestAnimationFrame(() => {\n item.style.opacity = '1';\n item.style.transform = 'scale(1)';\n });\n }\n\n /**\n * Hide an item with animation\n * @private\n * @param {HTMLElement} item - Item to hide\n */\n hideItem(item) {\n item.style.opacity = '0';\n item.style.transform = 'scale(0.95)';\n \n setTimeout(() => {\n if (item.style.opacity === '0') {\n item.classList.add(this.options.hiddenClass);\n this.visibleItems.delete(item);\n }\n }, this.options.animationDuration);\n }\n\n /**\n * Search items by text\n * @public\n * @param {string} query - Search query\n */\n search(query) {\n this.currentSearch = query.toLowerCase().trim();\n let matches = 0;\n \n this.items.forEach(item => {\n const searchText = this.options.searchKeys\n .map(key => item.dataset[key] || '')\n .join(' ')\n .toLowerCase();\n\n const matchesSearch = this.currentSearch === '' || \n searchText.includes(this.currentSearch);\n\n if (matchesSearch) {\n this.showItem(item);\n matches++;\n } else {\n this.hideItem(item);\n }\n });\n\n this.updateURL();\n \n setTimeout(() => {\n this.updateCounter();\n }, this.options.animationDuration);\n }\n\n /**\n * Sort items by multiple criteria\n * @public\n * @param {Array<{key: string, direction: string}>} criteria - Sort criteria\n */\n sortMultiple(criteria) {\n const items = [...this.items];\n \n items.sort((a, b) => {\n for (const criterion of criteria) {\n const valueA = a.dataset[criterion.key];\n const valueB = b.dataset[criterion.key];\n \n const comparison = criterion.direction === 'asc'\n ? valueA.localeCompare(valueB)\n : valueB.localeCompare(valueA);\n \n if (comparison !== 0) return comparison;\n }\n return 0;\n });\n\n items.forEach(item => this.container.appendChild(item));\n }\n\n /**\n * Filter items by numeric range\n * @public\n * @param {string} key - Data attribute key\n * @param {number} min - Minimum value\n * @param {number} max - Maximum value\n */\n addRangeFilter(key, min, max) {\n this.items.forEach(item => {\n const value = parseFloat(item.dataset[key]);\n const inRange = value >= min && value <= max;\n \n if (inRange) {\n this.showItem(item);\n } else {\n this.hideItem(item);\n }\n });\n\n setTimeout(() => {\n this.updateCounter();\n }, this.options.animationDuration);\n }\n\n /**\n * Update URL with current filter state\n * @private\n */\n updateURL() {\n const params = new URLSearchParams();\n \n // Séparer les filtres par type\n const filtersByType = {};\n for (const filter of this.currentFilters) {\n if (filter !== '*') {\n const [type, value] = filter.split(':');\n if (!filtersByType[type]) {\n filtersByType[type] = new Set();\n }\n filtersByType[type].add(value);\n }\n }\n \n // Ajouter chaque type de filtre à l'URL\n Object.entries(filtersByType).forEach(([type, values]) => {\n params.set(type, Array.from(values).join(','));\n });\n \n if (this.currentSearch) {\n params.set('search', this.currentSearch);\n }\n \n const newURL = `${window.location.pathname}${params.toString() ? '?' + params.toString() : ''}`;\n window.history.pushState({}, '', newURL);\n }\n\n /**\n * Load filter state from URL\n * @private\n */\n loadFromURL() {\n const params = new URLSearchParams(window.location.search);\n this.currentFilters.clear();\n \n // Si aucun filtre n'est présent, utiliser '*'\n let hasFilters = false;\n \n // Parcourir tous les paramètres\n for (const [type, values] of params.entries()) {\n if (type !== 'search') {\n hasFilters = true;\n values.split(',').forEach(value => {\n this.currentFilters.add(`${type}:${value}`);\n });\n }\n }\n \n if (!hasFilters) {\n this.currentFilters.add('*');\n }\n \n // Update active buttons\n this.filterButtons.forEach(button => {\n const filterValue = button.dataset.filter;\n if (this.currentFilters.has(filterValue) || \n (filterValue === '*' && this.currentFilters.has('*'))) {\n button.classList.add(this.options.activeClass);\n } else {\n button.classList.remove(this.options.activeClass);\n }\n });\n \n // Load search\n const search = params.get('search') || '';\n if (this.searchInput) {\n this.searchInput.value = search;\n }\n \n this.filter();\n if (search) {\n this.search(search);\n }\n }\n\n /**\n * Update results counter\n * @private\n * @returns {{total: number, visible: number}}\n */\n updateCounter() {\n const total = this.items.length;\n const visible = this.visibleItems.size;\n \n if (this.counter) {\n this.counter.textContent = `Showing ${visible} of ${total}`;\n }\n \n return { total, visible };\n }\n\n /**\n * Change filter mode\n * @public\n * @param {string} mode - New filter mode ('OR' or 'AND')\n */\n setFilterMode(mode) {\n if (['OR', 'AND'].includes(mode.toUpperCase())) {\n this.options.filterMode = mode.toUpperCase();\n this.filter();\n }\n }\n\n /**\n * Add filter by type and value\n * @public\n * @param {string} type - Filter type\n * @param {string} value - Filter value\n */\n addFilter(type, value) {\n this.currentFilters.add(`${type}:${value}`);\n this.filter();\n this.updateURL();\n }\n \n /**\n * Remove filter by type and value\n * @public\n * @param {string} type - Filter type\n */\n removeFilter(type, value) {\n this.currentFilters.delete(`${type}:${value}`);\n if (this.currentFilters.size === 0) {\n this.currentFilters.add('*');\n }\n this.filter();\n this.updateURL();\n }\n \n /**\n * Get active filters by type\n * @public\n * @param {string} type - Filter type\n */\n getActiveFiltersByType(type) {\n return [...this.currentFilters]\n .filter(filter => filter.startsWith(`${type}:`))\n .map(filter => filter.split(':')[1]);\n }\n}\n\nexport { Filter };"],"names":["constructor","options","this","containerSelector","itemSelector","filterButtonSelector","searchInputSelector","counterSelector","activeClass","hiddenClass","animationDuration","filterMode","searchKeys","debounceTime","container","document","querySelector","items","querySelectorAll","filterButtons","searchInput","counter","currentFilters","Set","currentSearch","visibleItems","init","addStyles","bindEvents","loadFromURL","updateCounter","styles","styleSheet","createElement","textContent","head","appendChild","forEach","button","addEventListener","handleFilterClick","func","wait","timeout","args","clearTimeout","setTimeout","later","debounce","e","search","target","value","window","filterValue","dataset","filter","resetFilters","toggleFilter","updateURL","btn","classList","remove","clear","add","resetCounter","delete","contains","size","item","has","showItem","itemCategories","categories","split","matchesAnyFilter","matchesAllFilters","hideItem","some","type","every","style","opacity","transform","offsetHeight","requestAnimationFrame","query","toLowerCase","trim","searchText","map","key","join","includes","sortMultiple","criteria","sort","a","b","criterion","valueA","valueB","comparison","direction","localeCompare","addRangeFilter","min","max","parseFloat","params","URLSearchParams","filtersByType","Object","entries","values","set","Array","from","newURL","location","pathname","toString","history","pushState","hasFilters","get","total","length","visible","setFilterMode","mode","toUpperCase","addFilter","removeFilter","getActiveFiltersByType","startsWith"],"mappings":"uPAgCA,MAmBIA,WAAAA,CAAYC,EAAU,IAClBC,KAAKD,QAAU,CACXE,kBAAmB,oBACnBC,aAAc,eACdC,qBAAsB,cACtBC,oBAAqB,iBACrBC,gBAAiB,kBACjBC,YAAa,SACbC,YAAa,SACbC,kBAAmB,IACnBC,WAAY,KACZC,WAAY,CAAC,SACbC,aAAc,OACXZ,GAIPC,KAAKY,UAAYC,SAASC,cAAcd,KAAKD,QAAQE,mBACrDD,KAAKe,MAAQF,SAASG,iBAAiBhB,KAAKD,QAAQG,cACpDF,KAAKiB,cAAgBJ,SAASG,iBAAiBhB,KAAKD,QAAQI,sBAC5DH,KAAKkB,YAAcL,SAASC,cAAcd,KAAKD,QAAQK,qBACvDJ,KAAKmB,QAAUN,SAASC,cAAcd,KAAKD,QAAQM,iBAGnDL,KAAKoB,eAAiB,IAAIC,IAAI,CAAC,MAC/BrB,KAAKsB,cAAgB,GACrBtB,KAAKuB,aAAe,IAAIF,IAAIrB,KAAKe,OAEjCf,KAAKwB,MACT,CAMAA,IAAAA,GACIxB,KAAKyB,YACLzB,KAAK0B,aACL1B,KAAK2B,cACL3B,KAAK4B,eACT,CAMAH,SAAAA,GACI,MAAMI,EAAS,kBACR7B,KAAKD,QAAQQ,0FAIdP,KAAKD,QAAQG,0HAGWF,KAAKD,QAAQS,wEACXR,KAAKD,QAAQS,+DAGvCR,KAAKD,QAAQI,8FAEWH,KAAKD,QAAQS,2DAGrCR,KAAKD,QAAQI,wBAAwBH,KAAKD,QAAQO,sEAKlDwB,EAAajB,SAASkB,cAAc,SAC1CD,EAAWE,YAAcH,EACzBhB,SAASoB,KAAKC,YAAYJ,EAC9B,CAMAJ,UAAAA,GACI1B,KAAKiB,cAAckB,SAAQC,IACvBA,EAAOC,iBAAiB,SAAS,IAAMrC,KAAKsC,kBAAkBF,IAAQ,IAGtEpC,KAAKkB,aACLlB,KAAKkB,YAAYmB,iBAAiB,QAnH9C,SAAkBE,EAAMC,GACpB,IAAIC,EACJ,OAAO,YAA6BC,GAKhCC,aAAaF,GACbA,EAAUG,YALIC,KACVF,aAAaF,GACbF,KAAQG,EAAK,GAGWF,GAEpC,CAyGuDM,EAAUC,IACjD/C,KAAKgD,OAAOD,EAAEE,OAAOC,MAAM,GAC5BlD,KAAKD,QAAQY,eAGpBwC,OAAOd,iBAAiB,YAAY,IAAMrC,KAAK2B,eACnD,CAOAW,iBAAAA,CAAkBF,GACd,MAAMgB,EAAchB,EAAOiB,QAAQC,OAEf,MAAhBF,EACApD,KAAKuD,eAELvD,KAAKwD,aAAaJ,EAAahB,GAGnCpC,KAAKsD,SACLtD,KAAKyD,WACT,CAMAF,YAAAA,GACIvD,KAAKiB,cAAckB,SAAQuB,GAAOA,EAAIC,UAAUC,OAAO5D,KAAKD,QAAQO,eACpEN,KAAKoB,eAAeyC,QACpB7D,KAAKoB,eAAe0C,IAAI,KACxB9D,KAAKiB,cAAc,GAAG0C,UAAUG,IAAI9D,KAAKD,QAAQO,aACjDN,KAAK+D,cACT,CAMAA,YAAAA,GACI/D,KAAKuB,aAAe,IAAIF,IAAIrB,KAAKe,OACjCf,KAAK4B,eACT,CAQA4B,YAAAA,CAAaJ,EAAahB,GACtBpC,KAAKoB,eAAe4C,OAAO,KAC3BhE,KAAKiB,cAAc,GAAG0C,UAAUC,OAAO5D,KAAKD,QAAQO,aAEhD8B,EAAOuB,UAAUM,SAASjE,KAAKD,QAAQO,cACvC8B,EAAOuB,UAAUC,OAAO5D,KAAKD,QAAQO,aACrCN,KAAKoB,eAAe4C,OAAOZ,GAEM,IAA7BpD,KAAKoB,eAAe8C,MACpBlE,KAAKuD,iBAGTnB,EAAOuB,UAAUG,IAAI9D,KAAKD,QAAQO,aAClCN,KAAKoB,eAAe0C,IAAIV,GAEhC,CAMAE,MAAAA,GACItD,KAAKe,MAAMoB,SAAQgC,IACf,GAAInE,KAAKoB,eAAegD,IAAI,KAExB,YADApE,KAAKqE,SAASF,GAIlB,MAAMG,EAAiB,IAAIjD,IAAI8C,EAAKd,QAAQkB,YAAYC,MAAM,MAAQ,KACpB,OAA5BxE,KAAKD,QAAQU,WAC7BT,KAAKyE,iBAAiBH,GACtBtE,KAAK0E,kBAAkBJ,IAGzBtE,KAAKqE,SAASF,GAEdnE,KAAK2E,SAASR,EAClB,IAGJvB,YAAW,KACP5C,KAAK4B,eAAe,GACrB5B,KAAKD,QAAQS,kBACpB,CAQAiE,gBAAAA,CAAiBH,GACb,MAAO,IAAItE,KAAKoB,gBAAgBwD,MAAKtB,IACjC,MAAOuB,EAAM3B,GAASI,EAAOkB,MAAM,KACnC,OAAOF,EAAeF,IAAI,GAAGS,KAAQ3B,IAAQ,GAErD,CAQAwB,iBAAAA,CAAkBJ,GACd,MAAO,IAAItE,KAAKoB,gBAAgB0D,OAAMxB,IAClC,MAAOuB,EAAM3B,GAASI,EAAOkB,MAAM,KACnC,OAAOF,EAAeF,IAAI,GAAGS,KAAQ3B,IAAQ,GAErD,CAOAmB,QAAAA,CAASF,GACLnE,KAAKuB,aAAauC,IAAIK,GACtBA,EAAKR,UAAUC,OAAO5D,KAAKD,QAAQQ,aACnC4D,EAAKY,MAAMC,QAAU,IACrBb,EAAKY,MAAME,UAAY,cAEvBd,EAAKe,aAELC,uBAAsB,KAClBhB,EAAKY,MAAMC,QAAU,IACrBb,EAAKY,MAAME,UAAY,UAAU,GAEzC,CAOAN,QAAAA,CAASR,GACLA,EAAKY,MAAMC,QAAU,IACrBb,EAAKY,MAAME,UAAY,cAEvBrC,YAAW,KACoB,MAAvBuB,EAAKY,MAAMC,UACXb,EAAKR,UAAUG,IAAI9D,KAAKD,QAAQQ,aAChCP,KAAKuB,aAAayC,OAAOG,GAC7B,GACDnE,KAAKD,QAAQS,kBACpB,CAOAwC,MAAAA,CAAOoC,GACHpF,KAAKsB,cAAgB8D,EAAMC,cAAcC,OAGzCtF,KAAKe,MAAMoB,SAAQgC,IACf,MAAMoB,EAAavF,KAAKD,QAAQW,WAC3B8E,KAAIC,GAAOtB,EAAKd,QAAQoC,IAAQ,KAChCC,KAAK,KACLL,cAEwC,KAAvBrF,KAAKsB,eACvBiE,EAAWI,SAAS3F,KAAKsB,eAGzBtB,KAAKqE,SAASF,GAGdnE,KAAK2E,SAASR,EAClB,IAGJnE,KAAKyD,YAELb,YAAW,KACP5C,KAAK4B,eAAe,GACrB5B,KAAKD,QAAQS,kBACpB,CAOAoF,YAAAA,CAAaC,GACT,MAAM9E,EAAQ,IAAIf,KAAKe,OAEvBA,EAAM+E,MAAK,CAACC,EAAGC,KACX,IAAK,MAAMC,KAAaJ,EAAU,CAC9B,MAAMK,EAASH,EAAE1C,QAAQ4C,EAAUR,KAC7BU,EAASH,EAAE3C,QAAQ4C,EAAUR,KAE7BW,EAAqC,QAAxBH,EAAUI,UACvBH,EAAOI,cAAcH,GACrBA,EAAOG,cAAcJ,GAE3B,GAAmB,IAAfE,EAAkB,OAAOA,CACjC,CACA,OAAO,CAAC,IAGZrF,EAAMoB,SAAQgC,GAAQnE,KAAKY,UAAUsB,YAAYiC,IACrD,CASAoC,cAAAA,CAAed,EAAKe,EAAKC,GACrBzG,KAAKe,MAAMoB,SAAQgC,IACf,MAAMjB,EAAQwD,WAAWvC,EAAKd,QAAQoC,IACtBvC,GAASsD,GAAOtD,GAASuD,EAGrCzG,KAAKqE,SAASF,GAEdnE,KAAK2E,SAASR,EAClB,IAGJvB,YAAW,KACP5C,KAAK4B,eAAe,GACrB5B,KAAKD,QAAQS,kBACpB,CAMAiD,SAAAA,GACI,MAAMkD,EAAS,IAAIC,gBAGbC,EAAgB,CAAA,EACtB,IAAK,MAAMvD,KAAUtD,KAAKoB,eACtB,GAAe,MAAXkC,EAAgB,CAChB,MAAOuB,EAAM3B,GAASI,EAAOkB,MAAM,KAC9BqC,EAAchC,KACfgC,EAAchC,GAAQ,IAAIxD,KAE9BwF,EAAchC,GAAMf,IAAIZ,EAC5B,CAIJ4D,OAAOC,QAAQF,GAAe1E,SAAQ,EAAE0C,EAAMmC,MAC1CL,EAAOM,IAAIpC,EAAMqC,MAAMC,KAAKH,GAAQtB,KAAK,KAAK,IAG9C1F,KAAKsB,eACLqF,EAAOM,IAAI,SAAUjH,KAAKsB,eAG9B,MAAM8F,EAAS,GAAGjE,OAAOkE,SAASC,WAAWX,EAAOY,WAAa,IAAMZ,EAAOY,WAAa,KAC3FpE,OAAOqE,QAAQC,UAAU,CAAE,EAAE,GAAIL,EACrC,CAMAzF,WAAAA,GACI,MAAMgF,EAAS,IAAIC,gBAAgBzD,OAAOkE,SAASrE,QACnDhD,KAAKoB,eAAeyC,QAGpB,IAAI6D,GAAa,EAGjB,IAAK,MAAO7C,EAAMmC,KAAWL,EAAOI,UACnB,WAATlC,IACA6C,GAAa,EACbV,EAAOxC,MAAM,KAAKrC,SAAQe,IACtBlD,KAAKoB,eAAe0C,IAAI,GAAGe,KAAQ3B,IAAQ,KAKlDwE,GACD1H,KAAKoB,eAAe0C,IAAI,KAI5B9D,KAAKiB,cAAckB,SAAQC,IACvB,MAAMgB,EAAchB,EAAOiB,QAAQC,OAC/BtD,KAAKoB,eAAegD,IAAIhB,IACP,MAAhBA,GAAuBpD,KAAKoB,eAAegD,IAAI,KAChDhC,EAAOuB,UAAUG,IAAI9D,KAAKD,QAAQO,aAElC8B,EAAOuB,UAAUC,OAAO5D,KAAKD,QAAQO,YACzC,IAIJ,MAAM0C,EAAS2D,EAAOgB,IAAI,WAAa,GACnC3H,KAAKkB,cACLlB,KAAKkB,YAAYgC,MAAQF,GAG7BhD,KAAKsD,SACDN,GACAhD,KAAKgD,OAAOA,EAEpB,CAOApB,aAAAA,GACI,MAAMgG,EAAQ5H,KAAKe,MAAM8G,OACnBC,EAAU9H,KAAKuB,aAAa2C,KAMlC,OAJIlE,KAAKmB,UACLnB,KAAKmB,QAAQa,YAAc,WAAW8F,QAAcF,KAGjD,CAAEA,QAAOE,UACpB,CAOAC,aAAAA,CAAcC,GACN,CAAC,KAAM,OAAOrC,SAASqC,EAAKC,iBAC5BjI,KAAKD,QAAQU,WAAauH,EAAKC,cAC/BjI,KAAKsD,SAEb,CAQA4E,SAAAA,CAAUrD,EAAM3B,GACZlD,KAAKoB,eAAe0C,IAAI,GAAGe,KAAQ3B,KACnClD,KAAKsD,SACLtD,KAAKyD,WACT,CAOA0E,YAAAA,CAAatD,EAAM3B,GACflD,KAAKoB,eAAe4C,OAAO,GAAGa,KAAQ3B,KACL,IAA7BlD,KAAKoB,eAAe8C,MACpBlE,KAAKoB,eAAe0C,IAAI,KAE5B9D,KAAKsD,SACLtD,KAAKyD,WACT,CAOA2E,sBAAAA,CAAuBvD,GACnB,MAAO,IAAI7E,KAAKoB,gBACXkC,QAAOA,GAAUA,EAAO+E,WAAW,GAAGxD,QACtCW,KAAIlC,GAAUA,EAAOkB,MAAM,KAAK,IACzC"}