Skip to content

Enhancement: Add new acf/fields/icon_picker/{tab_name}/icons filter #177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
76c88bc
Move icon list code into new render_icon_list_tab method
ockham Jun 17, 2025
1da6564
Add data-tab attribute
ockham Jun 17, 2025
920e852
Add new `acf/fields/icon_picker/{tab_name}/icons` filter
ockham Jun 17, 2025
d345535
Update class names
ockham Jun 18, 2025
1e98cee
Rename $dashiconsList to $iconsList, update selector
ockham Jun 18, 2025
50d92f6
Rename event handlers
ockham Jun 18, 2025
8d80f42
Start renaming methods
ockham Jun 18, 2025
51cdd52
Update initializeIconLists
ockham Jun 18, 2025
54ca32d
Remove now-obsolete initializeSelectedDashicon method
ockham Jun 18, 2025
34f9a6c
Update getIconsList
ockham Jun 18, 2025
68869b2
Less weird
ockham Jun 18, 2025
cc7eae6
Update renderIconList
ockham Jun 18, 2025
0bd4957
Rename renderDashiconHTML to renderIconHTML
ockham Jun 18, 2025
c8e3516
Update renderIconHTML
ockham Jun 18, 2025
6fc2fce
Update selectIcon
ockham Jun 18, 2025
0a4ef42
Update unselectIcon
ockham Jun 18, 2025
9d8aa4e
Update onIconRadioFocus
ockham Jun 18, 2025
84c745e
Update onIconClick
ockham Jun 18, 2025
5ba3ddc
Remove stray parens
ockham Jun 18, 2025
0ea3ac3
Pass element to unselectIcon
ockham Jun 18, 2025
e231d43
Fix search related methods
ockham Jun 18, 2025
088c728
Correctly set search results
ockham Jun 19, 2025
cf27600
Only set style attr if url is set
ockham Jun 19, 2025
fdc548f
Update alignIconListTabsToCurrentValue
ockham Jun 19, 2025
507bafa
Add mising const
ockham Jun 19, 2025
301f6db
Update scrollToSelectedIcon
ockham Jun 19, 2025
4c52ead
Various fixes
ockham Jun 19, 2025
9bac504
Minor comment wording tweaks
ockham Jun 19, 2025
09ec01f
Remove stray second function argument
ockham Jun 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 139 additions & 105 deletions assets/src/js/_acf-field-icon-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
wait: 'load',

events: {
showField: 'scrollToSelectedDashicon',
showField: 'scrollToSelectedIcon',
'input .acf-icon_url': 'onUrlChange',
'click .acf-icon-picker-dashicon': 'onDashiconClick',
'focus .acf-icon-picker-dashicon-radio': 'onDashiconRadioFocus',
'blur .acf-icon-picker-dashicon-radio': 'onDashiconRadioBlur',
'keydown .acf-icon-picker-dashicon-radio': 'onDashiconKeyDown',
'input .acf-dashicons-search-input': 'onDashiconSearch',
'keydown .acf-dashicons-search-input': 'onDashiconSearchKeyDown',
'click .acf-icon-picker-list-icon': 'onIconClick',
'focus .acf-icon-picker-list-icon-radio': 'onIconRadioFocus',
'blur .acf-icon-picker-list-icon-radio': 'onIconRadioBlur',
'keydown .acf-icon-picker-list-icon-radio': 'onIconKeyDown',
'input .acf-icon-list-search-input': 'onIconSearch',
'keydown .acf-icon-list-search-input': 'onIconSearchKeyDown',
'click .acf-icon-picker-media-library-button':
'onMediaLibraryButtonClick',
'click .acf-icon-picker-media-library-preview':
Expand All @@ -36,15 +36,15 @@
},

$selectedIcon() {
return this.$( '.acf-icon-picker-dashicon.active' );
return this.$( '.acf-icon-picker-list-icon.active' );
},

$selectedRadio() {
return this.$( '.acf-icon-picker-dashicon.active input' );
return this.$( '.acf-icon-picker-list-icon.active input' );
},

