1
+ ( ( ) => {
2
+ 'use strict' ;
3
+
4
+ class ResourceTagFilter {
5
+ constructor ( ) {
6
+ this . container = document . getElementById ( 'resource-gallery' ) ;
7
+ this . items = this . container ? Array . from ( this . container . children ) : [ ] ;
8
+ this . selectedTags = new Set ( ) ;
9
+
10
+ this . init ( ) ;
11
+ }
12
+
13
+ init ( ) {
14
+ if ( ! this . container ) {
15
+ console . warn ( 'Resource gallery not found' ) ;
16
+ return ;
17
+ }
18
+
19
+ this . bindEvents ( ) ;
20
+
21
+ this . updateDisplay ( ) ;
22
+ this . updateFilterStatus ( ) ;
23
+ }
24
+
25
+ getItemTags ( item ) {
26
+ // Resources use data-tags attribute
27
+ if ( item . dataset . tags ) {
28
+ return item . dataset . tags . split ( ',' ) . map ( tag => tag . trim ( ) . toLowerCase ( ) ) . filter ( tag => tag ) ;
29
+ }
30
+ return [ ] ;
31
+ }
32
+
33
+ bindEvents ( ) {
34
+ // Tag filter buttons
35
+ const tagButtons = document . querySelectorAll ( '.tag-filter-btn' ) ;
36
+ tagButtons . forEach ( button => {
37
+ button . addEventListener ( 'click' , ( e ) => this . handleTagClick ( e ) ) ;
38
+ } ) ;
39
+
40
+ // Clear all button
41
+ const clearButton = document . getElementById ( 'clear-filters' ) ;
42
+ if ( clearButton ) {
43
+ clearButton . addEventListener ( 'click' , ( ) => this . clearAllFilters ( ) ) ;
44
+ }
45
+
46
+ // Keyboard shortcuts
47
+ document . addEventListener ( 'keydown' , ( e ) => {
48
+ if ( e . target . classList . contains ( 'tag-filter-btn' ) ) {
49
+ if ( e . key === 'Enter' || e . key === ' ' ) {
50
+ e . preventDefault ( ) ;
51
+ this . handleTagClick ( e ) ;
52
+ }
53
+ }
54
+ } ) ;
55
+ }
56
+
57
+ handleTagClick ( event ) {
58
+ const button = event . target ;
59
+ const tag = button . dataset . tag . toLowerCase ( ) ;
60
+
61
+ // Handle multi-select with Shift key
62
+ if ( event . shiftKey ) {
63
+ this . toggleTag ( tag , button ) ;
64
+ } else {
65
+ this . clearAllFilters ( ) ;
66
+ this . toggleTag ( tag , button ) ;
67
+ }
68
+
69
+ this . updateDisplay ( ) ;
70
+ this . updateFilterStatus ( ) ;
71
+ }
72
+
73
+ toggleTag ( tag , button ) {
74
+ if ( this . selectedTags . has ( tag ) ) {
75
+ this . selectedTags . delete ( tag ) ;
76
+ button . classList . remove ( 'active' ) ;
77
+ button . setAttribute ( 'aria-pressed' , 'false' ) ;
78
+ } else {
79
+ this . selectedTags . add ( tag ) ;
80
+ button . classList . add ( 'active' ) ;
81
+ button . setAttribute ( 'aria-pressed' , 'true' ) ;
82
+ }
83
+
84
+ // Remove focus to prevent button from appearing highlighted after click
85
+ button . blur ( ) ;
86
+ }
87
+
88
+ clearAllFilters ( ) {
89
+ this . selectedTags . clear ( ) ;
90
+ const tagButtons = document . querySelectorAll ( '.tag-filter-btn' ) ;
91
+ tagButtons . forEach ( button => {
92
+ button . classList . remove ( 'active' ) ;
93
+ button . setAttribute ( 'aria-pressed' , 'false' ) ;
94
+ } ) ;
95
+ this . updateDisplay ( ) ;
96
+ this . updateFilterStatus ( ) ;
97
+ }
98
+
99
+ updateDisplay ( ) {
100
+ let visibleCount = 0 ;
101
+
102
+ this . items . forEach ( item => {
103
+ const itemTags = this . getItemTags ( item ) ;
104
+ const shouldShow = this . shouldShowItem ( itemTags ) ;
105
+
106
+ if ( shouldShow ) {
107
+ item . style . display = '' ;
108
+ item . classList . remove ( 'd-none' ) ;
109
+ visibleCount ++ ;
110
+ } else {
111
+ item . style . display = 'none' ;
112
+ item . classList . add ( 'd-none' ) ;
113
+ }
114
+ } ) ;
115
+ }
116
+
117
+ shouldShowItem ( itemTags ) {
118
+ // If no tags selected show all
119
+ if ( this . selectedTags . size === 0 ) {
120
+ return true ;
121
+ }
122
+
123
+ // Check if item has ALL of the selected tags
124
+ return Array . from ( this . selectedTags ) . every ( selectedTag =>
125
+ itemTags . some ( itemTag => itemTag === selectedTag )
126
+ ) ;
127
+ }
128
+
129
+ updateFilterStatus ( ) {
130
+ const resultsCountElement = document . getElementById ( 'results-count' ) ;
131
+ const activeFiltersElement = document . getElementById ( 'active-filters' ) ;
132
+
133
+ if ( resultsCountElement ) {
134
+ const visibleCount = this . items . filter ( item =>
135
+ ! item . classList . contains ( 'd-none' ) && item . style . display !== 'none'
136
+ ) . length ;
137
+
138
+ resultsCountElement . textContent =
139
+ visibleCount === this . items . length
140
+ ? 'All results'
141
+ : `${ visibleCount } of ${ this . items . length } results` ;
142
+ }
143
+
144
+ if ( activeFiltersElement ) {
145
+ if ( this . selectedTags . size === 0 ) {
146
+ activeFiltersElement . textContent = 'No filters active' ;
147
+ } else if ( this . selectedTags . size === 1 ) {
148
+ const tagList = Array . from ( this . selectedTags ) . join ( ', ' ) ;
149
+ activeFiltersElement . textContent = `Showing: ${ tagList } ` ;
150
+ } else {
151
+ const tagList = Array . from ( this . selectedTags ) . join ( ' + ' ) ;
152
+ activeFiltersElement . textContent = `Showing items with: ${ tagList } ` ;
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ document . addEventListener ( 'DOMContentLoaded' , function ( ) {
159
+ const resourceGallery = document . getElementById ( 'resource-gallery' ) ;
160
+ if ( resourceGallery ) {
161
+ new ResourceTagFilter ( ) ;
162
+ }
163
+ } ) ;
164
+ } ) ( ) ;
0 commit comments