mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
9106a994f1
19
CHANGES.md
19
CHANGES.md
@ -74,6 +74,25 @@
|
||||
- Fix text editor v1 focus [Taiga #13961](https://tree.taiga.io/project/penpot/issue/13961)
|
||||
|
||||
|
||||
## 2.14.3 (Unreleased)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- Add webp export format to plugin types [Github #8870](https://github.com/penpot/penpot/pull/8870)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix component "broken" after switch variant [Taiga #12984](https://tree.taiga.io/project/penpot/issue/12984)
|
||||
- Fix variants corner cases with selrect and points [Github #8882](https://github.com/penpot/penpot/pull/8882)
|
||||
- Fix dashboard navigation tabs overlap with projects content when scrolling [Taiga #13962](https://tree.taiga.io/project/penpot/issue/13962)
|
||||
- Fix text editor v1 focus [Taiga #13961](https://tree.taiga.io/project/penpot/issue/13961)
|
||||
- Fix highlight on frames after rename [Github #8938](https://github.com/penpot/penpot/pull/8938)
|
||||
- Fix TypeError in sd-token-uuid when resolving tokens interactively [Github #8929](https://github.com/penpot/penpot/pull/8929)
|
||||
- Fix path drawing preview passing shape instead of content to next-node
|
||||
- Fix swapped arguments in CLJS PathData `-nth` with default
|
||||
- Normalize PathData coordinates to safe integer bounds on read
|
||||
|
||||
|
||||
## 2.14.2
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
@ -2016,14 +2016,17 @@
|
||||
(defn- switch-fixed-layout-geom-change-value
|
||||
[prev-shape ; The shape before the switch
|
||||
current-shape ; The shape after the switch (a clean copy)
|
||||
origin-shape ; The original shape
|
||||
attr]
|
||||
;; When there is a layout with fixed h or v sizing, we need
|
||||
;; to keep the width/height (and recalculate selrect and points)
|
||||
(let [prev-width (-> prev-shape :selrect :width)
|
||||
current-width (-> current-shape :selrect :width)
|
||||
origin-width (-> origin-shape :selrect :width)
|
||||
|
||||
prev-height (-> prev-shape :selrect :height)
|
||||
current-height (-> current-shape :selrect :height)
|
||||
origin-height (-> origin-shape :selrect :height)
|
||||
|
||||
x (-> current-shape :selrect :x)
|
||||
y (-> current-shape :selrect :y)
|
||||
@ -2034,10 +2037,16 @@
|
||||
|
||||
final-width (if (= :fix h-sizing)
|
||||
current-width
|
||||
prev-width)
|
||||
(if (= origin-width current-width)
|
||||
prev-width ;; same-size: preserve override
|
||||
current-width)) ;; different-size: use new component's
|
||||
|
||||
final-height (if (= :fix v-sizing)
|
||||
current-height
|
||||
prev-height)
|
||||
(if (= origin-height current-height)
|
||||
prev-height ;; same-size: preserve override
|
||||
current-height)) ;; different-size: use new component's
|
||||
|
||||
selrect (assoc (:selrect current-shape)
|
||||
:width final-width
|
||||
:height final-height
|
||||
@ -2066,6 +2075,25 @@
|
||||
(or (:transform current-shape) (gmt/matrix)))))))
|
||||
|
||||
|
||||
(defn- equal-geometry?
|
||||
"Returns true when the value of `attr` in `shape` is considered equal
|
||||
to the corresponding value in `origin-shape`, ignoring positional
|
||||
displacement (x/y).
|
||||
For :selrect we compare width/height only;
|
||||
for :points we normalise each vector so the first point is the
|
||||
origin before comparing."
|
||||
[shape origin-shape attr]
|
||||
(or (and (= attr :selrect)
|
||||
(= (-> shape :selrect :width) (-> origin-shape :selrect :width))
|
||||
(= (-> shape :selrect :height) (-> origin-shape :selrect :height)))
|
||||
(and (= attr :points)
|
||||
(let [normalize-pts (fn [pts]
|
||||
(when (seq pts)
|
||||
(let [f (first pts)]
|
||||
(mapv #(gpt/subtract % f) pts))))]
|
||||
(= (normalize-pts (get shape :points))
|
||||
(normalize-pts (get origin-shape :points)))))))
|
||||
|
||||
|
||||
(defn update-attrs-on-switch
|
||||
"Copy attributes that have changed in the shape previous to the switch
|
||||
@ -2104,8 +2132,9 @@
|
||||
;; If the values are already equal, don't copy them
|
||||
(= (get previous-shape attr) (get current-shape attr))
|
||||
|
||||
;; If the value is the same as the origin, don't copy it
|
||||
(= (get previous-shape attr) (get origin-ref-shape attr))
|
||||
;; If :selrect/:points values are already equal ignoring displacement,
|
||||
;; don't copy them
|
||||
(equal-geometry? previous-shape origin-ref-shape attr)
|
||||
|
||||
;; If the attr is not touched, don't copy it
|
||||
(not (touched sync-group))
|
||||
@ -2154,8 +2183,21 @@
|
||||
|
||||
skip-operations? (or skip-operations?
|
||||
;; If we are going to reset the position data, skip the selrect attr
|
||||
(and reset-pos-data? (= attr :selrect)))
|
||||
|
||||
(and reset-pos-data? (= attr :selrect))
|
||||
;; Avoid copying composite geometry attrs (:selrect/:points) when the
|
||||
;; variant dimensions differ but neither sizing is :fix. Without this,
|
||||
;; :width/:height are correctly skipped by the check above
|
||||
;; but :selrect/:points would still carry the old override dimensions,
|
||||
;; leaving the shape in an inconsistent state. When :fix sizing is
|
||||
;; present, switch-fixed-layout-geom-change-value handles the composite
|
||||
;; attrs and must NOT be bypassed. Path shapes are also handled
|
||||
;; separately via switch-path-change-value.
|
||||
(and (contains? #{:selrect :points} attr)
|
||||
(not path-change?)
|
||||
(not (or (= :fix (:layout-item-h-sizing previous-shape))
|
||||
(= :fix (:layout-item-v-sizing previous-shape))))
|
||||
(or (not= (get origin-ref-shape :width) (get current-shape :width))
|
||||
(not= (get origin-ref-shape :height) (get current-shape :height)))))
|
||||
attr-val
|
||||
(when-not skip-operations?
|
||||
(cond
|
||||
@ -2179,7 +2221,7 @@
|
||||
(and (or (= :fix (:layout-item-h-sizing previous-shape))
|
||||
(= :fix (:layout-item-v-sizing previous-shape)))
|
||||
(contains? #{:points :selrect :width :height} attr))
|
||||
(switch-fixed-layout-geom-change-value previous-shape current-shape attr)
|
||||
(switch-fixed-layout-geom-change-value previous-shape current-shape origin-ref-shape attr)
|
||||
|
||||
:else
|
||||
(get previous-shape attr)))
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
(def ^:cosnt bool-group-style-properties bool/group-style-properties)
|
||||
(def ^:const bool-group-style-properties bool/group-style-properties)
|
||||
(def ^:const bool-style-properties bool/style-properties)
|
||||
|
||||
(defn get-default-bool-fills
|
||||
@ -79,7 +79,7 @@
|
||||
(defn close-subpaths
|
||||
"Given a content, searches a path for possible subpaths that can
|
||||
create closed loops and merge them; then return the transformed path
|
||||
conten as PathData instance"
|
||||
content as PathData instance"
|
||||
[content]
|
||||
(-> (subpath/close-subpaths content)
|
||||
(impl/from-plain)))
|
||||
|
||||
@ -30,6 +30,18 @@
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
(def ^:const SEGMENT-U8-SIZE 28)
|
||||
|
||||
(defn- normalize-coord
|
||||
"Normalize a coordinate value to be within safe integer bounds.
|
||||
Clamps values greater than max-safe-int to max-safe-int,
|
||||
and values less than min-safe-int to min-safe-int.
|
||||
Always returns a double."
|
||||
[v]
|
||||
(cond
|
||||
(> v sm/max-safe-int) (double sm/max-safe-int)
|
||||
(< v sm/min-safe-int) (double sm/min-safe-int)
|
||||
:else (double v)))
|
||||
|
||||
(def ^:const SEGMENT-U32-SIZE (/ SEGMENT-U8-SIZE 4))
|
||||
|
||||
(defprotocol IPathData
|
||||
@ -121,12 +133,12 @@
|
||||
(if (< index size)
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)
|
||||
c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))
|
||||
c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))
|
||||
type (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
@ -148,12 +160,12 @@
|
||||
(if (< index size)
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)
|
||||
c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))
|
||||
c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))
|
||||
type (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
@ -172,12 +184,12 @@
|
||||
[buffer index f]
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)
|
||||
c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))
|
||||
c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))
|
||||
type (case type
|
||||
1 :move-to
|
||||
2 :line-to
|
||||
@ -252,31 +264,31 @@
|
||||
(let [offset (* index SEGMENT-U8-SIZE)
|
||||
type (buf/read-short buffer offset)]
|
||||
(case (long type)
|
||||
1 (let [x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))]
|
||||
1 (let [x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))]
|
||||
{:command :move-to
|
||||
:params {:x (double x)
|
||||
:y (double y)}})
|
||||
:params {:x x
|
||||
:y y}})
|
||||
|
||||
2 (let [x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))]
|
||||
2 (let [x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))]
|
||||
{:command :line-to
|
||||
:params {:x (double x)
|
||||
:y (double y)}})
|
||||
:params {:x x
|
||||
:y y}})
|
||||
|
||||
3 (let [c1x (buf/read-float buffer (+ offset 4))
|
||||
c1y (buf/read-float buffer (+ offset 8))
|
||||
c2x (buf/read-float buffer (+ offset 12))
|
||||
c2y (buf/read-float buffer (+ offset 16))
|
||||
x (buf/read-float buffer (+ offset 20))
|
||||
y (buf/read-float buffer (+ offset 24))]
|
||||
3 (let [c1x (normalize-coord (buf/read-float buffer (+ offset 4)))
|
||||
c1y (normalize-coord (buf/read-float buffer (+ offset 8)))
|
||||
c2x (normalize-coord (buf/read-float buffer (+ offset 12)))
|
||||
c2y (normalize-coord (buf/read-float buffer (+ offset 16)))
|
||||
x (normalize-coord (buf/read-float buffer (+ offset 20)))
|
||||
y (normalize-coord (buf/read-float buffer (+ offset 24)))]
|
||||
{:command :curve-to
|
||||
:params {:x (double x)
|
||||
:y (double y)
|
||||
:c1x (double c1x)
|
||||
:c1y (double c1y)
|
||||
:c2x (double c2x)
|
||||
:c2y (double c2y)}})
|
||||
:params {:x x
|
||||
:y y
|
||||
:c1x c1x
|
||||
:c1y c1y
|
||||
:c2x c2x
|
||||
:c2y c2y}})
|
||||
|
||||
4 {:command :close-path
|
||||
:params {}}
|
||||
@ -462,7 +474,7 @@
|
||||
nil))
|
||||
|
||||
(-nth [_ i default]
|
||||
(if (d/in-range? i size)
|
||||
(if (d/in-range? size i)
|
||||
(read-segment buffer i)
|
||||
default))
|
||||
|
||||
@ -666,8 +678,6 @@
|
||||
(defn from-plain
|
||||
"Create a PathData instance from plain data structures"
|
||||
[segments]
|
||||
(assert (check-plain-content segments))
|
||||
|
||||
(let [total (count segments)
|
||||
buffer (buf/allocate (* total SEGMENT-U8-SIZE))]
|
||||
(loop [index 0]
|
||||
@ -677,30 +687,28 @@
|
||||
(case (get segment :command)
|
||||
:move-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))]
|
||||
x (normalize-coord (get params :x))
|
||||
y (normalize-coord (get params :y))]
|
||||
(buf/write-short buffer offset 1)
|
||||
(buf/write-float buffer (+ offset 20) x)
|
||||
(buf/write-float buffer (+ offset 24) y))
|
||||
|
||||
:line-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))]
|
||||
|
||||
x (normalize-coord (get params :x))
|
||||
y (normalize-coord (get params :y))]
|
||||
(buf/write-short buffer offset 2)
|
||||
(buf/write-float buffer (+ offset 20) x)
|
||||
(buf/write-float buffer (+ offset 24) y))
|
||||
|
||||
:curve-to
|
||||
(let [params (get segment :params)
|
||||
x (float (get params :x))
|
||||
y (float (get params :y))
|
||||
c1x (float (get params :c1x x))
|
||||
c1y (float (get params :c1y y))
|
||||
c2x (float (get params :c2x x))
|
||||
c2y (float (get params :c2y y))]
|
||||
|
||||
x (normalize-coord (get params :x))
|
||||
y (normalize-coord (get params :y))
|
||||
c1x (normalize-coord (get params :c1x x))
|
||||
c1y (normalize-coord (get params :c1y y))
|
||||
c2x (normalize-coord (get params :c2x x))
|
||||
c2y (normalize-coord (get params :c2y y))]
|
||||
(buf/write-short buffer offset 3)
|
||||
(buf/write-float buffer (+ offset 4) c1x)
|
||||
(buf/write-float buffer (+ offset 8) c1y)
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
(map (fn [[index _]] index))))
|
||||
|
||||
(defn handler-indices
|
||||
"Return an index where the key is the positions and the values the handlers"
|
||||
"Returns [[index prefix] ...] of all handlers associated with point."
|
||||
[content point]
|
||||
(->> (d/with-prev content)
|
||||
(d/enumerate)
|
||||
@ -76,7 +76,7 @@
|
||||
[])))))
|
||||
|
||||
(defn opposite-index
|
||||
"Calculates the opposite index given a prefix and an index"
|
||||
"Calculates the opposite handler index given a content, index and prefix."
|
||||
[content index prefix]
|
||||
|
||||
(let [point (if (= prefix :c2)
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
(d/without-keys shape dissoc-attrs))
|
||||
|
||||
(defn- make-corner-arc
|
||||
"Creates a curvle corner for border radius"
|
||||
"Creates a curve corner for border radius"
|
||||
[from to corner radius]
|
||||
(let [x (case corner
|
||||
:top-left (:x from)
|
||||
|
||||
@ -501,17 +501,15 @@
|
||||
|
||||
(defn backtrace-tokens-tree
|
||||
"Convert tokens into a nested tree with their name as the path.
|
||||
Generates a uuid per token to backtrace a token from an external source (StyleDictionary).
|
||||
Uses the existing token :id to backtrace a token from an external source (StyleDictionary).
|
||||
The backtrace can't be the name as the name might not exist when the user is creating a token."
|
||||
[tokens]
|
||||
(reduce
|
||||
(fn [acc [_ token]]
|
||||
(let [temp-id (random-uuid)
|
||||
token (assoc token :temp/id temp-id)
|
||||
path (get-token-path token)]
|
||||
(let [path (get-token-path token)]
|
||||
(-> acc
|
||||
(assoc-in (concat [:tokens-tree] path) token)
|
||||
(assoc-in [:ids temp-id] token))))
|
||||
(assoc-in [:ids (:id token)] token))))
|
||||
{:tokens-tree {} :ids {}}
|
||||
tokens))
|
||||
|
||||
|
||||
@ -2257,4 +2257,469 @@
|
||||
;; or if it needs recalculation, the test validates the behavior
|
||||
(t/is (or (nil? old-position-data)
|
||||
(nil? new-position-data)
|
||||
(not= old-position-data new-position-data)))))
|
||||
(not= old-position-data new-position-data)))))
|
||||
|
||||
;; ============================================================
|
||||
;; SELRECT CONSISTENCY TESTS
|
||||
;; These tests verify that after a variant switch, the composite
|
||||
;; geometry attributes (:selrect, :points) stay consistent with
|
||||
;; the scalar attributes (:width, :height) that are kept.
|
||||
;; ============================================================
|
||||
|
||||
(t/deftest test-switch-selrect-consistent-no-sizing-different-widths
|
||||
;; When no :fix sizing and variants have different widths,
|
||||
;; :width is correctly skipped (stays at new component width),
|
||||
;; but :selrect was being copied from the old shape, leaving
|
||||
;; selrect.width inconsistent with :width. This test verifies the fix.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 100 :height 50}
|
||||
:child2-params {:width 200 :height 50}})
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override width AND selrect consistently (simulating a real resize)
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-width 150
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :width new-width)
|
||||
(assoc :x2 (+ (:x1 sr) new-width)))]
|
||||
(-> shape
|
||||
(assoc :width new-width)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the width override before the switch
|
||||
(t/is (= (:width rect01) 150))
|
||||
(t/is (= (get-in rect01 [:selrect :width]) 150))
|
||||
;; Since the variants have different widths (100 vs 200), the override is not preserved
|
||||
(t/is (= (:width rect02') 200))
|
||||
;; The selrect must be consistent with :width
|
||||
(t/is (= (get-in rect02' [:selrect :width]) 200))))
|
||||
|
||||
(t/deftest test-switch-selrect-consistent-no-sizing-different-heights
|
||||
;; Same as above but for height.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 50 :height 100}
|
||||
:child2-params {:width 50 :height 200}})
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override height AND selrect consistently
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-height 150
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :height new-height)
|
||||
(assoc :y2 (+ (:y1 sr) new-height)))]
|
||||
(-> shape
|
||||
(assoc :height new-height)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the height override before the switch
|
||||
(t/is (= (:height rect01) 150))
|
||||
(t/is (= (get-in rect01 [:selrect :height]) 150))
|
||||
;; Since the variants have different heights (100 vs 200), the override is not preserved
|
||||
(t/is (= (:height rect02') 200))
|
||||
;; The selrect must be consistent with :height
|
||||
(t/is (= (get-in rect02' [:selrect :height]) 200))))
|
||||
|
||||
(t/deftest test-switch-with-v-sizing-fix-selrect-consistent-different-widths
|
||||
;; mixed-sizing scenario: v-sizing=:fix but variants differ in WIDTH.
|
||||
;; switch-fixed-layout-geom-change-value is triggered (because v-sizing=:fix).
|
||||
;; Without the fix, the function returned prev-width for the non-:fix dimension,
|
||||
;; leaving selrect.width inconsistent with :width.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 100 :height 50 :layout-item-v-sizing :fix}
|
||||
:child2-params {:width 200 :height 50 :layout-item-v-sizing :fix}})
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override width AND selrect consistently
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-width 150
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :width new-width)
|
||||
(assoc :x2 (+ (:x1 sr) new-width)))]
|
||||
(-> shape
|
||||
(assoc :width new-width)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the width override before the switch
|
||||
(t/is (= (:width rect01) 150))
|
||||
(t/is (= (get-in rect01 [:selrect :width]) 150))
|
||||
;; Since the variants have different widths (100 vs 200), the override is not preserved
|
||||
;; (v-sizing=:fix does not affect the horizontal dimension)
|
||||
(t/is (= (:width rect02') 200))
|
||||
;; The selrect must be consistent with :width
|
||||
(t/is (= (get-in rect02' [:selrect :width]) 200))
|
||||
;; v-sizing is preserved
|
||||
(t/is (= (:layout-item-v-sizing rect02') :fix))))
|
||||
|
||||
(t/deftest test-switch-with-h-sizing-fix-selrect-consistent-different-heights
|
||||
;; mixed-sizing scenario: h-sizing=:fix but variants differ in HEIGHT.
|
||||
;; switch-fixed-layout-geom-change-value is triggered (because h-sizing=:fix).
|
||||
;; Without the fix, the function returned prev-height for the non-:fix dimension,
|
||||
;; leaving selrect.height inconsistent with :height.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 50 :height 100 :layout-item-h-sizing :fix}
|
||||
:child2-params {:width 50 :height 200 :layout-item-h-sizing :fix}})
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override height AND selrect consistently
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-height 150
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :height new-height)
|
||||
(assoc :y2 (+ (:y1 sr) new-height)))]
|
||||
(-> shape
|
||||
(assoc :height new-height)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the height override before the switch
|
||||
(t/is (= (:height rect01) 150))
|
||||
(t/is (= (get-in rect01 [:selrect :height]) 150))
|
||||
;; Since the variants have different heights (100 vs 200), the override is not preserved
|
||||
;; (h-sizing=:fix does not affect the vertical dimension)
|
||||
(t/is (= (:height rect02') 200))
|
||||
;; The selrect must be consistent with :height
|
||||
(t/is (= (get-in rect02' [:selrect :height]) 200))
|
||||
;; h-sizing is preserved
|
||||
(t/is (= (:layout-item-h-sizing rect02') :fix))))
|
||||
|
||||
;; ============================================================
|
||||
;; FIXED-SIZING: "SAME-SIZE → PRESERVE OVERRIDE" PATH TESTS
|
||||
;; These tests exercise the branch inside switch-fixed-layout-geom-change-value
|
||||
;; where variants share the same value in the non-:fix dimension:
|
||||
;; (if (= origin-dim current-dim) prev-dim current-dim)
|
||||
;; When origin-dim == current-dim the user's override for that dimension
|
||||
;; must be preserved after the switch.
|
||||
;; ============================================================
|
||||
|
||||
(t/deftest test-switch-with-h-sizing-fix-same-height-override-preserved
|
||||
;; h-sizing=:fix, variants have SAME height (non-:fix dim, same-size).
|
||||
;; switch-fixed-layout-geom-change-value must return prev-height for the
|
||||
;; non-:fix dimension because origin-height == current-height.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 100 :height 50 :layout-item-h-sizing :fix}
|
||||
:child2-params {:width 200 :height 50 :layout-item-h-sizing :fix}})
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override height (the non-:fix dimension) and selrect consistently
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-height 75
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :height new-height)
|
||||
(assoc :y2 (+ (:y1 sr) new-height)))]
|
||||
(-> shape
|
||||
(assoc :height new-height)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the height override 75 before the switch
|
||||
(t/is (= (:height rect01) 75))
|
||||
;; h-sizing=:fix means width always takes the new component's value
|
||||
(t/is (= (:width rect02') 200))
|
||||
;; Height (non-:fix dim) is preserved because both variants have same height (50)
|
||||
(t/is (= (:height rect02') 75))
|
||||
;; selrect must be consistent with the preserved height
|
||||
(t/is (= (get-in rect02' [:selrect :height]) 75))
|
||||
(t/is (= (get-in rect02' [:selrect :width]) 200))
|
||||
;; h-sizing is preserved
|
||||
(t/is (= (:layout-item-h-sizing rect02') :fix))))
|
||||
|
||||
(t/deftest test-switch-with-v-sizing-fix-same-width-override-preserved
|
||||
;; v-sizing=:fix, variants have SAME width (non-:fix dim, same-size).
|
||||
;; switch-fixed-layout-geom-change-value must return prev-width for the
|
||||
;; non-:fix dimension because origin-width == current-width.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 100 :height 50 :layout-item-v-sizing :fix}
|
||||
:child2-params {:width 100 :height 100 :layout-item-v-sizing :fix}})
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override width (the non-:fix dimension) and selrect consistently
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-width 150
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :width new-width)
|
||||
(assoc :x2 (+ (:x1 sr) new-width)))]
|
||||
(-> shape
|
||||
(assoc :width new-width)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the width override 150 before the switch
|
||||
(t/is (= (:width rect01) 150))
|
||||
;; Width (non-:fix dim) is preserved because both variants have same width (100)
|
||||
(t/is (= (:width rect02') 150))
|
||||
;; selrect must be consistent with the preserved width
|
||||
(t/is (= (get-in rect02' [:selrect :width]) 150))
|
||||
;; v-sizing=:fix means height always takes the new component's value
|
||||
(t/is (= (:height rect02') 100))
|
||||
(t/is (= (get-in rect02' [:selrect :height]) 100))
|
||||
;; v-sizing is preserved
|
||||
(t/is (= (:layout-item-v-sizing rect02') :fix))))
|
||||
|
||||
(t/deftest test-switch-with-both-sizing-fix-overrides-discarded
|
||||
;; When both h-sizing=:fix and v-sizing=:fix, switch-fixed-layout-geom-change-value
|
||||
;; always uses current-width and current-height (the new component's values).
|
||||
;; Both width and height overrides are discarded because :fix always
|
||||
;; defers to the new component's dimension regardless of same-size or not.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 100 :height 50
|
||||
:layout-item-h-sizing :fix
|
||||
:layout-item-v-sizing :fix}
|
||||
:child2-params {:width 200 :height 100
|
||||
:layout-item-h-sizing :fix
|
||||
:layout-item-v-sizing :fix}})
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override both width and height (and selrect) consistently
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-width 150
|
||||
new-height 75
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :width new-width)
|
||||
(assoc :height new-height)
|
||||
(assoc :x2 (+ (:x1 sr) new-width))
|
||||
(assoc :y2 (+ (:y1 sr) new-height)))]
|
||||
(-> shape
|
||||
(assoc :width new-width)
|
||||
(assoc :height new-height)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had both overrides before the switch
|
||||
(t/is (= (:width rect01) 150))
|
||||
(t/is (= (:height rect01) 75))
|
||||
;; With both sizing :fix, both dimensions take the new component's values
|
||||
(t/is (= (:width rect02') 200))
|
||||
(t/is (= (:height rect02') 100))
|
||||
;; selrect must be consistent
|
||||
(t/is (= (get-in rect02' [:selrect :width]) 200))
|
||||
(t/is (= (get-in rect02' [:selrect :height]) 100))
|
||||
(t/is (= (:layout-item-h-sizing rect02') :fix))
|
||||
(t/is (= (:layout-item-v-sizing rect02') :fix))))
|
||||
|
||||
(t/deftest test-switch-same-size-variants-geometry-override-preserved
|
||||
;; When both variants have IDENTICAL dimensions (width=100, height=50),
|
||||
;; the guard that skips :selrect/:points must NOT fire
|
||||
;; (its condition `(or (not= origin.width current.width) ...)` is false).
|
||||
;; A geometry override should therefore be carried through correctly.
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 100 :height 50}
|
||||
:child2-params {:width 100 :height 50}}) ; same size!
|
||||
(thc/instantiate-component :c01
|
||||
:copy01
|
||||
:children-labels [:copy-r01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; Override width AND selrect consistently (simulating a real resize)
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id rect01)}
|
||||
(fn [shape]
|
||||
(let [new-width 150
|
||||
sr (:selrect shape)
|
||||
new-sr (-> sr
|
||||
(assoc :width new-width)
|
||||
(assoc :x2 (+ (:x1 sr) new-width)))]
|
||||
(-> shape
|
||||
(assoc :width new-width)
|
||||
(assoc :selrect new-sr))))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
page (thf/current-page file)
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
|
||||
|
||||
;; The rect had the width override 150 before the switch
|
||||
(t/is (= (:width rect01) 150))
|
||||
(t/is (= (get-in rect01 [:selrect :width]) 150))
|
||||
;; Both variants are identical in size (100x50), so the override IS preserved
|
||||
(t/is (= (:width rect02') 150))
|
||||
;; The guard must not have suppressed :selrect — it should be consistent
|
||||
(t/is (= (get-in rect02' [:selrect :width]) 150))))
|
||||
@ -13,6 +13,7 @@
|
||||
[app.common.geom.rect :as grc]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as trans]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.bool :as path.bool]
|
||||
@ -1418,3 +1419,60 @@
|
||||
;; Verify first and last entries specifically
|
||||
(t/is (= :move-to (first seq-types)))
|
||||
(t/is (= :close-path (last seq-types))))))
|
||||
|
||||
(t/deftest path-data-read-normalizes-out-of-bounds-coordinates
|
||||
(let [max-safe (double sm/max-safe-int)
|
||||
min-safe (double sm/min-safe-int)
|
||||
;; Create content with values exceeding safe bounds
|
||||
content-with-out-of-bounds
|
||||
[{:command :move-to :params {:x (+ max-safe 1000.0) :y (- min-safe 1000.0)}}
|
||||
{:command :line-to :params {:x (- min-safe 500.0) :y (+ max-safe 500.0)}}
|
||||
{:command :curve-to :params
|
||||
{:c1x (+ max-safe 200.0) :c1y (- min-safe 200.0)
|
||||
:c2x (+ max-safe 300.0) :c2y (- min-safe 300.0)
|
||||
:x (+ max-safe 400.0) :y (- min-safe 400.0)}}
|
||||
{:command :close-path :params {}}]
|
||||
|
||||
;; Create PathData from the content
|
||||
pdata (path/content content-with-out-of-bounds)
|
||||
|
||||
;; Read it back
|
||||
result (vec pdata)]
|
||||
|
||||
(t/testing "Coordinates exceeding max-safe-int are clamped to max-safe-int"
|
||||
(let [move-to (first result)
|
||||
line-to (second result)]
|
||||
(t/is (= max-safe (:x (:params move-to))) "x in move-to should be clamped to max-safe-int")
|
||||
(t/is (= min-safe (:y (:params move-to))) "y in move-to should be clamped to min-safe-int")
|
||||
(t/is (= min-safe (:x (:params line-to))) "x in line-to should be clamped to min-safe-int")
|
||||
(t/is (= max-safe (:y (:params line-to))) "y in line-to should be clamped to max-safe-int")))
|
||||
|
||||
(t/testing "Curve-to coordinates are clamped"
|
||||
(let [curve-to (nth result 2)]
|
||||
(t/is (= max-safe (:c1x (:params curve-to))) "c1x should be clamped")
|
||||
(t/is (= min-safe (:c1y (:params curve-to))) "c1y should be clamped")
|
||||
(t/is (= max-safe (:c2x (:params curve-to))) "c2x should be clamped")
|
||||
(t/is (= min-safe (:c2y (:params curve-to))) "c2y should be clamped")
|
||||
(t/is (= max-safe (:x (:params curve-to))) "x should be clamped")
|
||||
(t/is (= min-safe (:y (:params curve-to))) "y should be clamped")))
|
||||
|
||||
(t/testing "-lookup normalizes coordinates"
|
||||
(let [move-to (path.impl/-lookup pdata 0 (fn [_ _ _ _ _ x y] {:x x :y y}))]
|
||||
(t/is (= max-safe (:x move-to)) "lookup x should be clamped")
|
||||
(t/is (= min-safe (:y move-to)) "lookup y should be clamped")))
|
||||
|
||||
(t/testing "-walk normalizes coordinates"
|
||||
(let [coords (path.impl/-walk pdata
|
||||
(fn [_ _ _ _ _ x y]
|
||||
(when (and x y) {:x x :y y}))
|
||||
[])]
|
||||
(t/is (= max-safe (:x (first coords))) "walk first x should be clamped")
|
||||
(t/is (= min-safe (:y (first coords))) "walk first y should be clamped")))
|
||||
|
||||
(t/testing "-reduce normalizes coordinates"
|
||||
(let [[move-res] (path.impl/-reduce pdata
|
||||
(fn [acc _ _ _ _ _ _ x y]
|
||||
(if (and x y) (conj acc {:x x :y y}) acc))
|
||||
[])]
|
||||
(t/is (= max-safe (:x move-res)) "reduce first x should be clamped")
|
||||
(t/is (= min-safe (:y move-res)) "reduce first y should be clamped")))))
|
||||
|
||||
@ -551,7 +551,7 @@
|
||||
(.. sd-token -original -name))
|
||||
|
||||
(defn sd-token-uuid [^js sd-token]
|
||||
(uuid (.-uuid (.-id ^js sd-token))))
|
||||
(uuid (.-uuid (.. sd-token -original -id))))
|
||||
|
||||
(defn resolve-tokens
|
||||
[tokens]
|
||||
@ -560,15 +560,23 @@
|
||||
|
||||
(defn resolve-tokens-interactive
|
||||
"Interactive check of resolving tokens.
|
||||
Uses a ids map to backtrace the original token from the resolved StyleDictionary token.
|
||||
Uses a ids map to backtrace the original token from the resolved
|
||||
StyleDictionary token.
|
||||
|
||||
We have to pass in all tokens from all sets in the entire library to style dictionary
|
||||
so we know if references are missing / to resolve them and possibly show interactive previews (in the tokens form) to the user.
|
||||
We have to pass in all tokens from all sets in the entire library to
|
||||
style dictionary so we know if references are missing / to resolve
|
||||
them and possibly show interactive previews (in the tokens form) to
|
||||
the user.
|
||||
|
||||
Since we're using the :name path as the identifier we might be throwing away or overriding tokens in the tree that we pass to StyleDictionary.
|
||||
Since we're using the :name path as the identifier we might be
|
||||
throwing away or overriding tokens in the tree that we pass to
|
||||
StyleDictionary.
|
||||
|
||||
So to get back the original token from the resolved sd-token (see my updates for what an sd-token is) we include a temporary :id for the token that we pass to StyleDictionary,
|
||||
this way after the resolving computation we can restore any token, even clashing ones with the same :name path by just looking up that :id in the ids map."
|
||||
So to get back the original token from the resolved sd-token (see my
|
||||
updates for what an sd-token is) we include a temporary :id for the
|
||||
token that we pass to StyleDictionary, this way after the resolving
|
||||
computation we can restore any token, even clashing ones with the
|
||||
same :name path by just looking up that :id in the ids map."
|
||||
[tokens]
|
||||
(let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)]
|
||||
(resolve-tokens-tree tokens-tree #(get ids (sd-token-uuid %)))))
|
||||
@ -584,10 +592,11 @@
|
||||
(defonce !tokens-cache (atom nil))
|
||||
|
||||
(defn use-resolved-tokens
|
||||
"The StyleDictionary process function is async, so we can't use resolved values directly.
|
||||
"The StyleDictionary process function is async, so we can't use
|
||||
resolved values directly.
|
||||
|
||||
This hook will return the unresolved tokens as state until they are processed,
|
||||
then the state will be updated with the resolved tokens."
|
||||
This hook will return the unresolved tokens as state until they are
|
||||
processed, then the state will be updated with the resolved tokens."
|
||||
[tokens & {:keys [cache-atom interactive?]
|
||||
:or {cache-atom !tokens-cache}
|
||||
:as config}]
|
||||
|
||||
@ -57,3 +57,29 @@
|
||||
(t/is (= :error.token/number-too-large
|
||||
(get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code])))
|
||||
(done))))))))
|
||||
|
||||
(t/deftest resolve-tokens-interactive-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "resolves tokens interactively using backtrace ids map"
|
||||
(let [tokens (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :core-set)
|
||||
:name "core"))
|
||||
(ctob/add-token (cthi/id :core-set)
|
||||
(ctob/make-token {:name "borderRadius.sm"
|
||||
:value "12px"
|
||||
:type :border-radius}))
|
||||
(ctob/add-token (cthi/id :core-set)
|
||||
(ctob/make-token {:value "{borderRadius.sm} * 2"
|
||||
:name "borderRadius.md"
|
||||
:type :border-radius}))
|
||||
(ctob/get-all-tokens-map))]
|
||||
(-> (sd/resolve-tokens-interactive tokens)
|
||||
(rx/sub!
|
||||
(fn [resolved-tokens]
|
||||
(t/is (= 12 (get-in resolved-tokens ["borderRadius.sm" :resolved-value])))
|
||||
(t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit])))
|
||||
(t/is (= 24 (get-in resolved-tokens ["borderRadius.md" :resolved-value])))
|
||||
(t/is (= "px" (get-in resolved-tokens ["borderRadius.md" :unit])))
|
||||
(done))))))))
|
||||
|
||||
|
||||
@ -19,6 +19,6 @@
|
||||
"@github/copilot": "^1.0.21",
|
||||
"@types/node": "^25.5.2",
|
||||
"esbuild": "^0.28.0",
|
||||
"opencode-ai": "^1.4.0"
|
||||
"opencode-ai": "^1.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
## 1.5.0 (Unreleased)
|
||||
|
||||
- **plugin-types**: Added a flags subcontexts with the flag `naturalChildrenOrdering`
|
||||
- **plugins-runtime**: Added `version` field that returns the current version
|
||||
- **plugin-types**: Added a flags subcontexts with the flag `naturalChildrenOrdering`
|
||||
- **plugin-types**: Fix penpot.openPage() to navigate in same tab by default
|
||||
- **plugin-types**: Added `createVariantFromComponents`
|
||||
- **plugin-types**: Change return type of `combineAsVariants`
|
||||
- **plugin-types**: Added `textBounds` property for text shapes
|
||||
- **plugin-types**: Added flag `throwValidationErrors` to enable exceptions on validation
|
||||
- **plugin-types**: Fix missing `webp` export format in `Export.type`
|
||||
|
||||
## 1.4.2 (2026-01-21)
|
||||
|
||||
|
||||
4
plugins/libs/plugin-types/index.d.ts
vendored
4
plugins/libs/plugin-types/index.d.ts
vendored
@ -1548,9 +1548,9 @@ export interface EventsMap {
|
||||
*/
|
||||
export interface Export {
|
||||
/**
|
||||
* Type of the file to export. Can be one of the following values: png, jpeg, svg, pdf
|
||||
* Type of the file to export. Can be one of the following values: png, jpeg, webp, svg, pdf
|
||||
*/
|
||||
type: 'png' | 'jpeg' | 'svg' | 'pdf';
|
||||
type: 'png' | 'jpeg' | 'webp' | 'svg' | 'pdf';
|
||||
/**
|
||||
* For bitmap formats represent the scale of the original size to resize the export
|
||||
*/
|
||||
|
||||
106
pnpm-lock.yaml
generated
106
pnpm-lock.yaml
generated
@ -18,8 +18,8 @@ importers:
|
||||
specifier: ^0.28.0
|
||||
version: 0.28.0
|
||||
opencode-ai:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
|
||||
packages:
|
||||
|
||||
@ -227,67 +227,67 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
opencode-ai@1.4.0:
|
||||
resolution: {integrity: sha512-Cb5Vo5Rl1gvOIXC8gtgupwoa5+rufsp+6u5tIxIYLl5fReX+P2eugLSOkKH2KB5GC6BwxaEvapEZiPvQYsZSXA==}
|
||||
opencode-ai@1.4.3:
|
||||
resolution: {integrity: sha512-WwCSrLgJiS+sLIWoi9pa62vAw3l6VI3a+ShhjDDMUJBBG2FxU18xEhk8xhEedLMKyHo1p0nwD41+iKZ1y+rdAw==}
|
||||
hasBin: true
|
||||
|
||||
opencode-darwin-arm64@1.4.0:
|
||||
resolution: {integrity: sha512-rXdrH1Oejb+220ZCzkd1P+tCP7IhLTyfRbUr89vzvEWVRueh0vr2hvyrGDVv9LAskZAt/hwY3Wnw9CzjtxocdQ==}
|
||||
opencode-darwin-arm64@1.4.3:
|
||||
resolution: {integrity: sha512-d/MT28Is5yhdFY+36AqKc5r31zx8lXTQIYblfn5R8kdhamXijZVGdD0pHl3eJc1ZolUHNwzg2B+IqV22uyU9GQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-darwin-x64-baseline@1.4.0:
|
||||
resolution: {integrity: sha512-5xCXF8Xn9M2WQKZATc4llm9qrAc4JodmQj88Oibbz/rXIOr/A1ejXvaeqLOQkQyQweeEABlYXOf3eCiY5hx8Gw==}
|
||||
opencode-darwin-x64-baseline@1.4.3:
|
||||
resolution: {integrity: sha512-WTqf7WBNRZcv6pClqnN4F7X/T/osgcPGikNHkHUSLszKWg9flqz7Z68kHR4i9ae8Bn3ke9MQRgzRdOt2PgLL0w==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-darwin-x64@1.4.0:
|
||||
resolution: {integrity: sha512-PhBfT2EtPos7jcGBtVSz3+yKv2e1nQy1UrXiH4ILdSgwzroKU/0kMsRtWJeMPHIj1imUQmSVlnDcuxiCiCkozw==}
|
||||
opencode-darwin-x64@1.4.3:
|
||||
resolution: {integrity: sha512-8FUHeybVmaCYt4S2YmWcf32o/xa/ahCfI258bpWssrzs7Xg51JgUB/Csoble0I1mH7RpW39SKy/hHUtHGuJfJg==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
opencode-linux-arm64-musl@1.4.0:
|
||||
resolution: {integrity: sha512-1lc0Nj6OmtJF5NJn+AhV7rXOHzw+0p7rvXQu2iqd9V7DpUEbltyF6oiyuF54gBZjHpvSzFXu8a3YeTcuMEXdNA==}
|
||||
opencode-linux-arm64-musl@1.4.3:
|
||||
resolution: {integrity: sha512-3Ej2klaep8+fxcc44UyEuRpb/UFiNkwfzIDLIST83hFUtjzprjpTRqg6zHmOfzyfjNAaNpB4VZw6e9y3mGBpiQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-arm64@1.4.0:
|
||||
resolution: {integrity: sha512-XEM3tP7DTrYDEYCe9jqC/xtgzPJpCZTfinc5DjcPuh2hx+iHCGSr9+qG7tRGeCyvD9ibAFewNtpco5Is49JCrg==}
|
||||
opencode-linux-arm64@1.4.3:
|
||||
resolution: {integrity: sha512-9jpVSOEF7TX3gPPAHVAsBT9XEO3LgYafI+IUmOzbBB9CDiVVNJw6JmEffmSpSxY4nkAh322xnMbNjVGEyXQBRA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-baseline-musl@1.4.0:
|
||||
resolution: {integrity: sha512-URg1JXIUaNz0R4TLUT98rK2jozmh5ScAkkqxPK6LWj3XwJojJx23mJRNgLb26034PgNkUwXhrtdbnyPTSVlkqQ==}
|
||||
opencode-linux-x64-baseline-musl@1.4.3:
|
||||
resolution: {integrity: sha512-aned/3FQTHXXQv2PPKDprJwQaQkoadriQ6AByGhRl6/bHhSkhkiVl6cHHvYMKxYEwN4bVOydWhasfgm/xru/xw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-baseline@1.4.0:
|
||||
resolution: {integrity: sha512-GocjLGNgs41PLgSVPWxT3Do0StZkDB9QF3e3VIIAGzPmOVcpTZLdDvJPkZdRbRGcVfUcSRGquBbBgvwK9Zsw4w==}
|
||||
opencode-linux-x64-baseline@1.4.3:
|
||||
resolution: {integrity: sha512-HpzdgYaI90qqt0WokcyBhadgFQ0EYMhq4TZ4EcaSPuZTssS2Drb6kp70Si54uOJL/MUAdc9+E0BYYIAdOJ6h1g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64-musl@1.4.0:
|
||||
resolution: {integrity: sha512-yGb1uNO++BtkZ7X/LGLax9ppaEvsmn5s5GaAqcrYj/SyJA5cL2IYzEeMYRAsrb0b81fQCSq5SLEiWiMq1o59oA==}
|
||||
opencode-linux-x64-musl@1.4.3:
|
||||
resolution: {integrity: sha512-ibUevyDxVrwkp6FWu8UBCBsrzlKDT/uEug2NHCKaHIwo9uwVf5zsL/0ueHYqmH14SHK+M6wzWewYk6WuW9f0zQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-linux-x64@1.4.0:
|
||||
resolution: {integrity: sha512-Ops08slOBhHbKaYhERH8zMTjlM6mearVaA0udCDIx2fGqDbZRisoRyyI6Z44GPYBH02w8eGmvOvnF5fQYyq2fw==}
|
||||
opencode-linux-x64@1.4.3:
|
||||
resolution: {integrity: sha512-RS6TsDqTUrW5sefxD1KD9Xy9mSYGXAlr2DlGrdi8vNm9e/Bt4r4u557VB7f/Uj2CxTt2Gf7OWl08ZoPlxMJ5Gg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
opencode-windows-arm64@1.4.0:
|
||||
resolution: {integrity: sha512-47quWER7bCGRPWRXd3fsOyu5F/T4Y65FiS05kD+PYYV4iOJymlBQ34kpcJhNBOpQLYf9HSLbJ8AaJeb5dmUi+Q==}
|
||||
opencode-windows-arm64@1.4.3:
|
||||
resolution: {integrity: sha512-2ViH17WpIQbRVfQaOBMi49pu73gqTQYT/4/WxFjShmRagX40/KkG18fhvyDAZrBKfkhPtdwgFsFxMSYP9F6QCQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
opencode-windows-x64-baseline@1.4.0:
|
||||
resolution: {integrity: sha512-eGK9lF70XKzf9zBO7xil9+Vl7ZJUAgLK6bG6kug6RKxD6FsydY3Y6q/3tIW0+YZ0wyINOtEbTRfUHbO5TxV4FQ==}
|
||||
opencode-windows-x64-baseline@1.4.3:
|
||||
resolution: {integrity: sha512-SWYDli9SAKQd/pS/hVfuq1KEsc+gnAJdv+YtBmxaHOw57y0euqLwbGFUYFq78GAMGt/RnTYWZIEUbRK/ZiX3UA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
opencode-windows-x64@1.4.0:
|
||||
resolution: {integrity: sha512-DQ8CoxCsmFM38U1e73+hFuB6Wu0tbn6B4R7KwcL1JhvKvQaYYiukNfuLgcjjx5D7s81NP1SWlv6lw60wN0gq8g==}
|
||||
opencode-windows-x64@1.4.3:
|
||||
resolution: {integrity: sha512-UxmKDIw3t4XHST6JSUWHmSrCGIEK1LRTAOpO82HBC3XkIjH78gVIeauRR6RULjWAApmy9I1C3TukO2sDUi7Gvw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@ -434,55 +434,55 @@ snapshots:
|
||||
'@esbuild/win32-ia32': 0.28.0
|
||||
'@esbuild/win32-x64': 0.28.0
|
||||
|
||||
opencode-ai@1.4.0:
|
||||
opencode-ai@1.4.3:
|
||||
optionalDependencies:
|
||||
opencode-darwin-arm64: 1.4.0
|
||||
opencode-darwin-x64: 1.4.0
|
||||
opencode-darwin-x64-baseline: 1.4.0
|
||||
opencode-linux-arm64: 1.4.0
|
||||
opencode-linux-arm64-musl: 1.4.0
|
||||
opencode-linux-x64: 1.4.0
|
||||
opencode-linux-x64-baseline: 1.4.0
|
||||
opencode-linux-x64-baseline-musl: 1.4.0
|
||||
opencode-linux-x64-musl: 1.4.0
|
||||
opencode-windows-arm64: 1.4.0
|
||||
opencode-windows-x64: 1.4.0
|
||||
opencode-windows-x64-baseline: 1.4.0
|
||||
opencode-darwin-arm64: 1.4.3
|
||||
opencode-darwin-x64: 1.4.3
|
||||
opencode-darwin-x64-baseline: 1.4.3
|
||||
opencode-linux-arm64: 1.4.3
|
||||
opencode-linux-arm64-musl: 1.4.3
|
||||
opencode-linux-x64: 1.4.3
|
||||
opencode-linux-x64-baseline: 1.4.3
|
||||
opencode-linux-x64-baseline-musl: 1.4.3
|
||||
opencode-linux-x64-musl: 1.4.3
|
||||
opencode-windows-arm64: 1.4.3
|
||||
opencode-windows-x64: 1.4.3
|
||||
opencode-windows-x64-baseline: 1.4.3
|
||||
|
||||
opencode-darwin-arm64@1.4.0:
|
||||
opencode-darwin-arm64@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-darwin-x64-baseline@1.4.0:
|
||||
opencode-darwin-x64-baseline@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-darwin-x64@1.4.0:
|
||||
opencode-darwin-x64@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-linux-arm64-musl@1.4.0:
|
||||
opencode-linux-arm64-musl@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-linux-arm64@1.4.0:
|
||||
opencode-linux-arm64@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-baseline-musl@1.4.0:
|
||||
opencode-linux-x64-baseline-musl@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-baseline@1.4.0:
|
||||
opencode-linux-x64-baseline@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64-musl@1.4.0:
|
||||
opencode-linux-x64-musl@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-linux-x64@1.4.0:
|
||||
opencode-linux-x64@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-windows-arm64@1.4.0:
|
||||
opencode-windows-arm64@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-windows-x64-baseline@1.4.0:
|
||||
opencode-windows-x64-baseline@1.4.3:
|
||||
optional: true
|
||||
|
||||
opencode-windows-x64@1.4.0:
|
||||
opencode-windows-x64@1.4.3:
|
||||
optional: true
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user