Skip to content

Commit c305c0e

Browse files
authored
Merge pull request #18067 from craftcms/bugfix/18052-drag-sort-and-touch-capable-devices
Bugfix/18052 drag sort and devices without mouse pointer events
2 parents dddf438 + c6bbe3b commit c305c0e

37 files changed

+441
-188
lines changed

CHANGELOG-WIP.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
### Accessibility
1919
- Improved the accessibility of the Orientation setting within the Image Editor’s crop tool. ([#17690](https://github.com/craftcms/cms/pull/17690))
2020
- The Image Editor’s focal point tool is now keyboard accessible. ([#17880](https://github.com/craftcms/cms/pull/17880))
21+
- All sortable checkbox select options, selected Dashboard widgets, and site listings now have keyboard-accessible “Move up” and “Move down” action items. ([#18067](https://github.com/craftcms/cms/pull/18067))
2122

2223
### Administration
2324
- It’s now possible to divide entry sources into multiple index pages, via the Customize Sources modal. ([#17779](https://github.com/craftcms/cms/pull/17779))
25+
- The Customize Sources modal now supports mobile devices. ([#18067](https://github.com/craftcms/cms/pull/18067))
2426
- Added the “UI Label Format” entry type setting. ([#18044](https://github.com/craftcms/cms/pull/18044))
2527
- Added the “View user” GraphQL schema option for Craft Solo. ([#17863](https://github.com/craftcms/cms/pull/17863))
2628
- Users’ User Groups settings now show a component select input, and support inline group editing/creation on environments that allow administrative changes.
@@ -40,6 +42,7 @@
4042
- Added the `useIdnaNontransitionalToUnicode` config setting. ([#17946](https://github.com/craftcms/cms/pull/17946))
4143
- The `maxCachedCloudImageSize` config setting is now set to `0` by default. ([#17997](https://github.com/craftcms/cms/pull/17997))
4244
- System message emails are now rendered using GitHub-flavored Markdown. ([#18058](https://github.com/craftcms/cms/discussions/18058))
45+
- Drag-and-drop icons are now longer shown for devices that don’t support pointer events. ([#18067](https://github.com/craftcms/cms/pull/18067))
4346

4447
### Development
4548
- Reference tags now support fallback values when no attribute is specified. ([#17688](https://github.com/craftcms/cms/pull/17688))

src/templates/_components/fieldtypes/Matrix/settings.twig

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,8 @@
346346

347347
{% if readOnly %}
348348
config['disabled'] = true;
349-
{% else %}
350-
config['sortable'] = true;
351349
{% endif %}
352350

353-
Craft.ui.createCheckboxSelect(config).appendTo($defaultColumnsContainer);
351+
Craft.ui.createSortableCheckboxSelect(config).appendTo($defaultColumnsContainer);
354352
})();
355353
{% endjs %}

src/templates/settings/sites/index.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
{% endif %}
7474
{% if canSort %}
7575
<td class="thin"></td>
76+
<td class="thin"></td>
7677
{% endif %}
7778
{% if multiple %}
7879
<td class="thin"></td>
@@ -95,6 +96,7 @@
9596
{% endif %}
9697
{% if canSort %}
9798
<td class="thin"><a class="move icon" title="{{ 'Reorder'|t('app') }}" aria-label="{{ 'Reorder'|t('app') }}" role="button"></a></td>
99+
<td class="thin actions-container"></td>
98100
{% endif %}
99101
{% if multiple %}
100102
<td class="thin"><a class="delete icon{% if site.primary %} disabled{% endif %}" title="{{ 'Delete'|t('app') }}" aria-label="{{ 'Delete'|t('app') }}" role="button"></a></td>

src/web/assets/cp/dist/cp.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/dist/cp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/dist/css/cp.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/dist/css/cp.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/cp/src/css/_main.scss

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ body.rtl [data-icon-after='structure']::after {
672672
}
673673

674674
.action-btn::before {
675-
content: 'ellipsis';
675+
content: 'ellipsis' / '';
676676
}
677677

678678
/* headings */
@@ -4335,6 +4335,11 @@ table {
43354335
box-shadow: none;
43364336
border-radius: var(--border-radius, var(--radius-sm));
43374337

4338+
// adjust the margin-block
4339+
@media only screen and (width <= 767px) {
4340+
margin-block: calc(var(--s) * -1 + 2px) var(--s);
4341+
}
4342+
43384343
&::after {
43394344
content: '';
43404345
}
@@ -8482,7 +8487,7 @@ div.checkbox.checked:not(.indeterminate)::before,
84828487
.elements
84838488
.disabled
84848489
.checkbox::before {
8485-
content: 'check';
8490+
content: 'check' / '';
84868491
font-size: calc(var(--checkbox-size) * 0.8);
84878492
}
84888493

src/web/assets/cp/src/js/AdminTable.js

Lines changed: 136 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Craft.AdminTable = Garnish.Base.extend(
2626
this.$tbody = this.$table.children('tbody');
2727
this.totalItems = this.$tbody.children().length;
2828

29-
if (this.settings.sortable) {
29+
if (this.settings.sortable && Craft.hasMousePointerEvents()) {
3030
this.sorter = new Craft.DataTableSorter(this.$table, {
3131
onSortChange: this.reorderItems.bind(this),
3232
});
@@ -44,9 +44,17 @@ Craft.AdminTable = Garnish.Base.extend(
4444
}
4545
});
4646

47+
this.$tbody.children('tr').each((key, row) => {
48+
this.initRow(row);
49+
});
50+
4751
this.updateUI();
4852
},
4953

54+
initRow: function (row) {
55+
return new Craft.AdminTable.Row(this, row);
56+
},
57+
5058
addRow: function (row) {
5159
if (this.settings.maxItems && this.totalItems >= this.settings.maxItems) {
5260
// Sorry pal.
@@ -56,8 +64,8 @@ Craft.AdminTable = Garnish.Base.extend(
5664
var $row = $(row).appendTo(this.$tbody),
5765
$deleteBtn = $row.find('.delete');
5866

59-
if (this.settings.sortable) {
60-
this.sorter.addItems($row);
67+
if (this.settings.sortable && Craft.hasMousePointerEvents()) {
68+
this.sorter?.addItems($row);
6169
}
6270

6371
this.$deleteBtns = this.$deleteBtns.add($deleteBtn);
@@ -74,12 +82,7 @@ Craft.AdminTable = Garnish.Base.extend(
7482
}
7583

7684
// Get the new field order
77-
var ids = [];
78-
79-
for (var i = 0; i < this.sorter.$items.length; i++) {
80-
var id = $(this.sorter.$items[i]).attr(this.settings.idAttribute);
81-
ids.push(id);
82-
}
85+
let ids = this.getRowOrder();
8386

8487
// Send it to the server
8588
var data = {
@@ -100,6 +103,17 @@ Craft.AdminTable = Garnish.Base.extend(
100103
});
101104
},
102105

106+
getRowOrder: function () {
107+
var ids = [];
108+
// Get the new field order
109+
this.$tbody.children('tr').each((key, row) => {
110+
let id = $(row).attr(this.settings.idAttribute);
111+
ids.push(id);
112+
});
113+
114+
return ids;
115+
},
116+
103117
handleDeleteBtnClick: function (event) {
104118
if (this.settings.minItems && this.totalItems <= this.settings.minItems) {
105119
// Sorry pal.
@@ -193,12 +207,16 @@ Craft.AdminTable = Garnish.Base.extend(
193207

194208
// Disable the sort buttons if there's only one row
195209
if (this.settings.sortable) {
196-
var $moveButtons = this.$table.find('.move');
197-
198-
if (this.totalItems === 1) {
199-
$moveButtons.addClass('disabled');
210+
if (!Craft.hasMousePointerEvents()) {
211+
this.$table.find('.move').hide();
200212
} else {
201-
$moveButtons.removeClass('disabled');
213+
var $moveButtons = this.$table.find('.move');
214+
215+
if (this.totalItems === 1) {
216+
$moveButtons.addClass('disabled');
217+
} else {
218+
$moveButtons.removeClass('disabled');
219+
}
202220
}
203221
}
204222

@@ -248,3 +266,107 @@ Craft.AdminTable = Garnish.Base.extend(
248266
},
249267
}
250268
);
269+
270+
Craft.AdminTable.Row = Garnish.Base.extend({
271+
table: null,
272+
$row: null,
273+
$moveHandle: null,
274+
$actionMenuBtn: null,
275+
$actionMenu: null,
276+
actionDisclosure: null,
277+
moveUpBtn: null,
278+
moveDownBtn: null,
279+
280+
init: function (table, row) {
281+
this.table = table;
282+
this.$row = $(row);
283+
284+
this.initSortActions();
285+
},
286+
287+
initSortActions: function () {
288+
if (!this.table.settings.sortable) {
289+
return;
290+
}
291+
292+
// find the delete button and add the actions menu before it
293+
let $deleteButtonWrapper = this.$row.find('.delete').parent('td');
294+
295+
const menuId = 'menu-' + Math.floor(Math.random() * 1000000000);
296+
let $actionMenuBtnWrapper = this.$row.find('.actions-container');
297+
298+
if ($actionMenuBtnWrapper.length > 0) {
299+
this.$actionMenuBtn = $('<button/>', {
300+
class: 'btn action-btn',
301+
'aria-controls': menuId,
302+
'aria-label': Craft.t('app', 'Actions'),
303+
'data-disclosure-trigger': '',
304+
'data-icon': 'ellipsis',
305+
}).appendTo($actionMenuBtnWrapper);
306+
this.$actionMenu = $('<div/>', {
307+
id: menuId,
308+
class: 'menu menu--disclosure',
309+
}).appendTo($actionMenuBtnWrapper);
310+
311+
this.actionDisclosure = new Garnish.DisclosureMenu(this.$actionMenuBtn);
312+
this.moveUpBtn = this.actionDisclosure.addItem({
313+
icon: async () => await Craft.ui.icon('arrow-up'),
314+
label: Craft.t('app', 'Move up'),
315+
onActivate: () => {
316+
this.moveUp();
317+
},
318+
});
319+
this.moveDownBtn = this.actionDisclosure.addItem({
320+
icon: async () => await Craft.ui.icon('arrow-down'),
321+
label: Craft.t('app', 'Move down'),
322+
onActivate: () => {
323+
this.moveDown();
324+
},
325+
});
326+
327+
this.actionDisclosure.on('show', () => {
328+
if (this.getPrevItem()) {
329+
this.actionDisclosure.showItem(this.moveUpBtn);
330+
} else {
331+
this.actionDisclosure.hideItem(this.moveUpBtn);
332+
}
333+
if (this.getNextItem()) {
334+
this.actionDisclosure.showItem(this.moveDownBtn);
335+
} else {
336+
this.actionDisclosure.hideItem(this.moveDownBtn);
337+
}
338+
});
339+
}
340+
},
341+
342+
moveUp: function () {
343+
const $prev = this.getPrevItem();
344+
if ($prev) {
345+
this.$row.insertBefore($prev);
346+
this.$row.trigger('movedUp');
347+
this.table.reorderItems();
348+
}
349+
},
350+
351+
moveDown: function () {
352+
const $next = this.getNextItem();
353+
if ($next) {
354+
this.$row.insertAfter($next);
355+
this.$row.trigger('movedDown');
356+
this.table.reorderItems();
357+
}
358+
},
359+
360+
getPrevItem: function () {
361+
//const $row = this.$row.prevAll('tr');
362+
const $row = this.$row.prevAll('tr:has(.actions-container):first');
363+
364+
return $row.length ? $row : null;
365+
},
366+
367+
getNextItem: function () {
368+
const $row = this.$row.nextAll('tr:has(.actions-container):first');
369+
370+
return $row.length ? $row : null;
371+
},
372+
});

src/web/assets/cp/src/js/BaseElementIndex.js

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,11 +1491,7 @@ Craft.BaseElementIndex = Garnish.Base.extend(
14911491
getSourceActions: function () {
14921492
let actions = [];
14931493

1494-
if (
1495-
Craft.userIsAdmin &&
1496-
Craft.allowAdminChanges &&
1497-
!Garnish.isMobileBrowser(true)
1498-
) {
1494+
if (Craft.userIsAdmin && Craft.allowAdminChanges) {
14991495
actions.push({
15001496
label: Craft.t('app', 'Customize sources'),
15011497
administrative: true,
@@ -4457,7 +4453,7 @@ const ViewMenu = Garnish.Base.extend({
44574453
`input[value="${attribute}"]`
44584454
);
44594455
if (!$checkbox.prop('checked')) {
4460-
$checkbox.prop('checked', true);
4456+
$checkbox.prop('checked', true).trigger('change');
44614457
}
44624458
const $container = $checkbox.parent();
44634459

@@ -4723,20 +4719,21 @@ const ViewMenu = Garnish.Base.extend({
47234719
return $();
47244720
}
47254721

4726-
this.$tableColumnsContainer = Craft.ui.createCheckboxSelect({
4722+
this.$tableColumnsContainer = Craft.ui.createSortableCheckboxSelect({
47274723
options: columns.map((c) => ({
47284724
label: c.label,
47294725
value: c.attr,
47304726
})),
4731-
sortable: true,
47324727
});
47334728

47344729
this.updateTableColumnField();
47354730
this.tidyTableColumnField();
47364731

4737-
this.$tableColumnsContainer.data('dragSort').on('sortChange', () => {
4738-
this._onTableColumnChange();
4739-
});
4732+
this.$tableColumnsContainer
4733+
.data('sortableCheckboxSelect')
4734+
.on('sortChange', () => {
4735+
this._onTableColumnChange();
4736+
});
47404737

47414738
this._getTableColumnCheckboxes().on('change', (ev) => {
47424739
this._onTableColumnChange();

0 commit comments

Comments
 (0)