Skip to content
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

Fix a11y for repo topic management #23346

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
902be92
fix
wxiaoguang Mar 7, 2023
a42186e
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 7, 2023
c379fc5
add aria attributes to delete icon
wxiaoguang Mar 7, 2023
5dd48bb
make "delete icon button" slightly larger to be easier to click on mo…
wxiaoguang Mar 7, 2023
25f993b
fix lint
wxiaoguang Mar 7, 2023
fa2fa02
use exactly the same markup for labels
wxiaoguang Mar 7, 2023
77ee1f1
add more comments
wxiaoguang Mar 7, 2023
5e9aa1e
use svg, fine tune styles
wxiaoguang Mar 7, 2023
93a2d40
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 8, 2023
b3b89a6
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 8, 2023
a1e35b4
title: Remove topic "%s"
wxiaoguang Mar 8, 2023
02c65fe
fix aria-label
wxiaoguang Mar 8, 2023
2754740
use role=combobox for dropdown with input
wxiaoguang Mar 8, 2023
2166ad1
fix chrome crash
wxiaoguang Mar 8, 2023
5688480
only change the primary role if there is no role set
wxiaoguang Mar 8, 2023
af6d734
fix comments
wxiaoguang Mar 8, 2023
46be3c7
copy dropdown templates to a new object to avoid conflicting
wxiaoguang Mar 8, 2023
929c3aa
fix incorrect language menu behavior
wxiaoguang Mar 9, 2023
79bc9d8
capture blur event
wxiaoguang Mar 9, 2023
a6baa18
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 9, 2023
81c9a99
fix menu for focus/click on mobile
wxiaoguang Mar 9, 2023
41fbd31
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 11, 2023
da39792
clarify the combobox/menu by w3.org
wxiaoguang Mar 13, 2023
47b7811
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 13, 2023
c208669
fix incorrect dropdown init and fix incorrect visible detection
wxiaoguang Mar 13, 2023
e912d35
fix tippy bug
wxiaoguang Mar 13, 2023
1fbff84
always use combobox
wxiaoguang Mar 13, 2023
3b8f8bc
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 13, 2023
b17a767
fix one more visible check
wxiaoguang Mar 13, 2023
d0ac1f5
revert extracted changes
wxiaoguang Mar 15, 2023
81d6148
Merge branch 'main' into fix-repo-topic-edit
wxiaoguang Mar 15, 2023
e722799
fix merge
wxiaoguang Mar 15, 2023
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
1 change: 0 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2354,7 +2354,6 @@ tag.create_tag_from = Create new tag from '%s'
tag.create_success = Tag '%s' has been created.

topic.manage_topics = Manage Topics
topic.done = Done
topic.count_prompt = You cannot select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Expand Down
39 changes: 19 additions & 20 deletions templates/repo/home.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,33 @@
</div>
{{end}}
</div>
<div class="gt-mt-3" id="repo-topics">
{{range .Topics}}<a class="ui repo-topic large label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<a id="manage_topic" class="muted">{{.locale.Tr "repo.topic.manage_topics"}}</a>{{end}}
<div class="gt-df gt-ac gt-fw gt-mt-3" id="repo-topics">
{{range .Topics}}<a class="ui repo-topic large label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="ui button tiny tertiary gt-ml-2">{{.locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
silverwind marked this conversation as resolved.
Show resolved Hide resolved
</div>
{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
<div class="ui repo-topic-edit grid form gt-hidden" id="topic_edit">
<div class="fourteen wide column">
<div class="field">
<div class="ui fluid multiple search selection dropdown">
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
{{range .Topics}}
<div class="ui small label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important; cursor: default;">{{.Name}}{{svg "octicon-x" 16 "delete icon gt-ml-3 gt-mt-1"}}</div>
{{end}}
<div class="text"></div>
</div>
<div class="ui form gt-hidden gt-df gt-mt-4" id="topic_edit">
<div class="field gt-f1 gt-mr-3">
<div class="ui fluid multiple search selection dropdown"
data-text-count-prompt="{{.locale.Tr "repo.topic.count_prompt"}}"
data-text-format-prompt="{{.locale.Tr "repo.topic.format_prompt"}}"
data-text-remove="{{.locale.Tr "remove"}}"
>
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
{{range .Topics}}
{{/* keep the same as template (repo-topic-label), the style "display" is also added by Fomantic Dropdown automatically when generating new labels */}}
<div class="ui small label topic transition visible gt-cursor-default" data-value="{{.Name}}" style="display: inline-block !important;">{{.Name}}{{svg "octicon-x" 16 "delete icon gt-ml-3 gt-mt-1"}}</div>
{{end}}
<div class="text"></div>
</div>
</div>
<div class="two wide column">
<a class="ui button primary" role="button" tabindex="0" id="save_topic"
data-link="{{.RepoLink}}/topics">{{.locale.Tr "repo.topic.done"}}</a>
<div>
<button class="ui basic button secondary" id="cancel_topic_edit">{{.locale.Tr "cancel"}}</button>
<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{.locale.Tr "save"}}</button>
</div>
</div>
{{end}}
<div class="gt-hidden" id="validate_prompt">
<span id="count_prompt">{{.locale.Tr "repo.topic.count_prompt"}}</span>
<span id="format_prompt">{{.locale.Tr "repo.topic.format_prompt"}}</span>
</div>
{{if .Repository.IsArchived}}
<div class="ui warning message">
{{.locale.Tr "repo.archive.title"}}
Expand Down
7 changes: 4 additions & 3 deletions web_src/js/features/aria.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ function generateAriaId() {
return `_aria_auto_id_${ariaIdCounter++}`;
}

