PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.5.4
Secure Custom Fields v6.5.4
6.9.5 6.9.4 6.9.3 6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / assets / src / js / _acf-field-icon-picker.js
secure-custom-fields / assets / src / js Last commit date
bindings 1 year ago commands 1 year ago pro 1 year ago _acf-compatibility.js 1 year ago _acf-condition-types.js 1 year ago _acf-condition.js 1 year ago _acf-conditions.js 1 year ago _acf-field-accordion.js 1 year ago _acf-field-button-group.js 1 year ago _acf-field-checkbox.js 1 year ago _acf-field-color-picker.js 1 year ago _acf-field-date-picker.js 1 year ago _acf-field-date-time-picker.js 1 year ago _acf-field-file.js 1 year ago _acf-field-google-map.js 1 year ago _acf-field-icon-picker.js 1 year ago _acf-field-image.js 1 year ago _acf-field-link.js 1 year ago _acf-field-oembed.js 1 year ago _acf-field-page-link.js 1 year ago _acf-field-post-object.js 1 year ago _acf-field-radio.js 1 year ago _acf-field-range.js 1 year ago _acf-field-relationship.js 1 year ago _acf-field-select.js 1 year ago _acf-field-tab.js 1 year ago _acf-field-taxonomy.js 1 year ago _acf-field-time-picker.js 1 year ago _acf-field-true-false.js 1 year ago _acf-field-url.js 1 year ago _acf-field-user.js 1 year ago _acf-field-wysiwyg.js 1 year ago _acf-field.js 1 year ago _acf-fields.js 1 year ago _acf-helpers.js 1 year ago _acf-hooks.js 1 year ago _acf-internal-post-type.js 1 year ago _acf-media.js 1 year ago _acf-modal.js 1 year ago _acf-model.js 1 year ago _acf-notice.js 1 year ago _acf-panel.js 1 year ago _acf-popup.js 1 year ago _acf-postbox.js 1 year ago _acf-screen.js 1 year ago _acf-select2.js 1 year ago _acf-tinymce.js 1 year ago _acf-tooltip.js 1 year ago _acf-unload.js 1 year ago _acf-validation.js 1 year ago _acf.js 1 year ago _browse-fields-modal.js 1 year ago _field-group-compatibility.js 1 year ago _field-group-conditions.js 1 year ago _field-group-field.js 1 year ago _field-group-fields.js 1 year ago _field-group-locations.js 1 year ago _field-group-settings.js 1 year ago _field-group.js 1 year ago acf-escaped-html-notice.js 1 year ago acf-field-group.js 1 year ago acf-input.js 1 year ago acf-internal-post-type.js 1 year ago acf.js 1 year ago
_acf-field-icon-picker.js
454 lines
1 ( function ( $, undefined ) {
2 const Field = acf.Field.extend( {
3 type: 'icon_picker',
4
5 wait: 'load',
6
7 events: {
8 showField: 'scrollToSelectedIcon',
9 'input .acf-icon_url': 'onUrlChange',
10 'click .acf-icon-picker-list-icon': 'onIconClick',
11 'focus .acf-icon-picker-list-icon-radio': 'onIconRadioFocus',
12 'blur .acf-icon-picker-list-icon-radio': 'onIconRadioBlur',
13 'keydown .acf-icon-picker-list-icon-radio': 'onIconKeyDown',
14 'input .acf-icon-list-search-input': 'onIconSearch',
15 'keydown .acf-icon-list-search-input': 'onIconSearchKeyDown',
16 'click .acf-icon-picker-media-library-button':
17 'onMediaLibraryButtonClick',
18 'click .acf-icon-picker-media-library-preview':
19 'onMediaLibraryButtonClick',
20 },
21
22 $typeInput() {
23 return this.$(
24 'input[type="hidden"][data-hidden-type="type"]:first'
25 );
26 },
27
28 $valueInput() {
29 return this.$(
30 'input[type="hidden"][data-hidden-type="value"]:first'
31 );
32 },
33
34 $tabButton() {
35 return this.$( '.acf-tab-button' );
36 },
37
38 $selectedIcon() {
39 return this.$( '.acf-icon-picker-list-icon.active' );
40 },
41
42 $selectedRadio() {
43 return this.$( '.acf-icon-picker-list-icon.active input' );
44 },
45
46 $iconsList() {
47 return this.$( '.acf-icon-list:visible' );
48 },
49
50 $mediaLibraryButton() {
51 return this.$( '.acf-icon-picker-media-library-button' );
52 },
53
54 initialize() {
55 // Set up actions hook callbacks.
56 this.addActions();
57
58 // Initialize the state of the icon picker.
59 let typeAndValue = {
60 type: this.$typeInput().val(),
61 value: this.$valueInput().val()
62 };
63
64 // Store the type and value object.
65 this.set( 'typeAndValue', typeAndValue );
66
67 // Any time any acf tab is clicked, we will re-scroll to the selected icon.
68 $( '.acf-tab-button' ).on( 'click', () => {
69 this.initializeIconLists( this.get( 'typeAndValue' ) );
70 } );
71
72 // Fire the action which lets people know the state has been updated.
73 acf.doAction(
74 this.get( 'name' ) + '/type_and_value_change',
75 typeAndValue
76 );
77
78 this.initializeIconLists( typeAndValue );
79 this.alignMediaLibraryTabToCurrentValue( typeAndValue );
80 },
81
82 addActions() {
83 // Set up an action listener for when the type and value changes.
84 acf.addAction(
85 this.get( 'name' ) + '/type_and_value_change',
86 ( newTypeAndValue ) => {
87 // Align the visual state of each tab to the current value.
88 this.alignIconListTabsToCurrentValue( newTypeAndValue );
89 this.alignMediaLibraryTabToCurrentValue( newTypeAndValue );
90 this.alignUrlTabToCurrentValue( newTypeAndValue );
91 }
92 );
93 },
94
95 updateTypeAndValue( type, value ) {
96 const typeAndValue = {
97 type,
98 value,
99 };
100
101 // Update the values in the hidden fields, which are what will actually be saved.
102 acf.val( this.$typeInput(), type );
103 acf.val( this.$valueInput(), value );
104
105 // Fire an action to let each tab set itself according to the typeAndValue state.
106 acf.doAction(
107 this.get( 'name' ) + '/type_and_value_change',
108 typeAndValue
109 );
110
111 // Set the state.
112 this.set( 'typeAndValue', typeAndValue );
113 },
114
115 scrollToSelectedIcon() {
116 const innerElement = this.$selectedIcon();
117
118 // If no icon is selected, do nothing.
119 if ( innerElement.length === 0 ) {
120 return;
121 }
122
123 const scrollingDiv = innerElement.closest( '.acf-icon-list' );
124 scrollingDiv.scrollTop( 0 );
125
126 const distance = innerElement.position().top - 50;
127
128 if ( distance === 0 ) {
129 return;
130 }
131
132 scrollingDiv.scrollTop( distance );
133 },
134
135 initializeIconLists( typeAndValue ) {
136 const self = this;
137
138 this.$( '.acf-icon-list' ).each( function( i ) {
139 const tabName = $( this ).data( 'parent-tab' );
140 const icons = self.getIconsList( tabName ) || [];
141 self.set( tabName, icons );
142 self.renderIconList( $( this ) );
143
144 if ( typeAndValue.type === tabName ) {
145 // Select the correct icon.
146 self.selectIcon( $( this ), typeAndValue.value, false ).then( () => {
147 // Scroll to the selected icon.
148 self.scrollToSelectedIcon();
149 } );
150 }
151 } );
152 },
153
154 alignIconListTabsToCurrentValue( typeAndValue ) {
155 const icons = this.$( '.acf-icon-list' ).filter(
156 function () {
157 return (
158 $( this ).data( 'parent-tab' ) !== typeAndValue.type
159 );
160 }
161 );
162 const self = this;
163 icons.each( function () {
164 self.unselectIcon( $( this ) );
165 } );
166 },
167
168 renderIconHTML( tabName, icon ) {
169 const id = `${ this.get( 'name' ) }-${ icon.key }`;
170
171 let style = '';
172 if ( 'dashicons' !== tabName ) {
173 style = `background: center / contain url( ${ acf.strEscape(
174 icon.url
175 ) } ) no-repeat;`;
176 }
177
178 return `<div class="${ tabName } ${ acf.strEscape(
179 icon.key
180 ) } acf-icon-picker-list-icon" role="radio" data-icon="${ acf.strEscape(
181 icon.key
182 ) }" style="${ style }" title="${ acf.strEscape(
183 icon.label
184 ) }">
185 <label for="${ acf.strEscape( id ) }">${ acf.strEscape(
186 icon.label
187 ) }</label>
188 <input id="${ acf.strEscape(
189 id
190 ) }" type="radio" class="acf-icon-picker-list-icon-radio" name="acf-icon-picker-list-icon-radio" value="${ acf.strEscape(
191 icon.key
192 ) }">
193 </div>`;
194 },
195
196 renderIconList( $el ) {
197 const tabName = $el.data( 'parent-tab' );
198 const icons = this.get( tabName );
199
200 $el.empty();
201 if ( icons ) {
202 icons.forEach( ( icon ) => {
203 const iconHTML = this.renderIconHTML( tabName, icon );
204 $el.append( iconHTML );
205 } );
206 }
207 },
208
209 getIconsList( tabName ) {
210 if ( 'dashicons' === tabName ) {
211 const iconPickeri10n = acf.get( 'iconPickeri10n' ) || [];
212
213 return Object.entries( iconPickeri10n ).map(
214 ( [ key, value ] ) => {
215 return {
216 key,
217 label: value,
218 };
219 }
220 );
221 }
222
223 return acf.get( `iconPickerIcons_${ tabName }` );
224 },
225
226 getIconsBySearch( searchTerm, tabName ) {
227 const lowercaseSearchTerm = searchTerm.toLowerCase();
228 const icons = this.getIconsList( tabName);
229
230 const filteredIcons = icons.filter( function ( icon ) {
231 const lowercaseIconLabel = icon.label.toLowerCase();
232 return lowercaseIconLabel.indexOf( lowercaseSearchTerm ) > -1;
233 } );
234
235 return filteredIcons;
236 },
237
238 selectIcon( $el, icon, setFocus = true ) {
239 this.set( 'selectedIcon', icon );
240
241 // Select the new one.
242 const $newIcon = $el.find(
243 '.acf-icon-picker-list-icon[data-icon="' + icon + '"]'
244 );
245 $newIcon.addClass( 'active' );
246
247 const $input = $newIcon.find( 'input' );
248 const thePromise = $input.prop( 'checked', true ).promise();
249
250 if ( setFocus ) {
251 $input.trigger( 'focus' );
252 }
253
254 this.updateTypeAndValue( $el.data( 'parent-tab' ), icon );
255
256 return thePromise;
257 },
258
259 unselectIcon( $el ) {
260 // Remove the currently active dashicon, if any.
261 $el
262 .find( '.acf-icon-picker-list-icon' )
263 .removeClass( 'active' );
264 this.set( 'selectedIcon', false );
265 },
266
267 onIconRadioFocus( e ) {
268 const icon = e.target.value;
269 const $tabs = this.$( e.target ).closest(
270 '.acf-icon-picker-tabs'
271 );
272 const $iconsList = $tabs.find( '.acf-icon-list' );
273
274 const $newIcon = $iconsList.find(
275 '.acf-icon-picker-list-icon[data-icon="' + icon + '"]'
276 );
277 $newIcon.addClass( 'focus' );
278
279 // If this is a different icon than previously selected, select it.
280 if ( this.get( 'selectedIcon' ) !== icon ) {
281 this.unselectIcon( $iconsList );
282 this.selectIcon( $iconsList, icon );
283 }
284 },
285
286 onIconRadioBlur( e ) {
287 const icon = this.$( e.target );
288 const iconParent = icon.parent();
289
290 iconParent.removeClass( 'focus' );
291 },
292
293 onIconClick( e ) {
294 e.preventDefault();
295 const $iconList = this.$( e.target ).closest(
296 '.acf-icon-list'
297 );
298 const $iconElement = this.$( e.target );
299 const icon = $iconElement.find( 'input' ).val();
300
301 const $newIconElement = $iconList.find(
302 '.acf-icon-picker-list-icon[data-icon="' + icon + '"]'
303 );
304
305 // By forcing focus on the input, we fire onIconRadioFocus.
306 $newIconElement.find( 'input' ).prop( 'checked', true ).trigger( 'focus' );
307 },
308
309 onIconSearch( e ) {
310 const $tabs = this.$( e.target ).closest(
311 '.acf-icon-picker-tabs'
312 );
313 const $iconList = $tabs.find( '.acf-icon-list' );
314 const tabName = $tabs.data( 'tab' );
315 const searchTerm = e.target.value;
316 const filteredIcons = this.getIconsBySearch( searchTerm, tabName );
317
318 if ( filteredIcons.length > 0 || ! searchTerm ) {
319 this.set( tabName, filteredIcons );
320 $tabs.find( '.acf-icon-list-empty' ).hide();
321 $tabs.find( '.acf-icon-list ' ).show();
322 this.renderIconList( $iconList );
323
324 // Announce change of data to screen readers.
325 wp.a11y.speak(
326 acf.get( 'iconPickerA11yStrings' )
327 .newResultsFoundForSearchTerm,
328 'polite'
329 );
330 } else {
331 // Truncate the search term if it's too long.
332 const visualSearchTerm =
333 searchTerm.length > 30
334 ? searchTerm.substring( 0, 30 ) + '&hellip;'
335 : searchTerm;
336
337 $tabs.find( '.acf-icon-list ' ).hide();
338 $tabs.find( '.acf-icon-list-empty' )
339 .find( '.acf-invalid-icon-list-search-term' )
340 .text( visualSearchTerm );
341 $tabs.find( '.acf-icon-list-empty' ).css( 'display', 'flex' );
342 $tabs.find( '.acf-icon-list-empty' ).show();
343
344 // Announce change of data to screen readers.
345 wp.a11y.speak(
346 acf.get( 'iconPickerA11yStrings' ).noResultsForSearchTerm,
347 'polite'
348 );
349 }
350 },
351
352 onIconSearchKeyDown( e ) {
353 // Check if the pressed key is Enter (key code 13)
354 if ( e.which === 13 ) {
355 // Prevent submitting the entire form if someone presses enter after searching.
356 e.preventDefault();
357 }
358 },
359
360 onIconKeyDown( e ) {
361 if ( e.which === 13 ) {
362 // If someone presses enter while an icon is focused, prevent the form from submitting.
363 e.preventDefault();
364 }
365 },
366
367 alignMediaLibraryTabToCurrentValue( typeAndValue ) {
368 const type = typeAndValue.type;
369 const value = typeAndValue.value;
370
371 if ( type !== 'media_library' && type !== 'dashicons' ) {
372 // Hide the preview container on the media library tab.
373 this.$( '.acf-icon-picker-media-library-preview' ).hide();
374 }
375
376 if ( type === 'media_library' ) {
377 const previewUrl = this.get( 'mediaLibraryPreviewUrl' );
378 // Set the image file preview src.
379 this.$( '.acf-icon-picker-media-library-preview-img img' ).attr(
380 'src',
381 previewUrl
382 );
383
384 // Hide the dashicon preview.
385 this.$(
386 '.acf-icon-picker-media-library-preview-dashicon'
387 ).hide();
388
389 // Show the image file preview.
390 this.$( '.acf-icon-picker-media-library-preview-img' ).show();
391
392 // Show the preview container (it may have been hidden if nothing was ever selected yet).
393 this.$( '.acf-icon-picker-media-library-preview' ).show();
394 }
395
396 if ( type === 'dashicons' ) {
397 // Set the dashicon preview class.
398 this.$(
399 '.acf-icon-picker-media-library-preview-dashicon .dashicons'
400 ).attr( 'class', 'dashicons ' + value );
401
402 // Hide the image file preview.
403 this.$( '.acf-icon-picker-media-library-preview-img' ).hide();
404
405 // Show the dashicon preview.
406 this.$(
407 '.acf-icon-picker-media-library-preview-dashicon'
408 ).show();
409
410 // Show the preview container (it may have been hidden if nothing was ever selected yet).
411 this.$( '.acf-icon-picker-media-library-preview' ).show();
412 }
413 },
414
415 async onMediaLibraryButtonClick( e ) {
416 e.preventDefault();
417
418 await this.selectAndReturnAttachment().then( ( attachment ) => {
419 // When an attachment is selected, update the preview and the hidden fields.
420 this.set( 'mediaLibraryPreviewUrl', attachment.attributes.url );
421 this.updateTypeAndValue( 'media_library', attachment.id );
422 } );
423 },
424
425 selectAndReturnAttachment() {
426 return new Promise( ( resolve ) => {
427 acf.newMediaPopup( {
428 mode: 'select',
429 type: 'image',
430 title: acf.__( 'Select Image' ),
431 field: this.get( 'key' ),
432 multiple: false,
433 library: 'all',
434 allowedTypes: 'image',
435 select: resolve,
436 } );
437 } );
438 },
439
440 alignUrlTabToCurrentValue( typeAndValue ) {
441 if ( typeAndValue.type !== 'url' ) {
442 this.$( '.acf-icon_url' ).val( '' );
443 }
444 },
445
446 onUrlChange( event ) {
447 const currentValue = event.target.value;
448 this.updateTypeAndValue( 'url', currentValue );
449 },
450 } );
451
452 acf.registerFieldType( Field );
453 } )( jQuery );
454