diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 6f02fc25bac..2a4f5f74ca8 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -42,6 +42,7 @@ - User sessions are now treated as elevated immediately after login, per the `elevatedSessionDuration` config setting. ### Accessibility +- Added the “Disable autofocus” user accessibility preference. ([#12921](https://github.com/craftcms/cms/discussions/12921)) - Improved source item navigation for screen readers. ([#12054](https://github.com/craftcms/cms/pull/12054)) - Content tab menus are now implemented as disclosure menus. ([#12963](https://github.com/craftcms/cms/pull/12963)) - Element selection modals now show checkboxes for selectable elements. diff --git a/CHANGELOG.md b/CHANGELOG.md index db318432861..3ea3f9dfea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Sites’ Language settings can now be set to environment variables. ([#14235](https://github.com/craftcms/cms/pull/14235), [#14135](https://github.com/craftcms/cms/discussions/14135)) - Card views are once again multi-column on wider viewports. ([#14202](https://github.com/craftcms/cms/discussions/14202)) - Added the “Show cards in a grid” Matrix field setting. ([#14202](https://github.com/craftcms/cms/discussions/14202)) +- Added the “Disable autofocus” user accessibility preference. ([#12921](https://github.com/craftcms/cms/discussions/12921)) - Improved the accessibility of the global nav. ([#14240](https://github.com/craftcms/cms/pull/14240)) - Improved the accessibility of layout tabs. ([#14215](https://github.com/craftcms/cms/pull/14215)) - Improved the accessibility of overflow tab menus. ([#14214](https://github.com/craftcms/cms/pull/14214)) diff --git a/src/config/GeneralConfig.php b/src/config/GeneralConfig.php index 62a7428922a..4b41eaf653d 100644 --- a/src/config/GeneralConfig.php +++ b/src/config/GeneralConfig.php @@ -89,6 +89,7 @@ class GeneralConfig extends BaseConfig 'alwaysShowFocusRings' => false, 'useShapes' => false, 'underlineLinks' => false, + 'disableAutofocus' => false, 'notificationDuration' => 5000, ]; @@ -3266,6 +3267,7 @@ public function init(): void * - `alwaysShowFocusRings` - Whether focus rings should always be shown when an element has focus. * - `useShapes` – Whether shapes should be used to represent statuses. * - `underlineLinks` – Whether links should be underlined. + * - `disableAutofocus` – Whether search inputs should be focused on page load. * - `notificationDuration` – How long notifications should be shown before they disappear automatically (in * milliseconds). Set to `0` to show them indefinitely. * diff --git a/src/controllers/UsersController.php b/src/controllers/UsersController.php index 1b708f76308..4d1d2d8c1bd 100644 --- a/src/controllers/UsersController.php +++ b/src/controllers/UsersController.php @@ -1113,6 +1113,7 @@ public function actionSavePreferences(): Response 'alwaysShowFocusRings' => (bool)$this->request->getBodyParam('alwaysShowFocusRings', $user->getPreference('alwaysShowFocusRings')), 'useShapes' => (bool)$this->request->getBodyParam('useShapes', $user->getPreference('useShapes')), 'underlineLinks' => (bool)$this->request->getBodyParam('underlineLinks', $user->getPreference('underlineLinks')), + 'disableAutofocus' => $this->request->getBodyParam('disableAutofocus', $user->getPreference('disableAutofocus')), 'notificationDuration' => $this->request->getBodyParam('notificationDuration', $user->getPreference('notificationDuration')), ]; diff --git a/src/elements/User.php b/src/elements/User.php index c5d5c68c25c..a6612054520 100644 --- a/src/elements/User.php +++ b/src/elements/User.php @@ -2028,6 +2028,17 @@ public function getPreferredLocale(): ?string return $this->_validateLocale($this->getPreference('locale'), true); } + /** + * Returns whether the user prefers to have form fields autofocused on page load. + * + * @return bool + * @since 5.0.0 + */ + public function getAutofocusPreferred(): bool + { + return !$this->getPreference('disableAutofocus'); + } + /** * Validates and returns a locale ID. * diff --git a/src/fieldlayoutelements/TextField.php b/src/fieldlayoutelements/TextField.php index 3c2565c510a..8f524defabe 100644 --- a/src/fieldlayoutelements/TextField.php +++ b/src/fieldlayoutelements/TextField.php @@ -129,6 +129,9 @@ public function fields(): array */ protected function inputHtml(?ElementInterface $element = null, bool $static = false): ?string { + if ($this->attribute === 'title') { + $test = true; + } return Craft::$app->getView()->renderTemplate('_includes/forms/text.twig', [ 'type' => $this->inputType ?? $this->type, 'autocomplete' => $this->autocomplete, diff --git a/src/templates/_includes/forms/autosuggest.twig b/src/templates/_includes/forms/autosuggest.twig index e8fbb1beec7..6815b0c1d04 100644 --- a/src/templates/_includes/forms/autosuggest.twig +++ b/src/templates/_includes/forms/autosuggest.twig @@ -60,7 +60,7 @@ new Vue({ name: (name ?? '')|namespaceInputName, size: size ?? '', maxlength: maxlength ?? '', - autofocus: autofocus ?? false, + autofocus: (autofocus ?? false) and currentUser.getAutofocusPreferred() and not craft.app.request.isMobileBrowser(true), disabled: disabled ?? false, title: title ?? '', placeholder: placeholder ?? '', diff --git a/src/templates/_includes/forms/text.twig b/src/templates/_includes/forms/text.twig index 687392e9d18..6458a9b3aa8 100644 --- a/src/templates/_includes/forms/text.twig +++ b/src/templates/_includes/forms/text.twig @@ -17,7 +17,7 @@ name: name ?? false, value: value ?? false, maxlength: maxlength ?? false, - autofocus: (autofocus ?? false) and not craft.app.request.isMobileBrowser(true), + autofocus: (autofocus ?? false) and currentUser.getAutofocusPreferred() and not craft.app.request.isMobileBrowser(true), autocomplete: autocomplete is boolean ? (autocomplete ? 'on' : 'off') : autocomplete, autocorrect: (autocorrect ?? true) ? false : 'off', autocapitalize: (autocapitalize ?? true) ? false : 'none', diff --git a/src/templates/users/_preferences.twig b/src/templates/users/_preferences.twig index b6be2eccff2..8397bbe1e09 100644 --- a/src/templates/users/_preferences.twig +++ b/src/templates/users/_preferences.twig @@ -61,6 +61,18 @@ ], }) }} + {{ forms.checkboxGroupField({ + label: 'General Settings'|t('app'), + options: [ + { + label: 'Disable autofocus'|t('app'), + name: 'disableAutofocus', + id: 'disableAutofocus', + checked: currentUser.getPreference('disableAutofocus') ?? a11yDefaults['disableAutofocus'] ?? false, + }, + ], + }) }} + {{ forms.selectField({ label: 'Notification Duration'|t('app'), instructions: 'How long notifications should be shown before they disappear automatically.'|t('app'), diff --git a/src/translations/en/app.php b/src/translations/en/app.php index e725eaa5bc6..2cd829060be 100644 --- a/src/translations/en/app.php +++ b/src/translations/en/app.php @@ -537,6 +537,7 @@ 'Device type' => 'Device type', 'Dimensions' => 'Dimensions', 'Directories cannot be deleted while moving assets.' => 'Directories cannot be deleted while moving assets.', + 'Disable autofocus' => 'Disable autofocus', 'Disable focal point' => 'Disable focal point', 'Disable' => 'Disable', 'Disabled' => 'Disabled', diff --git a/src/web/assets/cp/CpAsset.php b/src/web/assets/cp/CpAsset.php index e7d90296ede..d8e1a806b39 100644 --- a/src/web/assets/cp/CpAsset.php +++ b/src/web/assets/cp/CpAsset.php @@ -504,6 +504,7 @@ private function _craftData(): array 'canAccessQueueManager' => $userSession->checkPermission('utility:queue-manager'), 'dataAttributes' => Html::$dataAttributes, 'defaultIndexCriteria' => [], + 'disableAutofocus' => (bool)($currentUser->getPreference('disableAutofocus') ?? false), 'editableCategoryGroups' => $upToDate ? $this->_editableCategoryGroups() : [], 'edition' => Craft::$app->getEdition(), 'elementTypeNames' => $elementTypeNames, diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js index 3b5450d89b2..5378c02abd6 100644 --- a/src/web/assets/cp/dist/cp.js +++ b/src/web/assets/cp/dist/cp.js @@ -1,3 +1,3 @@ /*! For license information please see cp.js.LICENSE.txt */ -(function(){var __webpack_modules__={463:function(){Craft.Accordion=Garnish.Base.extend({$trigger:null,targetSelector:null,_$target:null,init:function(t){var e=this;this.$trigger=$(t),this.$trigger.data("accordion")&&(console.warn("Double-instantiating an accordion trigger on an element"),this.$trigger.data("accordion").destroy()),this.$trigger.data("accordion",this),this.targetSelector=this.$trigger.attr("aria-controls")?"#".concat(this.$trigger.attr("aria-controls")):null,this.targetSelector&&(this._$target=$(this.targetSelector)),this.addListener(this.$trigger,"click","onTriggerClick"),this.addListener(this.$trigger,"keypress",(function(t){var i=t.keyCode;i!==Garnish.SPACE_KEY&&i!==Garnish.RETURN_KEY||(t.preventDefault(),e.onTriggerClick())}))},onTriggerClick:function(){"true"===this.$trigger.attr("aria-expanded")?this.hideTarget(this._$target):this.showTarget(this._$target)},showTarget:function(t){var e=this;if(t&&t.length){this.showTarget._currentHeight=t.height(),t.removeClass("hidden"),this.$trigger.removeClass("collapsed").addClass("expanded").attr("aria-expanded","true");for(var i=0;i=this.settings.maxItems)){var e=$(t).appendTo(this.$tbody),i=e.find(".delete");this.settings.sortable&&this.sorter.addItems(e),this.$deleteBtns=this.$deleteBtns.add(i),this.addListener(i,"click","handleDeleteBtnClick"),this.totalItems++,this.updateUI()}},reorderItems:function(){var t=this;if(this.settings.sortable){for(var e=[],i=0;i=this.settings.maxItems?$(this.settings.newItemBtnSelector).addClass("hidden"):$(this.settings.newItemBtnSelector).removeClass("hidden"))}},{defaults:{tableSelector:null,noItemsSelector:null,newItemBtnSelector:null,idAttribute:"data-id",nameAttribute:"data-name",sortable:!1,allowDeleteAll:!0,minItems:0,maxItems:null,reorderAction:null,deleteAction:null,reorderSuccessMessage:Craft.t("app","New order saved."),reorderFailMessage:Craft.t("app","Couldn’t save new order."),confirmDeleteMessage:Craft.t("app","Are you sure you want to delete “{name}”?"),deleteSuccessMessage:Craft.t("app","“{name}” deleted."),deleteFailMessage:Craft.t("app","Couldn’t delete “{name}”."),onReorderItems:$.noop,onDeleteItem:$.noop}})},6872:function(){Craft.AssetImageEditor=Garnish.Modal.extend({$body:null,$footer:null,$imageTools:null,$buttons:null,$cancelBtn:null,$replaceBtn:null,$saveBtn:null,$focalPointBtn:null,$editorContainer:null,$straighten:null,$croppingCanvas:null,$spinner:null,$constraintContainer:null,$constraintRadioInputs:null,$customConstraints:null,canvas:null,image:null,viewport:null,focalPoint:null,grid:null,croppingCanvas:null,clipper:null,croppingRectangle:null,cropperHandles:null,cropperGrid:null,croppingShade:null,imageStraightenAngle:0,viewportRotation:0,originalWidth:0,originalHeight:0,imageVerticeCoords:null,zoomRatio:1,animationInProgress:!1,currentView:"",assetId:null,cacheBust:null,draggingCropper:!1,scalingCropper:!1,draggingFocal:!1,previousMouseX:0,previousMouseY:0,shiftKeyHeld:!1,editorHeight:0,editorWidth:0,cropperState:!1,scaleFactor:1,flipData:{},focalPointState:!1,maxImageSize:null,lastLoadedDimensions:null,imageIsLoading:!1,mouseMoveEvent:null,croppingConstraint:!1,constraintOrientation:"landscape",showingCustomConstraint:!1,saving:!1,renderImage:null,renderCropper:null,_queue:null,init:function(t,e){var i=this;this._queue=new Craft.Queue,this.cacheBust=Date.now(),this.setSettings(e,Craft.AssetImageEditor.defaults),null===this.settings.allowDegreeFractions&&(this.settings.allowDegreeFractions=Craft.isImagick),Garnish.prefersReducedMotion()&&(this.settings.animationDuration=1),this.assetId=t,this.flipData={x:0,y:0},this.$container=$('').appendTo(Garnish.$bod),this.$body=$('
').appendTo(this.$container),this.$footer=$('