✨ Place a swapped child where the new variant has it

A variant switch with keep-touched carries a swapped child into the
new variant, but left it where it was in the old one. The next sync
then moved it to the place of the child it replaces, at a moment
unrelated to the switch. The switch now gives it that placement at
once, through the same step sync uses: position, constraints and
layout item attributes the user did not change.

Benchmark, against the same code without this change under the same
load (lower quantile, ms; without / with):
  deepest-edit-through-three-levels    36.0 /  36.0
  edit-through-a-chain-of-dependents   65.2 /  70.2
  library-update-into-copies           74.3 /  75.9
  main-edit-into-10-copies              1.6 /   1.5
  main-edit-into-100-copies            16.3 /  15.7
  main-edit-into-1000-copies          162.6 / 165.8
  variant-switch-into-copies           27.2 /  27.8

AI-assisted-by: claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
alonso.torres 2026-10-13 10:41:11 +02:00
parent 7105f3038e
commit 40e2b2934a
3 changed files with 43 additions and 14 deletions

View File

@ -2416,19 +2416,20 @@
(assoc attr (get origin attr)))))
dest))))
(defn- update-swapped-placement
"Synchronizes a swapped copy head with the main shape it replaces, in the
attributes that place it in its parent: position, constraints and layout
item attributes. Its content comes from its own component. A position set
by a layout is left to the layout."
[changes shape-inst shape-main root-inst root-main container]
(let [objects (:objects container)
touched (get shape-inst :touched #{})
(defn generate-take-placement
"Give the swapped copy head `shape-inst` the placement of `placed`, the
shape whose position it takes, expressed in the same coordinates: the
attributes that place it in its parent (position, constraints and layout
item attributes) that the user did not change. Its content comes from its
own component. With `layout-placed?`, the position is left to the layout.
`objects` holds the subtree of `shape-inst`, which moves with it."
[changes shape-inst placed objects layout-placed?]
(let [touched (get shape-inst :touched #{})
untouched? (fn [attr]
(not (touched (ctk/resolve-sync-group (:type shape-inst) attr))))
placement (reduce (fn [attrs attr]
(let [value (get shape-main attr)]
(let [value (get placed attr)]
(if (and (untouched? attr)
(not= value (get shape-inst attr)))
(assoc attrs attr value)
@ -2439,12 +2440,10 @@
position (fn [shape]
(gpt/point (dm/get-in shape [:selrect :x])
(dm/get-in shape [:selrect :y])))
delta (gpt/subtract (position (reposition-shape shape-main root-main root-inst))
(position shape-inst))
delta (gpt/subtract (position placed) (position shape-inst))
move? (and (untouched? :x)
(not (gpt/almost-zero? delta))
(or (not (ctsl/any-layout-immediate-child? objects shape-inst))
(ctsl/position-absolute? shape-inst)))]
(not layout-placed?)
(not (gpt/almost-zero? delta)))]
(cond-> changes
(seq placement)
(pcb/update-shapes [(:id shape-inst)]
@ -2462,6 +2461,18 @@
#(gsh/move % delta)
{:ignore-touched true}))))
(defn- update-swapped-placement
"Synchronizes a swapped copy head with the main shape it replaces, in the
attributes that place it in its parent."
[changes shape-inst shape-main root-inst root-main container]
(let [objects (:objects container)]
(generate-take-placement changes
shape-inst
(reposition-shape shape-main root-main root-inst)
objects
(and (ctsl/any-layout-immediate-child? objects shape-inst)
(not (ctsl/position-absolute? shape-inst))))))
(defn- update-flex-child-copy-attrs
"Synchronizes the attributes inside the flex-child items (main->copy)"
[changes shape-main shape-copy main-container main-component copy-container omit-touched?]

View File

@ -102,6 +102,13 @@
(= (:component-file %) (:component-file related-shape-in-new)))
(ctk/remove-swap-group)))
;; and its placement, as sync would give it
(cll/generate-take-placement orig-swapped-child
related-shape-in-new
objects
(and (ctsl/any-layout-immediate-child? objects related-shape-in-new)
(not (ctsl/position-absolute? orig-swapped-child))))
;; Delete new non-swapped item
(cls/generate-delete-shapes ldata page objects (d/ordered-set (:id related-shape-in-new)) {:allow-altering-copies true})
second)))

View File

@ -475,3 +475,14 @@
(propagate :card)
(thf/validate-file!))]
(t/is (= margin (:layout-item-margin (ths/get-shape file :copy-star))))))
(t/deftest switch-places-the-swap-where-the-new-variant-has-its-child
(let [file (-> (setup-variants)
(move-shape :child2 30 5))
rel (fn [file child parent]
(let [[x y] (position file child)
[px py] (position file parent)]
[(- x px) (- y py)]))
file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})]
(t/is (ctk/swapped? (ths/get-shape file' :copy-child)))
(t/is (= (rel file' :child2 :m02) (rel file' :copy-child :copy01)))))