|
312 | 312 | [:button.shortcut-filter-pill |
313 | 313 | {:key (name k) |
314 | 314 | :class (when active? "shortcut-filter-pill--active") |
| 315 | + :aria-pressed (str active?) |
315 | 316 | :on-click #(set-filter-key! (when-not (or (= k :All) (= filter-key k)) k))} |
316 | 317 | [:span.shortcut-filter-pill-title title] |
317 | 318 | [:span.shortcut-filter-pill-count (str " \u00B7 " cnt)]])] |
|
363 | 364 | {:id popup-id |
364 | 365 | :force-popover? true |
365 | 366 | :align "start" |
366 | | - :on-after-hide #(reset! *active-shortcut-id nil) |
| 367 | + :on-after-hide #(do (reset! *active-shortcut-id nil) |
| 368 | + (when anchor-el (.focus anchor-el))) |
367 | 369 | :content-props |
368 | 370 | {:class "p-0 w-auto" |
369 | 371 | :collision-padding 12 |
|
376 | 378 | (.preventDefault e) |
377 | 379 | false)))}})))) |
378 | 380 |
|
379 | | -(rum/defc shortcut-conflicts-display |
380 | | - [_k conflicts-map] |
381 | | - |
382 | | - [:div.cp__shortcut-conflicts-list-wrap |
383 | | - (for [[g ks] conflicts-map] |
384 | | - ^{:key (str g)} |
385 | | - [:section.relative |
386 | | - [:h2 (ui/icon "alert-triangle" {:size 15}) |
387 | | - [:span (t :keymap/conflicts-for-label)] |
388 | | - [:code (shortcut-utils/decorate-binding g)]] |
389 | | - [:ul |
390 | | - (for [v (vals ks) |
391 | | - :let [k (first v) |
392 | | - vs (second v)] |
393 | | - [id' handler-id] vs |
394 | | - :let [m (dh/shortcut-item id')] |
395 | | - :when (not (nil? m))] |
396 | | - [:li |
397 | | - {:key (str id')} |
398 | | - [:button.select-none.hover:underline |
399 | | - {:on-click (fn [^js e] (open-customize-shortcut-dialog! e id')) |
400 | | - :title (str handler-id) |
401 | | - :aria-label (dh/get-shortcut-desc m)} |
402 | | - [:code.inline-block.mr-1.text-xs |
403 | | - (shortcut-utils/decorate-binding k)] |
404 | | - [:span |
405 | | - (dh/get-shortcut-desc m) |
406 | | - (ui/icon "external-link" {:size 18})] |
407 | | - [:code [:small (str id')]]]])]])]) |
408 | | - |
409 | 381 | (defn- execute-undo! |
410 | 382 | "Restore previous bindings for all affected actions." |
411 | 383 | [snapshot] |
|
649 | 621 | (= state :conflict-cross) |
650 | 622 | nil |
651 | 623 |
|
652 | | - ;; Conflict-same / esc-hint + key => start new recording |
653 | | - (#{:conflict-same :esc-hint} state) |
654 | | - (when-let [kn (shortcut/keyname e)] |
655 | | - (set-rec-state! :recording) |
656 | | - (set-keystroke! (util/trim-safe kn))) |
657 | | - |
658 | | - ;; Idle / accepted / removed / reset / dismissing + key => start recording |
659 | | - (#{:idle :accepted :removed :reset :dismissing} state) |
| 624 | + ;; Any non-recording, non-conflict-cross state + key => start new recording |
| 625 | + (#{:conflict-same :esc-hint :idle :accepted :removed :reset :dismissing} state) |
660 | 626 | (when-let [kn (shortcut/keyname e)] |
661 | 627 | (set-rec-state! :recording) |
662 | 628 | (set-keystroke! (util/trim-safe kn))) |
|
675 | 641 | ;; === V3 LAYOUT === |
676 | 642 | [:div.shortcut-popover |
677 | 643 | {:tab-index -1 |
678 | | - :ref *ref-el} |
| 644 | + :ref *ref-el |
| 645 | + :role "dialog" |
| 646 | + :aria-label action-name} |
679 | 647 |
|
680 | 648 | ;; TITLE |
681 | 649 | [:div.shortcut-popover-title action-name] |
|
717 | 685 | (when (#{:idle :recording :accepted :removed :reset} render-state) |
718 | 686 | [:span.shortcut-input-placeholder (t :keymap/press-a-shortcut)])] |
719 | 687 |
|
720 | | - ;; FEEDBACK BANNER (conditional) |
721 | | - (let [undo-link |
722 | | - (when undo-snapshot |
723 | | - [:button.shortcut-feedback-action |
724 | | - {:on-click (fn [] |
725 | | - (execute-undo! undo-snapshot) |
726 | | - (when-let [own (some #(when (= (:action-id %) k) %) (:entries undo-snapshot))] |
727 | | - (set-current-binding! (:previous-binding own))) |
728 | | - (set-undo-snapshot! nil) |
729 | | - (set-rec-state! :idle))} |
730 | | - (t :keymap/undo)])] |
731 | | - (case rec-state |
732 | | - :conflict-cross |
733 | | - [:div.shortcut-feedback.shortcut-feedback--error |
734 | | - [:span (t :keymap/used-by) |
735 | | - [:span.shortcut-feedback-name (conflict-action-names key-conflicts)]] |
736 | | - (ui/tooltip |
737 | | - (shui/button {:variant :destructive |
738 | | - :size :xs |
739 | | - :on-click override-fn!} |
740 | | - (t :keymap/reassign)) |
741 | | - (t :keymap/reassign-tooltip))] |
742 | | - |
743 | | - :conflict-same |
744 | | - [:div.shortcut-feedback.shortcut-feedback--error |
745 | | - [:span (t :keymap/already-bound)]] |
746 | | - |
747 | | - :accepted |
748 | | - (cond |
749 | | - (:cross-context? accepted-info) |
750 | | - [:div.shortcut-feedback.shortcut-feedback--warning |
751 | | - [:span (t :keymap/also-used-for) |
752 | | - [:span.shortcut-feedback-name |
753 | | - (:cross-action-name accepted-info)] |
754 | | - (when-let [ctx (:cross-context-label accepted-info)] |
755 | | - (str (t :keymap/in-context) ctx))]] |
756 | | - |
757 | | - (:from accepted-info) |
758 | | - [:div.shortcut-feedback.shortcut-feedback--success |
759 | | - [:span (t :keymap/reassigned-from) |
760 | | - [:span.shortcut-feedback-name (:from accepted-info)]] |
761 | | - undo-link] |
762 | | - |
763 | | - :else |
764 | | - [:div.shortcut-feedback.shortcut-feedback--success |
765 | | - [:span (t :keymap/shortcut-added)]]) |
766 | | - |
767 | | - :removed |
768 | | - [:div.shortcut-feedback.shortcut-feedback--muted |
769 | | - [:span (t :keymap/shortcut-removed)] |
770 | | - undo-link] |
771 | | - |
772 | | - :reset |
773 | | - [:div.shortcut-feedback.shortcut-feedback--muted |
774 | | - [:span (t :keymap/reset-to-default)] |
775 | | - undo-link] |
776 | | - |
777 | | - :esc-hint |
778 | | - [:div.shortcut-feedback.shortcut-feedback--muted |
779 | | - [:span (t :keymap/esc-is-reserved)]] |
780 | | - |
781 | | - :dismissing |
782 | | - (let [prev (rum/deref *prev-rec-state) |
783 | | - variant (case prev |
784 | | - (:conflict-cross :conflict-same) "shortcut-feedback--error" |
785 | | - :accepted (if (:cross-context? accepted-info) |
786 | | - "shortcut-feedback--warning" |
787 | | - "shortcut-feedback--success") |
788 | | - "shortcut-feedback--muted")] |
789 | | - [:div {:class (str "shortcut-feedback " variant " is-dismissing") |
790 | | - :on-animation-end #(set-rec-state! :idle)}]) |
791 | | - |
792 | | - nil)) |
| 688 | + ;; FEEDBACK BANNER (conditional) — wrapped in live region for screen readers |
| 689 | + [:div {:role (if (#{:conflict-cross :conflict-same} rec-state) "alert" "status") |
| 690 | + :aria-live (if (#{:conflict-cross :conflict-same} rec-state) "assertive" "polite")} |
| 691 | + (let [undo-link |
| 692 | + (when undo-snapshot |
| 693 | + [:button.shortcut-feedback-action |
| 694 | + {:on-click (fn [] |
| 695 | + (execute-undo! undo-snapshot) |
| 696 | + (when-let [own (some #(when (= (:action-id %) k) %) (:entries undo-snapshot))] |
| 697 | + (set-current-binding! (:previous-binding own))) |
| 698 | + (set-undo-snapshot! nil) |
| 699 | + (set-rec-state! :idle))} |
| 700 | + (t :keymap/undo)])] |
| 701 | + (case rec-state |
| 702 | + :conflict-cross |
| 703 | + [:div.shortcut-feedback.shortcut-feedback--error |
| 704 | + [:span (t :keymap/used-by) |
| 705 | + [:span.shortcut-feedback-name (conflict-action-names key-conflicts)]] |
| 706 | + (ui/tooltip |
| 707 | + (shui/button {:variant :destructive |
| 708 | + :size :xs |
| 709 | + :on-click override-fn!} |
| 710 | + (t :keymap/reassign)) |
| 711 | + (t :keymap/reassign-tooltip))] |
| 712 | + |
| 713 | + :conflict-same |
| 714 | + [:div.shortcut-feedback.shortcut-feedback--error |
| 715 | + [:span (t :keymap/already-bound)]] |
| 716 | + |
| 717 | + :accepted |
| 718 | + (cond |
| 719 | + (:cross-context? accepted-info) |
| 720 | + [:div.shortcut-feedback.shortcut-feedback--warning |
| 721 | + [:span (t :keymap/also-used-for) |
| 722 | + [:span.shortcut-feedback-name |
| 723 | + (:cross-action-name accepted-info)] |
| 724 | + (when-let [ctx (:cross-context-label accepted-info)] |
| 725 | + (str (t :keymap/in-context) ctx))]] |
| 726 | + |
| 727 | + (:from accepted-info) |
| 728 | + [:div.shortcut-feedback.shortcut-feedback--success |
| 729 | + [:span (t :keymap/reassigned-from) |
| 730 | + [:span.shortcut-feedback-name (:from accepted-info)]] |
| 731 | + undo-link] |
| 732 | + |
| 733 | + :else |
| 734 | + [:div.shortcut-feedback.shortcut-feedback--success |
| 735 | + [:span (t :keymap/shortcut-added)]]) |
| 736 | + |
| 737 | + :removed |
| 738 | + [:div.shortcut-feedback.shortcut-feedback--muted |
| 739 | + [:span (t :keymap/shortcut-removed)] |
| 740 | + undo-link] |
| 741 | + |
| 742 | + :reset |
| 743 | + [:div.shortcut-feedback.shortcut-feedback--muted |
| 744 | + [:span (t :keymap/reset-to-default)] |
| 745 | + undo-link] |
| 746 | + |
| 747 | + :esc-hint |
| 748 | + [:div.shortcut-feedback.shortcut-feedback--muted |
| 749 | + [:span (t :keymap/esc-is-reserved)]] |
| 750 | + |
| 751 | + :dismissing |
| 752 | + (let [prev (rum/deref *prev-rec-state) |
| 753 | + variant (case prev |
| 754 | + (:conflict-cross :conflict-same) "shortcut-feedback--error" |
| 755 | + :accepted (if (:cross-context? accepted-info) |
| 756 | + "shortcut-feedback--warning" |
| 757 | + "shortcut-feedback--success") |
| 758 | + "shortcut-feedback--muted")] |
| 759 | + [:div {:class (str "shortcut-feedback " variant " is-dismissing") |
| 760 | + :on-animation-end #(set-rec-state! :idle)}]) |
| 761 | + |
| 762 | + nil))] |
793 | 763 |
|
794 | 764 | ;; SEPARATOR + TOOLBAR |
795 | 765 | (shui/separator) |
|
1049 | 1019 | [:li.shortcut-row.flex.items-start.justify-between.text-sm |
1050 | 1020 | {:key (str id) |
1051 | 1021 | :class (when (= active-id id) "active") |
1052 | | - :tab-index (when id 0) |
| 1022 | + :tab-index (when (and id (not disabled?)) 0) |
1053 | 1023 | :role (when id "button") |
1054 | 1024 | :aria-disabled (when disabled? "true") |
1055 | 1025 | :on-click row-action |
|
0 commit comments