$dashiconsList() {
return this.$( '.acf-dashicons-list' );
$iconsList() {
return this.$( '.acf-icon-list:visible' );
},

$mediaLibraryButton() {
Expand All @@ -64,9 +64,9 @@
// Store the type and value object.
this.set( 'typeAndValue', typeAndValue );

// Any time any acf tab is clicked, we will re-scroll to the selected dashicon.
// Any time any acf tab is clicked, we will re-scroll to the selected icon.
$( '.acf-tab-button' ).on( 'click', () => {
this.initializeDashiconsTab( this.get( 'typeAndValue' ) );
this.initializeIconLists( this.get( 'typeAndValue' ) );
} );

// Fire the action which lets people know the state has been updated.
Expand All @@ -75,7 +75,7 @@
typeAndValue
);

this.initializeDashiconsTab( typeAndValue );
this.initializeIconLists( typeAndValue );
this.alignMediaLibraryTabToCurrentValue( typeAndValue );
},

Expand All @@ -85,7 +85,7 @@
this.get( 'name' ) + '/type_and_value_change',
( newTypeAndValue ) => {
// Align the visual state of each tab to the current value.
this.alignDashiconsTabToCurrentValue( newTypeAndValue );
this.alignIconListTabsToCurrentValue( newTypeAndValue );
this.alignMediaLibraryTabToCurrentValue( newTypeAndValue );
this.alignUrlTabToCurrentValue( newTypeAndValue );
}
Expand All @@ -112,15 +112,15 @@
this.set( 'typeAndValue', typeAndValue );
},

