diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs index c609b43558..fd7f82ee03 100644 --- a/src/main/frontend/components/block.cljs +++ b/src/main/frontend/components/block.cljs @@ -3275,6 +3275,12 @@ :del-btn? (boolean (and icon' (not= (:type icon') :none))) :page-title (:block/title block) :preview-target-db-id (:db/id block) + ;; Page-title scope — explicit so it doesn't + ;; rely on the icon-picker's default. Makes + ;; the contract visible at the call site + ;; alongside the parallel default-icon-row + ;; in property/value.cljs. + :property :logseq.property/icon :button-opts (when (:page-title? config) ;; Drop shui's default sizing/padding so the ;; CSS rule on .ls-page-title .ls-page-icon diff --git a/src/main/frontend/components/icon.cljs b/src/main/frontend/components/icon.cljs index 5f3f956c1f..b39a40b429 100644 --- a/src/main/frontend/components/icon.cljs +++ b/src/main/frontend/components/icon.cljs @@ -58,7 +58,12 @@ (.getContext canvas "2d")))) (declare normalize-icon derive-initials derive-avatar-initials - (rum/dom-node state) + (.querySelector ".avatar-customize-zone"))] + (.setAttribute zone "data-prewarming" "") + (.setAttribute zone "data-expanded" "true") + (.-offsetHeight zone) + (.removeAttribute zone "data-expanded") + (.-offsetHeight zone) + (.removeAttribute zone "data-prewarming")) + ;; Attach a paste listener to the picker root so ⌘V anywhere in ;; the modal routes through the render-time clipboard handler. (let [node (rum/dom-node state) @@ -2958,7 +2997,8 @@ state)} [state {:keys [on-chosen on-back on-delete del-btn? current-icon avatar-context page-title - *color preview-target-db-id preview-target-db-ids]}] + *color preview-target-db-id preview-target-db-ids property] + :or {property :logseq.property/icon}}] (let [*search-q (::search-q state) *loading? (::loading? state) *loaded-assets (::loaded-assets state) @@ -3415,7 +3455,7 @@ :on-hover! (when (or preview-target-db-id (seq preview-target-db-ids)) (fn [c] (state/set-state! :ui/icon-hover-preview - (cond-> {:color c} + (cond-> {:color c :property property} preview-target-db-id (assoc :db-id preview-target-db-id) (seq preview-target-db-ids) (assoc :db-ids (set preview-target-db-ids)))))) :on-hover-end! (when (or preview-target-db-id (seq preview-target-db-ids)) @@ -3625,6 +3665,14 @@ hover-preview (state/sub :ui/icon-hover-preview) hover-icon (when (and hover-preview (= preview-target-db-id (:db-id hover-preview)) + ;; Gate on property scope so the + ;; tile only reflects previews from + ;; the picker that's editing *this* + ;; field. Without it, opening the + ;; Default Icon picker would tint + ;; the page-title's separately-mounted + ;; asset-picker tile (and vice versa). + (= property (:property hover-preview)) (= :avatar (get-in hover-preview [:icon :type]))) (:icon hover-preview)) ;; Committed (hover-free) base: what the avatar would @@ -3642,24 +3690,39 @@ (on-chosen nil (assoc-in preview-icon [:data :shape] new-shape) true)) + ;; All three commit fns clear `:ui/icon-hover-preview` + ;; (so the asset-picker tile stops reading a stale + ;; hover-icon and falls back to the freshly-committed + ;; value) and close the Fallback menu chain via the + ;; controlled `::fallback-menu-open?` atom. Closing the + ;; parent menu Radix-cascades the close into the + ;; sub-content too, so a tile-pick in the sub-picker + ;; dismisses the entire menu — matching the standard + ;; "click an item to commit and dismiss" pattern. + close-fallback-menu! (fn [] + (state/set-state! :ui/icon-hover-preview nil) + (reset! (::fallback-menu-open? state) false)) set-fallback-letters! (fn [] (on-chosen nil (-> preview-icon (assoc-in [:data :fallback-type] :letters) (update :data dissoc :fallback-icon)) - true)) + true) + (close-fallback-menu!)) set-fallback-icon! (fn [icon-name] (on-chosen nil (-> preview-icon (assoc-in [:data :fallback-type] :icon) (assoc-in [:data :fallback-icon] icon-name)) - true)) + true) + (close-fallback-menu!)) set-fallback-emoji! (fn [emoji-id] (on-chosen nil (-> preview-icon (assoc-in [:data :fallback-type] :emoji) (assoc-in [:data :fallback-icon] emoji-id)) - true)) + true) + (close-fallback-menu!)) ;; Live preview while hovering Shape / Fallback dropdown ;; rows. Broadcast a synthetic avatar (committed config ;; with the hovered field changed) into @@ -3670,18 +3733,13 @@ ;; fallback sub-picker uses on tile hover. preview-shape-on-hover! (fn [shape] - (js/console.log "[DEBUG band shape-hover] FIRED shape=" (pr-str shape) - "preview-target-db-id=" preview-target-db-id) (when preview-target-db-id (state/set-state! :ui/icon-hover-preview {:db-id preview-target-db-id - :icon (assoc-in committed-icon [:data :shape] shape)}) - (js/console.log "[DEBUG band shape-hover] AFTER set-state, value=" - (pr-str (:ui/icon-hover-preview @state/state))))) + :property property + :icon (assoc-in committed-icon [:data :shape] shape)}))) preview-fallback-on-hover! (fn [fb-type] - (js/console.log "[DEBUG band fallback-hover] FIRED fb-type=" (pr-str fb-type) - "preview-target-db-id=" preview-target-db-id) (when preview-target-db-id (let [base committed-icon ;; For Letters: drop fallback-icon so the avatar @@ -3704,6 +3762,7 @@ (or current-fb-icon "circle-dashed"))))] (state/set-state! :ui/icon-hover-preview {:db-id preview-target-db-id + :property property :icon next-icon})))) clear-preview-on-leave! (fn [] @@ -3780,14 +3839,7 @@ :aria-label "Customize avatar" :aria-expanded expanded?} [:div.preview-avatar - (icon preview-icon {:size 56}) - [:div.preview-cue - ;; Both glyphs always rendered, stacked; CSS shows the - ;; active one (and crossfades) based on data-expanded. - [:span.preview-cue-glyph.preview-cue-pencil - (shui/tabler-icon "pencil" {:size 11})] - [:span.preview-cue-glyph.preview-cue-check - (shui/tabler-icon "check" {:size 11})]]]] + (icon preview-icon {:size 56})]] [:div.cb-meta-stage ;; Resting state: the entire banner is one click target ;; that toggles the expanded customize panel. Layout: @@ -3843,17 +3895,30 @@ ;; mouseenter is suppressed for the item already ;; under the cursor at open time, which made the ;; first hover silently no-op. + ;; Leading tabler icons follow the codebase's canonical + ;; dropdown-menu-item pattern (see deps/shui/src/logseq/ + ;; shui/demo.cljs:18-50): tabler icon as the first + ;; child with `scale-90 pr-1 opacity-80` so the icon + ;; reads slightly smaller and dimmer than the label. (shui/dropdown-menu-item {:on-click #(set-shape! :circle) :on-focus #(preview-shape-on-hover! :circle)} + (shui/tabler-icon "circle" {:class "scale-90 pr-1 opacity-80"}) "Circle") (shui/dropdown-menu-item {:on-click #(set-shape! :rounded-rect) :on-focus #(preview-shape-on-hover! :rounded-rect)} + (shui/tabler-icon "square-rounded" {:class "scale-90 pr-1 opacity-80"}) "Rectangle")))] [:div.cb-row [:span.cb-label "Fallback"] (shui/dropdown-menu + ;; Controlled open state — see `::fallback-menu-open?` + ;; comment above. The map of props goes as the first + ;; arg to dropdown-menu, before the trigger/content + ;; children. + {:open @(::fallback-menu-open? state) + :on-open-change #(reset! (::fallback-menu-open? state) %)} (shui/dropdown-menu-trigger {:as-child true} [:button.cb-chip @@ -3899,9 +3964,16 @@ (shui/dropdown-menu-content {:align "end" :on-mouse-leave clear-preview-on-leave!} + ;; Same canonical pattern as Shape — tabler icon + ;; first, label after. `letter-case` is the closest + ;; tabler glyph to the chip's "Aa" cue. `circle-dashed` + ;; on the sub-trigger matches the placeholder we + ;; broadcast on hover when no fallback-icon has been + ;; committed yet, so menu and live-preview agree. (shui/dropdown-menu-item {:on-click set-fallback-letters! :on-focus #(preview-fallback-on-hover! :letters)} + (shui/tabler-icon "letter-case" {:class "scale-90 pr-1 opacity-80"}) "Letters") ;; Sub-menu pattern (matches content.cljs's "Add reaction" ;; → emoji picker): "Icon…" expands to the side instead @@ -3912,6 +3984,7 @@ (shui/dropdown-menu-sub (shui/dropdown-menu-sub-trigger {:on-focus #(preview-fallback-on-hover! :icon)} + (shui/tabler-icon "circle-dashed" {:class "scale-90 pr-1 opacity-80"}) "Icon…") ;; `dropdown-menu-sub-content` ships with `p-1` baked ;; into shui's popup-core defaults, AND content.cljs's @@ -3953,10 +4026,7 @@ ;; emojis route to set-fallback-emoji! ;; (which writes :fallback-type :emoji) ;; and tabler icons route to the - ;; existing :icon path. `(:type item)` - ;; is `:icon` or `:tabler-icon` for - ;; tabler tiles and `:emoji` for emoji - ;; tiles after normalization. + ;; existing :icon path. (let [glyph-id (or (get-in icon [:data :value]) (:id icon))] (cond @@ -3992,6 +4062,13 @@ :page-title page-title :preview-target-db-id preview-target-db-id :hover-wrap-fn fallback-hover-wrap-fn + ;; Propagate the asset-picker's property scope to + ;; the sub-picker so its hover broadcasts go to + ;; the same surface (e.g. when editing the class + ;; default-icon, the sub-picker's icon-grid hovers + ;; reach only the Default Icon field, not the + ;; page-title icon). + :property property ;; Suppress the picker's own color swatch and ;; delete button — the parent asset-picker ;; already owns both for the whole avatar, and @@ -5889,8 +5966,16 @@ 0))) s)} [state {:keys [on-chosen del-btn? icon-value page-title preview-target-db-id preview-target-db-ids - allowed-tabs hover-wrap-fn color-btn?] - :or {color-btn? true} + allowed-tabs hover-wrap-fn color-btn? property] + :or {color-btn? true + ;; `property` scopes hover-preview broadcasts so two pickers + ;; on the same entity but different properties (e.g. + ;; page-title's `:logseq.property/icon` and class + ;; default-icon's `:logseq.property.class/default-icon`) + ;; don't leak previews into each other's surfaces. Default + ;; to `:logseq.property/icon` because that's the dominant + ;; case; callers editing other fields pass their own. + property :logseq.property/icon} :as opts}] ;; `color-btn?` defaults to true so existing call sites are unchanged; ;; the avatar fallback sub-picker passes `:color-btn? false` because @@ -5956,18 +6041,28 @@ :custom-image {:type :image-placeholder :id "image-placeholder"}} preview-targets-set? (or preview-target-db-id (seq preview-target-db-ids)) - preview-base-target (cond-> {} + ;; Every preview write carries `:property` so readers in + ;; different fields editing the same entity (page-title icon vs + ;; class default-icon) can isolate by property. The default of + ;; `:logseq.property/icon` matches the existing implicit scope of + ;; sidebar / cmdk / page-title rendering — so unscoped callers + ;; keep working as before. + preview-base-target (cond-> {:property property} preview-target-db-id (assoc :db-id preview-target-db-id) (seq preview-target-db-ids) (assoc :db-ids (set preview-target-db-ids))) clear-tile-hover! (fn [] (when preview-targets-set? - ;; Stale-db-id guard: if a different picker has since taken over - ;; the slot, don't clear its preview. Prevents cleanup-races - ;; between pickers opening on different blocks back-to-back. + ;; Stale-db-id-and-property guard: if a different picker has + ;; since taken over the slot, don't clear its preview. + ;; Prevents cleanup-races between pickers on different blocks + ;; or on the same block but different properties (e.g. opening + ;; the Default Icon picker right after the page-title picker + ;; on the same class). (let [current (:ui/icon-hover-preview @state/state) - mine? (or (= preview-target-db-id (:db-id current)) - (= (set preview-target-db-ids) (:db-ids current)))] + mine? (and (= property (:property current)) + (or (= preview-target-db-id (:db-id current)) + (= (set preview-target-db-ids) (:db-ids current))))] (when (or (nil? current) mine?) (state/set-state! :ui/icon-hover-preview nil))))) broadcast-tile-hover! @@ -6000,8 +6095,12 @@ ;; closure and goes stale across keep-popup? flows (e.g. picking a color ;; on an inherited icon, which writes the icon to the entity for the first ;; time). Treat the :none sentinel (set on delete) as "no icon". + ;; `property` selects the right entity attribute — `:logseq.property/icon` + ;; for page-icon pickers, `:logseq.property.class/default-icon` for the + ;; class default-icon picker. Without this, a default-icon picker would + ;; read the unrelated `:logseq.property/icon` and stay stale across commits. del-btn? (if preview-target-db-id - (let [icon (some-> (model/sub-block preview-target-db-id) :logseq.property/icon)] + (let [icon (some-> (model/sub-block preview-target-db-id) (get property))] (and icon (not= (:type icon) :none))) del-btn?) ;; Same staleness problem applies to icon-value itself. shui's popup @@ -6015,7 +6114,18 @@ ;; downstream asset-picker's preview tile + Shape chip + body grid. icon-value (if preview-target-db-id (or (some-> (model/sub-block preview-target-db-id) - :logseq.property/icon) + ;; Pick the right entity attribute based on + ;; scope — `:logseq.property/icon` for the + ;; page-icon picker, `:logseq.property.class/ + ;; default-icon` for the class default-icon + ;; picker. The Default Icon commit writes to + ;; the latter, so without this lookup the + ;; reactive override returned the unrelated + ;; `:logseq.property/icon` (often nil or the + ;; old page-icon value) and the asset-picker + ;; tile + Fallback chip stayed frozen at the + ;; pre-edit state. + (get property)) icon-value) icon-value) normalized-icon-value (normalize-icon icon-value) @@ -6045,16 +6155,26 @@ (when (:type icon-item) (add-used-item! icon-item))))) *focus-region (::focus-region state) *highlighted-index (::highlighted-index state) + ;; Use `rum/react` (not bare deref) so changes to + ;; `*highlighted-index` from `keyboard-nav-controller` arrow-key + ;; handlers re-render icon-search. Without this, `highlighted-id` + ;; below was computed once at popup-show and never refreshed — + ;; the phantom `icon-hover-effects` component (which broadcasts + ;; the highlighted item to `:ui/icon-hover-preview`) never saw a + ;; fresh `current-id`, so keyboard nav silently no-op'd while + ;; mouse hover worked. One subscription is enough; the other + ;; reads can stay as bare derefs once the component is hooked up. + highlighted-idx (rum/react *highlighted-index) section-states @*section-states {flat-items :items sections :sections} (compute-flat-items @*tab result section-states) - highlighted-id (when-let [idx @*highlighted-index] + highlighted-id (when-let [idx highlighted-idx] (when (< idx (count flat-items)) (:id (nth flat-items idx)))) - highlighted-section (when-let [idx @*highlighted-index] + highlighted-section (when-let [idx highlighted-idx] (when-let [si (section-for-index idx sections)] (:label (nth sections si)))) ghost-highlighted-id (when (and (= @*focus-region :search) - (nil? @*highlighted-index) + (nil? highlighted-idx) (pos? (count flat-items))) (:id (first flat-items))) ghost-highlighted-section (when ghost-highlighted-id @@ -6113,7 +6233,12 @@ ;; here lets the asset-picker's color trigger drive the ;; same preview state. :preview-target-db-id preview-target-db-id - :preview-target-db-ids preview-target-db-ids}) + :preview-target-db-ids preview-target-db-ids + ;; Property scope for hover-preview isolation — + ;; flows down so asset-picker's own preview writes + ;; (Shape/Fallback hovers, color swatch) carry the + ;; same scope and target only the right surfaces. + :property property}) :text-picker ;; Level 2: Text Picker view @@ -6206,9 +6331,9 @@ ;; host React hooks (class component via rum/reactive mixin). (icon-hover-effects {:current-id highlighted-id - :current-item (when (and @*highlighted-index - (< @*highlighted-index (count flat-items))) - (nth flat-items @*highlighted-index)) + :current-item (when (and highlighted-idx + (< highlighted-idx (count flat-items))) + (nth flat-items highlighted-idx)) :broadcast! broadcast-tile-hover! :clear! clear-tile-hover!}) @@ -6311,7 +6436,7 @@ :on-hover! (when (or preview-target-db-id (seq preview-target-db-ids)) (fn [c] (state/set-state! :ui/icon-hover-preview - (cond-> {:color c} + (cond-> {:color c :property property} preview-target-db-id (assoc :db-id preview-target-db-id) (seq preview-target-db-ids) (assoc :db-ids (set preview-target-db-ids)))))) :on-hover-end! (when (or preview-target-db-id (seq preview-target-db-ids)) @@ -6453,10 +6578,17 @@ (rum/defc icon-picker-trigger-icon < rum/reactive "Reactive sub-component so the trigger icon re-renders on hover-preview changes - without forcing the parent (which uses React hooks) into a class component." - [icon-value preview-target-db-id icon-props] - (let [preview (when preview-target-db-id (state/sub :ui/icon-hover-preview)) - preview-active? (and preview (= (:db-id preview) preview-target-db-id)) + without forcing the parent (which uses React hooks) into a class component. + `property` scopes the preview match — defaults to `:logseq.property/icon` + so existing callers keep their behavior; property/value.cljs's + `default-icon-row` passes `:logseq.property.class/default-icon` so its + trigger only reflects previews from a Default-Icon-scoped picker." + [icon-value preview-target-db-id icon-props & [property]] + (let [property (or property :logseq.property/icon) + preview (when preview-target-db-id (state/sub :ui/icon-hover-preview)) + preview-active? (and preview + (= (:db-id preview) preview-target-db-id) + (= (:property preview) property)) preview-icon (when preview-active? (:icon preview)) ;; Source: previewed icon (cross-type swap) or the committed value. ;; Both go through the same color-overlay path below. @@ -6484,7 +6616,8 @@ (icon effective-icon-value (merge {:color? true} icon-props)))) (rum/defc icon-picker - [icon-value {:keys [empty-label disabled? initial-open? del-btn? on-chosen icon-props popup-opts button-opts page-title preview-target-db-id default-icon?]}] + [icon-value {:keys [empty-label disabled? initial-open? del-btn? on-chosen icon-props popup-opts button-opts page-title preview-target-db-id default-icon? property] + :or {property :logseq.property/icon}}] (let [*trigger-ref (rum/use-ref nil) ;; Optimistic post-commit override. Holds the just-committed ;; icon-value during the ~15ms SharedWorker round-trip between @@ -6524,7 +6657,8 @@ :page-title page-title :del-btn? del-btn? :preview-target-db-id preview-target-db-id - :default-icon? default-icon?})))] + :default-icon? default-icon? + :property property})))] (hooks/use-effect! (fn [] (when initial-open? @@ -6564,5 +6698,5 @@ (if has-icon? (if (vector? effective-icon-value) ; hiccup effective-icon-value - (icon-picker-trigger-icon effective-icon-value preview-target-db-id icon-props)) + (icon-picker-trigger-icon effective-icon-value preview-target-db-id icon-props property)) (or empty-label "Empty")))))) diff --git a/src/main/frontend/components/icon.css b/src/main/frontend/components/icon.css index e74e28fc99..e3cf79550a 100644 --- a/src/main/frontend/components/icon.css +++ b/src/main/frontend/components/icon.css @@ -1981,8 +1981,15 @@ gap: 10px; padding: 0; min-height: 32px; - transition: padding 200ms cubic-bezier(0.32, 0.72, 0, 1), - gap 200ms cubic-bezier(0.32, 0.72, 0, 1); + /* Padding and gap snap rather than transition. Earlier this row + animated 2 layout properties concurrently with 4 more across + `.cb-avatar-trigger` and `.cb-rail-wrap` — six layout-triggering + properties on three nested elements per frame. Even on Chrome the + work is non-trivial; on browsers with weaker GPU acceleration + (Dia, etc.) it drops frames perceptibly. Snapping these two is a + ~14px height + 4px gap jump at t=0, which the avatar morph + immediately covers — effectively invisible during user testing, + but cuts ~33% of per-frame layout work. */ } .avatar-customize-zone[data-expanded="true"] .cb-content { @@ -2006,9 +2013,15 @@ applies its own padding-top, so we drop this margin to 0 to avoid double-spacing. */ margin-top: 6px; + /* Animate only width + height (the visible morph). The 6px → 0px + margin-top shift snaps because (a) on browsers that struggle with + layout-heavy per-frame work, every property dropped is one fewer + reflow, and (b) the snap happens at t=0 while the avatar starts + growing — the size morph hides the position snap. `will-change` + deliberately omitted: for layout properties it doesn't help and + can confuse some compositors into spurious layer creation. */ transition: width 200ms cubic-bezier(0.32, 0.72, 0, 1), - height 200ms cubic-bezier(0.32, 0.72, 0, 1), - margin-top 200ms cubic-bezier(0.32, 0.72, 0, 1); + height 200ms cubic-bezier(0.32, 0.72, 0, 1); &:focus-visible { outline: 2px solid var(--lx-accent-09, var(--rx-accent-09)); @@ -2028,50 +2041,6 @@ width: 100% !important; height: 100% !important; } - - .preview-cue { - position: absolute; - bottom: -3px; - right: -3px; - width: 18px; - height: 18px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - background: var(--lx-gray-02, var(--rx-gray-02)); - border: 1px solid var(--lx-gray-05, var(--rx-gray-05)); - color: var(--lx-gray-11, var(--rx-gray-11)); - /* Hide the cue in compact state — at 18px the cue would crowd the - small avatar and add no information (the "Use icon" CTA on the - right is the affordance for compact state). It fades back in as - the avatar grows on expand. */ - opacity: 0; - transition: opacity 200ms cubic-bezier(0.32, 0.72, 0, 1), - background-color 160ms cubic-bezier(0.32, 0.72, 0, 1), - border-color 160ms cubic-bezier(0.32, 0.72, 0, 1), - color 160ms cubic-bezier(0.32, 0.72, 0, 1); - } - - &:hover .preview-cue { - border-color: var(--lx-gray-07, var(--rx-gray-07)); - } - - /* Both glyphs (pencil + check) overlap in the cue badge. Each crossfades - based on the parent zone's data-expanded attribute. transform pops them - onto their own layer so they don't repaint the avatar tile during the - swap. */ - .preview-cue-glyph { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - transition: opacity 160ms cubic-bezier(0.32, 0.72, 0, 1); - } - - .preview-cue-pencil { opacity: 1; } - .preview-cue-check { opacity: 0; } } /* Expanded state: drop the compact-state margin-top so the 56px avatar @@ -2106,16 +2075,6 @@ height: 11px !important; } -.avatar-customize-zone[data-expanded="true"] .preview-cue { - background: var(--lx-accent-09, var(--rx-accent-09)); - border-color: var(--lx-gray-02, var(--rx-gray-02)); - color: white; - opacity: 1; -} - -.avatar-customize-zone[data-expanded="true"] .preview-cue-pencil { opacity: 0; } -.avatar-customize-zone[data-expanded="true"] .preview-cue-check { opacity: 1; } - /* The right column hosts both .cb-banner (resting) and .cb-rows (expanded) on top of each other. Only the "active" child claims layout space — the other is `position: absolute` so it sits over the active one @@ -2410,6 +2369,7 @@ } } + /* Reset/Done rail — only shown when expanded; sits below the row stack separated by a 1px divider that runs the band's full width. */ /* Rail wrapper — height collapses to 0 in resting state, transitions to @@ -2454,6 +2414,21 @@ opacity: 1; } +/* Pre-warming pass — see asset-picker `:did-mount` (icon.cljs ~2900). + On first mount we briefly set `data-expanded` so the browser computes + the expanded layout once (caching it for the user's first real + click); during that pass we suppress every transition under the zone + so the user doesn't see a flash. The `*::before` pseudo-element + (gradient backdrop) needs the same treatment so its opacity fade + doesn't run during the warm-up. */ +.avatar-customize-zone[data-prewarming], +.avatar-customize-zone[data-prewarming] *, +.avatar-customize-zone[data-prewarming] *::before, +.avatar-customize-zone[data-prewarming] *::after { + transition: none !important; + animation: none !important; +} + .avatar-customize-zone .cb-rail { display: flex; align-items: center; diff --git a/src/main/frontend/components/property/value.cljs b/src/main/frontend/components/property/value.cljs index d63054df2d..0360934d51 100644 --- a/src/main/frontend/components/property/value.cljs +++ b/src/main/frontend/components/property/value.cljs @@ -146,12 +146,18 @@ (icon-component/icon-search {:on-chosen on-chosen! :icon-value icon - :del-btn? (some? icon)}) + :del-btn? (some? icon) + :preview-target-db-id (:db/id block) + ;; This row edits `:logseq.property/icon` — the same scope the + ;; page-title icon uses. Hover broadcasts target both surfaces. + :property :logseq.property/icon}) [:div.col-span-3.flex.flex-row.items-center.gap-2 (icon-component/icon-picker icon-value {:disabled? config/publishing? :del-btn? (some? icon-value) - :on-chosen on-chosen!})]))) + :on-chosen on-chosen! + :preview-target-db-id (:db/id block) + :property :logseq.property/icon})]))) (rum/defc default-icon-row < rum/reactive "Renders the Default Icon property for classes. @@ -213,6 +219,14 @@ :on-chosen on-chosen :page-title page-title :default-icon? true + :preview-target-db-id (:db/id block) + ;; Scope the hover-preview to the + ;; default-icon property so this row's + ;; picker doesn't leak previews into + ;; the page-title icon (which subscribes + ;; to `:logseq.property/icon`-scoped + ;; previews on the same class entity). + :property :logseq.property.class/default-icon :icon-props {:size 20}})])) (defn select-type?