// make the item has role=option, and add an id if there wasn't one yet.
// make the item has role=menuitem/option, and add an id if there wasn't one yet.
function prepareMenuItem($item) {
if (!$item.attr('id')) $item.attr('id', generateAriaId());
$item.attr({'role': 'menuitem', 'tabindex': '-1'});
$item.find('a').attr('tabindex', '-1'); // as above, the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element.
}

// when the menu items are loaded from AJAX requests, the items are created dynamically
// when the dropdown menu items are loaded from AJAX requests, the items are created dynamically
const defaultCreateDynamicMenu = $.fn.dropdown.settings.templates.menu;
$.fn.dropdown.settings.templates.menu = function(response, fields, preserveHTML, className) {
const ret = defaultCreateDynamicMenu(response, fields, preserveHTML, className);
Expand All @@ -33,7 +33,7 @@ function attachOneDropdownAria($dropdown) {
const $focusable = $textSearch.length ? $textSearch : $dropdown; // see comment below
if (!$focusable.length) return;

// prepare menu list
// prepare dropdown menu list
const $menu = $dropdown.find('> .menu');
if (!$menu.attr('id')) $menu.attr('id', generateAriaId());

Expand All @@ -50,6 +50,7 @@ function attachOneDropdownAria($dropdown) {
// - otherwise, the dropdown control (low-level code) handles the Enter event, hides the dropdown menu

// TODO: multiple selection is not supported yet.
// TODO: use combobox for dropdown with search input in the future

$focusable.attr({
'role': 'menu',
Expand Down
62 changes: 38 additions & 24 deletions web_src/js/features/repo-home.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
import $ from 'jquery';
import {stripTags} from '../utils.js';
import {hideElem, showElem} from '../utils/dom.js';
import {htmlEscape} from 'escape-goat';
import {svg} from '../svg.js';

const {appSubUrl, csrfToken} = window.config;

export function initRepoTopicBar() {
const mgrBtn = $('#manage_topic');
const editDiv = $('#topic_edit');
const viewDiv = $('#repo-topics');
if (!mgrBtn.length) return;

const saveBtn = $('#save_topic');
const topicListDiv = $('#repo-topics');
const topicDropdown = $('#topic_edit .dropdown');
const topicForm = $('#topic_edit.ui.form');
const topicPrompts = getPrompts();
const topicDropdownSearch = topicDropdown.find('input.search');
const topicForm = $('#topic_edit');
const topicPrompts = {
countPrompt: topicDropdown.attr('data-text-count-prompt'),
formatPrompt: topicDropdown.attr('data-text-format-prompt'),
remove: topicDropdown.attr('data-text-remove'),
};

function addLabelDeleteIconAria($el) {
$el.removeAttr('aria-hidden').attr({
'aria-label': topicPrompts.remove,
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
'role': 'button',
});
}

mgrBtn.on('click', () => {
hideElem(viewDiv);
showElem(editDiv);
hideElem(topicListDiv);
showElem(topicForm);
addLabelDeleteIconAria(topicDropdown.find('.delete.icon'));
topicDropdownSearch.focus();
});

function getPrompts() {
const hidePrompt = $('#validate_prompt');
const prompts = {
countPrompt: hidePrompt.children('#count_prompt').text(),
formatPrompt: hidePrompt.children('#format_prompt').text()
};
hidePrompt.remove();
return prompts;
}
$('#cancel_topic_edit').on('click', () => {
hideElem(topicForm);
showElem(topicListDiv);
mgrBtn.focus();
});

saveBtn.on('click', () => {
const topics = $('input[name=topics]').val();
Expand All @@ -36,20 +49,18 @@ export function initRepoTopicBar() {
topics
}, (_data, _textStatus, xhr) => {
if (xhr.responseJSON.status === 'ok') {
viewDiv.children('.topic').remove();
topicListDiv.children('.topic').remove();
if (topics.length) {
const topicArray = topics.split(',');

const last = viewDiv.children('a').last();
for (let i = 0; i < topicArray.length; i++) {
const link = $('<a class="ui repo-topic large label topic"></a>');
link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
link.text(topicArray[i]);
link.insertBefore(last);
link.insertBefore(mgrBtn); // insert all new topics before manage button
}
}
hideElem(editDiv);
showElem(viewDiv);
hideElem(topicForm);
showElem(topicListDiv);
}
}).fail((xhr) => {
if (xhr.status === 422) {
Expand Down Expand Up @@ -101,7 +112,7 @@ export function initRepoTopicBar() {
const query = stripTags(this.urlData.query.trim());
let found_query = false;
const current_topics = [];
topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, el) => {
topicDropdown.find('div.label.visible.topic').each((_, el) => {
current_topics.push(el.getAttribute('data-value'));
});

Expand Down Expand Up @@ -140,8 +151,11 @@ export function initRepoTopicBar() {
},
onLabelCreate(value) {
value = value.toLowerCase().trim();
this.attr('data-value', value).contents().first().replaceWith(value);
return $(this);
// `this` is the default label jQuery element, it's "<a class="ui small label">"
// we create a new div element to replace it, to keep the same as template (repo-topic-label), because we do not want the `<a>` tag which affects aria focus.
const $el = $(`<div class="ui small label topic gt-cursor-default" data-value="${htmlEscape(value)}">${htmlEscape(value)}${svg('octicon-x', 16, 'delete icon gt-ml-3 gt-mt-1')}</div>`);
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
addLabelDeleteIconAria($el.find('.delete.icon'));
return $el;
},
onAdd(addedValue, _addedText, $addedChoice) {
addedValue = addedValue.toLowerCase().trim();
Expand Down
4 changes: 4 additions & 0 deletions web_src/less/_base.less
Original file line number Diff line number Diff line change
Expand Up @@ -2329,6 +2329,10 @@ a.ui.label:hover {
color: var(--color-text);
}

.ui.tertiary.button:focus {
color: var(--color-text-dark);
}

.ui.primary.label,
.ui.primary.labels .label {
background-color: var(--color-primary) !important;
Expand Down
29 changes: 3 additions & 26 deletions web_src/less/_repository.less
Original file line number Diff line number Diff line change
Expand Up @@ -3010,21 +3010,10 @@ tbody.commit-list {
top: -2px;
}

#topic_edit {
margin-top: 5px;
}

#repo-topics {
margin-top: 5px;
display: flex;
align-items: center;
flex-wrap: wrap;
}

.repo-topic {
font-weight: normal !important;
#repo-topics .repo-topic {
font-weight: normal;
cursor: pointer;
margin: 2px !important;
margin: 2px;
}

#new-dependency-drop-list {
Expand All @@ -3042,18 +3031,6 @@ tbody.commit-list {
}
}

#manage_topic {
font-size: 12px;
}

.label + #manage_topic {
margin-left: 5px;
}

.ui.small.label.topic {
margin-bottom: 4px;
}

.repo-header {
display: flex;
align-items: center;
Expand Down
1 change: 1 addition & 0 deletions web_src/less/helpers.less
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
/* below class names match Tailwind CSS */
.gt-pointer-events-none { pointer-events: none !important; }
.gt-relative { position: relative !important; }
.gt-cursor-default { cursor: default !important; }

.gt-mono {
font-family: var(--fonts-monospace) !important;
Expand Down