scrollToSelectedDashicon() {
scrollToSelectedIcon() {
const innerElement = this.$selectedIcon();

// If no icon is selected, do nothing.
if ( innerElement.length === 0 ) {
return;
}

const scrollingDiv = this.$dashiconsList();
const scrollingDiv = innerElement.closest( '.acf-icon-list' );
scrollingDiv.scrollTop( 0 );

const distance = innerElement.position().top - 50;
Expand All @@ -132,92 +132,115 @@
scrollingDiv.scrollTop( distance );
},

initializeDashiconsTab( typeAndValue ) {
const dashicons = this.getDashiconsList() || [];
this.set( 'dashicons', dashicons );
this.renderDashiconList();
this.initializeSelectedDashicon( typeAndValue );
initializeIconLists( typeAndValue ) {
const self = this;

this.$( '.acf-icon-list' ).each( function( i ) {
const tabName = $( this ).data( 'parent-tab' );
const icons = self.getIconsList( tabName ) || [];
self.set( tabName, icons );
self.renderIconList( $( this ) );

if ( typeAndValue.type === tabName ) {
// Select the correct icon.
self.selectIcon( $( this ), typeAndValue.value, false ).then( () => {
// Scroll to the selected icon.
self.scrollToSelectedIcon();
} );
}
} );
},

initializeSelectedDashicon( typeAndValue ) {
if ( typeAndValue.type !== 'dashicons' ) {
return;
}
// Select the correct dashicon.
this.selectDashicon( typeAndValue.value, false ).then( () => {
// Scroll to the selected dashicon.
this.scrollToSelectedDashicon();
alignIconListTabsToCurrentValue( typeAndValue ) {
const icons = this.$( '.acf-icon-list' ).filter(
function () {
return (
$( this ).data( 'parent-tab' ) !== typeAndValue.type
);
}
);
const self = this;
icons.each( function () {
self.unselectIcon( $( this ) );
} );
},

alignDashiconsTabToCurrentValue( typeAndValue ) {
if ( typeAndValue.type !== 'dashicons' ) {
this.unselectDashicon();
renderIconHTML( tabName, icon ) {
const id = `${ this.get( 'name' ) }-${ icon.key }`;

let style = '';
if ( 'dashicons' !== tabName ) {
style = `background: center / contain url( ${ acf.strEscape(
icon.url
) } ) no-repeat;`;
}
},

renderDashiconHTML( dashicon ) {
const id = `${ this.get( 'name' ) }-${ dashicon.key }`;
return `<div class="dashicons ${ acf.strEscape(
dashicon.key
) } acf-icon-picker-dashicon" data-icon="${ acf.strEscape(
dashicon.key
return `<div class="${ tabName } ${ acf.strEscape(
icon.key
) } acf-icon-picker-list-icon" role="radio" data-icon="${ acf.strEscape(
icon.key
) }" style="${ style }" title="${ acf.strEscape(
icon.label
) }">
<label for="${ acf.strEscape( id ) }">${ acf.strEscape(
dashicon.label
icon.label
) }</label>
<input id="${ acf.strEscape(
id
) }" type="radio" class="acf-icon-picker-dashicon-radio" name="acf-icon-picker-dashicon-radio" value="${ acf.strEscape(
dashicon.key
) }" type="radio" class="acf-icon-picker-list-icon-radio" name="acf-icon-picker-list-icon-radio" value="${ acf.strEscape(
icon.key
) }">
</div>`;
},

renderDashiconList() {
const dashicons = this.get( 'dashicons' );
renderIconList( $el ) {
const tabName = $el.data( 'parent-tab' );
const icons = this.get( tabName );

this.$dashiconsList().empty();
dashicons.forEach( ( dashicon ) => {
this.$dashiconsList().append(
this.renderDashiconHTML( dashicon )
);
} );
$el.empty();
if ( icons ) {
icons.forEach( ( icon ) => {
const iconHTML = this.renderIconHTML( tabName, icon );
$el.append( iconHTML );
} );
}
},

getDashiconsList() {
const iconPickeri10n = acf.get( 'iconPickeri10n' ) || [];
getIconsList( tabName ) {
if ( 'dashicons' === tabName ) {
const iconPickeri10n = acf.get( 'iconPickeri10n' ) || [];

const dashicons = Object.entries( iconPickeri10n ).map(
( [ key, value ] ) => {
return {
key,
label: value,
};
}
);
return Object.entries( iconPickeri10n ).map(
( [ key, value ] ) => {
return {
key,
label: value,
};
}
);
}

return dashicons;
return acf.get( `iconPickerIcons_${ tabName }` );
},

getDashiconsBySearch( searchTerm ) {
getIconsBySearch( searchTerm, tabName ) {
const lowercaseSearchTerm = searchTerm.toLowerCase();
const dashicons = this.getDashiconsList();
const icons = this.getIconsList( tabName);

const filteredDashicons = dashicons.filter( function ( icon ) {
const filteredIcons = icons.filter( function ( icon ) {
const lowercaseIconLabel = icon.label.toLowerCase();
return lowercaseIconLabel.indexOf( lowercaseSearchTerm ) > -1;
} );

return filteredDashicons;
return filteredIcons;
},

selectDashicon( dashicon, setFocus = true ) {
this.set( 'selectedDashicon', dashicon );
selectIcon( $el, icon, setFocus = true ) {
this.set( 'selectedIcon', icon );

// Select the new one.
const $newIcon = this.$dashiconsList().find(
'.acf-icon-picker-dashicon[data-icon="' + dashicon + '"]'
const $newIcon = $el.find(
'.acf-icon-picker-list-icon[data-icon="' + icon + '"]'
);
$newIcon.addClass( 'active' );

Expand All @@ -228,64 +251,75 @@
$input.trigger( 'focus' );
}

this.updateTypeAndValue( 'dashicons', dashicon );
this.updateTypeAndValue( $el.data( 'parent-tab' ), icon );

return thePromise;
},

unselectDashicon() {
unselectIcon( $el ) {
// Remove the currently active dashicon, if any.
this.$dashiconsList()
.find( '.acf-icon-picker-dashicon' )
$el
.find( '.acf-icon-picker-list-icon' )
.removeClass( 'active' );
this.set( 'selectedDashicon', false );
this.set( 'selectedIcon', false );
},

onDashiconRadioFocus( e ) {
const dashicon = e.target.value;
onIconRadioFocus( e ) {
const icon = e.target.value;
const $tabs = this.$( e.target ).closest(
'.acf-icon-picker-tabs'
);
const $iconsList = $tabs.find( '.acf-icon-list' );

const $newIcon = this.$dashiconsList().find(
'.acf-icon-picker-dashicon[data-icon="' + dashicon + '"]'
const $newIcon = $iconsList.find(
'.acf-icon-picker-list-icon[data-icon="' + icon + '"]'
);
$newIcon.addClass( 'focus' );

// If this is a different icon than previously selected, select it.
if ( this.get( 'selectedDashicon' ) !== dashicon ) {
this.unselectDashicon();
this.selectDashicon( dashicon );
if ( this.get( 'selectedIcon' ) !== icon ) {
this.unselectIcon( $iconsList );
this.selectIcon( $iconsList, icon );
}
},

onDashiconRadioBlur( e ) {
onIconRadioBlur( e ) {
const icon = this.$( e.target );
const iconParent = icon.parent();

iconParent.removeClass( 'focus' );
},

onDashiconClick( e ) {
onIconClick( e ) {
e.preventDefault();
const $iconList = this.$( e.target ).closest(
'.acf-icon-list'
);
const $iconElement = this.$( e.target );
const icon = $iconElement.find( 'input' ).val();

const icon = this.$( e.target );
const dashicon = icon.find( 'input' ).val();

const $newIcon = this.$dashiconsList().find(
'.acf-icon-picker-dashicon[data-icon="' + dashicon + '"]'
const $newIconElement = $iconList.find(
'.acf-icon-picker-list-icon[data-icon="' + icon + '"]'
);

// By forcing focus on the input, we fire onDashiconRadioFocus.
$newIcon.find( 'input' ).prop( 'checked', true ).trigger( 'focus' );
// By forcing focus on the input, we fire onIconRadioFocus.
$newIconElement.find( 'input' ).prop( 'checked', true ).trigger( 'focus' );
},

onDashiconSearch( e ) {
onIconSearch( e ) {
const $tabs = this.$( e.target ).closest(
'.acf-icon-picker-tabs'
);
const $iconList = $tabs.find( '.acf-icon-list' );
const tabName = $tabs.data( 'tab' );
const searchTerm = e.target.value;
const filteredDashicons = this.getDashiconsBySearch( searchTerm );
const filteredIcons = this.getIconsBySearch( searchTerm, tabName );

if ( filteredDashicons.length > 0 || ! searchTerm ) {
this.set( 'dashicons', filteredDashicons );
this.$( '.acf-dashicons-list-empty' ).hide();
this.$( '.acf-dashicons-list ' ).show();
this.renderDashiconList();
if ( filteredIcons.length > 0 || ! searchTerm ) {
this.set( tabName, filteredIcons );
$tabs.find( '.acf-icon-list-empty' ).hide();
$tabs.find( '.acf-icon-list ' ).show();
this.renderIconList( $iconList );

// Announce change of data to screen readers.
wp.a11y.speak(
Expand All @@ -300,12 +334,12 @@
? searchTerm.substring( 0, 30 ) + '&hellip;'
: searchTerm;

this.$( '.acf-dashicons-list ' ).hide();
this.$( '.acf-dashicons-list-empty' )
.find( '.acf-invalid-dashicon-search-term' )
$tabs.find( '.acf-icon-list ' ).hide();
$tabs.find( '.acf-icon-list-empty' )
.find( '.acf-invalid-icon-list-search-term' )
.text( visualSearchTerm );
this.$( '.acf-dashicons-list-empty' ).css( 'display', 'flex' );
this.$( '.acf-dashicons-list-empty' ).show();
$tabs.find( '.acf-icon-list-empty' ).css( 'display', 'flex' );
$tabs.find( '.acf-icon-list-empty' ).show();

// Announce change of data to screen readers.
wp.a11y.speak(
Expand All @@ -315,15 +349,15 @@
}
},

onDashiconSearchKeyDown( e ) {
onIconSearchKeyDown( e ) {
// Check if the pressed key is Enter (key code 13)
if ( e.which === 13 ) {
// Prevent submitting the entire form if someone presses enter after searching.
e.preventDefault();
}
},

onDashiconKeyDown( e ) {
onIconKeyDown( e ) {
if ( e.which === 13 ) {
// If someone presses enter while an icon is focused, prevent the form from submitting.
e.preventDefault();
Expand Down
Loading
Loading