max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
3 files changed,
+71 insertions,
-39 deletions
| @@ -880,26 +880,12 @@ | |||
| 880 | 880 | font-weight: normal; | |
| 881 | 881 | } | |
| 882 | 882 | ||
| 883 | - | .data-table th.sortable { | |
| 884 | - | cursor: pointer; | |
| 885 | - | transition: background 0.2s ease; | |
| 886 | - | user-select: none; | |
| 887 | - | } | |
| 888 | - | ||
| 889 | - | .data-table th.sortable:hover { | |
| 890 | - | background: var(--border); | |
| 891 | - | } | |
| 892 | - | ||
| 893 | - | .data-table th.sortable:focus-visible { | |
| 894 | - | outline: 2px solid var(--focus-ring); | |
| 895 | - | outline-offset: 2px; | |
| 896 | - | } | |
| 897 | - | ||
| 898 | - | .data-table th.sortable::after { | |
| 899 | - | content: " ^"; | |
| 900 | - | opacity: 0.3; | |
| 901 | - | font-size: var(--text-fine); | |
| 902 | - | } | |
| 883 | + | /* `.data-table th.sortable` and its four rules were here and had no consumer: | |
| 884 | + | nothing in templates/ ever put `sortable` on a `th`. The one sortable table | |
| 885 | + | in the app is templates/partials/tabs/project_content.html, which used | |
| 886 | + | `.sortable-th` further down this file. Both are gone; the affordance and the | |
| 887 | + | caret are `.table-heading[data-sortable]` in the generated static/layout.css. | |
| 888 | + | See the block that replaced `.sortable-th` for what stayed behind. */ | |
| 903 | 889 | ||
| 904 | 890 | .data-table tbody tr { | |
| 905 | 891 | transition: background 0.1s ease; | |
| @@ -8681,11 +8667,51 @@ | |||
| 8681 | 8667 | margin-bottom: var(--gap-bound); | |
| 8682 | 8668 | } | |
| 8683 | 8669 | ||
| 8684 | - | /* Sortable table column header */ | |
| 8685 | - | .sortable-th { cursor: pointer; } | |
| 8686 | - | .sortable-th:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; } | |
| 8687 | - | .sort-arrow { opacity: 0.4; } | |
| 8688 | - | .sort-arrow--active { opacity: 1; } | |
| 8670 | + | /* Sortable table column header | |
| 8671 | + | ||
| 8672 | + | The cursor and the caret are NOT here. `.table-heading[data-sortable]` and | |
| 8673 | + | `.table-heading[aria-sort="..."]::after` are generated into static/layout.css | |
| 8674 | + | by makeover-webview out of `Column::sortable` and `Column::sorted`. This file | |
| 8675 | + | carried two hand-written copies of the same fact -- `.sortable-th` here and | |
| 8676 | + | `.data-table th.sortable` up at the data-table block -- and a third channel in | |
| 8677 | + | tab-project-content.js, which wrote the glyph as text and toggled | |
| 8678 | + | `.sort-arrow--active`. Three vocabularies, one state, and the generated pair | |
| 8679 | + | matched nothing because no heading carried `.table-heading`. | |
| 8680 | + | ||
| 8681 | + | The focus ring stays: it is this app's focus treatment, not a restatement of | |
| 8682 | + | the caret, and makeover emits no focus rule for a heading. */ | |
| 8683 | + | .table-heading[data-sortable] { | |
| 8684 | + | user-select: none; | |
| 8685 | + | transition: background 0.2s ease; | |
| 8686 | + | } | |
| 8687 | + | ||
| 8688 | + | .table-heading[data-sortable]:hover { | |
| 8689 | + | background: var(--border); | |
| 8690 | + | } | |
| 8691 | + | ||
| 8692 | + | .table-heading[data-sortable]:focus-visible { | |
| 8693 | + | outline: 2px solid var(--focus-ring); | |
| 8694 | + | outline-offset: 2px; | |
| 8695 | + | } | |
| 8696 | + | ||
| 8697 | + | /* makeover-tui and makeover-immediate emit their caret with a leading space; | |
| 8698 | + | the webview emitter appends the bare glyph, so the gap is the app's to put | |
| 8699 | + | back. Reserved on every sortable heading and not only the sorted one, or | |
| 8700 | + | pressing a header reflows the row it sits in. | |
| 8701 | + | ||
| 8702 | + | Box only, never `content`. This file outranks the `makeover` layer, so | |
| 8703 | + | declaring `content` on the sorted arm would win over the generated caret and | |
| 8704 | + | put the arrow back to nothing -- the same override this block deletes, one | |
| 8705 | + | property along. The empty box goes on the arm the generator does not match. */ | |
| 8706 | + | .table-heading[data-sortable]:not([aria-sort])::after { | |
| 8707 | + | content: ''; | |
| 8708 | + | } | |
| 8709 | + | ||
| 8710 | + | .table-heading[data-sortable]::after { | |
| 8711 | + | display: inline-block; | |
| 8712 | + | min-width: 1ch; | |
| 8713 | + | margin-left: var(--gap-bound); | |
| 8714 | + | } | |
| 8689 | 8715 | ||
| 8690 | 8716 | /* Data-table column-width utilities (numeric width hints, | |
| 8691 | 8717 | kept as classes to avoid inline `style="width: NN%"`) */ |
| @@ -254,15 +254,21 @@ | |||
| 254 | 254 | } | |
| 255 | 255 | } | |
| 256 | 256 | ||
| 257 | - | var arrows = document.querySelectorAll('.sort-arrow'); | |
| 258 | - | for (var i = 0; i < arrows.length; i++) { | |
| 259 | - | var col = parseInt(arrows[i].dataset.col, 10); | |
| 260 | - | if (col === colIndex) { | |
| 261 | - | arrows[i].textContent = contentSortState.asc ? '▲' : '▼'; | |
| 262 | - | arrows[i].classList.add('sort-arrow--active'); | |
| 257 | + | // One channel, and it is `aria-sort`. The caret is drawn by the generated | |
| 258 | + | // `.table-heading[aria-sort]::after` in static/layout.css, so the arrow and | |
| 259 | + | // the order a screen reader announces cannot disagree. This used to write | |
| 260 | + | // the glyph itself into a `.sort-arrow` span and toggle a modifier class, | |
| 261 | + | // which said nothing to assistive tech and duplicated rules the stylesheet | |
| 262 | + | // already had. | |
| 263 | + | // | |
| 264 | + | // `data-arg` is the column index the heading already carries for the sort | |
| 265 | + | // itself, so the heading is matched by the same number it dispatches on. | |
| 266 | + | var headings = table.querySelectorAll('[data-sortable]'); | |
| 267 | + | for (var i = 0; i < headings.length; i++) { | |
| 268 | + | if (parseInt(headings[i].dataset.arg, 10) === colIndex) { | |
| 269 | + | headings[i].setAttribute('aria-sort', contentSortState.asc ? 'ascending' : 'descending'); | |
| 263 | 270 | } else { | |
| 264 | - | arrows[i].textContent = ''; | |
| 265 | - | arrows[i].classList.remove('sort-arrow--active'); | |
| 271 | + | headings[i].removeAttribute('aria-sort'); | |
| 266 | 272 | } | |
| 267 | 273 | } | |
| 268 | 274 | } |
| @@ -62,12 +62,12 @@ | |||
| 62 | 62 | <tr> | |
| 63 | 63 | <th class="col-3"><input type="checkbox" id="select-all" data-change="toggleSelectAllFromEl"></th> | |
| 64 | 64 | <th class="col-5">#</th> | |
| 65 | - | <th class="col-32 sortable-th" tabindex="0" data-action="sortContent" data-arg="2" data-arg2="text" title="Sort by title">Item <span class="sort-arrow" data-col="2"></span></th> | |
| 66 | - | <th class="col-12 sortable-th" tabindex="0" data-action="sortContent" data-arg="3" data-arg2="text" title="Sort by type">Type <span class="sort-arrow" data-col="3"></span></th> | |
| 67 | - | <th class="col-10 sortable-th" tabindex="0" data-action="sortContent" data-arg="4" data-arg2="money" title="Sort by price">Price <span class="sort-arrow" data-col="4"></span></th> | |
| 68 | - | <th class="col-10 sortable-th" tabindex="0" data-action="sortContent" data-arg="5" data-arg2="num" title="Sort by sales">Sales <span class="sort-arrow" data-col="5"></span></th> | |
| 69 | - | <th class="col-10 sortable-th" tabindex="0" data-action="sortContent" data-arg="6" data-arg2="money" title="Sort by revenue">Revenue <span class="sort-arrow" data-col="6"></span></th> | |
| 70 | - | <th class="col-8 sortable-th" tabindex="0" data-action="sortContent" data-arg="7" data-arg2="text" title="Sort by status">Status <span class="sort-arrow" data-col="7"></span></th> | |
| 65 | + | <th class="col-32 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="2" data-arg2="text" title="Sort by title">Item</th> | |
| 66 | + | <th class="col-12 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="3" data-arg2="text" title="Sort by type">Type</th> | |
| 67 | + | <th class="col-10 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="4" data-arg2="money" title="Sort by price">Price</th> | |
| 68 | + | <th class="col-10 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="5" data-arg2="num" title="Sort by sales">Sales</th> | |
| 69 | + | <th class="col-10 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="6" data-arg2="money" title="Sort by revenue">Revenue</th> | |
| 70 | + | <th class="col-8 table-heading" data-sortable tabindex="0" data-action="sortContent" data-arg="7" data-arg2="text" title="Sort by status">Status</th> | |
| 71 | 71 | <th class="col-10">Actions</th> | |
| 72 | 72 | </tr> | |
| 73 | 73 | </thead> |