'',
'phoneCountryCode' => $default_country,
- 'countryList' => array(),
'fullPhoneNumber' => '',
'countryPrefix' => '',
+ // combobox state
+ 'useCombobox' => true,
+ 'comboboxOpen' => false,
+ 'searchTerm' => '',
+ 'allCountries' => array(),
+ 'filteredCountries' => array(),
+ 'selectedCountry' => array(),
)
);
?>
>
-
-
-
+
-
+
type="tel"
required="true"
@@ -1065,9 +1121,10 @@ public function render_telephone_field( $id, $label, $value, $class, $required,
data-wp-bind--aria-invalid='state.fieldHasErrors'
data-wp-bind--value='context.phoneNumber'
aria-errormessage="-phone-error-message"
- data-wp-on--input='actions.onPhoneNumberChange'
+ data-wp-on--input='actions.phoneNumberInputHandler'
data-wp-on--blur='actions.onFieldBlur'
- data-wp-class--has-value='state.hasFieldValue'
+ data-wp-on--focus='actions.phoneNumberFocusHandler'
+ data-wp-class--has-value='context.phoneNumber'
/>
get_error_div( $id, 'phone' );
+ $field = $label . $input . $this->get_error_div( $id, 'telephone' );
return $field;
}
@@ -2824,16 +2881,275 @@ private function enqueue_slider_field_assets() {
);
}
+ /**
+ * Gets an array of translatable country names indexed by their two-letter country codes.
+ *
+ * @since $$next-version$$
+ *
+ * @return array Array of country names with two-letter country codes as keys.
+ */
+ public function get_translatable_countries() {
+ return array(
+ 'AF' => __( 'Afghanistan', 'jetpack-forms' ),
+ 'AL' => __( 'Albania', 'jetpack-forms' ),
+ 'DZ' => __( 'Algeria', 'jetpack-forms' ),
+ 'AS' => __( 'American Samoa', 'jetpack-forms' ),
+ 'AD' => __( 'Andorra', 'jetpack-forms' ),
+ 'AO' => __( 'Angola', 'jetpack-forms' ),
+ 'AI' => __( 'Anguilla', 'jetpack-forms' ),
+ 'AG' => __( 'Antigua and Barbuda', 'jetpack-forms' ),
+ 'AR' => __( 'Argentina', 'jetpack-forms' ),
+ 'AM' => __( 'Armenia', 'jetpack-forms' ),
+ 'AW' => __( 'Aruba', 'jetpack-forms' ),
+ 'AU' => __( 'Australia', 'jetpack-forms' ),
+ 'AT' => __( 'Austria', 'jetpack-forms' ),
+ 'AZ' => __( 'Azerbaijan', 'jetpack-forms' ),
+ 'BS' => __( 'Bahamas', 'jetpack-forms' ),
+ 'BH' => __( 'Bahrain', 'jetpack-forms' ),
+ 'BD' => __( 'Bangladesh', 'jetpack-forms' ),
+ 'BB' => __( 'Barbados', 'jetpack-forms' ),
+ 'BY' => __( 'Belarus', 'jetpack-forms' ),
+ 'BE' => __( 'Belgium', 'jetpack-forms' ),
+ 'BZ' => __( 'Belize', 'jetpack-forms' ),
+ 'BJ' => __( 'Benin', 'jetpack-forms' ),
+ 'BM' => __( 'Bermuda', 'jetpack-forms' ),
+ 'BT' => __( 'Bhutan', 'jetpack-forms' ),
+ 'BO' => __( 'Bolivia', 'jetpack-forms' ),
+ 'BA' => __( 'Bosnia and Herzegovina', 'jetpack-forms' ),
+ 'BW' => __( 'Botswana', 'jetpack-forms' ),
+ 'BR' => __( 'Brazil', 'jetpack-forms' ),
+ 'IO' => __( 'British Indian Ocean Territory', 'jetpack-forms' ),
+ 'VG' => __( 'British Virgin Islands', 'jetpack-forms' ),
+ 'BN' => __( 'Brunei', 'jetpack-forms' ),
+ 'BG' => __( 'Bulgaria', 'jetpack-forms' ),
+ 'BF' => __( 'Burkina Faso', 'jetpack-forms' ),
+ 'BI' => __( 'Burundi', 'jetpack-forms' ),
+ 'KH' => __( 'Cambodia', 'jetpack-forms' ),
+ 'CM' => __( 'Cameroon', 'jetpack-forms' ),
+ 'CA' => __( 'Canada', 'jetpack-forms' ),
+ 'CV' => __( 'Cape Verde', 'jetpack-forms' ),
+ 'KY' => __( 'Cayman Islands', 'jetpack-forms' ),
+ 'CF' => __( 'Central African Republic', 'jetpack-forms' ),
+ 'TD' => __( 'Chad', 'jetpack-forms' ),
+ 'CL' => __( 'Chile', 'jetpack-forms' ),
+ 'CN' => __( 'China', 'jetpack-forms' ),
+ 'CX' => __( 'Christmas Island', 'jetpack-forms' ),
+ 'CC' => __( 'Cocos (Keeling) Islands', 'jetpack-forms' ),
+ 'CO' => __( 'Colombia', 'jetpack-forms' ),
+ 'KM' => __( 'Comoros', 'jetpack-forms' ),
+ 'CG' => __( 'Congo - Brazzaville', 'jetpack-forms' ),
+ 'CD' => __( 'Congo - Kinshasa', 'jetpack-forms' ),
+ 'CK' => __( 'Cook Islands', 'jetpack-forms' ),
+ 'CR' => __( 'Costa Rica', 'jetpack-forms' ),
+ 'HR' => __( 'Croatia', 'jetpack-forms' ),
+ 'CU' => __( 'Cuba', 'jetpack-forms' ),
+ 'CY' => __( 'Cyprus', 'jetpack-forms' ),
+ 'CZ' => __( 'Czech Republic', 'jetpack-forms' ),
+ 'CI' => __( "Côte d'Ivoire", 'jetpack-forms' ),
+ 'DK' => __( 'Denmark', 'jetpack-forms' ),
+ 'DJ' => __( 'Djibouti', 'jetpack-forms' ),
+ 'DM' => __( 'Dominica', 'jetpack-forms' ),
+ 'DO' => __( 'Dominican Republic', 'jetpack-forms' ),
+ 'EC' => __( 'Ecuador', 'jetpack-forms' ),
+ 'EG' => __( 'Egypt', 'jetpack-forms' ),
+ 'SV' => __( 'El Salvador', 'jetpack-forms' ),
+ 'GQ' => __( 'Equatorial Guinea', 'jetpack-forms' ),
+ 'ER' => __( 'Eritrea', 'jetpack-forms' ),
+ 'EE' => __( 'Estonia', 'jetpack-forms' ),
+ 'SZ' => __( 'Eswatini', 'jetpack-forms' ),
+ 'ET' => __( 'Ethiopia', 'jetpack-forms' ),
+ 'FK' => __( 'Falkland Islands', 'jetpack-forms' ),
+ 'FO' => __( 'Faroe Islands', 'jetpack-forms' ),
+ 'FJ' => __( 'Fiji', 'jetpack-forms' ),
+ 'FI' => __( 'Finland', 'jetpack-forms' ),
+ 'FR' => __( 'France', 'jetpack-forms' ),
+ 'GF' => __( 'French Guiana', 'jetpack-forms' ),
+ 'PF' => __( 'French Polynesia', 'jetpack-forms' ),
+ 'GA' => __( 'Gabon', 'jetpack-forms' ),
+ 'GM' => __( 'Gambia', 'jetpack-forms' ),
+ 'GE' => __( 'Georgia', 'jetpack-forms' ),
+ 'DE' => __( 'Germany', 'jetpack-forms' ),
+ 'GH' => __( 'Ghana', 'jetpack-forms' ),
+ 'GI' => __( 'Gibraltar', 'jetpack-forms' ),
+ 'GR' => __( 'Greece', 'jetpack-forms' ),
+ 'GL' => __( 'Greenland', 'jetpack-forms' ),
+ 'GD' => __( 'Grenada', 'jetpack-forms' ),
+ 'GP' => __( 'Guadeloupe', 'jetpack-forms' ),
+ 'GU' => __( 'Guam', 'jetpack-forms' ),
+ 'GT' => __( 'Guatemala', 'jetpack-forms' ),
+ 'GG' => __( 'Guernsey', 'jetpack-forms' ),
+ 'GN' => __( 'Guinea', 'jetpack-forms' ),
+ 'GW' => __( 'Guinea-Bissau', 'jetpack-forms' ),
+ 'GY' => __( 'Guyana', 'jetpack-forms' ),
+ 'HT' => __( 'Haiti', 'jetpack-forms' ),
+ 'HN' => __( 'Honduras', 'jetpack-forms' ),
+ 'HK' => __( 'Hong Kong', 'jetpack-forms' ),
+ 'HU' => __( 'Hungary', 'jetpack-forms' ),
+ 'IS' => __( 'Iceland', 'jetpack-forms' ),
+ 'IN' => __( 'India', 'jetpack-forms' ),
+ 'ID' => __( 'Indonesia', 'jetpack-forms' ),
+ 'IR' => __( 'Iran', 'jetpack-forms' ),
+ 'IQ' => __( 'Iraq', 'jetpack-forms' ),
+ 'IE' => __( 'Ireland', 'jetpack-forms' ),
+ 'IM' => __( 'Isle of Man', 'jetpack-forms' ),
+ 'IL' => __( 'Israel', 'jetpack-forms' ),
+ 'IT' => __( 'Italy', 'jetpack-forms' ),
+ 'JM' => __( 'Jamaica', 'jetpack-forms' ),
+ 'JP' => __( 'Japan', 'jetpack-forms' ),
+ 'JE' => __( 'Jersey', 'jetpack-forms' ),
+ 'JO' => __( 'Jordan', 'jetpack-forms' ),
+ 'KZ' => __( 'Kazakhstan', 'jetpack-forms' ),
+ 'KE' => __( 'Kenya', 'jetpack-forms' ),
+ 'KI' => __( 'Kiribati', 'jetpack-forms' ),
+ 'XK' => __( 'Kosovo', 'jetpack-forms' ),
+ 'KW' => __( 'Kuwait', 'jetpack-forms' ),
+ 'KG' => __( 'Kyrgyzstan', 'jetpack-forms' ),
+ 'LA' => __( 'Laos', 'jetpack-forms' ),
+ 'LV' => __( 'Latvia', 'jetpack-forms' ),
+ 'LB' => __( 'Lebanon', 'jetpack-forms' ),
+ 'LS' => __( 'Lesotho', 'jetpack-forms' ),
+ 'LR' => __( 'Liberia', 'jetpack-forms' ),
+ 'LY' => __( 'Libya', 'jetpack-forms' ),
+ 'LI' => __( 'Liechtenstein', 'jetpack-forms' ),
+ 'LT' => __( 'Lithuania', 'jetpack-forms' ),
+ 'LU' => __( 'Luxembourg', 'jetpack-forms' ),
+ 'MO' => __( 'Macao', 'jetpack-forms' ),
+ 'MG' => __( 'Madagascar', 'jetpack-forms' ),
+ 'MW' => __( 'Malawi', 'jetpack-forms' ),
+ 'MY' => __( 'Malaysia', 'jetpack-forms' ),
+ 'MV' => __( 'Maldives', 'jetpack-forms' ),
+ 'ML' => __( 'Mali', 'jetpack-forms' ),
+ 'MT' => __( 'Malta', 'jetpack-forms' ),
+ 'MH' => __( 'Marshall Islands', 'jetpack-forms' ),
+ 'MQ' => __( 'Martinique', 'jetpack-forms' ),
+ 'MR' => __( 'Mauritania', 'jetpack-forms' ),
+ 'MU' => __( 'Mauritius', 'jetpack-forms' ),
+ 'YT' => __( 'Mayotte', 'jetpack-forms' ),
+ 'MX' => __( 'Mexico', 'jetpack-forms' ),
+ 'FM' => __( 'Micronesia', 'jetpack-forms' ),
+ 'MD' => __( 'Moldova', 'jetpack-forms' ),
+ 'MC' => __( 'Monaco', 'jetpack-forms' ),
+ 'MN' => __( 'Mongolia', 'jetpack-forms' ),
+ 'ME' => __( 'Montenegro', 'jetpack-forms' ),
+ 'MS' => __( 'Montserrat', 'jetpack-forms' ),
+ 'MA' => __( 'Morocco', 'jetpack-forms' ),
+ 'MZ' => __( 'Mozambique', 'jetpack-forms' ),
+ 'MM' => __( 'Myanmar', 'jetpack-forms' ),
+ 'NA' => __( 'Namibia', 'jetpack-forms' ),
+ 'NR' => __( 'Nauru', 'jetpack-forms' ),
+ 'NP' => __( 'Nepal', 'jetpack-forms' ),
+ 'NL' => __( 'Netherlands', 'jetpack-forms' ),
+ 'NC' => __( 'New Caledonia', 'jetpack-forms' ),
+ 'NZ' => __( 'New Zealand', 'jetpack-forms' ),
+ 'NI' => __( 'Nicaragua', 'jetpack-forms' ),
+ 'NE' => __( 'Niger', 'jetpack-forms' ),
+ 'NG' => __( 'Nigeria', 'jetpack-forms' ),
+ 'NU' => __( 'Niue', 'jetpack-forms' ),
+ 'NF' => __( 'Norfolk Island', 'jetpack-forms' ),
+ 'KP' => __( 'North Korea', 'jetpack-forms' ),
+ 'MK' => __( 'North Macedonia', 'jetpack-forms' ),
+ 'MP' => __( 'Northern Mariana Islands', 'jetpack-forms' ),
+ 'NO' => __( 'Norway', 'jetpack-forms' ),
+ 'OM' => __( 'Oman', 'jetpack-forms' ),
+ 'PK' => __( 'Pakistan', 'jetpack-forms' ),
+ 'PW' => __( 'Palau', 'jetpack-forms' ),
+ 'PS' => __( 'Palestine', 'jetpack-forms' ),
+ 'PA' => __( 'Panama', 'jetpack-forms' ),
+ 'PG' => __( 'Papua New Guinea', 'jetpack-forms' ),
+ 'PY' => __( 'Paraguay', 'jetpack-forms' ),
+ 'PE' => __( 'Peru', 'jetpack-forms' ),
+ 'PH' => __( 'Philippines', 'jetpack-forms' ),
+ 'PN' => __( 'Pitcairn Islands', 'jetpack-forms' ),
+ 'PL' => __( 'Poland', 'jetpack-forms' ),
+ 'PT' => __( 'Portugal', 'jetpack-forms' ),
+ 'PR' => __( 'Puerto Rico', 'jetpack-forms' ),
+ 'QA' => __( 'Qatar', 'jetpack-forms' ),
+ 'RO' => __( 'Romania', 'jetpack-forms' ),
+ 'RU' => __( 'Russia', 'jetpack-forms' ),
+ 'RW' => __( 'Rwanda', 'jetpack-forms' ),
+ 'RE' => __( 'Réunion', 'jetpack-forms' ),
+ 'BL' => __( 'Saint Barthélemy', 'jetpack-forms' ),
+ 'SH' => __( 'Saint Helena', 'jetpack-forms' ),
+ 'KN' => __( 'Saint Kitts and Nevis', 'jetpack-forms' ),
+ 'LC' => __( 'Saint Lucia', 'jetpack-forms' ),
+ 'MF' => __( 'Saint Martin', 'jetpack-forms' ),
+ 'PM' => __( 'Saint Pierre and Miquelon', 'jetpack-forms' ),
+ 'VC' => __( 'Saint Vincent and the Grenadines', 'jetpack-forms' ),
+ 'WS' => __( 'Samoa', 'jetpack-forms' ),
+ 'SM' => __( 'San Marino', 'jetpack-forms' ),
+ 'SA' => __( 'Saudi Arabia', 'jetpack-forms' ),
+ 'SN' => __( 'Senegal', 'jetpack-forms' ),
+ 'RS' => __( 'Serbia', 'jetpack-forms' ),
+ 'SC' => __( 'Seychelles', 'jetpack-forms' ),
+ 'SL' => __( 'Sierra Leone', 'jetpack-forms' ),
+ 'SG' => __( 'Singapore', 'jetpack-forms' ),
+ 'SK' => __( 'Slovakia', 'jetpack-forms' ),
+ 'SI' => __( 'Slovenia', 'jetpack-forms' ),
+ 'SB' => __( 'Solomon Islands', 'jetpack-forms' ),
+ 'SO' => __( 'Somalia', 'jetpack-forms' ),
+ 'ZA' => __( 'South Africa', 'jetpack-forms' ),
+ 'GS' => __( 'South Georgia and the South Sandwich Islands', 'jetpack-forms' ),
+ 'KR' => __( 'South Korea', 'jetpack-forms' ),
+ 'ES' => __( 'Spain', 'jetpack-forms' ),
+ 'LK' => __( 'Sri Lanka', 'jetpack-forms' ),
+ 'SD' => __( 'Sudan', 'jetpack-forms' ),
+ 'SR' => __( 'Suriname', 'jetpack-forms' ),
+ 'SJ' => __( 'Svalbard and Jan Mayen', 'jetpack-forms' ),
+ 'SE' => __( 'Sweden', 'jetpack-forms' ),
+ 'CH' => __( 'Switzerland', 'jetpack-forms' ),
+ 'SY' => __( 'Syria', 'jetpack-forms' ),
+ 'ST' => __( 'São Tomé and Príncipe', 'jetpack-forms' ),
+ 'TW' => __( 'Taiwan', 'jetpack-forms' ),
+ 'TJ' => __( 'Tajikistan', 'jetpack-forms' ),
+ 'TZ' => __( 'Tanzania', 'jetpack-forms' ),
+ 'TH' => __( 'Thailand', 'jetpack-forms' ),
+ 'TL' => __( 'Timor-Leste', 'jetpack-forms' ),
+ 'TG' => __( 'Togo', 'jetpack-forms' ),
+ 'TK' => __( 'Tokelau', 'jetpack-forms' ),
+ 'TO' => __( 'Tonga', 'jetpack-forms' ),
+ 'TT' => __( 'Trinidad and Tobago', 'jetpack-forms' ),
+ 'TN' => __( 'Tunisia', 'jetpack-forms' ),
+ 'TR' => __( 'Turkey', 'jetpack-forms' ),
+ 'TM' => __( 'Turkmenistan', 'jetpack-forms' ),
+ 'TC' => __( 'Turks and Caicos Islands', 'jetpack-forms' ),
+ 'TV' => __( 'Tuvalu', 'jetpack-forms' ),
+ 'VI' => __( 'U.S. Virgin Islands', 'jetpack-forms' ),
+ 'UG' => __( 'Uganda', 'jetpack-forms' ),
+ 'UA' => __( 'Ukraine', 'jetpack-forms' ),
+ 'AE' => __( 'United Arab Emirates', 'jetpack-forms' ),
+ 'GB' => __( 'United Kingdom', 'jetpack-forms' ),
+ 'US' => __( 'United States', 'jetpack-forms' ),
+ 'UY' => __( 'Uruguay', 'jetpack-forms' ),
+ 'UZ' => __( 'Uzbekistan', 'jetpack-forms' ),
+ 'VU' => __( 'Vanuatu', 'jetpack-forms' ),
+ 'VA' => __( 'Vatican City', 'jetpack-forms' ),
+ 'VE' => __( 'Venezuela', 'jetpack-forms' ),
+ 'VN' => __( 'Vietnam', 'jetpack-forms' ),
+ 'WF' => __( 'Wallis and Futuna', 'jetpack-forms' ),
+ 'YE' => __( 'Yemen', 'jetpack-forms' ),
+ 'ZM' => __( 'Zambia', 'jetpack-forms' ),
+ 'ZW' => __( 'Zimbabwe', 'jetpack-forms' ),
+ );
+ }
+
/**
* Enqueues scripts and styles needed for the slider field.
*
- * @since 5.1.0
+ * @since $$next-version$$
*
* @return void
*/
private function enqueue_phone_field_assets() {
$version = defined( 'JETPACK__VERSION' ) ? \JETPACK__VERSION : '0.1';
+ // combobox styles
+ \wp_enqueue_style(
+ 'jetpack-form-combobox',
+ plugins_url( '../../dist/contact-form/css/combobox.css', __FILE__ ),
+ array(),
+ $version
+ );
+
\wp_enqueue_style(
'jetpack-form-phone-field',
plugins_url( '../../dist/contact-form/css/phone-field.css', __FILE__ ),
diff --git a/projects/packages/forms/src/contact-form/class-contact-form-plugin.php b/projects/packages/forms/src/contact-form/class-contact-form-plugin.php
index 7b15940968a4d..83491a1c3cd8a 100644
--- a/projects/packages/forms/src/contact-form/class-contact-form-plugin.php
+++ b/projects/packages/forms/src/contact-form/class-contact-form-plugin.php
@@ -515,6 +515,10 @@ public static function block_attributes_to_shortcode_attributes( $atts, $type, $
unset( $atts['default'] );
}
+ if ( ! isset( $atts['showCountrySelector'] ) || ! $atts['showCountrySelector'] ) {
+ unset( $atts['default'] );
+ }
+
$input_attrs = self::get_block_support_classes_and_styles( $block_name, $inner_block['attrs'] );
$atts['inputclasses'] = 'wp-block-jetpack-input jetpack-field__input-element';
$atts['inputclasses'] .= isset( $input_attrs['class'] ) ? ' ' . $input_attrs['class'] : '';
diff --git a/projects/packages/forms/src/contact-form/css/combobox.scss b/projects/packages/forms/src/contact-form/css/combobox.scss
new file mode 100644
index 0000000000000..12b88cd3fc775
--- /dev/null
+++ b/projects/packages/forms/src/contact-form/css/combobox.scss
@@ -0,0 +1,167 @@
+// Custom Combobox Styles for Country Selector
+.jetpack-custom-combobox {
+ letter-spacing: normal; // don't let input attributes rule over this
+ background-color: inherit;
+
+ // keyboardShortcuts adds a wrapping div, so we need to style it
+ > div {
+ color: currentColor;
+ background-color: inherit;
+ }
+
+ .jetpack-combobox-trigger {
+ width: 100%;
+ border: 0 solid #ddd;
+ border-radius: 4px;
+ background: transparent;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font: inherit;
+ line-height: inherit;
+ padding-block: 0;
+ padding-inline: 0;
+ gap: 0.4em;
+ color: inherit;
+
+ &:focus {
+ outline: 0;
+ text-decoration: none;
+ box-shadow: none;
+ }
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ .jetpack-combobox-trigger-arrow {
+ transition: transform 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 0.6em;
+ margin-left: 4px;
+
+ svg {
+ width: 100%;
+ height: 100%;
+ color: inherit;
+ }
+
+ &.is-open {
+ transform: scaleY(-1);
+ }
+ }
+ }
+
+ .jetpack-combobox-dropdown {
+ position: absolute;
+ left: -1px;
+ top: 100%;
+ width: -webkit-fill-available;
+ min-width: 60%;
+
+ background-color: var(--jetpack--contact-form--input-background-fallback);
+ border-color: var(--jetpack--contact-form--border-color, currentColor);
+ border-width: var(--jetpack--contact-form--border-size, 1px);
+ border-radius: var(--jetpack--contact-form--border-radius, 4px);
+ // some line-heights are too small,
+ //use max() to ensure a minimum height of 8 * 25px
+ max-height: max(calc(var(--jetpack--contact-form--line-height, 25px) * 8), 200px);
+ z-index: 1000;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+
+ &.jetpack-combobox-open {
+ display: block;
+ }
+
+ .jetpack-combobox-search {
+ width: 100%;
+ padding: 8px 12px;
+ border: none;
+ border-bottom: 1px solid #eee;
+ outline: none;
+ font-size: 14px;
+ position: sticky;
+ top: 0;
+ color: inherit;
+ background-color: inherit;
+ z-index: 1;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+
+ &:focus {
+ outline: none;
+ box-shadow: none;
+ }
+
+ }
+ // for styles on search input, themes keep finding ways of messing with this
+ input[type="text"].jetpack-combobox-search {
+ background-color: inherit !important;
+ }
+
+ .jetpack-combobox-options {
+ max-height: calc(var(--jetpack--contact-form--line-height, 25px) * 7);
+ overflow-y: auto;
+ color: currentColor;
+ }
+
+ .jetpack-combobox-option {
+ padding: 8px 12px;
+ cursor: pointer;
+ display: flex;
+ align-items: flex-start;
+ font-size: 14px;
+ transition: background-color 0.1s;
+ gap: 8px;
+ background-color: transparent;
+ border: none;
+ line-height: inherit;
+ color: currentColor;
+
+ &:hover,
+ &.is-focused {
+ background-color: var(--wp--preset--color--secondary, #f5f5f5);
+ color: var(--wp--preset--color--background, #000);
+ }
+
+ &.jetpack-combobox-option-selected {
+ background-color: var(--wp--preset--color--secondary, #f5f5f5);
+ font-weight: 600;
+ color: var(--wp--preset--color--background, #000);
+
+ &:hover {
+ background-color: var(--wp--preset--color--secondary, #f5f5f5);
+ }
+ }
+ }
+ }
+}
+
+
+.jetpack-combobox-option-icon {
+ color: currentColor;
+ pointer-events: none;
+}
+
+.jetpack-combobox-option-value {
+ font-weight: 500;
+ pointer-events: none;
+ color: currentColor;
+}
+
+.jetpack-combobox-option-description {
+ pointer-events: none;
+ color: currentColor;
+}
+
+// Editor styles
+.wp-block .jetpack-custom-combobox {
+
+ .jetpack-combobox-option {
+ width: 100%;
+ }
+}
diff --git a/projects/packages/forms/src/contact-form/css/grunion.scss b/projects/packages/forms/src/contact-form/css/grunion.scss
index 00c4a5ca92040..618e4ea5aca5d 100644
--- a/projects/packages/forms/src/contact-form/css/grunion.scss
+++ b/projects/packages/forms/src/contact-form/css/grunion.scss
@@ -69,7 +69,7 @@ that needs to mimic the input element styles */
font: inherit;
border: 1px solid #8c8f94;
border-radius: 0;
- background-color: var(--jetpack--contact-form--background-color);
+ background-color: var(--jetpack--contact-form--input-background);
}
:where(.contact-form textarea) {
@@ -1212,7 +1212,7 @@ on production builds, the attributes are being reordered, causing side-effects
align-items: baseline;
}
-.contact-form :is([type="submit"],button:not([type="reset"])) {
+.contact-form :is([type="submit"]) {
display: inline-flex;
justify-content: center;
align-items: center;
diff --git a/projects/packages/forms/src/contact-form/css/phone-field.scss b/projects/packages/forms/src/contact-form/css/phone-field.scss
index 2cbdb43adb49c..f832addfd463b 100644
--- a/projects/packages/forms/src/contact-form/css/phone-field.scss
+++ b/projects/packages/forms/src/contact-form/css/phone-field.scss
@@ -9,7 +9,7 @@
display: flex;
gap: 8px;
background-color: var(--jetpack--contact-form--input-background);
- border-color: var(--jetpack--contact-form--border-color);
+ border-color: var(--jetpack--contact-form--border-color, currentColor);
border-width: var(--jetpack--contact-form--border-size);
border-style: var(--jetpack--contact-form--border-style);
border-radius: var(--jetpack--contact-form--border-radius);
@@ -17,27 +17,28 @@
font-size: var(--jetpack--contact-form--font-size);
padding: var(--jetpack--contact-form--input-padding);
line-height: var(--jetpack--contact-form--line-height);
+ align-items: center;
&:has(.jetpack-field__input-element:focus) {
outline-width: 2px;
outline-style: solid;
- // mimics default focus outline color, need to fix this
+ // mimics default focus outline color, this is something we cannot
+ // fully control as the probe would fail to capture :focus styles
outline-color: rgb(0, 95, 204);
}
.jetpack-field__input-prefix:not([hidden]) {
- display: flex;
- width: 40%;
+ background-color: inherit;
.jetpack-field__input-element {
+ width: 100%;
+ text-overflow: ellipsis;
+ white-space: nowrap;
@media (max-width: #{ (gb.$break-mobile) }) {
max-width: calc(gb.$break-mobile / 3);
}
- width: 100%;
- text-overflow: ellipsis;
- white-space: nowrap;
}
}
@@ -75,11 +76,15 @@
.is-style-animated & {
.jetpack-field__input-phone-wrapper {
- padding-top: 1.4em;
+ padding-top: max(var(--jetpack--contact-form--input-padding-top, 0), 1.4em);
padding-left: var(--jetpack--contact-form--animated-left-offset);
padding-right: var(--jetpack--contact-form--animated-right-offset);
- &:not(:has(*:focus, *:active)) {
+ &:has(.jetpack-field__input-element:focus) {
+ outline: unset;
+ }
+
+ &:not(:has(*:focus, *:active)):not(.is-combobox-open) {
.jetpack-field__input-prefix:not(:has(~ .has-placeholder),:has(~ .has-value)) {
pointer-events: none;
@@ -88,23 +93,34 @@
}
}
- .animated-label__label:has(~* .jetpack-field__input-element:focus),
- .animated-label__label:has(~* .jetpack-field__input-element.has-value),
- .animated-label__label:has(~* .jetpack-field__input-element.has-placeholder) {
- transform: translateY(0);
- top: calc(2px + var(--jetpack--contact-form--border-top-size, var(--jetpack--contact-form--border-size, 1px)));
+ .animated-label__label {
- .grunion-label-text {
- font-size: 0.75em;
+ &:has(~.jetpack-field__input-phone-wrapper.is-combobox-open),
+ &:has(~.jetpack-field__input-phone-wrapper:focus-within),
+ &:has(~* .jetpack-field__input-element:focus),
+ &:has(~* .jetpack-field__input-element.has-value),
+ &:has(~* .jetpack-field__input-element.has-placeholder) {
+ transform: translateY(0);
+ top: calc(2px + var(--jetpack--contact-form--border-top-size, var(--jetpack--contact-form--border-size, 1px)));
+
+ .grunion-label-text {
+ font-size: 0.75em;
+ }
}
}
+
}
.is-style-outlined & {
.jetpack-field__input-phone-wrapper {
+ z-index: unset;
- &:not(:has(*:focus, *:active)) {
+ &:has(.jetpack-field__input-element:focus) {
+ outline: unset;
+ }
+
+ &:not(:has(*:focus, *:active)):not(.is-combobox-open) {
// notched label is game of superpositions with z-index,
// while no input element is selected, force transparent so label is visible
@@ -119,13 +135,18 @@
}
}
- .notched-label:has(~* .jetpack-field__input-element:focus) .notched-label__label,
- .notched-label:has(~* .jetpack-field__input-element.has-value) .notched-label__label,
- .notched-label:has(~* .jetpack-field__input-element.has-placeholder) .notched-label__label {
- top: calc(var(--jetpack--contact-form--border-top-size, var(--jetpack--contact-form--border-size)) * -1);
+ .notched-label {
+
+ &:has(~.jetpack-field__input-phone-wrapper.is-combobox-open) .notched-label__label,
+ &:has(~.jetpack-field__input-phone-wrapper:focus-within) .notched-label__label,
+ &:has(~* .jetpack-field__input-element:focus) .notched-label__label,
+ &:has(~* .jetpack-field__input-element.has-value) .notched-label__label,
+ &:has(~* .jetpack-field__input-element.has-placeholder) .notched-label__label {
+ top: calc(var(--jetpack--contact-form--border-top-size, var(--jetpack--contact-form--border-size)) * -1);
- .grunion-label-text {
- font-size: 0.8em;
+ .grunion-label-text {
+ font-size: 0.8em;
+ }
}
}
diff --git a/projects/packages/forms/src/modules/field-phone/view.js b/projects/packages/forms/src/modules/field-phone/view.js
index 9b091ae4460c8..07cf9168e86f6 100644
--- a/projects/packages/forms/src/modules/field-phone/view.js
+++ b/projects/packages/forms/src/modules/field-phone/view.js
@@ -1,10 +1,28 @@
-import { store, getContext } from '@wordpress/interactivity';
+import { store, getContext, getConfig, getElement, withSyncEvent } from '@wordpress/interactivity';
import parsePhoneNumber, { AsYouType } from 'libphonenumber-js';
import { countries } from '../../blocks/field-telephone/country-list';
import { isEmptyValue } from '../../contact-form/js/validate-helper';
const NAMESPACE = 'jetpack/form';
const asYouTypes = {};
+const phoneInputRefs = {};
+const searchInputRefs = {};
+const optionsListRefs = {};
+const updateSelection = selectedCountry => {
+ const context = getContext();
+ context.phoneCountryCode = selectedCountry.code;
+ context.countryPrefix = selectedCountry.value;
+ context.fullPhoneNumber = context.countryPrefix + ' ' + context.phoneNumber;
+ asYouTypes[ context.fieldId ] = new AsYouType( context.phoneCountryCode );
+ context.filteredCountries = context.filteredCountries.map( country => ( {
+ ...country,
+ selected: country.code === selectedCountry.code,
+ } ) );
+ context.allCountries = context.allCountries.map( country => ( {
+ ...country,
+ selected: country.code === selectedCountry.code,
+ } ) );
+};
const { actions } = store( NAMESPACE, {
state: {
@@ -50,7 +68,7 @@ const { actions } = store( NAMESPACE, {
context.phoneCountryCode = context.defaultCountry;
context.phoneNumber = '';
},
- onPhoneNumberChange( event ) {
+ phoneNumberInputHandler( event ) {
const context = getContext();
const fieldId = context.fieldId;
const value = event.target.value;
@@ -63,39 +81,155 @@ const { actions } = store( NAMESPACE, {
asYouTypes[ fieldId ].reset();
asYouTypes[ fieldId ].input( groomedValue );
if ( asYouTypes[ fieldId ].getCountry() ) {
- context.phoneCountryCode = asYouTypes[ fieldId ].getCountry();
+ const countryCode = asYouTypes[ fieldId ].getCountry();
context.phoneNumber = asYouTypes[ fieldId ].getNationalNumber();
- asYouTypes[ fieldId ] = new AsYouType( context.phoneCountryCode );
- context.countryPrefix = countries.find(
- item => item.code === context.phoneCountryCode
- )?.value;
+ context.selectedCountry = context.allCountries.find(
+ country => country.code === countryCode
+ );
+ updateSelection( context.selectedCountry );
} else {
context.phoneNumber = value;
}
- context.fullPhoneNumber = context.countryPrefix + ' ' + context.phoneNumber;
+
actions.updateField( fieldId, value );
},
- onPhoneCountryChange( event ) {
+ phoneCountryChangeHandler() {
+ const context = getContext();
+ // this context.filtered is from the template iterator
+ context.selectedCountry = { ...context.filtered };
+ updateSelection( context.selectedCountry );
+ context.comboboxOpen = false;
+ phoneInputRefs[ context.fieldId ]?.focus?.();
+ },
+ phoneComboboxInputHandler( event ) {
+ const context = getContext();
+ const searchTerm = event.target.value.toLowerCase();
+ context.filteredCountries = context.allCountries.filter(
+ country =>
+ country.country.toLowerCase().includes( searchTerm ) ||
+ country.code.toLowerCase().includes( searchTerm ) ||
+ country.value.includes( searchTerm )
+ );
+ optionsListRefs[ context.fieldId ].scrollTo?.( { top: 0, behavior: 'instant' } );
+ },
+ phoneComboboxKeydownHandler: withSyncEvent( event => {
+ const context = getContext();
+ if ( event.key === 'Escape' ) {
+ context.comboboxOpen = false;
+ } else if ( event.key === 'Enter' ) {
+ event.preventDefault();
+ // Select either the currently selected country or the first filtered option if available
+ if ( context.filteredCountries.length > 0 ) {
+ const selectedCountry =
+ context.filteredCountries.find( country => country.selected ) ||
+ context.filteredCountries[ 0 ];
+ context.selectedCountry = selectedCountry;
+ updateSelection( context.selectedCountry );
+ context.comboboxOpen = false;
+ // Focus on the ref input
+ phoneInputRefs[ context.fieldId ]?.focus?.();
+ }
+ } else if ( event.key === 'ArrowDown' ) {
+ event.preventDefault();
+ if ( context.filteredCountries.length > 0 ) {
+ // Find index of currently selected country in filtered list
+ const selectedIndex = context.filteredCountries.findIndex( country => country.selected );
+
+ // If there's a next country in filtered list, select it, otherwise wrap to first
+ const nextIndex =
+ selectedIndex === context.filteredCountries.length - 1 ? 0 : selectedIndex + 1;
+ context.selectedCountry = context.filteredCountries[ nextIndex ];
+ updateSelection( context.selectedCountry );
+ setTimeout( () => {
+ // Find and scroll the newly selected option into view
+ const selectedOption = optionsListRefs[ context.fieldId ].querySelector(
+ '.jetpack-combobox-option-selected'
+ );
+ selectedOption?.scrollIntoView?.( {
+ block: 'nearest',
+ container: 'nearest',
+ behavior: 'instant',
+ } );
+ }, 0 );
+ }
+ } else if ( event.key === 'ArrowUp' ) {
+ event.preventDefault();
+ if ( context.filteredCountries.length > 0 ) {
+ // Find index of currently selected country in filtered list
+ const selectedIndex = context.filteredCountries.findIndex( country => country.selected );
+
+ // If there's a previous country in filtered list, select it, otherwise wrap to last
+ const prevIndex =
+ selectedIndex <= 0 ? context.filteredCountries.length - 1 : selectedIndex - 1;
+ context.selectedCountry = context.filteredCountries[ prevIndex ];
+ updateSelection( context.selectedCountry );
+ setTimeout( () => {
+ // Find and scroll the newly selected option into view
+ const selectedOption = optionsListRefs[ context.fieldId ].querySelector(
+ '.jetpack-combobox-option-selected'
+ );
+ selectedOption?.scrollIntoView?.( {
+ block: 'nearest',
+ container: 'nearest',
+ behavior: 'instant',
+ } );
+ }, 0 );
+ }
+ }
+ } ),
+ phoneNumberFocusHandler() {
const context = getContext();
- context.phoneCountryCode = event?.target?.value || context.defaultCountry;
- context.countryPrefix = countries.find(
- item => item.code === context.phoneCountryCode
- )?.value;
- asYouTypes[ context.fieldId ] = new AsYouType( context.phoneCountryCode );
- context.fullPhoneNumber = context.countryPrefix + ' ' + context.phoneNumber;
+ context.comboboxOpen = false;
+ },
+ phoneComboboxToggle() {
+ const context = getContext();
+ context.comboboxOpen = ! context.comboboxOpen;
+ if ( context.comboboxOpen ) {
+ setTimeout( () => {
+ searchInputRefs[ context.fieldId ]?.focus?.();
+ optionsListRefs[ context.fieldId ]
+ .querySelector( '.jetpack-combobox-option-selected' )
+ ?.scrollIntoView?.( { block: 'nearest', container: 'nearest' } );
+ }, 0 );
+ }
+ },
+ phoneComboboxDocumentClickHandler( event ) {
+ const { ref } = getElement();
+ if ( ref.contains( event.target ) ) {
+ return;
+ }
+ const context = getContext();
+ context.comboboxOpen = false;
},
},
callbacks: {
- initializeCountrySelector() {
+ initializePhoneField() {
+ const element = getElement().ref;
+ const context = getContext();
+ // store refs for quick access later and less intensive dom scouting
+ phoneInputRefs[ context.fieldId ] = element.querySelector( 'input[type="tel"]' );
+ searchInputRefs[ context.fieldId ] = element.parentElement.querySelector(
+ '.jetpack-combobox-search'
+ );
+ optionsListRefs[ context.fieldId ] = element.parentElement.querySelector(
+ '.jetpack-combobox-options'
+ );
+ },
+ initializePhoneFieldCustomComboBox() {
const context = getContext();
- if ( context.showCountrySelector ) {
- context.countryList = countries.map( country => ( {
- ...country,
- label: country.country + ' ' + country.flag + ' ' + country.value,
- value: country.code,
- selected: country.code === context.defaultCountry,
- } ) );
+ if ( ! context.showCountrySelector ) {
+ return;
}
+ const config = getConfig( 'jetpack/field-phone' );
+ context.allCountries = countries.map( country => ( {
+ ...country,
+ country: config?.i18n?.countryNames?.[ country.code ] || '',
+ selected: country.code === context.defaultCountry,
+ } ) );
+ context.filteredCountries = [ ...context.allCountries ];
+ context.selectedCountry = context.filteredCountries.find(
+ country => country.code === context.defaultCountry
+ );
asYouTypes[ context.fieldId ] = new AsYouType( context.defaultCountry );
},
},
diff --git a/projects/packages/forms/tests/php/contact-form/Contact_Form_Test.php b/projects/packages/forms/tests/php/contact-form/Contact_Form_Test.php
index 87e44ae072265..95ccb5735c7fc 100644
--- a/projects/packages/forms/tests/php/contact-form/Contact_Form_Test.php
+++ b/projects/packages/forms/tests/php/contact-form/Contact_Form_Test.php
@@ -1703,9 +1703,9 @@ public function assertValidPhoneField( $html, $attributes ) {
// Get label.
$label = $this->getFirstElement( $wrapper_div, 'label' );
- // Inputs.
- $visible_input = $this->getFirstElement( $wrapper_div, 'input', 0 );
- $input = $this->getFirstElement( $wrapper_div, 'input', 1 );
+ // Inputs. (0 is the comboxbox search input, 1 is the visible input and 2 is the hidden, actual, input)
+ $visible_input = $this->getFirstElement( $wrapper_div, 'input', 1 );
+ $input = $this->getFirstElement( $wrapper_div, 'input', 2 );
// Label matches for matches input ID.
$this->assertEquals(
@@ -1728,8 +1728,8 @@ public function assertValidPhoneField( $html, $attributes ) {
$this->assertEquals( $input->getAttribute( 'value' ), $attributes['default'], 'value and default doesn\'t match' );
$this->assertEquals(
+ 'jetpack-field__input-element',
$visible_input->getAttribute( 'class' ),
- "{$attributes['type']} {$attributes['class']} grunion-field",
'input class attribute doesn\'t match'
);
}
diff --git a/projects/plugins/jetpack/changelog/add-forms-country-combobox b/projects/plugins/jetpack/changelog/add-forms-country-combobox
new file mode 100644
index 0000000000000..ff3461477521a
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-forms-country-combobox
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Forms: phone field can now carry a country selector combobox