Placing a widget without a mouse, in two dimensions #124
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
#125 The dashboard is a grid a person arranges by dragging
Postulo/postulo
Reference
Postulo/postulo#124
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
Second of three, and a prerequisite rather than a refinement: the grid cannot ship without
an answer here, because dragging reaches only some of the people who use this page.
What exists
The arrange page is a list of rows with four POSTs — Move up, Move down, Take off,
Add — and one more for Back to the standard arrangement. Every action is a form
submission, so it works with no JavaScript, from a keyboard, and under the back button.
DashboardView's own docstring says why it was built that way:The board does drag, and
static/js/app.jsstates the rule that governs every drag inthis application:
That rule is what makes this a dependency. Adding dragging to the dashboard is allowed by
it; replacing the buttons with dragging is not.
What this asks for
The control that works everywhere, for placing a widget in a two-dimensional grid — so that
dragging can be added on top of it rather than instead of it.
Worth being careful about
One dimension is not two. Move up and Move down are a complete vocabulary for a
list. A grid needs more: four directions, or a cell picker (a row and a column select per
widget), or a "move this one" mode where the next click chooses the destination. Naming
that mechanism is the whole of this issue, and the grid issue depends on the answer.
Scripts off has to keep working. The current page needs no JavaScript at all. Whatever
is chosen must still be a form that posts, or the dashboard becomes the first part of
Postulo that cannot be arranged without scripts.
Touch is not covered by HTML5 drag. The board's dragging does not fire on a phone,
which the comment above says outright. If dragging is meant to work on a touch screen it is
pointer events, not
dragstart— and the phone is #73's subject, which this should notquietly assume has been solved.
Focus and announcement. Moving a widget has to take focus with it and say what
happened, or somebody using a screen reader drags into silence. The board's status menu is
the precedent for a keyboard route that produces the same server call as the drag.
axe walks this page in both themes on every run. A drag handle that is a bare element
with a cursor is a finding, not a detail: it needs a name, a role and a visible focus state.
Reduced motion. Anything that animates a widget snapping into place respects
prefers-reduced-motion, which the stylesheet already brackets for its other animations.Target size. SC 2.5.8 has been an active constraint on this project twice recently
(#115, and the telephone rows in #90). A drag handle and a remove control on the same row
are two targets that have to be 24 by 24 with room between them.
Done in
23ac44d7.Naming the mechanism, which was the whole of this issue
Of the three you listed, the answer is four directions — and the reason is that the dashboard is a flow, not a matrix. Widgets have widths and fill rows in order, so there is no cell to name:
So:
The old up and down were renamed to left and right, because the words had to be freed for the direction a list could not express.
On a narrow screen there is one column and the two axes coincide. That is not a degradation — it is what up means when there is only one column, and the same POST does it.
rows_of()computes the rows from the widths against the same twelve columns the template draws with, rather than storing them, so it stays true the day somebody changes what a widget spans. Out of range is a no-op rather than a wrap: pressing up on the top row means to find out that it is the top row.Everything you flagged
Scripts off keeps working. Every arrow is a form that posts, and a test asserts the arranging list carries no
draggableand nohx-. Nothing was replaced by dragging; the control is what dragging will be added on top of.Focus and announcement. The redirect carries
#widget-<key>and each row istabindex="-1", so the page comes back on the widget that moved rather than at the top. A message says which row and place it landed in — row and place rather than a direction, because that is what somebody actually wants to know, it is the same sentence for all four buttons, and two numbers is what a two-dimensional control owes. The messages partial already carriesrole="status".axe walks this page in both themes. Every arrow is an icon-only button with an
sr-onlyname — move X up a row, one place earlier, and so on. The browser suite passes, 54 tests, both themes.Target size (SC 2.5.8). Each arrow is a fixed 32×32 button in a gap-1 cluster, with the remove control separated by a gap-2. No two targets share an edge.
Reduced motion. Nothing animates: a move is a page load, which is the same non-animation the rest of this page has always had.
Touch. Not assumed solved. This control works on a phone because it is a button, which is the point — HTML5 drag would not, as the comment in
app.jssays outright, and #73 is where the phone gets its own attention.Two details worth knowing about
A button that cannot act is disabled rather than hidden, so the cluster keeps its shape and the arrow somebody reaches for is where it was last time.
can_move()is what the template asks.The two horizontal arrows joined the icon set, and
assets/css/app.cssalready mirrorsarrow-leftandarrow-rightunderdir="rtl"— an arrow meaning one place earlier that points at the far margin in Arabic is worse than no arrow.tests/test_template_lint.pyis what holds the icon list and the flip rule together, and it passes.Also
tests/test_widget_placement.py, 16 tests. Suite 4388 passed, 29 skipped; browser suite 54 passed.The grid now has its control. Whatever it stores, this is the vocabulary that has to keep reaching it.