diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index 525b1d0613..e81f841baf 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -367,6 +367,9 @@ (when (cf/check-browser? :safari) (mf/deref refs/selected-zoom)) + vbox + (mf/deref refs/vbox) + shape (cond-> shape (some? text-modifier) (dwt/apply-text-modifier text-modifier) @@ -385,13 +388,20 @@ selrect-width (:width selrect) max-width (max width selrect-width) max-height (max height selrect-height) + ;; During auto-width editing we keep the shape width trimmed, but the caret + ;; must be able to move after trailing spaces. Expand only the editor + ;; overlay up to one viewport width to avoid clipping caret rendering. + viewport-width (or (:width vbox) 0) + overlay-width (if (= (:grow-type shape) :auto-width) + (+ max-width viewport-width) + max-width) valign (-> shape :content :vertical-align) y (:y selrect) y (case valign "bottom" (+ y (- selrect-height height)) "center" (+ y (/ (- selrect-height height) 2)) y)] - [(assoc selrect :y y :width max-width :height max-height) transform]) + [(assoc selrect :y y :width overlay-width :height max-height) transform]) (let [bounds (gst/shape->rect shape) x (mth/min (dm/get-prop bounds :x) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss index b06ed24005..21a3db0497 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss @@ -48,7 +48,10 @@ font-size: 0; } - [data-itype="inline"] { + // Text spans emitted by @penpot/text-editor use `data-itype="span"`. + // Keep whitespace rules attached to the real node type so trailing spaces + // are handled consistently while editing. + [data-itype="span"] { box-sizing: content-box; display: inline; line-height: inherit; @@ -68,11 +71,11 @@ .grow-type-auto-width { [data-itype="span"], [data-itype="paragraph"] { - white-space: nowrap; - } - - [data-itype="span"] { - white-space-collapse: preserve; + // Keep auto-width editing on a single preformatted line so trailing + // spaces are part of caret geometry and browser selection math. + white-space: pre; + overflow-wrap: normal; + word-break: keep-all; } } diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 151d380e7d..eb61350ba7 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -157,6 +157,10 @@ text-editor-ref (mf/use-ref nil) last-vern-ref (mf/use-ref nil) + ;; WASM grid overlay was visible last run (`hover-grid?` true). Used so `clear-grid` + ;; (expensive: `_hide_grid` + full `request-render`) runs only when hiding the overlay. + prev-hover-grid-shown?-ref (mf/use-ref false) + ;; STATE REFS disable-paste-ref (mf/use-ref false) in-viewport-ref (mf/use-ref false) @@ -253,9 +257,10 @@ (not page-transition?)) show-text-editor? (and editing-shape (= :text (:type editing-shape)) (not page-transition?)) - hover-grid? (and (some? @hover-top-frame-id) - (ctl/grid-layout? objects @hover-top-frame-id) - (not page-transition?)) + has-grid? (and (some? @hover-top-frame-id) + (ctl/grid-layout? objects @hover-top-frame-id)) + + hover-grid? (and has-grid? (not page-transition?)) show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape) (not page-transition?)) show-presence? (and page-id (not page-transition?)) @@ -427,11 +432,19 @@ (when (and @canvas-init? @initialized?) (wasm.api/set-canvas-background background))) - (mf/with-effect [@canvas-init? hover-grid? @hover-top-frame-id] + ;; Grid overlay: `clear-grid` must run only when the overlay was shown and is now off + ;; (e.g. leave grid frame, or `page-transition?`). Do not call it on every + ;; `hover-top-frame-id` change while not hovering a grid frame. + (mf/with-effect [@canvas-init? hover-grid?] (when @canvas-init? - (if hover-grid? - (wasm.api/show-grid @hover-top-frame-id) - (wasm.api/clear-grid)))) + (when (and (not hover-grid?) (mf/ref-val prev-hover-grid-shown?-ref)) + (wasm.api/clear-grid)) + (mf/set-ref-val! prev-hover-grid-shown?-ref hover-grid?))) + + (mf/with-effect [@canvas-init? has-grid? hover-grid? + (if (and has-grid? hover-grid?) @hover-top-frame-id ::no-grid-hover-id)] + (when (and @canvas-init? hover-grid?) + (wasm.api/show-grid @hover-top-frame-id))) (hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?) (hooks/setup-viewport-size vport viewport-ref) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index c3e1a899c0..1f16c29779 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -334,7 +334,7 @@ (defn request-render [_requester] - (when (and wasm/context-initialized? (not @wasm/context-lost?)) + (when (and wasm/context-initialized? (not @wasm/context-lost?) (not @wasm/disable-request-render?)) (if @shapes-loading? (register-deferred-render!) (when-not @pending-render @@ -1699,6 +1699,8 @@ [] (when wasm/context-initialized? (try + (set! wasm/context-initialized? false) + ;; Cancel any pending animation frame to prevent race conditions (when wasm/internal-frame-id (js/cancelAnimationFrame wasm/internal-frame-id) @@ -1709,8 +1711,6 @@ (reset! shapes-loading? false) (reset! deferred-render? false) - ;; TODO: perform corresponding cleaning - (set! wasm/context-initialized? false) (h/call wasm/internal-module "_clean_up") ;; Remove event listener for WebGL context lost diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index 5c43ba4899..25c2908575 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -26,6 +26,9 @@ (defonce context-initialized? false) (defonce context-lost? (atom false)) +;; When we're rendering in a sync way we want to stop the asynchrous `request-render` +(defonce disable-request-render? (atom false)) + (defonce serializers #js {:blur-type shared/RawBlurType diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index d90536619d..d7692d63d4 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -170,6 +170,7 @@ (render/calculate-dimensions objects thumbnail-aspect-ratio)) zoom (/ width (:width vbox))] + (reset! wasm/disable-request-render? true) (wasm.api/initialize-viewport objects zoom vbox :background bgcolor diff --git a/mcp/package.json b/mcp/package.json index 6324fe3898..2318a80951 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/mcp", - "version": "2.15.0-rc.1.153", + "version": "2.16.0-rc.1.206", "description": "MCP server for Penpot integration", "bin": { "penpot-mcp": "./bin/mcp-local.js" diff --git a/mcp/scripts/set-version b/mcp/scripts/set-version old mode 100644 new mode 100755 diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index 7661d93088..e4f0d29311 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -551,30 +551,27 @@ fn child_position( child_axis: &ChildAxis, track: &TrackData, ) -> Point { + let mid_point = (track.across_size - child_axis.across_size + child_axis.margin_across_start + - child_axis.margin_across_end) + / 2.0; + let end_point = track.across_size - child_axis.across_size - child_axis.margin_across_end; + let across_position = match child.layout_item { Some(LayoutItem { align_self: Some(align_self), .. }) => match align_self { - AlignSelf::Center => { - (track.across_size - child_axis.across_size + child_axis.margin_across_start - - child_axis.margin_across_end) - / 2.0 - } - AlignSelf::End => { - track.across_size - child_axis.across_size - child_axis.margin_across_end - } - _ => child_axis.margin_across_start, + AlignSelf::Center => mid_point, + AlignSelf::End => end_point, + _ => match layout_data.align_items { + AlignItems::Center => mid_point, + AlignItems::End => end_point, + _ => child_axis.margin_across_start, + }, }, _ => match layout_data.align_items { - AlignItems::Center => { - (track.across_size - child_axis.across_size + child_axis.margin_across_start - - child_axis.margin_across_end) - / 2.0 - } - AlignItems::End => { - track.across_size - child_axis.across_size - child_axis.margin_across_end - } + AlignItems::Center => mid_point, + AlignItems::End => end_point, _ => child_axis.margin_across_start, }, };