Skip to content

Commit

Permalink
Merge pull request #15489 from craftcms/feature/pt-476-the-tag-field-…
Browse files Browse the repository at this point in the history
…does-not-have-an-appropriate-role-for-its

Tag field updates
  • Loading branch information
brandonkelly authored Aug 18, 2024
2 parents c4da76d + a36e819 commit c004169
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
- Element conditions can now have a “Not Related To” rule. ([#15496](https://github.com/craftcms/cms/pull/15496))
- Asset chips and cards no longer include the “Replace file” action. ([#15498](https://github.com/craftcms/cms/issues/15498))

### Accessibility
- Improved the accessibility of Tags fields.

### Development
- Added the `notRelatedTo` and `andNotRelatedTo` element query params. ([#15496](https://github.com/craftcms/cms/pull/15496))
- Added the `notRelatedTo` GraphQL element query argument. ([#15496](https://github.com/craftcms/cms/pull/15496))
Expand Down
1 change: 1 addition & 0 deletions src/fields/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ protected function inputHtml(mixed $value, ?ElementInterface $element, bool $inl
'elementType' => static::elementType(),
'id' => $this->getInputId(),
'describedBy' => $this->describedBy,
'labelId' => $this->getLabelId(),
'name' => $this->handle,
'elements' => $value,
'tagGroupId' => $tagGroup->id,
Expand Down
4 changes: 4 additions & 0 deletions src/templates/_components/fieldtypes/Tags/input.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
{% from "_includes/forms" import text %}

<div id="{{ id }}" class="elementselect tagselect">
<span class="visually-hidden" role="status"></span>

<ul class="elements">
{%- apply spaceless %}
{% for element in elements %}
Expand All @@ -27,6 +29,8 @@
width: 'auto',
placeholder: selectionLabel,
describedBy: describedBy ?? false,
role: 'combobox',
labelledBy: labelId ?? false,
}) }}
<div class="spinner hidden"></div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/templates/_includes/forms/text.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
]|filter) %}

{%- set orientation = orientation ?? inputAttributes.dir ?? craft.app.locale.getOrientation() %}
{%- set expanded = expanded ?? (role ?? false) == 'combobox' ? 'false' : false ?? false %}

{%- set inputAttributes = {
class: class,
Expand All @@ -29,9 +30,11 @@
min: min ?? false,
max: max ?? false,
dir: orientation,
role: role ?? false,
aria: {
labelledby: (inputAttributes.aria.label ?? false) ? false : (labelledBy ?? false),
describedby: describedBy ?? false,
expanded: expanded,
},
}|merge(inputAttributes ?? [], recursive=true) %}

Expand Down
1 change: 1 addition & 0 deletions src/templates/_layouts/base.twig
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
</head>
<body {{ attr(bodyAttributes) }}>
{{ beginBody() }}
{% include '_layouts/components/global-live-region' %}
{% block body %}{% endblock %}
{% include '_layouts/components/notifications' %}
{% block foot %}{% endblock %}
Expand Down
1 change: 1 addition & 0 deletions src/templates/_layouts/components/global-live-region.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="global-live-region" class="visually-hidden" role="status"></div>
2 changes: 2 additions & 0 deletions src/translations/en/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@
'Error' => 'Error',
'Error:' => 'Error:',
'Everything in {edition}, plus…' => 'Everything in {edition}, plus…',
'Existing {type}' => 'Existing {type}',
'Expand' => 'Expand',
'Expanded' => 'Expanded',
'Expired' => 'Expired',
Expand Down Expand Up @@ -892,6 +893,7 @@
'Loaded Project Config Data' => 'Loaded Project Config Data',
'Loading Plugin Store…' => 'Loading Plugin Store…',
'Loading' => 'Loading',
'Loading complete' => 'Loading complete',
'Local Folder' => 'Local Folder',
'Local copies of remote images, generated thumbnails' => 'Local copies of remote images, generated thumbnails',
'Local filesystems cannot be located above system directories.' => 'Local filesystems cannot be located above system directories.',
Expand Down
3 changes: 3 additions & 0 deletions src/web/assets/cp/CpAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ private function _registerTranslations(View $view): void
'Couldn’t reorder items.',
'Couldn’t save new order.',
'Create',
'Create {type}',
'Customize sources',
'Default Sort',
'Default Table Columns',
Expand Down Expand Up @@ -197,6 +198,7 @@ private function _registerTranslations(View $view): void
'Enter the name of the folder',
'Enter your password to log back in.',
'Error',
'Existing {type}',
'Export Type',
'Export',
'Export…',
Expand Down Expand Up @@ -234,6 +236,7 @@ private function _registerTranslations(View $view): void
'License transferred.',
'Limit',
'Loading',
'Loading complete',
'Make not required',
'Make optional',
'Make required',
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/css/cp.css.map

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/web/assets/cp/src/css/_main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7251,7 +7251,8 @@ $min2ColWidth: 400px;

.menu li {
& > a,
& > button {
& > button,
& > .menu-item {
&[data-icon]::before,
[data-icon]::before,
span.icon:not([data-icon]) {
Expand Down
25 changes: 24 additions & 1 deletion src/web/assets/cp/src/js/CP.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Craft.CP = Garnish.Base.extend(

$nav: null,
$navToggle: null,
$globalLiveRegion: null,
$globalSidebar: null,
$globalContainer: null,
$mainContainer: null,
Expand Down Expand Up @@ -77,6 +78,7 @@ Craft.CP = Garnish.Base.extend(
// Find all the key elements
this.$nav = $('#nav');
this.$navToggle = $('#primary-nav-toggle');
this.$globalLiveRegion = $('#global-live-region');
this.$globalSidebar = $('#global-sidebar');
this.$globalContainer = $('#global-container');
this.$mainContainer = $('#main-container');
Expand Down Expand Up @@ -922,7 +924,28 @@ Craft.CP = Garnish.Base.extend(
},

/**
* Dispays a notification.
* Updates the global live region with a screen reader announcement
*
* @param {string} message
*/
announce: function (message) {
if (!message) return;

this.$globalLiveRegion.empty().text(message);

// Clear message after interval
setTimeout(() => {
const currentMessage = this.$globalLiveRegion.text();

// Check that this is the same message and hasn't been updated since
if (message !== currentMessage) return;

this.$globalLiveRegion.empty();
}, 5000);
},

/**
* Displays a notification.
*
* @param {string} type `notice`, `success`, or `error`
* @param {string} message
Expand Down
71 changes: 62 additions & 9 deletions src/web/assets/cp/src/js/TagSelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(
let $nextOption = $hoverOption
.parent()
.nextAll()
.find('button:not(.disabled)')
.find('.menu-item:not(.disabled)')
.first();
if ($nextOption.length) {
this.focusOption($nextOption);
Expand All @@ -94,7 +94,7 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(
let $prevOption = $hoverOption
.parent()
.prevAll()
.find('button:not(.disabled)')
.find('.menu-item:not(.disabled)')
.last();
if ($prevOption.length) {
this.focusOption($prevOption);
Expand Down Expand Up @@ -132,12 +132,21 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(
});
},

get fieldName() {
const $legend = this.$container.closest('fieldset').find('legend')[0];
return $legend.innerText;
},

focusOption: function ($option) {
this.searchMenu.$options.removeClass('hover');
this.searchMenu.$ariaOptions.attr('aria-selected', 'false');

const activeDescendant = $option.parent('li').attr('id');

$option.addClass('hover');
this.searchMenu.$menuList.attr(
this.$addTagInput.attr(
'aria-activedescendant',
$option.attr('id')
$option.parent('li').attr('id')
);
},

Expand All @@ -162,6 +171,7 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(

if (val) {
this.$spinner.removeClass('hidden');
Craft.cp.announce(Craft.t('app', 'Loading'));

var excludeIds = [];

Expand Down Expand Up @@ -193,15 +203,23 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(
this.killSearchMenu();
}
this.$spinner.addClass('hidden');
var $menu = $('<div class="menu tagmenu"/>').appendTo(Garnish.$bod),
Craft.cp.announce(Craft.t('app', 'Loading complete'));
var $menu = $('<div class="menu tagmenu"/>')
.attr('aria-label', this.fieldName)
.appendTo(Garnish.$bod),
$ul = $('<ul/>').appendTo($menu);

var $li;
let optionLabel;

for (var i = 0; i < response.data.tags.length; i++) {
$li = $('<li/>').appendTo($ul);
optionLabel = `${Craft.t('app', 'Existing {type}', {
type: Craft.t('app', 'Tag'),
})}: ${response.data.tags[i].title}`;
$li.attr('aria-label', optionLabel);

$('<button class="menu-item" data-icon="tag"/>')
$('<div class="menu-item" data-icon="tag"/>')
.appendTo($li)
.text(response.data.tags[i].title)
.data('id', response.data.tags[i].id)
Expand All @@ -210,18 +228,36 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(

if (!response.data.exactMatch) {
$li = $('<li/>').appendTo($ul);
$('<button class="menu-item" data-icon="plus"/>')
optionLabel = `${Craft.t('app', 'Create {type}', {
type: Craft.t('app', 'Tag'),
})}: ${data.search}`;
$li.attr('aria-label', optionLabel);

$('<div class="menu-item" data-icon="plus"/>')
.appendTo($li)
.text(data.search);
}

$ul.find('button:not(.disabled):first').addClass('hover');
$ul.find('.menu-item:not(.disabled):first').addClass('hover');

this.searchMenu = new Garnish.Menu($menu, {
attachToElement: this.$addTagInput,
anchor: this.$addTagInput,
onOptionSelect: this.selectTag.bind(this),
});

// Add required ARIA attributes
this.$addTagInput.attr('aria-controls', this.searchMenu.menuId);

this.searchMenu.on('show', () => {
this.$addTagInput.attr('aria-expanded', 'true');
this.focusSelectedOption();
});

this.searchMenu.on('hide', () => {
this.$addTagInput.attr('aria-expanded', 'false');
this.$addTagInput.removeAttr('aria-activedescendant');
});

this.addListener($menu, 'mousedown', () => {
this._ignoreBlur = true;
});
Expand All @@ -235,12 +271,29 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(
}

this.$spinner.addClass('hidden');
Craft.cp.announce(Craft.t('app', 'Loading complete'));
});
} else {
// No need to update the live region here
this.$spinner.addClass('hidden');
}
},

focusSelectedOption: function () {
let $option = this.searchMenu.$options.filter('.hover:first');

if ($option.length) {
this.focusOption($option);
} else {
this.focusFirstOption();
}
},

focusFirstOption: function () {
const $option = this.searchMenu.$options.first();
this.focusOption($option);
},

selectTag: function (option) {
var $option = $(option);

Expand Down

0 comments on commit c004169

Please sign in to comment.