diff --git a/CHANGELOG.md b/CHANGELOG.md index c28c8e512..10673876c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ Homebrew can be moved, switched off and repaired instead of lost, and a characte - **Characters:** the builder shows choices waiting on a higher level beside the level picker, and says when each comes back. - **Characters:** with more than one class, a "1st" badge marks the starting class; tap another class's badge to make it the starting class, and choices that only apply to the starting class follow. - **Characters:** a Choices list in the builder shows every choice a character stores, including ones whose homebrew isn't loaded in this browser, and lets those be cleared. +- **Characters:** the character builder's Back and Next repeat at the bottom of each page, and open the next page at its top. +- **Homebrew:** the add buttons on homebrew builders repeat below a long list of traits or options. ### Changed diff --git a/src/cljs/orcpub/character_builder.cljs b/src/cljs/orcpub/character_builder.cljs index e3e26b692..bdb688d62 100644 --- a/src/cljs/orcpub/character_builder.cljs +++ b/src/cljs/orcpub/character_builder.cljs @@ -2065,6 +2065,43 @@ (defn sorted-selection-set [selections] (into (sorted-set-by compare-paths) selections)) +(defn- show-page-top! + "After the next render, scrolls the builder's page to its top, just below the sticky header, + and moves focus to the page title so a screen reader starts there. No args; returns nil. + GOTCHA: the page change must already be in the db (dispatch-sync), or this measures the old page." + [] + (r/after-render + (fn [] + (when-let [col (.getElementById js/document "options-column")] + (let [header (or (some-> (.querySelector js/document ".sticky-header") .-offsetHeight) 0)] + (.scrollTo js/window 0 (- (+ (.-scrollY js/window) (.-top (.getBoundingClientRect col))) header)) + (some-> (.getElementById js/document "builder-page-title") (.focus #js {:preventScroll true}))))))) + +(defn page-nav-bar + "Back / page title / Next bar for the character builder; wraps at either end. + Args: page-index, the current page index; page-name, the title shown; bottom?, true for the + copy below the page, whose buttons also bring the new page's top into view. + Returns hiccup." + [page-index page-name bottom?] + (let [go (fn [i] + (if bottom? + (do (dispatch-sync [:set-page i]) + (show-page-top!)) + (dispatch [:set-page i])))] + [:div.flex.justify-cont-s-b.p-t-5.p-10.align-items-t + [:button.form-button.p-5-10.m-r-5 + {:on-click (fn [_] (go (let [prev (dec page-index)] + (if (neg? prev) (dec (count pages)) prev))))} + "Back"] + [:div.flex-grow-1 + (if bottom? + [:h3.f-w-b.f-s-20.t-a-c page-name] + [:h3#builder-page-title.f-w-b.f-s-20.t-a-c {:tab-index -1} page-name])] + [:button.form-button.p-5-10.m-l-5 + {:on-click (fn [_] (go (let [next (inc page-index)] + (if (>= next (count pages)) 0 next))))} + "Next"]])) + (defn new-options-column [num-columns] (let [character @(subscribe [:character]) built-template @(subscribe [:built-template]) @@ -2084,25 +2121,7 @@ [picks-notice] [:div#options-column.b-1.b-rad-5 [section-tabs available-selections built-template character page-index] - [:div.flex.justify-cont-s-b.p-t-5.p-10.align-items-t - [:button.form-button.p-5-10.m-r-5 - {:on-click - (fn [_] - (dispatch [:set-page (let [prev (dec page-index)] - (if (neg? prev) - (dec (count pages)) - prev))]))} - "Back"] - [:div.flex-grow-1 - [:h3.f-w-b.f-s-20.t-a-c (:name page)]] - [:button.form-button.p-5-10.m-l-5 - {:on-click - (fn [_] - (dispatch [:set-page (let [next (inc page-index)] - (if (>= next (count pages)) - 0 - next))]))} - "Next"]] + [page-nav-bar page-index (:name page) false] (let [ui-fn-selections (mapcat (fn [{:keys [key group? ui-fn]}] (if group? @@ -2171,7 +2190,9 @@ selection num-columns remaining)]))) - sorted-selections)))])])]])) + sorted-selections)))])]) + ;; Repeated below the page so a long page needs no scroll back up. + [page-nav-bar page-index (:name page) true]]])) (defn set-random-name diff --git a/src/cljs/orcpub/dnd/e5/views.cljs b/src/cljs/orcpub/dnd/e5/views.cljs index 544707945..b21538f04 100644 --- a/src/cljs/orcpub/dnd/e5/views.cljs +++ b/src/cljs/orcpub/dnd/e5/views.cljs @@ -6498,6 +6498,15 @@ #(dispatch [toggle-map-prop-event kw key])]]) @(subscribe [::mi/custom-and-standard-weapons]))))])]) +(defn add-trait-button + "The add button of a builder's traits list. + Args: add-trait-event, the event vector to dispatch; button-title, the label + or nil for \"add feature / trait\". Returns hiccup." + [add-trait-event button-title] + [:button.form-button.m-l-5 + {:on-click (make-event-handler add-trait-event)} + (or button-title "add feature / trait")]) + (defn option-traits [option add-trait-event edit-trait-name-event @@ -6509,9 +6518,7 @@ [:div.p-t-10.p-b-10.f-w-b.flex.justify-cont-s-b.align-items-c [:div.f-s-24.f-w-b.m-b-10 (or title "Features/Traits")] [:div - [:button.form-button.m-l-5 - {:on-click (make-event-handler add-trait-event)} - (or button-title "add feature / trait")]]] + [add-trait-button add-trait-event button-title]]] [:div (if (seq (:traits option)) (doall @@ -6559,7 +6566,11 @@ {:on-click #(dispatch [add-trait-event])} [:span "There are currently no features/traits, click "] [:span.orange.underline "here"] - [:span " or on the button above to add one."]])]]) + [:span " or on the button above to add one."]])] + ;; Repeated below a non-empty list so a long one needs no scroll back up. + (when (seq (:traits option)) + [:div.p-t-10.flex.justify-cont-end + [add-trait-button add-trait-event button-title]])]) (defn option-saving-throw-advantages [option toggle-map-prop-event] [:div.m-b-20 @@ -8102,6 +8113,14 @@ freqs (frequencies (remove nil? keys))] (set (map first (filter #(> (val %) 1) freqs))))) +(defn add-option-button + "The selection builder's Add Option button; dispatches ::selections/add-option. + No args. Returns hiccup." + [] + [:button.form-button + {:on-click #(dispatch [::selections/add-option])} + "Add Option"]) + (defn selection-builder [] (let [selection @(subscribe [::selections/builder-item]) options (:options selection) @@ -8132,9 +8151,7 @@ [:div [:div.flex.justify-cont-s-b [:div.f-s-24.f-w-b "Options"] - [:button.form-button - {:on-click #(dispatch [::selections/add-option])} - "Add Option"]] + [add-option-button]] ;; Summary warning for duplicate names (when has-dupes? [:div.p-10.m-b-10.red @@ -8189,7 +8206,11 @@ [textarea-field {:value description :on-change #(dispatch [::selections/set-selection-path-prop [:options i :description] %])}]]])) - options))]]])) + options))] + ;; Repeated below a non-empty list so a long one needs no scroll back up. + (when (seq options) + [:div.p-t-10.flex.justify-cont-end + [add-option-button]])]])) (defn language-builder [] (let [language @(subscribe [::langs/builder-item])]