Update chooser widgets to use new dropdown component

pull/10557/head
Thibaud Colas 2023-06-15 16:45:21 +01:00
rodzic d288b2f81e
commit 1f15f52a6d
10 zmienionych plików z 103 dodań i 141 usunięć

Wyświetl plik

@ -1,8 +1,4 @@
.chooser {
// Add space to the end for hoverable area to extend outside of the field without impacting layout
padding-inline-end: theme('spacing.8');
margin-inline-end: calc(0 - theme('spacing.8'));
&.blank .chosen {
display: none;
}
@ -68,29 +64,6 @@
@apply w-body-text-large;
}
.chooser__actions {
display: flex;
gap: theme('spacing.[1.5]');
.button {
// Subdued border as there can be a lot of chooser action buttons on a page.
border-color: theme('colors.border-button-small-outline-default');
}
// Hiding for devices capable of hover states only,
// with opacity only so keyboard focus can move to the first interactive element to reveal it.
@media (hover: hover) {
opacity: 0;
// Ensure the hovering over the comment icon will still show chooser actions.
// Uses a form-field class as well as widget classes, as all core and custom choosers use different widgets.
:is(.w-field--model_choice_field, .w-field--admin_task_chooser, .w-field--admin_page_chooser, .w-field--document_chooser_widget, .w-field--admin_image_chooser, .w-field--admin_snippet_chooser):is(:hover, :focus-within)
& {
opacity: 1;
}
}
}
.chooser__image {
max-width: 200px;
max-height: 140px;
@ -106,5 +79,4 @@
.w-field--admin_image_chooser,
.w-field--admin_snippet_chooser {
display: inline-block;
padding-inline-end: 0;
}

Wyświetl plik

@ -10,12 +10,16 @@ export class Chooser {
this.initHTMLElements(id);
this.state = this.getStateFromHTML();
for (const btn of this.chooserElement.querySelectorAll('.action-choose')) {
for (const btn of this.chooserElement.querySelectorAll(
'[data-chooser-action-choose]',
)) {
btn.addEventListener('click', () => {
this.openChooserModal();
});
}
for (const btn of this.chooserElement.querySelectorAll('.action-clear')) {
for (const btn of this.chooserElement.querySelectorAll(
'[data-chooser-action-clear]',
)) {
btn.addEventListener('click', () => {
this.clear();
});
@ -31,7 +35,9 @@ export class Chooser {
'[data-chooser-title]',
);
this.input = document.getElementById(id);
this.editLink = this.chooserElement.querySelector('.edit-link');
this.editLink = this.chooserElement.querySelector(
'[data-chooser-edit-link]',
);
}
getStateFromHTML() {
@ -100,9 +106,9 @@ export class Chooser {
const editUrl = newState[this.editUrlStateKey];
if (editUrl) {
this.editLink.setAttribute('href', editUrl);
this.editLink.classList.remove('w-hidden');
this.editLink.hidden = false;
} else {
this.editLink.classList.add('w-hidden');
this.editLink.hidden = true;
}
}
}
@ -117,11 +123,7 @@ export class Chooser {
}
focus() {
if (this.state) {
this.chooserElement.querySelector('.chosen .action-choose').focus();
} else {
this.chooserElement.querySelector('.unchosen .action-choose').focus();
}
this.chooserElement.querySelector('button').focus();
}
getModalOptions() {

Wyświetl plik

@ -3,24 +3,25 @@
exports[`telepath: wagtail.widgets.PageChooser it renders correctly 1`] = `
"<div id=\\"the-id-chooser\\" class=\\"chooser page-chooser\\" data-chooser-url=\\"/admin/choose-page/\\">
<div class=\\"chosen\\">
<div class=\\"chooser__preview\\" role=\\"presentation\\"></div>
<div class=\\"chooser__title\\" data-chooser-title=\\"\\" id=\\"the-id-title\\">Welcome to the Wagtail Bakery!</div>
<ul class=\\"chooser__actions\\">
<li>
<button type=\\"button\\" class=\\"button action-choose button-small button-secondary\\" aria-describedby=\\"the-id-title\\">
<div class=\\"chooser__preview\\" role=\\"presentation\\"></div>
<div class=\\"chooser__title\\" data-chooser-title=\\"\\" id=\\"the-id-title\\">Welcome to the Wagtail Bakery!</div>
<div data-controller=\\"w-dropdown\\">
<button type=\\"button\\" class=\\"w-dropdown__toggle\\" data-w-dropdown-target=\\"toggle\\" aria-label=\\"Actions\\" aria-describedby=\\"the-id-title\\">
dots-horizontal
</button>
<div class=\\"w-dropdown__content\\" data-w-dropdown-target=\\"content\\" hidden=\\"\\">
<button type=\\"button\\" data-chooser-action-choose=\\"\\" aria-describedby=\\"the-id-title\\">
Choose another page
</button>
</li>
<li>
<a href=\\"/admin/pages/60/edit/\\" class=\\"edit-link button button-small button-secondary\\" target=\\"_blank\\" rel=\\"noreferrer\\" aria-describedby=\\"the-id-title\\">
<a href=\\"/admin/pages/60/edit/\\" data-chooser-edit-link=\\"\\" target=\\"_blank\\" rel=\\"noreferrer\\" aria-describedby=\\"the-id-title\\">
Edit this page
</a>
</li>
</ul>
</div>
</div>
</div>
<div class=\\"unchosen\\">
<button type=\\"button\\" class=\\"button action-choose button-small button-secondary chooser__choose-button\\">
<svg class=\\"icon icon-doc-empty-inverse\\" aria-hidden=\\"true\\"><use href=\\"#icon-doc-empty-inverse\\"></use></svg>Choose a page
<button type=\\"button\\" data-chooser-action-choose=\\"\\" class=\\"button button-small button-secondary chooser__choose-button\\">
Choose a page
</button>
</div>
</div>
@ -28,55 +29,29 @@ exports[`telepath: wagtail.widgets.PageChooser it renders correctly 1`] = `
`;
exports[`telepath: wagtail.widgets.PageChooser setState() changes the current page 1`] = `
"<div id=\\"the-id-chooser\\" class=\\"chooser page-chooser\\" data-chooser-url=\\"/admin/choose-page/\\">
<div class=\\"chosen\\">
<div class=\\"chooser__preview\\" role=\\"presentation\\"></div>
<div class=\\"chooser__title\\" data-chooser-title=\\"\\" id=\\"the-id-title\\">Anadama</div>
<ul class=\\"chooser__actions\\">
<li>
<button type=\\"button\\" class=\\"button action-choose button-small button-secondary\\" aria-describedby=\\"the-id-title\\">
Choose another page
</button>
</li>
<li>
<a href=\\"/admin/pages/34/edit/\\" class=\\"edit-link button button-small button-secondary\\" target=\\"_blank\\" rel=\\"noreferrer\\" aria-describedby=\\"the-id-title\\">
<a
aria-describedby="the-id-title"
data-chooser-edit-link=""
href="/admin/pages/34/edit/"
rel="noreferrer"
target="_blank"
>
Edit this page
</a>
</li>
</ul>
</div>
<div class=\\"unchosen\\">
<button type=\\"button\\" class=\\"button action-choose button-small button-secondary chooser__choose-button\\">
<svg class=\\"icon icon-doc-empty-inverse\\" aria-hidden=\\"true\\"><use href=\\"#icon-doc-empty-inverse\\"></use></svg>Choose a page
</button>
</div>
</div>
<input type=\\"hidden\\" name=\\"the-name\\" id=\\"the-id\\" value=\\"34\\">"
</a>
`;
exports[`telepath: wagtail.widgets.PageChooser setState() to null clears the fields 1`] = `
"<div id=\\"the-id-chooser\\" class=\\"chooser page-chooser blank\\" data-chooser-url=\\"/admin/choose-page/\\">
<div class=\\"chosen\\">
<div class=\\"chooser__preview\\" role=\\"presentation\\"></div>
<div class=\\"chooser__title\\" data-chooser-title=\\"\\" id=\\"the-id-title\\">Welcome to the Wagtail Bakery!</div>
<ul class=\\"chooser__actions\\">
<li>
<button type=\\"button\\" class=\\"button action-choose button-small button-secondary\\" aria-describedby=\\"the-id-title\\">
Choose another page
</button>
</li>
<li>
<a href=\\"/admin/pages/60/edit/\\" class=\\"edit-link button button-small button-secondary\\" target=\\"_blank\\" rel=\\"noreferrer\\" aria-describedby=\\"the-id-title\\">
<a
aria-describedby="the-id-title"
data-chooser-edit-link=""
href="/admin/pages/60/edit/"
rel="noreferrer"
target="_blank"
>
Edit this page
</a>
</li>
</ul>
</div>
<div class=\\"unchosen\\">
<button type=\\"button\\" class=\\"button action-choose button-small button-secondary chooser__choose-button\\">
<svg class=\\"icon icon-doc-empty-inverse\\" aria-hidden=\\"true\\"><use href=\\"#icon-doc-empty-inverse\\"></use></svg>Choose a page
</button>
</div>
</div>
<input type=\\"hidden\\" name=\\"the-name\\" id=\\"the-id\\" value=\\"\\">"
</a>
`;

Wyświetl plik

@ -16,7 +16,7 @@ $.get = jest.fn().mockImplementation((url, data, cb) => {
describe('modal-workflow', () => {
beforeEach(() => {
document.body.innerHTML =
'<div id="content"><button class="button action-choose" id="trigger">Open</button></div>';
'<div id="content"><button data-chooser-action-choose id="trigger">Open</button></div>';
});
it('exposes module as global', () => {

Wyświetl plik

@ -17,7 +17,7 @@ const PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = {
wagtail.ui.initDropDowns();
/* Set up dropdown links to open in the modal */
// eslint-disable-next-line func-names
$('.c-dropdown__item .u-link', modal.body).on('click', function () {
$('[data-locale-selector-link]', modal.body).on('click', function () {
modal.loadUrl(this.href);
return false;
});
@ -133,7 +133,7 @@ const PAGE_CHOOSER_MODAL_ONLOAD_HANDLERS = {
return false;
});
// eslint-disable-next-line func-names
$('.c-dropdown__item .u-link', modal.body).on('click', function () {
$('[data-locale-selector-link]', modal.body).on('click', function () {
modal.loadUrl(this.href);
return false;
});

Wyświetl plik

@ -8,37 +8,43 @@ describe('telepath: wagtail.widgets.PageChooser', () => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
// Unpack and render a radio select widget
const widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.PageChooser',
// Copy of wagtailadmin/widgets/chooser.html. Make sure to update when making changes to the template.
// Copy of wagtailadmin/widgets/chooser.html without verbose markup. Make sure to update when making changes to the template.
_args: [
`<div id="__ID__-chooser" class="chooser page-chooser blank" data-chooser-url="/admin/choose-page/">
<div class="chosen">
<div class="chooser__preview" role="presentation"></div>
<div class="chooser__title" data-chooser-title id="__ID__-title"></div>
<ul class="chooser__actions">
<li>
<button type="button" class="button action-choose button-small button-secondary" aria-describedby="__ID__-title">
<div class="chooser__preview" role="presentation"></div>
<div class="chooser__title" data-chooser-title id="__ID__-title"></div>
<div data-controller="w-dropdown">
<button
type="button"
class="w-dropdown__toggle"
data-w-dropdown-target="toggle"
aria-label="Actions"
aria-describedby="__ID__-title"
>
dots-horizontal
</button>
<div class="w-dropdown__content" data-w-dropdown-target="content" hidden>
<button type="button" data-chooser-action-choose aria-describedby="__ID__-title">
Choose another page
</button>
</li>
<li>
<a
href=""
class="edit-link button button-small button-secondary"
data-chooser-edit-link
target="_blank"
rel="noreferrer"
aria-describedby="__ID__-title"
>
Edit this page
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="unchosen">
<button type="button" class="button action-choose button-small button-secondary chooser__choose-button">
<svg class="icon icon-doc-empty-inverse" aria-hidden="true"><use href="#icon-doc-empty-inverse"></use></svg>Choose a page
<button type="button" data-chooser-action-choose class="button button-small button-secondary chooser__choose-button">
Choose a page
</button>
</div>
</div>
@ -90,29 +96,33 @@ describe('telepath: wagtail.widgets.PageChooser', () => {
adminTitle: 'Anadama',
editUrl: '/admin/pages/34/edit/',
});
expect(document.body.innerHTML).toMatchSnapshot();
expect(
document.querySelector('[data-chooser-edit-link]'),
).toMatchSnapshot();
expect(document.querySelector('input').value).toBe('34');
});
test('setState() to null clears the fields', () => {
boundWidget.setState(null);
expect(document.body.innerHTML).toMatchSnapshot();
expect(
document.querySelector('[data-chooser-edit-link]'),
).toMatchSnapshot();
expect(document.querySelector('input').value).toBe('');
});
test('focus() focuses the choose-another-page button when widget is populated', () => {
test('focus() focuses the toggle button when widget is populated', () => {
boundWidget.focus();
expect(document.activeElement).toBe(
document.querySelector('.chosen button'),
document.querySelector('.chooser button'),
);
});
test('focus() focuses the choose-a-page button when widget is blank', () => {
test('focus() focuses the toggle button when widget is blank', () => {
boundWidget.setState(null);
boundWidget.focus();
expect(document.activeElement).toBe(
document.querySelector('.unchosen button'),
document.querySelector('.chooser button'),
);
});
});

Wyświetl plik

@ -4,9 +4,9 @@ function createTaskChooser(id) {
const chooserElement = $('#' + id + '-chooser');
const taskName = chooserElement.find('[data-chooser-title]');
const input = $('#' + id);
const editAction = chooserElement.find('.edit-link');
const editAction = chooserElement.find('[data-chooser-edit-link]');
$('.action-choose', chooserElement).on('click', () => {
$('[data-chooser-action-choose]', chooserElement).on('click', () => {
// eslint-disable-next-line no-undef
ModalWorkflow({
url: chooserElement.data('chooserUrl'),

Wyświetl plik

@ -21,7 +21,6 @@ describe('telepath: wagtail.widgets.Widget', () => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
// Unpack and render a simple text block widget
const widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.Widget',
_args: [
@ -70,7 +69,6 @@ describe('telepath: wagtail.widgets.RadioSelect', () => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
// Unpack and render a radio select widget
const widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.RadioSelect',
_args: [
@ -132,7 +130,6 @@ describe('telepath: wagtail.widgets.CheckboxInput', () => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
// Unpack and render a radio select widget
const widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.CheckboxInput',
_args: ['<input type="checkbox" name="__NAME__" id="__ID__">', '__ID__'],
@ -181,7 +178,6 @@ describe('telepath: wagtail.widgets.Select', () => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
// Unpack and render a radio select widget
const widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.Select',
_args: [
@ -246,7 +242,6 @@ describe('telepath: wagtail.widgets.DraftailRichTextArea', () => {
// Create a placeholder to render the widget
document.body.innerHTML = '<div id="placeholder"></div>';
// Unpack and render a Draftail input
const widgetDef = window.telepath.unpack({
_type: 'wagtail.widgets.DraftailRichTextArea',
_args: [

Wyświetl plik

@ -3,9 +3,6 @@
{% comment %}
Either the chosen or unchosen div will be shown, depending on the presence
of the 'blank' class on the container.
Any element with the 'action-choose' class will open the page chooser modal
when clicked.
{% endcomment %}
{% fragment as title_id %}{{ attrs.id }}-title{% endfragment %}
<div id="{{ attrs.id }}-chooser" class="chooser {% block chooser_class %}{% if classname %}{{ classname }}{% endif %}{% endblock %} {% if not value %}blank{% endif %}" {% block chooser_attributes %}{% if chooser_url %}data-chooser-url="{{ chooser_url }}"{% endif %}{% endblock %}>
@ -13,30 +10,41 @@
<div class="chosen">
{% block chosen_icon %}
<div class="chooser__preview" role="presentation">
{% if icon %}{% icon name=icon %}{% endif %}
{% if icon %}{% icon name=icon classname="default" %}{% endif %}
</div>
{% endblock chosen_icon %}
{% block chosen_state_view %}
<div class="chooser__title" data-chooser-title id="{{ title_id }}">{{ display_title }}</div>
{% endblock %}
<ul class="chooser__actions">
<li><button type="button" class="button action-choose button-small button-secondary" aria-describedby="{{ title_id }}">{{ widget.choose_another_text }}</button></li>
{% trans "Actions" as toggle_label %}
{% dropdown toggle_icon="dots-horizontal" toggle_aria_label=toggle_label toggle_describedby=title_id %}
<button type="button" data-chooser-action-choose aria-describedby="{{ title_id }}">
{% icon name="resubmit" %}
{{ widget.choose_another_text }}
</button>
{% if widget.show_edit_link %}
<li>
{% block edit_link %}
<a href="{% block edit_chosen_item_url %}{{ edit_url }}{% endblock %}" aria-describedby="{{ title_id }}" class="edit-link button button-small button-secondary{% if not edit_url %} w-hidden{% endif %}" target="_blank" rel="noreferrer">{{ widget.link_to_chosen_text }}</a>
{% endblock %}
</li>
{% block edit_link %}
<a data-chooser-edit-link href="{% block edit_chosen_item_url %}{{ edit_url }}{% endblock %}" aria-describedby="{{ title_id }}" {% if not edit_url %}hidden{% endif %} target="_blank" rel="noreferrer">
{% icon name="edit" %}
{{ widget.link_to_chosen_text }}
</a>
{% endblock %}
{% endif %}
{% if not widget.is_required and widget.show_clear_link %}
<li><button type="button" class="button action-clear button-small button-secondary" aria-describedby="{{ title_id }}">{{ widget.clear_choice_text }}</button></li>
<button type="button" data-chooser-action-clear aria-describedby="{{ title_id }}">
{% icon name="bin" %}
{{ widget.clear_choice_text }}
</button>
{% endif %}
</ul>
{% enddropdown %}
</div>
<div class="unchosen">
<button type="button" class="button action-choose button-small button-secondary chooser__choose-button">{% block unchosen_icon %}{% icon name=icon|default:"plus-inverse" %}{% endblock unchosen_icon %}{{ widget.choose_one_text }}</button>
<button type="button" data-chooser-action-choose class="button button-small button-secondary chooser__choose-button">
{% block unchosen_icon %}{% icon name=icon|default:"plus-inverse" %}{% endblock unchosen_icon %}
{{ widget.choose_one_text }}
</button>
</div>
</div>

Wyświetl plik

@ -1100,7 +1100,7 @@ class TestPageChooserPanel(TestCase):
result,
)
self.assertIn(
'<a href="/admin/pages/%d/edit/" aria-describedby="id_page-title" class="edit-link button button-small button-secondary" target="_blank" rel="noreferrer">Edit this page</a>'
'<a data-chooser-edit-link href="/admin/pages/%d/edit/" aria-describedby="id_page-title"'
% self.christmas_page.id,
result,
)