Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Alejandro Alonso 2026-07-02 13:52:25 +02:00
commit b82ab0c830
107 changed files with 2587 additions and 1408 deletions

View File

@ -139,8 +139,8 @@
[:map {:title "OpenOverlayInteraction"}
[:action-type [:= :open-overlay]]
[:event-type [::sm/one-of event-types]]
[:overlay-position ::gpt/point]
[:overlay-pos-type [::sm/one-of overlay-positioning-types]]
[:overlay-position {:optional true} ::gpt/point]
[:overlay-pos-type {:optional true} [::sm/one-of overlay-positioning-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
[:close-click-outside {:optional true} :boolean]
[:background-overlay {:optional true} :boolean]
@ -151,8 +151,8 @@
[:map {:title "ToggleOverlayInteraction"}
[:action-type [:= :toggle-overlay]]
[:event-type [::sm/one-of event-types]]
[:overlay-position ::gpt/point]
[:overlay-pos-type [::sm/one-of overlay-positioning-types]]
[:overlay-position {:optional true} ::gpt/point]
[:overlay-pos-type {:optional true} [::sm/one-of overlay-positioning-types]]
[:destination {:optional true} [:maybe ::sm/uuid]]
[:close-click-outside {:optional true} :boolean]
[:background-overlay {:optional true} :boolean]

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -27,16 +27,16 @@
"@11ty/eleventy": "^3.1.6",
"@11ty/eleventy-navigation": "^1.0.5",
"@11ty/eleventy-plugin-rss": "^3.0.0",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
"@tigersway/eleventy-plugin-ancestry": "^1.0.3",
"@types/markdown-it": "14.1.2",
"elasticlunr": "^0.9.5",
"eleventy-plugin-metagen": "^1.8.3",
"eleventy-plugin-metagen": "^1.8.4",
"eleventy-plugin-nesting-toc": "^1.3.0",
"eleventy-plugin-youtube-embed": "^1.10.2",
"luxon": "^3.4.4",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.0.1",
"eleventy-plugin-youtube-embed": "^1.13.2",
"luxon": "^3.7.2",
"markdown-it": "^14.2.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-plantuml": "^1.4.1"
},
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b"

28
docs/pnpm-lock.yaml generated
View File

@ -18,7 +18,7 @@ importers:
specifier: ^3.0.0
version: 3.0.0
'@11ty/eleventy-plugin-syntaxhighlight':
specifier: ^5.0.0
specifier: ^5.0.2
version: 5.0.2
'@tigersway/eleventy-plugin-ancestry':
specifier: ^1.0.3
@ -30,22 +30,22 @@ importers:
specifier: ^0.9.5
version: 0.9.5
eleventy-plugin-metagen:
specifier: ^1.8.3
specifier: ^1.8.4
version: 1.8.4
eleventy-plugin-nesting-toc:
specifier: ^1.3.0
version: 1.3.0
eleventy-plugin-youtube-embed:
specifier: ^1.10.2
specifier: ^1.13.2
version: 1.13.2
luxon:
specifier: ^3.4.4
specifier: ^3.7.2
version: 3.7.2
markdown-it:
specifier: ^14.1.0
specifier: ^14.2.0
version: 14.2.0
markdown-it-anchor:
specifier: ^9.0.1
specifier: ^9.2.0
version: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.2.0)
markdown-it-plantuml:
specifier: ^1.4.1
@ -436,8 +436,8 @@ packages:
resolution: {integrity: sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==}
engines: {node: '>=6.0'}
js-yaml@3.14.2:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
js-yaml@3.15.0:
resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==}
hasBin: true
js-yaml@4.2.0:
@ -692,8 +692,8 @@ packages:
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
undici@7.27.2:
resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==}
undici@7.28.0:
resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==}
engines: {node: '>=20.18.1'}
unpipe@1.0.0:
@ -943,7 +943,7 @@ snapshots:
parse5: 7.3.0
parse5-htmlparser2-tree-adapter: 7.1.0
parse5-parser-stream: 7.1.2
undici: 7.27.2
undici: 7.28.0
whatwg-mimetype: 4.0.0
chokidar@3.6.0:
@ -1126,7 +1126,7 @@ snapshots:
gray-matter@4.0.3:
dependencies:
js-yaml: 3.14.2
js-yaml: 3.15.0
kind-of: 6.0.3
section-matter: 1.0.0
strip-bom-string: 1.0.0
@ -1188,7 +1188,7 @@ snapshots:
iso-639-1@3.1.5: {}
js-yaml@3.14.2:
js-yaml@3.15.0:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
@ -1410,7 +1410,7 @@ snapshots:
uc.micro@2.1.0: {}
undici@7.27.2: {}
undici@7.28.0: {}
unpipe@1.0.0: {}

View File

@ -0,0 +1,3 @@
minimumReleaseAgeExclude:
- undici@7.28.0
- js-yaml@3.15.0

View File

@ -31,6 +31,7 @@ desc: Learn how to create, manage and apply Penpot Design Tokens using W3C DTCG
<li><strong>Description:</strong> You can also choose to add a description to your token.</li>
</ul>
<p>Once you have named the token and assigned it a value, click <strong>Save</strong> to store the token and start referencing it.</p>
<p>Token value fields suggest existing tokens as you type. Pick a suggestion to insert a reference, or keep typing a custom value.</p>
<h2 id="design-tokens-aliases">Referencing tokens into values (aliases)</h2>
<p>When assigning a value to a token, you can reference existing tokens - these are called aliases at the <a href="https://www.designtokens.org/glossary/" target="_blank">DTCG Glossary</a>.</p>
@ -111,7 +112,7 @@ desc: Learn how to create, manage and apply Penpot Design Tokens using W3C DTCG
<p>This ensures the spacing is at least 8, even if the base token is smaller.</p>
<h2 id="design-tokens-edit">Editing a token</h2>
<p>Tokens can be edited by right-clicking the token and selecting <strong>Edit token</strong>. This will allow you to change the tokens name, value and description. Once the changes are made, click <strong>Save</strong>.</p>
<p>Tokens can be edited by right-clicking the token and selecting <strong>Edit token</strong>. This will allow you to change the tokens name, value and description. Value fields offer the same token suggestions as when creating a token. Once the changes are made, click <strong>Save</strong>.</p>
<figure>
<img src="/img/design-tokens/05-tokens-edit.webp" alt="Tokens edit" />
</figure>
@ -141,12 +142,15 @@ desc: Learn how to create, manage and apply Penpot Design Tokens using W3C DTCG
<li><strong>Size and position:</strong> width, height, x, y</li>
<li><strong>Rotation and border radius</strong></li>
<li><strong>Layout spacing:</strong> gaps, paddings, margins, min and max widths and heights</li>
<li><strong>Typography:</strong> letter spacing and line height</li>
<li><strong>Typography:</strong> letter spacing, line height, and composite typography tokens on text layers</li>
<li><strong>Stroke width</strong></li>
<li><strong>Shadows:</strong> x, y, blur, spread</li>
<li><strong>Blur</strong> amount</li>
</ul>
<p>Click the token control on a field to open a list of applicable tokens from your active sets and themes. Select a token to apply it; a token pill shows the bound token. Use the pill menu to detach the token when you need a custom value again.</p>
<p>On text layers, the Typography section shows a composite typography token row when one is applied. Open the token list from the section header to pick or switch tokens without going to the Tokens panel. Hover a token name to see its resolved values (font family, size, weight, and the rest).</p>
<p>When you multi-select text layers that share typography tokens, the Typography section shows the applied token. If the selection mixes different tokens, detach them from there or apply a new one to the whole selection.</p>
<p>Typography options in the dropdown list show resolved values on hover, so you can tell styles apart before applying them.</p>
<p class="advice">When a numeric field has a token applied, drag-to-change is disabled for that field. Edit the token or detach it first.</p>
<figure>
<img src="/img/design-tokens/41-design-tab-token-dropdown.webp" alt="Token list on a numeric field in the Design sidebar">
@ -528,6 +532,7 @@ ExtraBold Italic
<h4 id="apply-typography-token">Apply a Typography token</h4>
<p>A <strong>Typography composite token</strong> can be applied to a full text layer to set all typography properties at once. This lets you manage complete text styles using a single token instead of combining multiple individual ones.</p>
<p>Apply it from the Tokens panel, or from the Typography section in the Design sidebar when a text layer is selected.</p>
<p>When applying a Typography composite token to a layer, any previously applied <em>Typography composite token</em> or <em>style</em> will be detached. The same happens in reverse. Only one of them can be active at a time.</p>
<h3 id="design-tokens-shadow">Shadow</h3>

View File

@ -57,7 +57,7 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
<li><strong>Color type</strong> - Solid, gradient, or image.</li>
<li><strong>Sliders</strong> - Easily manage settings like brightness, saturation or opacity.</li>
<li><strong>Values</strong> - Set precise color values. Available formats depend on the selected color model.</li>
<li><strong>Libraries</strong> - Switch between recent colors and libraries.</li>
<li><strong>Libraries</strong> - Switch between recent colors and libraries. Color tokens in a library can be shown as a grid or a list. Use the list view when you need to scan names or work with long token lists.</li>
<li><strong>Color palette</strong> - A quick launcher of the palette with the selected library.</li>
</ol>
@ -116,6 +116,7 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
<li><strong>Style</strong> - solid, dotted, dashed, mixed</li>
<li><strong>Visibility</strong> - show or hide the stroke without removing it</li>
</ul>
<p>When the style is set to <strong>dashed</strong>, two extra fields appear to control the <strong>dash</strong> and <strong>gap</strong> length in pixels.</p>
<p>You can add as many strokes as you want to the same layer.</p>
<figure>
<img alt="Multiple strokes" src="/img/styling/stroke-multiple.webp"/>

View File

@ -461,8 +461,18 @@ You can choose to edit individual nodes or create new ones. Press <kbd>Esc</kbd>
</ul>
<h3 id="blur">Blur</h3>
<p>You can set a blur for each and every layer at Penpot.</p>
<p><strong></strong>Applying a lot and/or big values for blurs can affect Penpots performance as it requires a lot from the browser.</p>
<p>You can add blur effects to layers from the Design sidebar. Penpot supports two types:</p>
<ul>
<li><strong>Layer blur</strong> blurs the layer content itself.</li>
<li><strong>Background blur</strong> blurs whatever sits behind the layer. Use it for frosted-glass or depth effects. Background blur requires the <a href="/user-guide/first-steps/troubleshooting-webgl/">WebGL renderer</a> to be enabled.</li>
</ul>
<p class="advice">Background blur is not included in exported files yet (PNG, SVG, PDF, and others). The exporter still uses the legacy SVG renderer, so the effect only shows in the workspace and View mode. Export support is a current priority and will land in an upcoming release.</p>
<figure>
<img src="/img/styling/background-blur.webp" alt="Colorful shapes behind a frosted panel with background blur applied" />
<figcaption>Background blur affects the layers behind the shape, not the shape itself.</figcaption>
</figure>
<p>Click <strong>+</strong> in the Blur section to add an effect. You can apply both types on the same layer. Use the type selector to switch between them, the eye icon to show or hide an effect without removing it, and the value field to set the blur amount.</p>
<p class="advice">Applying many or large blur values can affect Penpots performance, as it requires a lot from the browser.</p>
<figure>
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/blur.webp" height="auto">
<source src="/img/styling/blur.mp4" type="video/mp4">

View File

@ -42,6 +42,10 @@ desc: This Penpot user guide explains how to prototype interactions, connect boa
<strong>10)</strong> Flow indicator and launcher
</p>
<h2 id="interaction-settings">Interaction settings</h2>
<p>When a layer with interactions is selected, the Prototype sidebar lists every connection attached to it. From there you can change the trigger, action, destination, animation, and other options.</p>
<p>Destination dropdowns include a search field. Type to filter boards on the current page when your file has many screens.</p>
<h2 id="interaction-triggers">Interaction triggers</h2>
<figure>
<img src="/img/prototype/prototype-trigger.webp" alt="Prototype trigger options">

View File

@ -64,7 +64,7 @@
"@storybook/addon-vitest": "10.4.6",
"@storybook/react-vite": "10.4.6",
"@tokens-studio/sd-transforms": "2.0.3",
"@types/node": "^26.0.1",
"@types/node": "^26.1.0",
"@vitest/browser": "4.1.9",
"@vitest/browser-playwright": "^4.1.9",
"@vitest/coverage-v8": "4.1.9",
@ -80,7 +80,7 @@
"getopts": "^2.3.0",
"gettext-parser": "^9.0.2",
"highlight.js": "^11.10.0",
"js-beautify": "^1.15.4",
"js-beautify": "^2.0.3",
"jsdom": "^29.0.2",
"lodash": "^4.18.1",
"lodash.debounce": "^4.0.8",
@ -95,7 +95,7 @@
"playwright": "1.61.1",
"postcss": "^8.5.16",
"postcss-clean": "^1.2.2",
"postcss-modules": "^6.0.1",
"postcss-modules": "^9.0.0",
"postcss-scss": "^4.0.9",
"prettier": "3.9.4",
"pretty-time": "^1.1.0",
@ -122,11 +122,11 @@
"tdigest": "^0.1.2",
"tinycolor2": "^1.6.0",
"typescript": "^6.0.2",
"vite": "^8.1.0",
"vite": "^8.1.2",
"vitest": "^4.1.9",
"wait-on": "^9.0.4",
"watcher": "^2.3.1",
"workerpool": "^10.0.1",
"workerpool": "^10.0.3",
"xregexp": "^5.1.2"
}
}

View File

@ -0,0 +1,382 @@
{
"~:features": {
"~#set": [
"design-tokens/v1",
"layout/grid",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "Tooltip check",
"~:revn": 36,
"~:modified-at": "~m1737542758402",
"~:vern": 0,
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false,
"~:version": 60,
"~:project-id": "~u0df61468-6cbf-8067-8005-6b453ce996d0",
"~:created-at": "~m1737536563847",
"~:data": {
"~:pages": [
"~u4530574a-7a0a-807b-8008-0107b2c4628e"
],
"~:pages-index": {
"~u4530574a-7a0a-807b-8008-0107b2c4628e": {
"~:id": "~u4530574a-7a0a-807b-8008-0107b2c4628e",
"~:name": "Page 1",
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u4530574a-7a0a-807b-8008-0107b2c4628e\",\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^I\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ua7df6ab8-bee5-802f-8008-010d9ff3da26\",\"~uf2256046-b127-808e-8008-411c4cba63d8\",\"~u1de8c150-538b-80b2-8008-40356208fc39\",\"~uf2256046-b127-808e-8008-411c4cba63d9\",\"~u1de8c150-538b-80b2-8008-4036773cc8d0\",\"~u886f2846-a2c5-808d-8008-4115e86119e9\",\"~u1de8c150-538b-80b2-8008-403b5150a59b\",\"~u886f2846-a2c5-808d-8008-4115e86119ea\",\"~u20f9ccb0-68e2-80d9-8008-413fa842a6b5\",\"~u20f9ccb0-68e2-80d9-8008-413fb1f2ef51\"]]]",
"~u1de8c150-538b-80b2-8008-403b5150a59b": "[\"~#shape\",[\"^ \",\"~:y\",419,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Deleted token rect\",\"~:width\",162,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",-147,\"~:y\",419]],[\"^<\",[\"^ \",\"~:x\",15,\"~:y\",419]],[\"^<\",[\"^ \",\"~:x\",15,\"~:y\",565]],[\"^<\",[\"^ \",\"~:x\",-147,\"~:y\",565]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:r1\",20,\"~:id\",\"~u1de8c150-538b-80b2-8008-403b5150a59b\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"blue\",\"^A\",\"deleted\",\"^=\",\"deleted\",\"^@\",\"deleted\",\"~:r4\",\"deleted\"],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",-147,\"~:proportion\",1,\"^F\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",-147,\"~:y\",419,\"^8\",162,\"~:height\",146,\"~:x1\",-147,\"~:y1\",419,\"~:x2\",15,\"~:y2\",565]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#0a20bf\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^L\",146,\"~:flip-y\",null]]",
"~u20f9ccb0-68e2-80d9-8008-413fa842a6b5": "[\"~#shape\",[\"^ \",\"~:y\",369,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle with token\",\"~:width\",274.99999999999994,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",-536,\"~:y\",369]],[\"^<\",[\"^ \",\"~:x\",-261.00000000000006,\"~:y\",369]],[\"^<\",[\"^ \",\"~:x\",-261.00000000000006,\"~:y\",565]],[\"^<\",[\"^ \",\"~:x\",-536,\"~:y\",565]]],\"~:r2\",50,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",50,\"~:r1\",50,\"~:id\",\"~u20f9ccb0-68e2-80d9-8008-413fa842a6b5\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"out-ref\",\"^A\",\"base-50\",\"^=\",\"base-50\",\"^@\",\"base-50\",\"~:r4\",\"base-50\"],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",-536,\"~:proportion\",1,\"^F\",50,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",-536,\"~:y\",369,\"^8\",274.99999999999994,\"~:height\",196,\"~:x1\",-536,\"~:y1\",369,\"~:x2\",-261.00000000000006,\"~:y2\",565]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#da1fea\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^L\",196,\"~:flip-y\",null]]",
"~u1de8c150-538b-80b2-8008-4036773cc8d0": "[\"~#shape\",[\"^ \",\"~:y\",588,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^7\",\"paragraph-set\",\"^8\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:font-style\",\"normal\",\"^8\",[[\"^ \",\"^9\",\"1.2\",\"^:\",\"normal\",\"~:text-transform\",\"none\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-alegreya-sans-sc\",\"~:font-size\",\"23\",\"~:font-weight\",\"400\",\"~:text-direction\",\"ltr\",\"~:font-variant-id\",\"regular\",\"~:text-decoration\",\"none\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-opacity\",1]],\"~:font-family\",\"Alegreya Sans SC\",\"~:text\",\"Text with deleted token\"]],\"^;\",\"none\",\"^<\",\"left\",\"^=\",\"gfont-alegreya-sans-sc\",\"~:key\",\"fu3vh\",\"^>\",\"23\",\"^?\",\"400\",\"^@\",\"ltr\",\"^7\",\"paragraph\",\"^A\",\"regular\",\"^B\",\"none\",\"^C\",\"0\",\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"^G\",\"Alegreya Sans SC\"]]]]],\"~:hide-in-viewer\",false,\"~:name\",\"Text with deleted token\",\"~:width\",198,\"^7\",\"^H\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",-161,\"~:y\",588]],[\"^N\",[\"^ \",\"~:x\",37,\"~:y\",588]],[\"^N\",[\"^ \",\"~:x\",37,\"~:y\",641]],[\"^N\",[\"^ \",\"~:x\",-161,\"~:y\",641]]],\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u1de8c150-538b-80b2-8008-4036773cc8d0\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:typography\",\"deleted-typo\"],\"~:position-data\",[[\"~#rect\",[\"^ \",\"~:y\",613,\"^:\",\"normal\",\"^;\",\"none\",\"^>\",\"23px\",\"^?\",\"400\",\"~:y1\",2,\"^L\",176.203125,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",-161,\"~:x1\",0,\"~:y2\",25,\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"~:x2\",176.203125,\"~:direction\",\"ltr\",\"^G\",\"\\\"Alegreya Sans SC\\\"\",\"~:height\",23,\"^H\",\"Text with deleted \"]],[\"^U\",[\"^ \",\"~:y\",640.59375,\"^:\",\"normal\",\"^;\",\"none\",\"^>\",\"23px\",\"^?\",\"400\",\"^V\",29.59375,\"^L\",56.9375,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",-161,\"^W\",0,\"^X\",52.59375,\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"^Y\",56.9375,\"^Z\",\"ltr\",\"^G\",\"\\\"Alegreya Sans SC\\\"\",\"^[\",23,\"^H\",\"token\"]]],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:x\",-161,\"~:selrect\",[\"^U\",[\"^ \",\"~:x\",-161,\"~:y\",588,\"^L\",198,\"^[\",53,\"^W\",-161,\"^V\",588,\"^Y\",37,\"^X\",641]],\"~:flip-x\",null,\"^[\",53,\"~:flip-y\",null]]",
"~u20f9ccb0-68e2-80d9-8008-413fb1f2ef51": "[\"~#shape\",[\"^ \",\"~:y\",609,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^7\",\"paragraph-set\",\"^8\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:font-style\",\"normal\",\"^8\",[[\"^ \",\"^9\",\"1.2\",\"^:\",\"normal\",\"~:text-transform\",\"none\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-arizonia\",\"~:font-size\",\"23\",\"~:font-weight\",\"400\",\"~:text-direction\",\"ltr\",\"~:font-variant-id\",\"regular\",\"~:text-decoration\",\"none\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#da1fea\",\"~:fill-opacity\",1]],\"~:font-family\",\"Arizonia\",\"~:text\",\"Text with token\"]],\"^;\",\"none\",\"^<\",\"left\",\"^=\",\"gfont-arizonia\",\"~:key\",\"fu3vh\",\"^>\",\"23\",\"^?\",\"400\",\"^@\",\"ltr\",\"^7\",\"paragraph\",\"^A\",\"regular\",\"^B\",\"none\",\"^C\",\"0\",\"^D\",[[\"^ \",\"^E\",\"#da1fea\",\"^F\",1]],\"^G\",\"Arizonia\"]]]]],\"~:hide-in-viewer\",false,\"~:name\",\"Text with token\",\"~:width\",198,\"^7\",\"^H\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",-516,\"~:y\",609]],[\"^N\",[\"^ \",\"~:x\",-318,\"~:y\",609]],[\"^N\",[\"^ \",\"~:x\",-318,\"~:y\",662]],[\"^N\",[\"^ \",\"~:x\",-516,\"~:y\",662]]],\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u20f9ccb0-68e2-80d9-8008-413fb1f2ef51\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:typography\",\"out-typo\",\"~:fill\",\"out-ref\"],\"~:position-data\",[[\"~#rect\",[\"^ \",\"~:y\",637,\"^:\",\"normal\",\"^;\",\"none\",\"^>\",\"23px\",\"^?\",\"400\",\"~:y1\",-1,\"^L\",117.453125,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",-516,\"~:x1\",0,\"~:y2\",28,\"^D\",[[\"^ \",\"^E\",\"#da1fea\",\"^F\",1]],\"~:x2\",117.453125,\"~:direction\",\"ltr\",\"^G\",\"Arizonia\",\"~:height\",29,\"^H\",\"Text with token\"]]],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",-516,\"~:selrect\",[\"^V\",[\"^ \",\"~:x\",-516,\"~:y\",609,\"^L\",198,\"^10\",53,\"^X\",-516,\"^W\",609,\"^Z\",-318,\"^Y\",662]],\"~:flip-x\",null,\"^10\",53,\"~:flip-y\",null]]",
"~uf2256046-b127-808e-8008-411c4cba63d8": "[\"~#shape\",[\"^ \",\"~:y\",267,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle with not active reference\",\"~:width\",287,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",390,\"~:y\",267]],[\"^<\",[\"^ \",\"~:x\",677,\"~:y\",267]],[\"^<\",[\"^ \",\"~:x\",677,\"~:y\",579]],[\"^<\",[\"^ \",\"~:x\",390,\"~:y\",579]]],\"~:r2\",50,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u4530574a-7a0a-807b-8008-0107b2c4628e\",\"~:r3\",50,\"~:r1\",50,\"~:id\",\"~uf2256046-b127-808e-8008-411c4cba63d8\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"in-color\",\"^=\",\"in-br\",\"^A\",\"in-br\",\"^B\",\"in-br\",\"~:r4\",\"in-br\"],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",390,\"~:proportion\",1,\"^G\",50,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",390,\"~:y\",267,\"^8\",287,\"~:height\",312,\"~:x1\",390,\"~:y1\",267,\"~:x2\",677,\"~:y2\",579]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#da1fea\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^M\",312,\"~:flip-y\",null]]",
"~uf2256046-b127-808e-8008-411c4cba63d9": "[\"~#shape\",[\"^ \",\"~:y\",602,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^7\",\"paragraph-set\",\"^8\",[[\"^ \",\"~:line-height\",\"1.2\",\"^8\",[[\"^ \",\"^9\",\"1.2\",\"~:text-transform\",\"none\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-arizonia\",\"~:font-size\",\"14\",\"~:font-weight\",\"200\",\"~:text-direction\",\"ltr\",\"~:font-variant-id\",\"regular\",\"~:weight\",\"200\",\"~:text-decoration\",\"none\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#da1fea\",\"~:fill-opacity\",1]],\"~:font-family\",\"Arizonia\",\"~:text\",\"Text with not active reference\"]],\"^:\",\"none\",\"^;\",\"left\",\"^<\",\"gfont-arizonia\",\"~:key\",\"d22fo\",\"^=\",\"14\",\"^>\",\"200\",\"^?\",\"ltr\",\"^7\",\"paragraph\",\"^@\",\"regular\",\"^A\",\"200\",\"^B\",\"none\",\"^C\",\"0\",\"^D\",[[\"^ \",\"^E\",\"#da1fea\",\"^F\",1]],\"^G\",\"Arizonia\"]]]]],\"~:hide-in-viewer\",false,\"~:name\",\"Text with not active reference\",\"~:width\",139,\"^7\",\"^H\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",479,\"~:y\",602]],[\"^N\",[\"^ \",\"~:x\",618,\"~:y\",602]],[\"^N\",[\"^ \",\"~:x\",618,\"~:y\",630]],[\"^N\",[\"^ \",\"~:x\",479,\"~:y\",630]]],\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~uf2256046-b127-808e-8008-411c4cba63d9\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"in-color\",\"~:typography\",\"in-typo\"],\"~:position-data\",[[\"~#rect\",[\"^ \",\"~:y\",619,\"~:font-style\",\"normal\",\"^:\",\"none\",\"^=\",\"14px\",\"^>\",\"400\",\"~:y1\",-1,\"^L\",97.46875,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",479,\"~:x1\",0,\"~:y2\",17,\"^D\",[[\"^ \",\"^E\",\"#da1fea\",\"^F\",1]],\"~:x2\",97.46875,\"~:direction\",\"ltr\",\"^G\",\"Arizonia\",\"~:height\",18,\"^H\",\"Text with not active \"]],[\"^V\",[\"^ \",\"~:y\",635.796875,\"^W\",\"normal\",\"^:\",\"none\",\"^=\",\"14px\",\"^>\",\"400\",\"^X\",15.796875,\"^L\",42.8125,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",479,\"^Y\",0,\"^Z\",33.796875,\"^D\",[[\"^ \",\"^E\",\"#da1fea\",\"^F\",1]],\"^[\",42.8125,\"^10\",\"ltr\",\"^G\",\"Arizonia\",\"^11\",18,\"^H\",\"reference\"]]],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:x\",479,\"~:selrect\",[\"^V\",[\"^ \",\"~:x\",479,\"~:y\",602,\"^L\",139,\"^11\",28,\"^Y\",479,\"^X\",602,\"^[\",618,\"^Z\",630]],\"~:flip-x\",null,\"^11\",28,\"~:flip-y\",null]]",
"~u1de8c150-538b-80b2-8008-40356208fc39": "[\"~#shape\",[\"^ \",\"~:y\",609,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^7\",\"paragraph-set\",\"^8\",[[\"^ \",\"~:line-height\",\"1.2\",\"^8\",[[\"^ \",\"^9\",\"1.2\",\"~:text-transform\",\"none\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-ar-one-sans\",\"~:font-size\",\"14\",\"~:font-weight\",\"200\",\"~:text-direction\",\"ltr\",\"~:font-variant-id\",\"regular\",\"~:weight\",\"200\",\"~:text-decoration\",\"none\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ef0c0c\",\"~:fill-opacity\",1]],\"~:font-family\",\"AR One Sans\",\"~:text\",\"Text with not active token\"]],\"^:\",\"none\",\"^;\",\"left\",\"^<\",\"gfont-ar-one-sans\",\"~:key\",\"d22fo\",\"^=\",\"14\",\"^>\",\"200\",\"^?\",\"ltr\",\"^7\",\"paragraph\",\"^@\",\"regular\",\"^A\",\"200\",\"^B\",\"none\",\"^C\",\"0\",\"^D\",[[\"^ \",\"^E\",\"#ef0c0c\",\"^F\",1]],\"^G\",\"AR One Sans\"]]]]],\"~:hide-in-viewer\",false,\"~:name\",\"Text with not active token\",\"~:width\",139,\"^7\",\"^H\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",812,\"~:y\",609]],[\"^N\",[\"^ \",\"~:x\",951,\"~:y\",609]],[\"^N\",[\"^ \",\"~:x\",951,\"~:y\",637]],[\"^N\",[\"^ \",\"~:x\",812,\"~:y\",637]]],\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u1de8c150-538b-80b2-8008-40356208fc39\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"red\",\"~:typography\",\"typo-2\"],\"~:position-data\",[[\"~#rect\",[\"^ \",\"~:y\",626,\"~:font-style\",\"normal\",\"^:\",\"none\",\"^=\",\"14px\",\"^>\",\"400\",\"~:y1\",-2,\"^L\",134.625,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",812,\"~:x1\",0,\"~:y2\",17,\"^D\",[[\"^ \",\"^E\",\"#ef0c0c\",\"^F\",1]],\"~:x2\",134.625,\"~:direction\",\"ltr\",\"^G\",\"\\\"AR One Sans\\\"\",\"~:height\",19,\"^H\",\"Text with not active \"]],[\"^V\",[\"^ \",\"~:y\",642.796875,\"^W\",\"normal\",\"^:\",\"none\",\"^=\",\"14px\",\"^>\",\"400\",\"^X\",14.796875,\"^L\",37.40625,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",812,\"^Y\",0,\"^Z\",33.796875,\"^D\",[[\"^ \",\"^E\",\"#ef0c0c\",\"^F\",1]],\"^[\",37.40625,\"^10\",\"ltr\",\"^G\",\"\\\"AR One Sans\\\"\",\"^11\",19,\"^H\",\"token\"]]],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:x\",812,\"~:selrect\",[\"^V\",[\"^ \",\"~:x\",812,\"~:y\",609,\"^L\",139,\"^11\",28,\"^Y\",812,\"^X\",609,\"^[\",951,\"^Z\",637]],\"~:flip-x\",null,\"^11\",28,\"~:flip-y\",null]]",
"~u886f2846-a2c5-808d-8008-4115e86119ea": "[\"~#shape\",[\"^ \",\"~:y\",399,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle with deleted reference\",\"~:width\",162,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",193,\"~:y\",399]],[\"^<\",[\"^ \",\"~:x\",355,\"~:y\",399]],[\"^<\",[\"^ \",\"~:x\",355,\"~:y\",545]],[\"^<\",[\"^ \",\"~:x\",193,\"~:y\",545]]],\"~:r2\",20,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",20,\"~:r1\",20,\"~:id\",\"~u886f2846-a2c5-808d-8008-4115e86119ea\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"ref-grfeen\",\"^A\",\"ref-1\",\"^=\",\"ref-1\",\"^@\",\"ref-1\",\"~:r4\",\"ref-1\"],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",193,\"~:proportion\",1,\"^F\",20,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",193,\"~:y\",399,\"^8\",162,\"~:height\",146,\"~:x1\",193,\"~:y1\",399,\"~:x2\",355,\"~:y2\",545]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#0a20bf\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^L\",146,\"~:flip-y\",null]]",
"~u886f2846-a2c5-808d-8008-4115e86119e9": "[\"~#shape\",[\"^ \",\"~:y\",586,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:content\",[\"^ \",\"~:type\",\"root\",\"~:children\",[[\"^ \",\"^7\",\"paragraph-set\",\"^8\",[[\"^ \",\"~:line-height\",\"1.2\",\"~:font-style\",\"normal\",\"^8\",[[\"^ \",\"^9\",\"1.2\",\"^:\",\"normal\",\"~:text-transform\",\"none\",\"~:text-align\",\"left\",\"~:font-id\",\"gfont-adlam-display\",\"~:font-size\",\"23\",\"~:font-weight\",\"400\",\"~:text-direction\",\"ltr\",\"~:font-variant-id\",\"regular\",\"~:text-decoration\",\"none\",\"~:letter-spacing\",\"0\",\"~:fills\",[[\"^ \",\"~:fill-color\",\"#000000\",\"~:fill-opacity\",1]],\"~:font-family\",\"ADLaM Display\",\"~:text\",\"Text with deleted reference\"]],\"^;\",\"none\",\"^<\",\"left\",\"^=\",\"gfont-adlam-display\",\"~:key\",\"fu3vh\",\"^>\",\"23\",\"^?\",\"400\",\"^@\",\"ltr\",\"^7\",\"paragraph\",\"^A\",\"regular\",\"^B\",\"none\",\"^C\",\"0\",\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"^G\",\"ADLaM Display\"]]]]],\"~:hide-in-viewer\",false,\"~:name\",\"Text with deleted reference\",\"~:width\",137,\"^7\",\"^H\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",198,\"~:y\",586]],[\"^N\",[\"^ \",\"~:x\",335,\"~:y\",586]],[\"^N\",[\"^ \",\"~:x\",335,\"~:y\",683]],[\"^N\",[\"^ \",\"~:x\",198,\"~:y\",683]]],\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u886f2846-a2c5-808d-8008-4115e86119e9\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"ref-grfeen\",\"~:typography\",\"ref-typo\"],\"~:position-data\",[[\"~#rect\",[\"^ \",\"~:y\",614,\"^:\",\"normal\",\"^;\",\"none\",\"^>\",\"23px\",\"^?\",\"400\",\"~:y1\",-2,\"^L\",107.921875,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",198,\"~:x1\",0,\"~:y2\",28,\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"~:x2\",107.921875,\"~:direction\",\"ltr\",\"^G\",\"\\\"ADLaM Display\\\"\",\"~:height\",30,\"^H\",\"Text with \"]],[\"^V\",[\"^ \",\"~:y\",641.59375,\"^:\",\"normal\",\"^;\",\"none\",\"^>\",\"23px\",\"^?\",\"400\",\"^W\",25.59375,\"^L\",88.890625,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",198,\"^X\",0,\"^Y\",55.59375,\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"^Z\",88.890625,\"^[\",\"ltr\",\"^G\",\"\\\"ADLaM Display\\\"\",\"^10\",30,\"^H\",\"deleted \"]],[\"^V\",[\"^ \",\"~:y\",669.1875,\"^:\",\"normal\",\"^;\",\"none\",\"^>\",\"23px\",\"^?\",\"400\",\"^W\",53.1875,\"^L\",103.46875,\"^B\",\"none\",\"^C\",\"normal\",\"~:x\",198,\"^X\",0,\"^Y\",83.1875,\"^D\",[[\"^ \",\"^E\",\"#000000\",\"^F\",1]],\"^Z\",103.46875,\"^[\",\"ltr\",\"^G\",\"\\\"ADLaM Display\\\"\",\"^10\",30,\"^H\",\"reference\"]]],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:x\",198,\"~:selrect\",[\"^V\",[\"^ \",\"~:x\",198,\"~:y\",586,\"^L\",137,\"^10\",97,\"^X\",198,\"^W\",586,\"^Z\",335,\"^Y\",683]],\"~:flip-x\",null,\"^10\",97,\"~:flip-y\",null]]",
"~ua7df6ab8-bee5-802f-8008-010d9ff3da26": "[\"~#shape\",[\"^ \",\"~:y\",274,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle with not active token\",\"~:width\",287,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",723,\"~:y\",274]],[\"^<\",[\"^ \",\"~:x\",1010,\"~:y\",274]],[\"^<\",[\"^ \",\"~:x\",1010,\"~:y\",586]],[\"^<\",[\"^ \",\"~:x\",723,\"~:y\",586]]],\"~:r2\",100,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~u4530574a-7a0a-807b-8008-0107b2c4628e\",\"~:r3\",100,\"~:r1\",100,\"~:id\",\"~ua7df6ab8-bee5-802f-8008-010d9ff3da26\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:applied-tokens\",[\"^ \",\"~:fill\",\"red\",\"^B\",\"border-radius\",\"^=\",\"border-radius\",\"^A\",\"border-radius\",\"~:r4\",\"border-radius\"],\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",723,\"~:proportion\",1,\"^G\",100,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",723,\"~:y\",274,\"^8\",287,\"~:height\",312,\"~:x1\",723,\"~:y1\",274,\"~:x2\",1010,\"~:y2\",586]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#ef0c0c\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^M\",312,\"~:flip-y\",null]]"
}
}
}
},
"~:tokens-lib": {
"~#penpot/tokens-lib": {
"~:sets": {
"~#ordered-map": [
[
"S-Global",
{
"~#penpot/token-set": {
"~:id": "~uf71719b3-63a1-8157-8008-40346917de8c",
"~:name": "Global",
"~:description": "",
"~:modified-at": "~m1782813028640",
"~:tokens": {
"~#ordered-map": [
[
"base-50",
{
"~#penpot/token": {
"~:id": "~uf71719b3-63a1-8157-8008-40346917de87",
"~:name": "base-50",
"~:type": "~:border-radius",
"~:value": "50",
"~:description": "",
"~:modified-at": "~m1782742859871"
}
}
],
[
"out-ref",
{
"~#penpot/token": {
"~:id": "~uf2256046-b127-808e-8008-411bec9b1109",
"~:name": "out-ref",
"~:type": "~:color",
"~:value": "rgb(218, 31, 234)",
"~:description": "",
"~:modified-at": "~m1782803549804"
}
}
],
[
"out-typo",
{
"~#penpot/token": {
"~:id": "~uf2256046-b127-808e-8008-411bf90df57e",
"~:name": "out-typo",
"~:type": "~:typography",
"~:value": {
"~:font-family": [
"Arizonia"
]
},
"~:description": "",
"~:modified-at": "~m1782803562551"
}
}
]
]
}
}
}
],
[
"G-Mode",
{
"~#ordered-map": [
[
"S-Dark",
{
"~#penpot/token-set": {
"~:id": "~uf71719b3-63a1-8157-8008-40346917de8d",
"~:name": "Mode/Dark",
"~:description": "",
"~:modified-at": "~m1782803647245",
"~:tokens": {
"~#ordered-map": [
[
"red",
{
"~#penpot/token": {
"~:id": "~uf71719b3-63a1-8157-8008-40346917de88",
"~:name": "red",
"~:type": "~:color",
"~:value": "#ef0c0c",
"~:description": "",
"~:modified-at": "~m1782742859871"
}
}
],
[
"ref-grfeen",
{
"~#penpot/token": {
"~:id": "~u886f2846-a2c5-808d-8008-4115e5fdae2e",
"~:name": "ref-grfeen",
"~:type": "~:color",
"~:value": "{green}",
"~:description": "",
"~:modified-at": "~m1782801970166"
}
}
],
[
"ref-typo",
{
"~#penpot/token": {
"~:id": "~u886f2846-a2c5-808d-8008-41160c4641f4",
"~:name": "ref-typo",
"~:type": "~:typography",
"~:value": "{typo1}",
"~:description": "",
"~:modified-at": "~m1782802009369"
}
}
],
[
"ref-1",
{
"~#penpot/token": {
"~:id": "~u886f2846-a2c5-808d-8008-411628261dc7",
"~:name": "ref-1",
"~:type": "~:border-radius",
"~:value": "{b1}",
"~:description": "",
"~:modified-at": "~m1782802037912"
}
}
],
[
"in-br",
{
"~#penpot/token": {
"~:id": "~uf2256046-b127-808e-8008-411c0aced089",
"~:name": "in-br",
"~:type": "~:border-radius",
"~:value": "{base-50}",
"~:description": "",
"~:modified-at": "~m1782803580731"
}
}
],
[
"in-color",
{
"~#penpot/token": {
"~:id": "~uf2256046-b127-808e-8008-411c263c999b",
"~:name": "in-color",
"~:type": "~:color",
"~:value": "{out-ref}",
"~:description": "",
"~:modified-at": "~m1782803608818"
}
}
],
[
"in-typo",
{
"~#penpot/token": {
"~:id": "~uf2256046-b127-808e-8008-411c472f33d3",
"~:name": "in-typo",
"~:type": "~:typography",
"~:value": "{out-typo}",
"~:description": "",
"~:modified-at": "~m1782803642556"
}
}
]
]
}
}
}
],
[
"S-Light",
{
"~#penpot/token-set": {
"~:id": "~uf71719b3-63a1-8157-8008-40346917de8e",
"~:name": "Mode/Light",
"~:description": "",
"~:modified-at": "~m1782812999332",
"~:tokens": {
"~#ordered-map": [
[
"red",
{
"~#penpot/token": {
"~:id": "~uf71719b3-63a1-8157-8008-40346917de89",
"~:name": "red",
"~:type": "~:color",
"~:value": "#ec9090",
"~:description": "",
"~:modified-at": "~m1782742859871"
}
}
],
[
"typo-2",
{
"~#penpot/token": {
"~:id": "~u1de8c150-538b-80b2-8008-40357ed6d13a",
"~:name": "typo-2",
"~:type": "~:typography",
"~:value": {
"~:font-family": [
"AR One Sans"
],
"~:font-weight": "200"
},
"~:description": "",
"~:modified-at": "~m1782812999332"
}
}
],
[
"border-radius",
{
"~#penpot/token": {
"~:id": "~uf2256046-b127-808e-8008-4134c130c292",
"~:name": "border-radius",
"~:type": "~:border-radius",
"~:value": "{base-50} * 2",
"~:description": "",
"~:modified-at": "~m1782810058947"
}
}
]
]
}
}
}
]
]
}
]
]
},
"~:themes": {
"~#ordered-map": [
[
"",
{
"~#ordered-map": [
[
"__PENPOT__HIDDEN__TOKEN__THEME__",
{
"~#penpot/token-theme": {
"~:id": "~u00000000-0000-0000-0000-000000000000",
"~:name": "__PENPOT__HIDDEN__TOKEN__THEME__",
"~:group": "",
"~:description": "",
"~:is-source": false,
"~:external-id": "",
"~:modified-at": "~m1782812922760",
"~:sets": {
"~#set": [
"Global"
]
}
}
}
]
]
}
],
[
"App",
{
"~#ordered-map": [
[
"Web",
{
"~#penpot/token-theme": {
"~:id": "~u37900259-7f15-8003-8007-9ee75dd2f2d2",
"~:name": "Web",
"~:group": "App",
"~:description": "",
"~:is-source": false,
"~:external-id": "37900259-7f15-8003-8007-9ee75dd2f2d2",
"~:modified-at": "~m1782810072529",
"~:sets": {
"~#set": [
"Mode/Dark",
"Global"
]
}
}
}
],
[
"Mobile",
{
"~#penpot/token-theme": {
"~:id": "~u37900259-7f15-8003-8007-9ee77eb567bd",
"~:name": "Mobile",
"~:group": "App",
"~:description": "",
"~:is-source": false,
"~:external-id": "37900259-7f15-8003-8007-9ee77eb567bd",
"~:modified-at": "~m1782810072529",
"~:sets": {
"~#set": [
"Mode/Light",
"Global"
]
}
}
}
]
]
}
]
]
},
"~:active-themes": {
"~#set": [
"/__PENPOT__HIDDEN__TOKEN__THEME__"
]
}
}
},
"~:id": "~u55608328-aed3-807a-8008-41341f2f7247",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -209,7 +209,10 @@ test("Renders a file with emoji and text decoration", async ({ page }) => {
pageId: "82d128e1-d3b1-80a5-8006-ae60fedcd5e8",
});
await workspace.waitForFirstRenderWithoutUI();
await expect(workspace.canvas).toHaveScreenshot();
await expect(workspace.canvas).toHaveScreenshot({
maxDiffPixelRatio: 0,
threshold: 0.1,
});
});
test("Renders a file with multiple emoji", async ({ page }) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -331,3 +331,33 @@ test("BUG 14098 - Fix text editor having 0 width or height", async ({ page }) =>
const textEditor = workspace.page.locator(`div[class*="viewport"]`).first().getByRole('textbox').first();
await expect(textEditor).toBeVisible();
});
test("Preserves empty fill after editing text without changes", async ({ page }) => {
const initialText = "Hello";
const workspace = new WasmWorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
await workspace.mockRPC("update-file?id=*", "text-editor/update-file.json");
await workspace.goToWorkspace();
await workspace.createTextShape(190, 150, 300, 200, initialText);
await workspace.textEditor.stopEditing();
const fillColorButton = workspace.page.getByRole("button", {
name: "#000000",
});
await expect(fillColorButton).toBeVisible();
await workspace.page.getByRole("button", { name: "Remove color" }).click();
await expect(fillColorButton).toHaveCount(0);
await workspace.doubleClickLeafLayer(initialText);
await workspace.textEditor.waitForEditor();
await workspace.moveButton.click();
await workspace.clickAt(100, 100);
await workspace.clickLeafLayer(initialText);
await expect(fillColorButton).toHaveCount(0);
await expect(workspace.page.getByTestId("add-fill")).toBeVisible();
});

View File

@ -907,7 +907,7 @@ test.describe("Tokens: Detach token", () => {
await expect(page.getByText("Don't remap")).toBeVisible();
await page.getByText("Don't remap").click();
const brokenPill = borderRadiusSection.getByRole("button", {
name: "is not in any active set",
name: "{borderRadius.sm} token does not exist or has been deleted",
});
await expect(brokenPill).toBeVisible();
@ -1775,3 +1775,375 @@ test("BUG: 14234, Numeric input token filtering must be case sensitive", async (
measuresSection.getByText("dim-up", { exact: true }),
).not.toBeVisible();
});
test("BUG: 10471, Correct tooltip on right sidebar tokens", async ({
page,
}) => {
const {
workspacePage,
tokensSidebar,
tokenContextMenuForToken,
tokenThemesSetsSidebar,
tokenSetGroupItems,
} = await setupTokensFileRender(page, {
file: "workspace/get-file-token-tooltip.json",
});
await page.getByRole("tab", { name: "Layers" }).click();
const borderRadiusSection = page.getByRole("region", {
name: "Border radius section",
});
const fillSection = workspacePage.rightSidebar.getByRole("region", {
name: "Fill section",
});
const typographySection = workspacePage.rightSidebar.getByRole("region", {
name: "Text section",
});
// ----------------------------------
// Select rectangle with active token
// ----------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Rectangle with token" })
.click();
await expect(borderRadiusSection).toBeVisible();
const brPill = borderRadiusSection.getByRole("button", { name: "base-50" });
await expect(brPill).toBeVisible();
await brPill.hover();
await expect(page.getByRole("tooltip", { name: "base-50" })).toBeVisible();
await expect(fillSection).toBeVisible();
const fillTokenPill = fillSection.getByLabel("out-ref", {
exact: true,
});
await expect(fillTokenPill).toBeVisible();
await fillTokenPill.hover();
const colorTokenTooltip = page.getByRole("tooltip", {
name: "out-ref",
});
await expect(colorTokenTooltip).toBeVisible();
await expect(colorTokenTooltip).toHaveText(
"Name: out-refResolved value: #da1fea",
);
// ----------------------------------
// Select text with active token
// ----------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Text with token" })
.click();
await expect(fillSection).toBeVisible();
const textFillTokenPill = fillSection.getByLabel("out-ref", {
exact: true,
});
await expect(textFillTokenPill).toBeVisible();
await textFillTokenPill.hover();
await expect(colorTokenTooltip).toBeVisible();
await expect(colorTokenTooltip).toHaveText(
"Name: out-refResolved value: #da1fea",
);
await expect(typographySection).toBeVisible();
const typographyTokenPill = typographySection.getByLabel("out-typo", {
exact: true,
});
await expect(typographyTokenPill).toBeVisible();
await typographyTokenPill.hover();
const typographyTokenTooltip = page.getByRole("tooltip", {
name: "out-typo",
});
await expect(typographyTokenTooltip).toBeVisible();
await expect(typographyTokenTooltip).toHaveText(
'Name: out-typoResolved value:- font-family: "Arizonia"',
);
// -----------------------------------------
// Select rectangle layer with deleted token
// -----------------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Deleted token rect" })
.click();
await expect(borderRadiusSection).toBeVisible();
const deletedBrPill = borderRadiusSection.getByRole("button", {
name: "{deleted} token does not exist or",
});
await expect(deletedBrPill).toBeVisible();
await deletedBrPill.hover();
await expect(
page.getByRole("tooltip", { name: "{deleted} token does not exist or" }),
).toBeVisible();
await expect(fillSection).toBeVisible();
const fillBrokenTokenPill = fillSection.getByLabel("blue", {
exact: true,
});
await expect(fillBrokenTokenPill).toBeVisible();
await fillBrokenTokenPill.hover();
const fillBrokenTokenTooltip = page.getByRole("tooltip", {
name: "blue",
});
await expect(fillBrokenTokenTooltip).toBeVisible();
await expect(fillBrokenTokenTooltip).toHaveText(
"{blue} token does not exist or has been deleted.",
);
// ------------------------------------
// Select text layer with deleted token
// ------------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Text with deleted token" })
.click();
await expect(typographySection).toBeVisible();
const brokenTypographyTokenPill = typographySection.getByLabel(
"deleted-typo",
{ exact: true },
);
await expect(brokenTypographyTokenPill).toBeVisible();
await brokenTypographyTokenPill.hover();
const brokenTypographyTokenTooltip = page.getByRole("tooltip", {
name: "deleted-typo",
});
await expect(brokenTypographyTokenTooltip).toBeVisible();
await expect(brokenTypographyTokenTooltip).toHaveText(
"{deleted-typo} token does not exist or has been deleted.",
);
// ---------------------------------------------------
// Select rectangle layer with deleted reference token
// ---------------------------------------------------
await page.getByRole("tab", { name: "Tokens" }).click();
await tokenThemesSetsSidebar.getByRole("button", { name: "Dark" }).click();
await tokenThemesSetsSidebar
.getByRole("button", { name: "Dark" })
.getByRole("checkbox")
.click();
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Rectangle with deleted reference" })
.click();
await expect(borderRadiusSection).toBeVisible();
const deletedReferenceBrPill = borderRadiusSection.getByRole("button", {
name: "Reference in {ref-1} is not valid or is not in any active set.",
});
await expect(deletedReferenceBrPill).toBeVisible();
await deletedReferenceBrPill.hover();
await expect(
page.getByRole("tooltip", {
name: "Reference in {ref-1} is not valid or is not in any active set.",
}),
).toBeVisible();
await expect(fillSection).toBeVisible();
const fillDeletedReferenceTokenPill = fillSection.getByLabel("ref-grfeen", {
exact: true,
});
await expect(fillDeletedReferenceTokenPill).toBeVisible();
await fillDeletedReferenceTokenPill.hover();
const fillDeletedReferenceTokenTooltip = page.getByRole("tooltip", {
name: "Reference in {ref-grfeen} is not valid or is not in any active set.",
});
await expect(fillDeletedReferenceTokenTooltip).toBeVisible();
await expect(fillDeletedReferenceTokenTooltip).toHaveText(
"Reference in {ref-grfeen} is not valid or is not in any active set.",
);
// ----------------------------------------------
// Select text layer with deleted reference token
// ----------------------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Text with deleted reference" })
.click();
await expect(typographySection).toBeVisible();
const deletedRefTypographyTokenPill = typographySection.getByLabel(
"ref-typo",
{ exact: true },
);
await expect(deletedRefTypographyTokenPill).toBeVisible();
await deletedRefTypographyTokenPill.hover();
const deletedRefTypographyTokenTooltip = page.getByRole("tooltip", {
name: "Reference in {ref-typo} is not valid or is",
});
await expect(deletedRefTypographyTokenTooltip).toBeVisible();
await expect(deletedRefTypographyTokenTooltip).toHaveText(
"Reference in {ref-typo} is not valid or is not in any active set.",
);
await expect(fillSection).toBeVisible();
const fillTextDeletedReferenceTokenPill = fillSection.getByLabel(
"ref-grfeen",
{
exact: true,
},
);
await expect(fillTextDeletedReferenceTokenPill).toBeVisible();
await fillTextDeletedReferenceTokenPill.hover();
const fillTextDeletedReferenceTokenTooltip = page.getByRole("tooltip", {
name: "Reference in {ref-grfeen} is not valid or is not in any active set.",
});
await expect(fillTextDeletedReferenceTokenTooltip).toBeVisible();
await expect(fillTextDeletedReferenceTokenTooltip).toHaveText(
"Reference in {ref-grfeen} is not valid or is not in any active set.",
);
// -----------------------------------------------------------
// Select rectangle layer with reference token on inactive set
// -----------------------------------------------------------
await page.getByRole("tab", { name: "Tokens" }).click();
await tokenThemesSetsSidebar.getByRole("button", { name: "Global" }).click();
await tokenThemesSetsSidebar
.getByRole("button", { name: "Global" })
.getByRole("checkbox")
.click();
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Rectangle with not active reference" })
.click();
await expect(borderRadiusSection).toBeVisible();
const nonActiveReferenceBrPill = borderRadiusSection.getByRole("button", {
name: "Reference in {in-br} is not valid or is not in any active set.",
});
await expect(nonActiveReferenceBrPill).toBeVisible();
await nonActiveReferenceBrPill.hover();
await expect(
page.getByRole("tooltip", {
name: "Reference in {in-br} is not valid or is not in any active set.",
}),
).toBeVisible();
await expect(fillSection).toBeVisible();
const fillNonActiveReferenceTokenPill = fillSection.getByLabel("in-color", {
exact: true,
});
await expect(fillNonActiveReferenceTokenPill).toBeVisible();
await fillNonActiveReferenceTokenPill.hover();
const fillNonActiveReferenceTokenTooltip = page.getByRole("tooltip", {
name: "Reference in {in-color} is not valid or is not in any active set.",
});
await expect(fillNonActiveReferenceTokenTooltip).toBeVisible();
await expect(fillNonActiveReferenceTokenTooltip).toHaveText(
"Reference in {in-color} is not valid or is not in any active set.",
);
// ------------------------------------------------------
// Select text layer with reference token on inactive set
// ------------------------------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Text with not active reference" })
.click();
await expect(typographySection).toBeVisible();
const notActiveRefTypographyTokenPill = typographySection.getByLabel(
"in-typo",
{ exact: true },
);
await expect(notActiveRefTypographyTokenPill).toBeVisible();
await notActiveRefTypographyTokenPill.hover();
const notActiveRefTypographyTokenTooltip = page.getByRole("tooltip", {
name: "Reference in {in-typo} is not valid or is",
});
await expect(notActiveRefTypographyTokenTooltip).toBeVisible();
await expect(notActiveRefTypographyTokenTooltip).toHaveText(
"Reference in {in-typo} is not valid or is not in any active set.",
);
await expect(fillSection).toBeVisible();
const fillTextNotActiveReferenceTokenPill = fillSection.getByLabel(
"in-color",
{
exact: true,
},
);
await expect(fillTextNotActiveReferenceTokenPill).toBeVisible();
await fillTextNotActiveReferenceTokenPill.hover();
const fillTextNotActiveReferenceTokenTooltip = page.getByRole("tooltip", {
name: "Reference in {in-color} is not valid or is not in any active set.",
});
await expect(fillTextNotActiveReferenceTokenTooltip).toBeVisible();
await expect(fillTextNotActiveReferenceTokenTooltip).toHaveText(
"Reference in {in-color} is not valid or is not in any active set.",
);
// -------------------------------------------------
// Select rectangle layer with token on inactive set
// -------------------------------------------------
await page.getByRole("tab", { name: "Tokens" }).click();
await tokenThemesSetsSidebar.getByRole("button", { name: "Dark" }).click();
await tokenThemesSetsSidebar
.getByRole("button", { name: "Dark" })
.getByRole("checkbox")
.click();
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Rectangle with not active token" })
.click();
await expect(borderRadiusSection).toBeVisible();
const nonActiveBrPill = borderRadiusSection.getByRole("button", {
name: "{border-radius} token is not in any active set",
});
await expect(nonActiveBrPill).toBeVisible();
await nonActiveBrPill.hover();
await expect(
page.getByRole("tooltip", {
name: "{border-radius} token is not in any active set or has an invalid value",
}),
).toBeVisible();
await expect(fillSection).toBeVisible();
const fillNonActiveTokenPill = fillSection.getByLabel("red", {
exact: true,
});
await expect(fillNonActiveTokenPill).toBeVisible();
await fillNonActiveTokenPill.hover();
const fillNonActiveTokenTooltip = page.getByRole("tooltip", {
name: "{red} token is not in any active set or has an invalid value.",
});
await expect(fillNonActiveTokenTooltip).toBeVisible();
await expect(fillNonActiveTokenTooltip).toHaveText(
"{red} token is not in any active set or has an invalid value.",
);
// --------------------------------------------
// Select text layer with token on inactive set
// --------------------------------------------
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Text with not active token" })
.click();
await expect(typographySection).toBeVisible();
const notActiveTypographyTokenPill = typographySection.getByLabel(
"typo-2",
{ exact: true },
);
await expect(notActiveTypographyTokenPill).toBeVisible();
await notActiveTypographyTokenPill.hover();
const notActiveTypographyTokenTooltip = page.getByRole("tooltip", {
name: "{typo-2} token is not in any active set or has an invalid value.",
});
await expect(notActiveTypographyTokenTooltip).toBeVisible();
await expect(notActiveTypographyTokenTooltip).toHaveText(
"{typo-2} token is not in any active set or has an invalid value.",
);
await expect(fillSection).toBeVisible();
const fillTextNotActiveTokenPill = fillSection.getByLabel(
"red",
{
exact: true,
},
);
await expect(fillTextNotActiveTokenPill).toBeVisible();
await fillTextNotActiveTokenPill.hover();
const fillTextNotActiveTokenTooltip = page.getByRole("tooltip", {
name: "{red} token is not in any active set or has an invalid value.",
});
await expect(fillTextNotActiveTokenTooltip).toBeVisible();
await expect(fillTextNotActiveTokenTooltip).toHaveText(
"{red} token is not in any active set or has an invalid value.",
);
});

564
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -204,6 +204,10 @@
(mf/with-effect [options]
(mf/set-ref-val! options-ref options))
(mf/with-effect [default-selected options]
(reset! selected-id*
(get-selected-option-id options default-selected)))
[:div {:class [wrapper-class (stl/css :select-wrapper)]
:on-click on-click
:ref select-ref

View File

@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.refs :as refs]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.tooltip :refer [tooltip*]]
@ -43,10 +44,25 @@
token-has-errors]}]
(let [set-active? (some? id)
all-tokens-map (mf/deref refs/workspace-all-tokens-map)
token-exists? (contains? all-tokens-map label)
token-not-active (and token-exists? (not set-active?))
content (cond
token-has-errors (tr "workspace.tokens.ref-not-valid")
(not set-active?) (tr "ds.inputs.token-field.no-active-token-option" label)
:else label)
(not token-exists?)
(tr "options.deleted-token-with-name" label)
(and token-exists? (not set-active?))
(tr "ds.inputs.token-field.no-active-token-option" label)
(and token-exists? token-has-errors)
(tr "workspace.tokens.ref-not-valid" label)
:else
label)
broken-state (or (not token-exists?)
token-has-errors
token-not-active)
default-id (mf/use-id)
id (d/nilv id default-id)
@ -84,15 +100,13 @@
[:button {:on-click on-click
:ref pill-ref
:class (stl/css-case :pill true
:no-set-pill (or (not set-active?)
token-has-errors)
:no-set-pill broken-state
:pill-disabled disabled)
:disabled disabled
:aria-labelledby (dm/str id "-pill")
:on-key-down on-token-key-down}
value
(when (or (not set-active?)
token-has-errors)
(when broken-state
[:div {:class (stl/css :pill-dot)}])]]]
(when-not ^boolean disabled

View File

@ -159,6 +159,7 @@ $arrow-side: 12px;
padding: var(--sp-s) var(--sp-m);
grid-area: content;
block-size: fit-content;
overflow-wrap: anywhere;
}
.tooltip-trigger {

View File

@ -193,10 +193,12 @@
.interaction-row-position {
grid-column: 4 / span 5;
display: grid;
grid-template:
"topleft top topright" 1fr
"left center right" 1fr
"bottomleft bottom bottomright" 1fr / repeat(3, 1fr);
grid-template-areas:
"topleft top topright"
"left center right"
"bottomleft bottom bottomright";
grid-template-rows: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
inline-size: calc($sz-32 * 3);
block-size: calc($sz-32 * 3);
border-radius: $br-8;

View File

@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.refs :as refs]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.shared.token-option :as to]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
@ -25,31 +26,46 @@
token (->> (:typography active-tokens)
(d/seek #(= (:name %) token-name)))
has-errors (some? (:errors token))
display-name (or (:name token) token-name)
resolved-value (:resolved-value token)
not-active (or (nil? token)
(empty? (:typography active-tokens)))
on-detach
(mf/use-fn
(mf/deps display-name)
(fn []
(detach-token display-name)))
all-tokens-map (mf/deref refs/workspace-all-tokens-map)
token-exists? (contains? all-tokens-map token-name)
has-errors (and token-exists?
(some? (:errors token)))
not-active (and
token-exists?
(or (nil? token)
(empty? (:typography active-tokens))))
broken-state (or (not token-exists?)
has-errors
not-active)
tooltip-content (cond
not-active
(tr "options.deleted-token")
(tr "ds.inputs.token-field.no-active-token-option" token-name)
(not token-exists?)
(tr "options.deleted-token-with-name" token-name)
has-errors
(tr "not-active-token.no-name")
(tr "workspace.tokens.ref-not-valid" token-name)
:else
(mf/html [:> to/resolved-value-tooltip* {:token-name token-name
:resolved-value resolved-value}]))]
[:div {:class (stl/css-case :token-typography-row true
:token-typography-row-with-errors has-errors
:token-typography-row-not-active not-active)}
(when (or has-errors not-active)
:token-typography-row-with-errors broken-state)}
(when broken-state
[:div {:class (stl/css :error-dot)}])
[:> icon* {:icon-id i/text-typography
:class (stl/css :icon)}]

View File

@ -35,8 +35,7 @@
}
}
.token-typography-row-with-errors,
.token-typography-row-not-active {
.token-typography-row-with-errors {
--token-typography-row-background-color: var(--color-background-primary);
--token-typography-row-foreground-color: var(--color-foreground-secondary);
--token-typography-row-border-color: var(--color-token-border);

View File

@ -89,42 +89,72 @@
(let [token-name (or (:name token) applied-token-name)]
(detach-token token-name))))
has-errors (some? (:errors token))
token-name (:name token)
resolved (:resolved-value token)
not-active (or (empty? active-tokens)
(nil? token))
;; Tooltip content for the swatch and token name, based on the token's state
all-tokens-map (mf/deref refs/workspace-all-tokens-map)
token-exists? (contains? all-tokens-map applied-token-name)
has-errors (and token-exists?
(some? (:errors token)))
not-active (and
token-exists?
(or (empty? active-tokens)
(nil? token)))
id (dm/str (:id token) "-name")
token-name-ref (mf/use-ref nil)
broken-state (or (not token-exists?)
has-errors
not-active)
swatch-tooltip-content (cond
not-active
(tr "options.deleted-token")
(tr "ds.inputs.token-field.no-active-token-option" applied-token-name)
(not token-exists?)
(tr "options.deleted-token-with-name" applied-token-name)
has-errors
(tr "not-active-token.no-name")
(tr "workspace.tokens.ref-not-valid" applied-token-name)
:else
(tr "workspace.tokens.resolved-value" resolved))
name-tooltip-content (cond
not-active
(tr "options.deleted-token")
has-errors
(tr "not-active-token.no-name")
(tr "workspace.tokens.ref-not-valid" applied-token-name)
not-active
(tr "ds.inputs.token-field.no-active-token-option" applied-token-name)
(not token-exists?)
(tr "options.deleted-token-with-name" applied-token-name)
:else
#(mf/html
[:div
[:span (dm/str (tr "workspace.tokens.token-name") ": ")]
[:span {:class (stl/css :token-name-tooltip)} applied-token-name]]))]
[:span {:class (stl/css :token-name-tooltip)} applied-token-name]
[:div
[:span (tr "inspect.tabs.styles.token-resolved-value")]
[:span {:class (stl/css :resolved-value)} (dm/str " " resolved)]]]))]
[:div {:class (stl/css :color-info)}
[:div {:class (stl/css-case :token-color-wrapper true
:token-color-with-errors has-errors
:token-color-not-active not-active)}
:token-color-with-errors broken-state)}
[:div {:class (stl/css :color-bullet-wrapper)}
(when (or has-errors not-active)
(when broken-state
[:div {:class (stl/css :error-dot)}])
[:> swatch* {:background color
:tooltip-content swatch-tooltip-content
:on-click on-swatch-click-token
:has-errors (or has-errors not-active)
:has-errors broken-state
:size "small"}]]
[:> tooltip* {:content name-tooltip-content
:id id

View File

@ -171,7 +171,7 @@
:cell (ctl/get-cell-by-shape-id (first parents) (first ids))}])
(when is-layout-child?
[:& layout-item-menu*
[:> layout-item-menu*
{:ids ids
:type type
:values layout-item-values

View File

@ -127,7 +127,7 @@
(cond
;; If there are errors, show the appropriate message
ref-not-in-active-set
(tr "workspace.tokens.ref-not-valid")
(tr "workspace.tokens.ref-not-valid" name)
is-name-collision
(wte/resolve-error-message (first errors))
@ -290,7 +290,7 @@
:token-pill-disabled disabled?
:token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?))
:token-pill-invalid (and can-edit? errors?)
:token-pill-invalid-applied (and full-applied? errors? can-edit?)
:token-pill-invalid-applied (and (or half-applied? full-applied?) errors? can-edit?)
:token-pill-viewer is-viewer?
:token-pill-applied-viewer (and is-viewer? has-selected?
(or half-applied? full-applied?))

View File

@ -507,7 +507,8 @@
(when (and (seq selected-shapes)
(not transform)
(not text-editing?)
(not edition))
(not edition)
(not mode-inspect?))
[:> msr/selection-size-badge*
{:selrect (gsh/shapes->rect selected-shapes)
:zoom zoom}])

View File

@ -778,6 +778,7 @@
(not transform)
(not text-editing?)
(not edition)
(not mode-inspect?)
(not page-transition?))
[:> msr/selection-size-badge*
{:selrect (gsh/shapes->rect selected-shapes)

View File

@ -151,7 +151,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :rowGap value)
(not (r/check-permission plugin-id "content:write"))
@ -169,7 +169,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :columnGap value)
(not (r/check-permission plugin-id "content:write"))
@ -187,7 +187,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -205,7 +205,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -223,7 +223,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :topPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -241,7 +241,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -259,7 +259,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -277,7 +277,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :leftPadding value)
(not (r/check-permission plugin-id "content:write"))

View File

@ -141,7 +141,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :rowGap value)
(not (r/check-permission plugin-id "content:write"))
@ -159,7 +159,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :columnGap value)
(not (r/check-permission plugin-id "content:write"))
@ -177,7 +177,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -195,7 +195,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -213,7 +213,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :topPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -231,14 +231,14 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
(u/not-valid plugin-id :righPadding "Plugin doesn't have 'content:write' permission")
(u/not-valid plugin-id :rightPadding "Plugin doesn't have 'content:write' permission")
(not (u/page-active? page-id))
(u/not-valid plugin-id :righPadding "Cannot modify a page that is not currently active")
(u/not-valid plugin-id :rightPadding "Cannot modify a page that is not currently active")
:else
(st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value}}))))}
@ -249,7 +249,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
@ -267,7 +267,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :leftPadding value)
(not (r/check-permission plugin-id "content:write"))

View File

@ -336,7 +336,7 @@
(d/without-nils
{:type (-> (obj/get guide "type") parse-keyword)
:display (obj/get guide "display")
:params (-> (obj/get guide "params") parse-frame-guide-column-params)})))
:params (-> (obj/get guide "params") parse-frame-guide-square-params)})))
(defn parse-frame-guide
[^js guide]
@ -488,8 +488,8 @@
{:action-type action-type
:destination (-> (obj/get action "destination") (obj/get "$id"))
:relative-to (-> (obj/get action "relativeTo") (obj/get "$id"))
:overlay-pos-type (-> (obj/get action "position") parse-keyword)
:overlay-position (-> (obj/get action "manualPositionLocation") parse-point (d/nilv (gpt/point 0 0)))
:overlay-pos-type (or (-> (obj/get action "position") parse-keyword) :center)
:overlay-position (-> (obj/get action "manualPositionLocation") parse-point)
:close-click-outside (obj/get action "closeWhenClickOutside")
:background-overlay (obj/get action "addBackgroundOverlay")
:animation (-> (obj/get action "animation") parse-animation)}

View File

@ -112,7 +112,7 @@
:set
(fn [_ value]
(cond
(or (not (number? value)) (not (pos? value)))
(or (not (sm/valid-safe-int? value)) (neg? value))
(u/not-valid plugin-id :delay value)
:else
@ -422,7 +422,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(or (not (sm/valid-safe-int? value)) (< value 0))
(or (not (sm/valid-safe-number? value)) (< value 0))
(u/not-valid plugin-id :borderRadius value)
(not (r/check-permission plugin-id "content:write"))
@ -441,7 +441,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :borderRadiusTopLeft value)
(not (r/check-permission plugin-id "content:write"))
@ -460,7 +460,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :borderRadiusTopRight value)
(not (r/check-permission plugin-id "content:write"))
@ -479,7 +479,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :borderRadiusBottomRight value)
(not (r/check-permission plugin-id "content:write"))
@ -498,7 +498,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (sm/valid-safe-number? value))
(u/not-valid plugin-id :borderRadiusBottomLeft value)
(not (r/check-permission plugin-id "content:write"))

View File

@ -453,6 +453,23 @@
(defn token-theme-proxy? [p]
(obj/type-of? p "TokenThemeProxy"))
(defn- resolve-token-set
"Resolves an addSet/removeSet argument to a token set. A proxy is returned
as-is; an id is located in the file's token library."
[file-id set-arg]
(if (token-set-proxy? set-arg)
set-arg
(u/locate-token-set file-id set-arg)))
(defn- token-set-name
"Reads the name from a resolved token set, supporting both proxies (whose
getter falls back to the freshly-created name) and located sets."
[set]
(when (some? set)
(if (token-set-proxy? set)
(obj/get set "name")
(ctob/get-name set))))
(defn token-theme-proxy
[plugin-id file-id id]
(obj/reify {:name "TokenThemeProxy"
@ -535,27 +552,20 @@
:addSet
{:enumerable false
:schema [:tuple [:fn token-set-proxy?]]
:fn (fn [token-set]
;; Resolve the set name before the theme lookup. The proxy's :name
;; getter now falls back to `initial-name` when state hasn't
;; propagated, so this is safe even for freshly created sets.
;; Guard against nil to prevent `enable-set` from conj'ing nil
;; into the theme's :sets — which would send `:sets #{nil}` to the
;; backend and crash the workspace.
(let [set-name (obj/get token-set "name")
:schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]]
:fn (fn [set-arg]
(let [set-name (token-set-name (resolve-token-set file-id set-arg))
theme (u/locate-token-theme file-id id)]
(when (and (some? set-name) (some? theme))
(when (and set-name theme)
(st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name))))))}
:removeSet
{:enumerable false
:schema [:tuple [:fn token-set-proxy?]]
:fn (fn [token-set]
;; Same nil guard as addSet — see comment above.
(let [set-name (obj/get token-set "name")
:schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]]
:fn (fn [set-arg]
(let [set-name (token-set-name (resolve-token-set file-id set-arg))
theme (u/locate-token-theme file-id id)]
(when (and (some? set-name) (some? theme))
(when (and set-name theme)
(st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name))))))}
:duplicate

View File

@ -311,12 +311,16 @@
(defn error-messages
[explain]
(->> (:errors explain)
(reduce csm/interpret-schema-problem {})
(flatten-error-map)
(map (fn [[field message]]
(tr "plugins.validation.message" field message)))
(str/join ". ")))
(let [msg (->> (:errors explain)
(reduce csm/interpret-schema-problem {})
(flatten-error-map)
(map (fn [[field message]]
(tr "plugins.validation.message" field message)))
(str/join ". "))]
;; Return nil (not "") when the explain has no mappable errors, so
;; `handle-error` can fall back to a non-empty message instead of
;; surfacing a bare "Value not valid. Code: :error" (#9692).
(when-not (str/blank? msg) msg)))
(defn handle-error
"Function to be used in plugin proxies methods to handle errors and print a readable
@ -340,8 +344,8 @@
(if explain
(do
(js/console.error (sm/humanize-explain explain))
(error-messages explain))
(ex-data cause))]
(or (error-messages explain) (pr-str explain)))
(or (ex-data cause) (ex-message cause) (str cause)))]
(js/console.log (.-stack cause))
(not-valid plugin-id :error message))))))

View File

@ -1423,18 +1423,20 @@
(set-shape-layout shape)
(set-layout-data shape)
(let [is-text? (= type :text)
pending_thumbnails (into [] (concat
(when is-text? (set-shape-text-content id content))
text-content-pending (when is-text? (set-shape-text-content id content))
pending-thumbnails (into [] (concat
text-content-pending
(when is-text? (set-shape-text-images id content true))
(set-shape-fills id fills true)
(set-shape-strokes id strokes true)))
pending_full (into [] (concat
pending-full (into [] (concat
(when is-text? (set-shape-text-images id content false))
(set-shape-fills id fills false)
(set-shape-strokes id strokes false)))]
(perf/end-measure "set-object")
{:thumbnails pending_thumbnails
:full pending_full}))))
{:thumbnails pending-thumbnails
:full pending-full
:font-pending-ids (if (some :callback text-content-pending) [id] [])}))))
(defn- update-text-layouts
"Synchronously update text layouts for all shapes and send rect updates
@ -1442,8 +1444,29 @@
[text-ids]
(run! f/update-text-layout text-ids))
(defn- force-update-text-layouts
"Like update-text-layouts but forces a relayout. Use after pending fonts
resolve so layouts (and the extrect/tiles derived from them) use real glyph
metrics instead of fallback-font estimates."
[text-ids]
(run! f/force-update-text-layout text-ids))
(defn- relayout-after-fonts!
"Relayout text shapes once their pending fonts have resolved. Shapes in
`font-pending-ids` had a font fetched, so they get a forced relayout to pick
up the real glyph metrics; the remaining text shapes get a normal layout."
[shapes font-pending-ids]
(let [force-ids (set font-pending-ids)
text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes)
forced (filterv force-ids text-ids)
rest-ids (filterv (complement force-ids) text-ids)]
(when (seq forced)
(force-update-text-layouts forced))
(when (seq rest-ids)
(update-text-layouts rest-ids))))
(defn process-pending
[shapes thumbnails full on-complete]
[shapes thumbnails full font-pending-ids on-complete]
(let [pending-thumbnails
(d/index-by :key :callback thumbnails)
@ -1467,11 +1490,7 @@
(rx/catch #(rx/empty))))
(rx/subs!
(fn [_]
;; Fonts are now loaded — recompute text layouts so Skia
;; uses the real metrics instead of fallback-font estimates.
(let [text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes)]
(when (seq text-ids)
(update-text-layouts text-ids)))
(relayout-after-fonts! shapes font-pending-ids)
(request-render "images-loaded"))
noop-fn
(fn [] (when (fn? on-complete) (on-complete)))))
@ -1480,8 +1499,8 @@
(defn process-object
[shape]
(let [{:keys [thumbnails full]} (set-object shape)]
(process-pending [shape] thumbnails full noop-fn)))
(let [{:keys [thumbnails full font-pending-ids]} (set-object shape)]
(process-pending [shape] thumbnails full font-pending-ids noop-fn)))
(defn process-objects
"Like process-object but for multiple shapes at once. Accumulates all
@ -1490,37 +1509,43 @@
just the first shape that triggered the fetch."
[shapes]
(let [total-shapes (count shapes)
{:keys [thumbnails full]}
(loop [index 0 thumbnails-acc (transient []) full-acc (transient [])]
{:keys [thumbnails full font-pending-ids]}
(loop [index 0 thumbnails-acc (transient []) full-acc (transient []) font-acc (transient [])]
(if (< index total-shapes)
(let [shape (nth shapes index)
{:keys [thumbnails full]} (set-object shape)]
{:keys [thumbnails full font-pending-ids]} (set-object shape)]
(recur (inc index)
(reduce conj! thumbnails-acc thumbnails)
(reduce conj! full-acc full)))
{:thumbnails (persistent! thumbnails-acc) :full (persistent! full-acc)}))]
(process-pending shapes thumbnails full noop-fn)))
(reduce conj! full-acc full)
(reduce conj! font-acc font-pending-ids)))
{:thumbnails (persistent! thumbnails-acc)
:full (persistent! full-acc)
:font-pending-ids (persistent! font-acc)}))]
(process-pending shapes thumbnails full font-pending-ids noop-fn)))
(defn- process-shapes-chunk
"Process shapes starting at `start-index` until the time budget is exhausted.
Returns {:thumbnails [...] :full [...] :next-index n}"
[shapes start-index thumbnails-acc full-acc]
Returns {:thumbnails [...] :full [...] :font-pending-ids [...] :next-index n}"
[shapes start-index thumbnails-acc full-acc font-pending-acc]
(let [total (count shapes)
deadline (+ (js/performance.now) CHUNK_TIME_BUDGET_MS)]
(loop [index start-index
t-acc (transient thumbnails-acc)
f-acc (transient full-acc)]
f-acc (transient full-acc)
fp-acc (transient font-pending-acc)]
(if (and (< index total)
;; Check performance.now every 8 shapes to reduce overhead
(or (pos? (bit-and (- index start-index) 7))
(<= (js/performance.now) deadline)))
(let [shape (nth shapes index)
{:keys [thumbnails full]} (set-object shape)]
{:keys [thumbnails full font-pending-ids]} (set-object shape)]
(recur (inc index)
(reduce conj! t-acc thumbnails)
(reduce conj! f-acc full)))
(reduce conj! f-acc full)
(reduce conj! fp-acc font-pending-ids)))
{:thumbnails (persistent! t-acc)
:full (persistent! f-acc)
:font-pending-ids (persistent! fp-acc)
:next-index index}))))
(defn- set-objects-async
@ -1531,16 +1556,16 @@
(let [total-shapes (count shapes)]
(p/create
(fn [resolve _reject]
(letfn [(process-next-chunk [index thumbnails-acc full-acc]
(letfn [(process-next-chunk [index thumbnails-acc full-acc font-pending-acc]
(if (< index total-shapes)
;; Process one time-budgeted chunk
(let [{:keys [thumbnails full next-index]}
(let [{:keys [thumbnails full font-pending-ids next-index]}
(process-shapes-chunk shapes index
thumbnails-acc full-acc)]
thumbnails-acc full-acc font-pending-acc)]
;; Yield to browser, then continue with next chunk
(-> (yield-to-browser)
(p/then (fn [_]
(process-next-chunk next-index thumbnails full)))))
(process-next-chunk next-index thumbnails full font-pending-ids)))))
;; All chunks done - finalize
(do
(perf/end-measure "set-objects")
@ -1587,13 +1612,11 @@
(rx/reduce conj [])))
(rx/subs!
(fn [_]
(let [text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes)]
(when (seq text-ids)
(update-text-layouts text-ids)))
(relayout-after-fonts! shapes font-pending-acc)
(request-render "images-loaded"))
noop-fn
noop-fn)))))))]
(process-next-chunk 0 [] []))))))
(process-next-chunk 0 [] [] []))))))
;; This is a version of process-pending that doesn't have sideffects
@ -1646,22 +1669,25 @@
"Synchronously process all shapes (for small shape counts)."
[shapes render-callback on-shapes-ready]
(let [total-shapes (count shapes)
{:keys [thumbnails full]}
(loop [index 0 thumbnails-acc (transient []) full-acc (transient [])]
{:keys [thumbnails full font-pending-ids]}
(loop [index 0 thumbnails-acc (transient []) full-acc (transient []) font-acc (transient [])]
(if (< index total-shapes)
(let [shape (nth shapes index)
{:keys [thumbnails full]} (set-object shape)]
{:keys [thumbnails full font-pending-ids]} (set-object shape)]
(recur (inc index)
(reduce conj! thumbnails-acc thumbnails)
(reduce conj! full-acc full)))
{:thumbnails (persistent! thumbnails-acc) :full (persistent! full-acc)}))]
(reduce conj! full-acc full)
(reduce conj! font-acc font-pending-ids)))
{:thumbnails (persistent! thumbnails-acc)
:full (persistent! full-acc)
:font-pending-ids (persistent! font-acc)}))]
(perf/end-measure "set-objects")
(when on-shapes-ready (on-shapes-ready))
;; Rebuild the tile index so _render knows which shapes
;; map to which tiles after a page switch.
(h/call wasm/internal-module "_set_view_end")
(reset! view-interaction-active? false)
(process-pending shapes thumbnails full
(process-pending shapes thumbnails full font-pending-ids
(fn []
(if render-callback
(render-callback)

View File

@ -119,6 +119,16 @@
(aget shape-id-buffer 2)
(aget shape-id-buffer 3)))))
(defn force-update-text-layout
[id]
(when wasm/context-initialized?
(let [shape-id-buffer (uuid/get-u32 id)]
(h/call wasm/internal-module "_force_update_shape_text_layout_for"
(aget shape-id-buffer 0)
(aget shape-id-buffer 1)
(aget shape-id-buffer 2)
(aget shape-id-buffer 3)))))
;; IMPORTANT: Only TTF fonts can be stored.
(defn- store-font-buffer
[font-data font-array-buffer emoji? fallback?]

View File

@ -250,13 +250,15 @@
(api/set-shape-svg-raw-content (api/get-static-markup shape))
(cfh/text-shape? shape)
(let [pending-thumbnails (into [] (concat (api/set-shape-text-content id v)))
pending-full (into [] (concat (api/set-shape-text-images id v)))]
(let [text-content-pending (api/set-shape-text-content id v)
pending-thumbnails (vec text-content-pending)
pending-full (vec (api/set-shape-text-images id v))
font-pending-ids (when (some :callback text-content-pending) [id])]
;; FIXME: this is a hack to process the pending tasks
;; asynchronously we should probably modify set-wasm-attr!
;; to return a list of callbacks to be executed in a
;; second pass.
(api/process-pending [shape] pending-thumbnails pending-full api/noop-fn)
(api/process-pending [shape] pending-thumbnails pending-full font-pending-ids api/noop-fn)
nil))
:grow-type

View File

@ -42,8 +42,7 @@
(let [attrs (or attrs [])
value-empty? (fn [v]
(or (nil? v)
(and (string? v) (empty? v))
(and (coll? v) (empty? v))))]
(and (string? v) (empty? v))))]
(reduce (fn [acc key]
(let [style (.-style element)
value (if (contains? styles/mapping key)

View File

@ -14,6 +14,12 @@ export default {
"at-rule-no-unknown": null,
"declaration-property-value-no-unknown": null,
"property-no-unknown": [true, { ignoreProperties: ["text-box"] }],
"declaration-block-no-redundant-longhand-properties": [
true,
{
ignoreShorthands: ["grid-template"],
},
],
"selector-pseudo-class-no-unknown": [
true,
{ ignorePseudoClasses: ["global"] }, // TODO: Avoid global selector usage and remove this exception

View File

@ -11,7 +11,8 @@
[app.plugins.api :as api]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]))
[frontend-tests.helpers.wasm :as thw]
[potok.v2.core :as ptk]))
(defn- flows-of
"The vals of the current page flows from the store."
@ -80,3 +81,27 @@
(.addInteraction board1 "click" #js {:type "open-url" :url "https://example.com"})
(t/is (empty? (flows-of store context page))
"open-url interactions do not create a flow"))))))
(def ^:private plugin-id "00000000-0000-0000-0000-000000000000")
(defn- throws?
[thunk]
(try (thunk) false (catch :default _ true)))
(t/deftest interaction-delay-accepts-zero
;; Regression: the InteractionProxy `:delay` setter rejected 0 via
;; `(not (pos? value))`, but the model (`set-delay` -> `check-safe-int`) allows
;; 0 (an immediate after-delay interaction). With `throwValidationErrors`
;; enabled, setting 0 must NOT throw (its validation guard passes), while a
;; negative value must still be rejected.
(thw/with-wasm-mocks*
(fn []
(let [store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
^js context (api/create-context plugin-id)
_ (set! st/state store)
^js board1 (.createBoard context)
^js board2 (.createBoard context)
^js inter (.addInteraction board1 "after-delay" #js {:type "navigate-to" :destination board2} 300)]
(ptk/emit! store #(assoc-in % [:plugins :flags plugin-id :throw-validation-errors] true))
(t/is (not (throws? #(set! (.-delay inter) 0))) "delay = 0 must be accepted")
(t/is (throws? #(set! (.-delay inter) -1)) "negative delay must be rejected")))))

View File

@ -269,3 +269,50 @@
(doseq [[label thunk] (setter-specs m)]
(t/is (not (throws? thunk)) (str label " must be allowed on the active page")))))))
(t/deftest test-layout-gap-padding-accepts-fractional-values
;; Regression: the flex/grid gap and padding setters validated with
;; `valid-safe-int?`, but the layout model types `:row-gap`/`:column-gap` and
;; `:p1`-`:p4` as `safe-number` (and the sidebar accepts decimals), so a
;; fractional value was wrongly rejected. With `throwValidationErrors` on (set
;; by `setup`) and page2 active, a fractional value must be accepted (no throw).
(thw/with-wasm-mocks*
(fn []
(let [{:keys [store page2-id ^js flex ^js grid]} (setup)]
(activate-page! store page2-id)
(doseq [[label thunk]
[["flex.rowGap" #(set! (.-rowGap flex) 10.5)]
["flex.columnGap" #(set! (.-columnGap flex) 3.25)]
["flex.verticalPadding" #(set! (.-verticalPadding flex) 4.5)]
["flex.horizontalPadding" #(set! (.-horizontalPadding flex) 4.5)]
["flex.topPadding" #(set! (.-topPadding flex) 1.5)]
["flex.rightPadding" #(set! (.-rightPadding flex) 1.5)]
["flex.bottomPadding" #(set! (.-bottomPadding flex) 1.5)]
["flex.leftPadding" #(set! (.-leftPadding flex) 1.5)]
["grid.rowGap" #(set! (.-rowGap grid) 7.5)]
["grid.columnGap" #(set! (.-columnGap grid) 2.25)]
["grid.verticalPadding" #(set! (.-verticalPadding grid) 4.5)]
["grid.horizontalPadding" #(set! (.-horizontalPadding grid) 4.5)]
["grid.topPadding" #(set! (.-topPadding grid) 1.5)]
["grid.rightPadding" #(set! (.-rightPadding grid) 1.5)]
["grid.bottomPadding" #(set! (.-bottomPadding grid) 1.5)]
["grid.leftPadding" #(set! (.-leftPadding grid) 1.5)]]]
(t/is (not (throws? thunk)) (str label " must accept a fractional value")))))))
(t/deftest test-border-radius-accepts-fractional-values
;; Regression: the ShapeProxy borderRadius setters validated with
;; `valid-safe-int?`, but the model types `:r1`-`:r4` as `safe-number` (and the
;; radius sidebar input has min 0, not integer-only), so a fractional radius was
;; wrongly rejected. With `throwValidationErrors` on and page2 active, a
;; fractional radius must be accepted (no throw).
(thw/with-wasm-mocks*
(fn []
(let [{:keys [store page2-id ^js rect]} (setup)]
(activate-page! store page2-id)
(doseq [[label thunk]
[["borderRadius" #(set! (.-borderRadius rect) 7.5)]
["borderRadiusTopLeft" #(set! (.-borderRadiusTopLeft rect) 2.5)]
["borderRadiusTopRight" #(set! (.-borderRadiusTopRight rect) 2.5)]
["borderRadiusBottomRight" #(set! (.-borderRadiusBottomRight rect) 2.5)]
["borderRadiusBottomLeft" #(set! (.-borderRadiusBottomLeft rect) 2.5)]]]
(t/is (not (throws? thunk)) (str label " must accept a fractional value")))))))

View File

@ -7,9 +7,31 @@
(ns frontend-tests.plugins.parser-test
(:require
[app.common.geom.point :as gpt]
[app.common.schema :as sm]
[app.common.types.grid :as ctg]
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.plugins.parser :as parser]
[cljs.test :as t :include-macros true]))
(defn- overlay-action
[{:keys [type destination position manual-position-location]}]
(let [action (js-obj "type" type
"destination" (js-obj "$id" destination))]
(when (some? position)
(unchecked-set action "position" position))
(when (some? manual-position-location)
(unchecked-set action "manualPositionLocation" manual-position-location))
action))
(defn- parse-overlay-interaction
[action]
(parser/parse-interaction "click" (overlay-action action) nil))
(defn- valid-interaction?
[interaction]
(sm/validate ctsi/schema:interaction interaction))
(t/deftest test-parse-point-returns-gpt-point-record
;; Regression test for issue #8409.
;;
@ -32,18 +54,6 @@
(t/is (= 0 (:x result)))
(t/is (= 0 (:y result))))))
(t/deftest test-parse-overlay-action-defaults-manual-position
(let [destination #js {"$id" (random-uuid)}
action (parser/parse-action
#js {:type "open-overlay"
:destination destination
:position "center"})]
(t/is (= :open-overlay (:action-type action)))
(t/is (= :center (:overlay-pos-type action)))
(t/is (gpt/point? (:overlay-position action)))
(t/is (= 0 (:x (:overlay-position action))))
(t/is (= 0 (:y (:overlay-position action))))))
(t/deftest test-parse-frame-guide-calls-guide-parser
(let [column (parser/parse-frame-guide
#js {:type "column"
@ -61,3 +71,105 @@
(t/is (= :row (:type row)))
(t/is (= false (:display row)))
(t/is (= :center (get-in row [:params :type])))))
(t/deftest test-parse-frame-guides
;; Regression test for issue #9773.
;;
;; `parse-frame-guide` returned the parser fns for column/row instead of
;; calling them with the guide, and the `board.guides` setter validated
;; against an unregistered `::ctg/grid` reference (now `ctg/schema:grid`).
;; Parsed guides must be plain maps that validate against the same direct
;; schema the setter uses, and clearing (empty input) must validate too.
(let [column #js {:type "column" :display true
:params #js {:color #js {:color "#DE4762" :opacity 0.2}
:type "stretch" :size 12 :gutter 16 :margin 16}}
square #js {:type "square" :display true
:params #js {:color #js {:color "#DE4762" :opacity 0.2} :size 8}}
parsed (parser/parse-frame-guides #js [column square])]
(t/is (= :column (-> parsed first :type)))
(t/is (= :square (-> parsed second :type)))
(t/is (map? (-> parsed first :params)))
(t/is (sm/validate [:vector ctg/schema:grid] parsed)))
(t/testing "clearing guides with an empty vector validates"
(t/is (sm/validate [:vector ctg/schema:grid] (parser/parse-frame-guides #js [])))))
(t/deftest test-parse-overlay-action-position-is-optional
(t/testing "open-overlay defaults omitted position to center"
(let [destination (uuid/next)
result (parse-overlay-interaction {:type "open-overlay"
:destination destination})]
(t/is (= :open-overlay (:action-type result)))
(t/is (= :click (:event-type result)))
(t/is (= destination (:destination result)))
(t/is (= :center (:overlay-pos-type result)))
(t/is (not (contains? result :overlay-position)))
(t/is (valid-interaction? result))))
(t/testing "toggle-overlay preserves manualPositionLocation"
(let [destination (uuid/next)
result (parse-overlay-interaction
{:type "toggle-overlay"
:destination destination
:position "manual"
:manual-position-location #js {:x 10 :y 20}})
position (:overlay-position result)]
(t/is (= :toggle-overlay (:action-type result)))
(t/is (= :manual (:overlay-pos-type result)))
(t/is (gpt/point? position))
(t/is (= 10 (:x position)))
(t/is (= 20 (:y position)))
(t/is (valid-interaction? result))))
(t/testing "explicit center position does not require manualPositionLocation"
(let [destination (uuid/next)
result (parse-overlay-interaction {:type "open-overlay"
:destination destination
:position "center"})]
(t/is (= :center (:overlay-pos-type result)))
(t/is (not (contains? result :overlay-position)))
(t/is (valid-interaction? result))))
(t/testing "manual position without manualPositionLocation still parses"
(let [destination (uuid/next)
result (parse-overlay-interaction {:type "open-overlay"
:destination destination
:position "manual"})]
(t/is (= :manual (:overlay-pos-type result)))
(t/is (not (contains? result :overlay-position)))
(t/is (valid-interaction? result)))))
(t/deftest test-parse-close-overlay-without-animation-validates
(t/testing "close-overlay without animation parses and validates"
(let [result (parser/parse-interaction "click" #js {:type "close-overlay"} nil)]
(t/is (= {:event-type :click
:action-type :close-overlay}
result))
(t/is (false? (contains? result :animation)))
(t/is (true? (sm/validate ctsi/schema:interaction result)))))
(t/testing "close-overlay preserves destination without animation"
(let [destination-id (uuid/next)
result (parser/parse-interaction
"click"
#js {:type "close-overlay"
:destination #js {"$id" destination-id}}
nil)]
(t/is (= destination-id (:destination result)))
(t/is (false? (contains? result :animation)))
(t/is (true? (sm/validate ctsi/schema:interaction result)))))
(t/testing "close-overlay preserves an explicit dissolve animation"
(let [result (parser/parse-interaction
"click"
#js {:type "close-overlay"
:animation #js {:type "dissolve"
:duration 300
:easing "linear"}}
nil)]
(t/is (= {:animation-type :dissolve
:duration 300
:easing :linear}
(:animation result)))
(t/is (true? (sm/validate ctsi/schema:interaction result))))))

View File

@ -11,6 +11,7 @@
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st]
@ -338,3 +339,66 @@
result (get-resolved-value token {(:name token) token})]
(t/is (array? result))
(t/is (= ["Inter" "Arial"] (vec result)))))
(t/deftest token-theme-add-set-accepts-token-set-id
(let [plugin-id "plugin-id"
file-id (uuid/next)
theme-id (uuid/next)
set-id (uuid/next)
token-set (ctob/make-token-set :id set-id :name "Core")
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom [])
invalid (atom [])]
(with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))]
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)]
(.addSet theme-proxy (str set-id))
(t/is (= #{"Core"} (-> @emitted first :theme :sets)))
(t/is (empty? @invalid))))))
(t/deftest token-theme-add-set-accepts-token-set-proxy
(let [plugin-id "plugin-id"
file-id (uuid/next)
theme-id (uuid/next)
set-id (uuid/next)
token-set (ctob/make-token-set :id set-id :name "Core")
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom [])
invalid (atom [])]
(with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))]
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)
set-proxy (ptok/token-set-proxy plugin-id file-id set-id "Core")]
(.addSet theme-proxy set-proxy)
(t/is (= #{"Core"} (-> @emitted first :theme :sets)))
(t/is (empty? @invalid))))))
(t/deftest token-theme-add-set-rejects-invalid-arguments
(let [plugin-id "plugin-id"
file-id (uuid/next)
theme-id (uuid/next)
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom [])
invalid (atom [])]
(with-redefs [u/locate-token-set (constantly nil)
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))]
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)]
;; Non-id, non-proxy arguments are rejected by the schema coercer.
(.addSet theme-proxy 42)
(.removeSet theme-proxy nil)
(t/is (empty? @emitted))
(t/is (= 2 (count @invalid)))
(t/is (every? #(= :error (first %)) @invalid))))))

View File

@ -54,3 +54,36 @@
;; No validation errors -> no output (callers join with ". " and would
;; otherwise emit an empty string, which is fine).
(t/is (empty? (flatten-error-map {}))))
;; ---------------------------------------------------------------------
;; Issue #9692 — `handle-error` must surface a useful message instead of a
;; bare "Value not valid. Code: :error".
;;
;; `not-valid` is redefined to capture the rendered message directly, so the
;; assertions don't depend on `st/state` or the console.
(t/deftest test-handle-error-plain-js-error
;; A plain JS error has no `::sm/explain` and CLJS `ex-data` returns nil, so
;; the handler must fall back to the error's own message rather than nil.
(let [captured (atom ::none)]
(with-redefs [plugins.utils/not-valid (fn [_plugin-id _code value]
(reset! captured value) nil)]
((plugins.utils/handle-error #uuid "00000000-0000-0000-0000-000000000000")
(js/Error. "boom: not a function")))
(t/is (= "boom: not a function" @captured))))
(t/deftest test-handle-error-empty-explain
;; An explain whose errors don't render any message must not produce an
;; empty string; the handler falls back to the raw explain.
(let [captured (atom ::none)
cause (ex-info "invalid" {:app.common.schema/explain {:errors [] :value 1}})]
(with-redefs [plugins.utils/not-valid (fn [_plugin-id _code value]
(reset! captured value) nil)]
((plugins.utils/handle-error #uuid "00000000-0000-0000-0000-000000000000") cause))
(t/is (string? @captured))
(t/is (not= "" @captured))))
(t/deftest test-error-messages-empty-returns-nil
;; `error-messages` returns nil (not "") on an explain with no mappable
;; errors, so `handle-error` can distinguish "no message" from a real one.
(t/is (nil? (plugins.utils/error-messages {:errors []}))))

View File

@ -51,7 +51,7 @@
(let [shapes [(make-shape :text) (make-shape :text) (make-shape :rect)]
visited-ids (atom [])
mock-set (fn [s] (swap! visited-ids conj (:id s)) {:thumbnails [] :full []})
mock-pend (fn [_sh _t _f _cb] nil)]
mock-pend (fn [_sh _t _f _fp _cb] nil)]
(with-mocks* mock-set mock-pend #(wasm.api/process-objects shapes))
@ -66,7 +66,7 @@
(let [shapes [(make-shape :text) (make-shape :text)]
captured (atom nil)
mock-set (fn [_s] {:thumbnails [] :full []})
mock-pend (fn [sh t f cb] (reset! captured {:shapes sh :thumbnails t :full f :cb cb}))]
mock-pend (fn [sh t f _fp cb] (reset! captured {:shapes sh :thumbnails t :full f :cb cb}))]
(with-mocks* mock-set mock-pend #(wasm.api/process-objects shapes))
@ -99,7 +99,7 @@
{:thumbnails [] :full []}))
mock-pend
(fn [sh t f _cb] (reset! captured {:shapes sh :thumbnails t :full f}))]
(fn [sh t f _fp _cb] (reset! captured {:shapes sh :thumbnails t :full f}))]
(with-mocks* mock-set mock-pend #(wasm.api/process-objects shapes))

View File

@ -16,7 +16,7 @@
},
"devDependencies": {
"@playwright/test": "1.61.1",
"@types/node": "^26.0.1",
"@types/node": "^26.1.0",
"@vitest/browser": "^4.1.9",
"@vitest/coverage-v8": "^4.1.9",
"@vitest/ui": "^4.1.9",
@ -25,7 +25,7 @@
"jsdom": "^29.1.1",
"playwright": "1.61.1",
"prettier": "^3.9.4",
"vite": "^8.1.1",
"vite": "^8.1.2",
"vitest": "^4.1.9"
},
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b"

View File

@ -378,9 +378,8 @@ msgstr "صدّر التحديد"
msgid "dashboard.export-standard-multi"
msgstr "تحميل %s ملفات أساسية (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"ملف أو أكثر تريد تصديرهم يستخدمون مكتبات مشتركة. ماذا تريد أن تفعل في "
"أصولهم*؟"

View File

@ -367,9 +367,8 @@ msgstr "Selecció d'exportació"
msgid "dashboard.export-standard-multi"
msgstr "Baixa %s fitxers estàndard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Un o més fitxers que voleu exportar utilitzen biblioteques compartides. Què "
"voleu fer amb els seus recursos*?"

View File

@ -462,9 +462,8 @@ msgstr "Výběr exportu"
msgid "dashboard.export-standard-multi"
msgstr "Stáhnout %s soubory (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Jeden nebo více souborů, které chcete exportovat, používá sdílené knihovny. "
"Co chcete dělat s jejich položkami*?"
@ -6309,9 +6308,6 @@ msgstr "Momentálně nemáte žádné motivy."
msgid "workspace.tokens.original-value"
msgstr "Původní hodnota: %s"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Reference není platná nebo není v žádné aktivní sadě"
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:59, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:87, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:105, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:296, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:489, src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs:298, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:189, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:324, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:259, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:381, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:505, src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
#, fuzzy

View File

@ -589,9 +589,8 @@ msgstr "Auswahl exportieren"
msgid "dashboard.export-standard-multi"
msgstr "%s Standarddateien herunterladen (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Eine oder mehrere Dateien, die Sie exportieren möchten, verwenden geteilte "
"Bibliotheken. Was möchten Sie mit den Assets* aus diesen Bibliotheken "
@ -1295,10 +1294,6 @@ msgstr "Keine Treffer gefunden."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Token-Liste öffnen"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} ist nicht Teil eines aktiven Sets oder ungültig."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Auth-Provider für dieses Profil nicht erlaubt"
@ -7836,10 +7831,6 @@ msgstr "Innenabstände"
msgid "workspace.tokens.radius"
msgstr "Radius"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referenz ist ungültig oder befindet sich nicht in einem aktiven Set"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Geben Sie einen Typografie-Alias für diesen Token ein"

View File

@ -613,14 +613,7 @@ msgstr "Export selection"
msgid "dashboard.export-standard-multi"
msgstr "Download %s standard files (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
msgstr ""
"One or more files that you want to download are using shared libraries. "
"What do you want to do with their assets*?"
#: src/app/main/ui/dashboard/file_menu.cljs:267
#: src/app/main/ui/dashboard/file_menu.cljs:266
msgid "dashboard.file-menu.delete-files-permanently-option"
msgid_plural "dashboard.file-menu.delete-files-permanently-option"
msgstr[0] "Delete file"
@ -1471,7 +1464,7 @@ msgstr "Open token list"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} is not in any active set or has an invalid value."
msgstr "{%s} token is not in any active set or has an invalid value."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
@ -2070,9 +2063,10 @@ msgid "feedback.type.issue"
msgstr "Issue"
#: src/app/main/ui/exports/files.cljs:131
#, fuzzy
msgid "files-download-modal.description-1"
msgstr ""
"One or more files that you want to download are using shared libraries. "
"What do you want to do with their assets*?"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-2"
@ -4480,10 +4474,6 @@ msgstr "Cancel subscription"
msgid "nitrate.subscription.settings.renew-with-code"
msgstr "Renew with activation code"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:44, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:110
msgid "not-active-token.no-name"
msgstr "This token is not in any active set or has an invalid value."
#: src/app/main/ui/static.cljs:309
msgid "not-found.desc-message.doesnt-exist"
msgstr "This page doesn't exist"
@ -5026,7 +5016,12 @@ msgstr "Penpot"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:42, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:101, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:108
msgid "options.deleted-token"
msgstr "This token does not exists or has been deleted."
msgstr "This token does not exist or has been deleted."
#: src/app/main/ui/ds/controls/utilities/token_field.cljs
msgid "options.deleted-token-with-name"
msgstr "{%s} token does not exist or has been deleted."
#: src/app/plugins/utils.cljs:318
msgid "plugins.validation.message"
@ -9460,7 +9455,7 @@ msgstr "Radius"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Reference is not valid or is not in any active set"
msgstr "Reference in {%s} is not valid or is not in any active set."
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"

View File

@ -610,9 +610,8 @@ msgstr "Exportar selección"
msgid "dashboard.export-standard-multi"
msgstr "Descargar %s archivos estándar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Uno o mas ficheros que quieres descargar usan librerias compartidas. ¿Qué "
"quieres hacer con los recursos*?"
@ -4348,10 +4347,6 @@ msgstr "Cancelar subscripción"
msgid "nitrate.subscription.settings.renew-with-code"
msgstr "Renovar con código de activación"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:44, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:110
msgid "not-active-token.no-name"
msgstr "Este token no está disponible en ningún set o tiene un valor inválido."
#: src/app/main/ui/static.cljs:309
msgid "not-found.desc-message.doesnt-exist"
msgstr "Esta página no existe"
@ -4897,6 +4892,10 @@ msgstr "Penpot"
msgid "options.deleted-token"
msgstr "Este token no existe o ha sido borrado."
#: src/app/main/ui/ds/controls/utilities/token_field.cljs
msgid "options.deleted-token-with-name"
msgstr "El token {%s} no existe o ha sido borrado."
#: src/app/main/ui/auth/recovery.cljs:88
msgid "profile.recovery.go-to-login"
msgstr "Ir al login"
@ -9165,7 +9164,7 @@ msgstr "Valor original: %s"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "La referencia no es válida o no se encuentra en ningún set activo."
msgstr "La referencia en {%s} no es válida o no se encuentra en ningún set activo."
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
#, unused

View File

@ -375,9 +375,8 @@ msgstr "Selección de exportación"
msgid "dashboard.export-standard-multi"
msgstr "Descargar %s archivos estándar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Uno o más archivos que desea exportar utilizan bibliotecas compartidas. "
"¿Qué quiere hacer con sus activos*?"

View File

@ -360,9 +360,8 @@ msgstr "Esportatu aukeraketa"
msgid "dashboard.export-standard-multi"
msgstr "Deskargatu %s fitxategi estandar (.svn + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Esportatu nahi duzun fitxategi bat edo gehiagok partekatutako liburutegiak "
"darabiltzate. Zer egin nahi duzu baliabideekin*?"

View File

@ -420,9 +420,8 @@ msgstr "انتخاب اکسپورت"
msgid "dashboard.export-standard-multi"
msgstr "دانلود %s عدد فایل های استاندارد (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"یک یا چند فایلی که می‌خواهید اکسپورت کنید از کتابخانه‌های مشترک استفاده "
"می‌کنند. با دارایی‌های آن‌ها چه می‌خواهید بکنید*؟"

View File

@ -588,9 +588,8 @@ msgstr "Exporter la sélection"
msgid "dashboard.export-standard-multi"
msgstr "Télécharger %s fichiers standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Un ou plusieurs fichiers que vous souhaitez exporter utilisent des "
"bibliothèques partagées. Que voulez-vous faire avec leurs ressources?"
@ -1294,10 +1293,6 @@ msgstr "Aucune correspondance."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Ouvrir la liste des tokens"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} n'est pas disponible dans la collection ou le thème actif."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Le fournisseur d'authentification n'est pas autorisé pour ce profil"
@ -8119,10 +8114,6 @@ msgstr "Marges intérieures"
msgid "workspace.tokens.radius"
msgstr "Rayons"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "La référence n'est pas valide ou n'est pas dans une collection active"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Entrer un alias de typographie pour un token"

View File

@ -616,9 +616,8 @@ msgstr "Exporter la sélection"
msgid "dashboard.export-standard-multi"
msgstr "Télécharger %s fichiers standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Un ou plus des fichiers que tu veux télécharger utilisent une bibliothèque "
"partagée. Que veux-tu faire avec ces atouts*?"
@ -1482,10 +1481,6 @@ msgstr "Aucun résultat trouvé."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Ouvrir la liste de tokens"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} n'est disponible dans aucune collection ou est invalide."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Fournisseur d'authentification non permis pour ce profil utilisateur"
@ -4510,10 +4505,6 @@ msgstr "Annuler l'abonnement"
msgid "nitrate.subscription.settings.renew-with-code"
msgstr "Renouveler avec code d'activation"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:44, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:110
msgid "not-active-token.no-name"
msgstr "Ce token n'est disponible dans aucun ensemble ou a une valeur invalide."
#: src/app/main/ui/static.cljs:309
msgid "not-found.desc-message.doesnt-exist"
msgstr "Cette page n'existe pas"
@ -9426,10 +9417,6 @@ msgstr "Marges intérieures"
msgid "workspace.tokens.radius"
msgstr "Rayon"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "La référence est invalide ou n'existe dans aucun ensemble actif"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Entrer un alias de token typographique"

View File

@ -358,9 +358,8 @@ msgstr "Exportar selección"
msgid "dashboard.export-standard-multi"
msgstr "Descargar %s ficheiros estándar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Un ou máis ficheiros dos que queres exportar usan bibliotecas compartidas. "
"Que queres facer cos recursos?"

View File

@ -351,9 +351,8 @@ msgstr "Fitar da zavi"
msgid "dashboard.export-standard-multi"
msgstr "Sauke %s cikakken kundi (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr "za ka iya fitar da kundi daya ko fiye ta hanyar tura taska. \"me \"*?"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:336

View File

@ -561,9 +561,8 @@ msgstr "ייצוא הבחירה"
msgid "dashboard.export-standard-multi"
msgstr "הורדת %s קבצים תקניים (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"אחד או יותר מהקבצים שברצונך לייצא משתמשים בספריות משותפות. מה לעשות עם "
"המשאבים שלהן*?"
@ -7994,10 +7993,6 @@ msgstr "ריפודים"
msgid "workspace.tokens.radius"
msgstr "רדיוס"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "ההפניה לא תקפה או שאינה באף סדרה פעילה"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "נא למלא כינוי לטיפוגרפיית אסימון"

View File

@ -558,9 +558,8 @@ msgstr "निर्यात चयन"
msgid "dashboard.export-standard-multi"
msgstr "%s मानक फ़ाइलें डाउनलोड करें (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"एक या एक से अधिक फ़ाइलें जिन्हें आप निर्यात करना चाहते हैं, वे साझा की गई "
"लाइब्रेरीज़ का उपयोग कर रही हैं। आप उनके एसेट्स के साथ क्या करना चाहते हैं?"
@ -7929,10 +7928,6 @@ msgstr "पैडिंग्स"
msgid "workspace.tokens.radius"
msgstr "त्रिज्या"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "संदर्भ मान्य नहीं है या किसी सक्रिय सेट में नहीं है"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "token टाइपोग्राफी उपनाम दर्ज करें"

View File

@ -461,9 +461,8 @@ msgstr "Izvezi odabir"
msgid "dashboard.export-standard-multi"
msgstr "Preuzmi %s standardne datoteke (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Jedna ili više datoteka koju želiš izvesti koristi zajedničke biblioteke. "
"Što želiš učiniti s njihovim stavkama*?"
@ -6330,10 +6329,6 @@ msgstr "Trenutno nemate nijednu temu."
msgid "workspace.tokens.original-value"
msgstr "Izvorna vrijednost: %s"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referenca nije važeća ili nije ni u jednom aktivnom skupu"
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:59, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:87, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:105, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:296, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:489, src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs:298, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:189, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:324, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:259, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:381, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:505, src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
#, fuzzy
msgid "workspace.tokens.resolved-value"

View File

@ -494,9 +494,8 @@ msgstr "Ekspor pilihan"
msgid "dashboard.export-standard-multi"
msgstr "Unduh %s berkas standar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Satu atau lebih berkas yang ingin Anda ekspor menggunakan pustaka bersama. "
"Apa yang ingin Anda lakukan dengan asetnya*?"
@ -6675,10 +6674,6 @@ msgstr "Padding"
msgid "workspace.tokens.radius"
msgstr "Radius"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referensi tidak valid atau tidak dalam set aktif mana pun"
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
#, unused
msgid "workspace.tokens.reference-error"

View File

@ -615,9 +615,8 @@ msgstr "Esporta selezionati"
msgid "dashboard.export-standard-multi"
msgstr "Scarica %s file standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Uno o più file che desideri scaricare utilizzano librerie condivise. Che "
"cosa desideri fare con le loro risorse*?"
@ -1485,10 +1484,6 @@ msgstr "Nessuna corrispondenza trovata."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Apri elenco token"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} non è disponibile in nessun set o tema attivo."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Provider di autenticazione non consentito per questo profilo"
@ -4041,10 +4036,6 @@ msgstr ""
msgid "modals.update-remote-component.message"
msgstr "Aggiorna un componente in una libreria condivisa"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:44, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:110
msgid "not-active-token.no-name"
msgstr "Questo token non è presente in alcun set attivo o ha un valore non valido."
#: src/app/main/ui/static.cljs:309
msgid "not-found.desc-message.doesnt-exist"
msgstr "Questa pagina non esiste"
@ -8543,10 +8534,6 @@ msgstr "Padding"
msgid "workspace.tokens.radius"
msgstr "Raggio"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Il riferimento non è valido o non è presente in nessun set attivo"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Inserisci un alias tipografico del token"

View File

@ -554,10 +554,11 @@ msgstr "선택 영역 내보내기"
msgid "dashboard.export-standard-multi"
msgstr "표준 파일 %s개(.svg + .json) 다운로드"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
msgstr "다운로드하려는 하나 이상의 파일이 공유 라이브러리를 사용 중입니다. 해당 에셋*을 어떻게 처리하시겠습니까?"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"다운로드하려는 하나 이상의 파일이 공유 라이브러리를 사용 중입니다. 해당 에셋*"
"을 어떻게 처리하시겠습니까?"
#: src/app/main/ui/dashboard/file_menu.cljs:267
msgid "dashboard.file-menu.delete-files-permanently-option"
@ -1225,10 +1226,6 @@ msgstr "일치하는 항목이 없습니다."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "token 목록 열기"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "이 token은 활성 세트에 없거나 유효하지 않은 값을 가지고 있습니다."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "이 프로필에 허용되지 않는 인증 제공자입니다"
@ -7713,10 +7710,6 @@ msgstr "패딩"
msgid "workspace.tokens.radius"
msgstr "반지름"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "참조가 유효하지 않거나 활성 세트에 없습니다"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "타이포그래피 token 별칭 입력"

View File

@ -334,9 +334,8 @@ msgstr "Nėra elementų su eksporto nustatymais."
msgid "dashboard.export-shapes.title"
msgstr "Eksportuoti pažymėtą sritį"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Viename ar keliuose failuose, kuriuos norite eksportuoti, naudojamos "
"bendros bibliotekos. Ką norite daryti su jų komponentais*?"

View File

@ -507,9 +507,8 @@ msgstr "Izgūt atlasi"
msgid "dashboard.export-standard-multi"
msgstr "Lejupielādēt %s standarta datnes (. svg +. json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Viena vai vairākas lejupielādējamās datnes izmanto koplietojamas "
"bibliotēkas. Ko iesākt ar to līdzekļiem*?"
@ -7476,10 +7475,6 @@ msgstr "Atbīdes"
msgid "workspace.tokens.radius"
msgstr "Rādiuss"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Atsauce nav derīga vai tā nav nevienā aktīvā kopā"
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
#, unused
msgid "workspace.tokens.reference-error"

View File

@ -357,9 +357,8 @@ msgstr "Eksport Pemilihan"
msgid "dashboard.export-standard-multi"
msgstr "Muat turun %s fail standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Satu atau lebih fail yang anda ingin eksport menggunakan perpustakaan "
"kongsi. Apa yang anda mahu lakukan dengan aset mereka*?"

View File

@ -585,9 +585,8 @@ msgstr "Selectie exporteren"
msgid "dashboard.export-standard-multi"
msgstr "%s standaardbestanden downloaden (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Een of meer bestanden die je wilt downloaden maken gebruik van gedeelde "
"bibliotheken. Wat wil je doen met hun assets*?"
@ -1283,10 +1282,6 @@ msgstr "Geen overeenkomsten gevonden."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Lijst met tokens openen"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} is niet beschikbaar in een actieve verzameling of thema."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Auth-provider is niet toegestaan voor dit profiel"
@ -8125,10 +8120,6 @@ msgstr "Vulling"
msgid "workspace.tokens.radius"
msgstr "Radius"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referentie is niet geldig of zit niet in een actieve verzameling"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Voer een alias voor tokentypografie in"

View File

@ -362,9 +362,8 @@ msgstr "Eksportuj wybrane"
msgid "dashboard.export-standard-multi"
msgstr "Pobierz %s plików standardowych (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Co najmniej jeden plik, który chcesz wyeksportować, korzysta z bibliotek "
"udostępnionych. Co chcesz zrobić z ich zasobami*?"

View File

@ -497,9 +497,8 @@ msgstr "Exportar seleção"
msgid "dashboard.export-standard-multi"
msgstr "Baixar %s arquivos padrões (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Um ou mais arquivos que você deseja exportar estão usando bibliotecas "
"compartilhadas. O que você quer fazer com seus recursos*?"
@ -1108,10 +1107,6 @@ msgstr "Nenhum resultado encontrado."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Abrir lista de tokens"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} não está em nenhum conjunto ativo ou possui um valor inválido."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Provedor de autenticação não permitido para este perfil"

View File

@ -475,9 +475,8 @@ msgstr "Exportar seleção"
msgid "dashboard.export-standard-multi"
msgstr "Descarregar %s ficheiros standard (svg + json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Um ou mais ficheiros que queres exportar estão a utilizar bibliotecas "
"partilhadas. O que queres fazer com os recursos*?"

View File

@ -506,9 +506,8 @@ msgstr "Exportă selecția"
msgid "dashboard.export-standard-multi"
msgstr "Descarcă %s fișiere standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Unul sau mai multe fișiere pe care dorești să le exporți folosesc "
"biblioteci partajate. Ce vrei să faci cu obiectele lor*?"
@ -1125,10 +1124,6 @@ msgstr "Nu a fost găsit nimic."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Deschide lista de token-uri"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} nu este în nici un set activ sau are o valoare invalidă."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Furnizor de autentificare neautorizat pentru acest profil"
@ -7753,10 +7748,6 @@ msgstr "Margini interioare"
msgid "workspace.tokens.radius"
msgstr "Raze"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referința nu este validă sau nu este în nici unul dintre seturile active"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Introdu un alias de token tipografic"

View File

@ -579,9 +579,8 @@ msgstr "Выбор экспорта"
msgid "dashboard.export-standard-multi"
msgstr "Скачать стандартные файлы (.svg + .json) (%s)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Один или несколько файлов на экспорт используют общие библиотеки. Что нужно "
"сделать с их ресурсами*?"

View File

@ -411,9 +411,8 @@ msgstr "Избор извоза"
msgid "dashboard.export-standard-multi"
msgstr "Преузмите &s стандардних датотека (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Једна или више датотека које желите да извезете користе дељене библиотеке. "
"Шта желите да урадите са њиховим средстрвима*?"

View File

@ -611,9 +611,8 @@ msgstr "Exportera markerade"
msgid "dashboard.export-standard-multi"
msgstr "Ladda ner %s standardfiler (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"En eller flera filer som du vill exportera använder delade bibliotek. Vad "
"vill du göra med deras tillgångar*?"
@ -1470,10 +1469,6 @@ msgstr "Inga träffar hittades."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Öppna token-lista"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} är inte i någon aktiv uppsättning eller har ett ogiltigt värde."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Autentiseringsleverantör inte tillåten för denna profil"
@ -4478,12 +4473,6 @@ msgstr "Avsluta prenumeration"
msgid "nitrate.subscription.settings.renew-with-code"
msgstr "Förnya med aktiveringskod"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:44, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:110
msgid "not-active-token.no-name"
msgstr ""
"Denna token finns inte i någon aktiv uppsättning eller har ett ogiltigt "
"värde."
#: src/app/main/ui/static.cljs:309
msgid "not-found.desc-message.doesnt-exist"
msgstr "Denna sida existerar inte"
@ -9373,10 +9362,6 @@ msgstr "Utfyllnader"
msgid "workspace.tokens.radius"
msgstr "Radie"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referensen är inte giltig eller finns inte i någon aktiv uppsättning"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Ange ett alias för token-typografi"

View File

@ -612,9 +612,8 @@ msgstr "Seçimi dışa aktar"
msgid "dashboard.export-standard-multi"
msgstr "%s standart dosyayı indir (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"İndirmek istediğiniz bir veya daha fazla dosya, paylaşılan kütüphaneleri "
"kullanıyor. Bunların varlıklarıyla ne yapmak istiyorsunuz*?"
@ -1479,10 +1478,6 @@ msgstr "Eşleşme bulunamadı."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Token listesini aç"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "{%s} herhangi bir etkin kümede bulunmuyor veya geçersiz bir değere sahip."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Kimlik doğrulama sağlayıcısına bu profil için izin verilmiyor"
@ -4483,12 +4478,6 @@ msgstr "Aboneliği iptal et"
msgid "nitrate.subscription.settings.renew-with-code"
msgstr "Etkinleştirme koduyla yenile"
#: src/app/main/ui/workspace/sidebar/options/menus/token_typography_row.cljs:44, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:103, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:110
msgid "not-active-token.no-name"
msgstr ""
"Bu token herhangi bir etkin kümede yer almıyor veya geçersiz bir değere "
"sahip."
#: src/app/main/ui/static.cljs:309
msgid "not-found.desc-message.doesnt-exist"
msgstr "Bu sayfa yok"
@ -9380,10 +9369,6 @@ msgstr "Dolgular"
msgid "workspace.tokens.radius"
msgstr "Yarıçap"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Referans geçerli değil veya herhangi bir etkin kümede bulunmuyor"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Bir token tipografi takma adı girin"

View File

@ -583,9 +583,8 @@ msgstr "Вибір експорту"
msgid "dashboard.export-standard-multi"
msgstr "Завантажити %s стандартних файлів (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr ""
"Файли, які Ви хочете експортувати, використовують спільні бібліотеки. Що "
"плануєте зробити з їхніми ресурсами*?"
@ -1282,12 +1281,6 @@ msgstr "Збігів не виявлено."
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "Відкрити список токенів"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:48
msgid "ds.inputs.token-field.no-active-token-option"
msgstr ""
"Цей токен не міститься в жодному з активних наборів або має недійсне "
"значення."
#: src/app/main/data/auth.cljs:346
msgid "errors.auth-provider-not-allowed"
msgstr "Провайдер автентифікації не дозволений для цього профілю"
@ -8041,10 +8034,6 @@ msgstr "Внутрішні відступи"
msgid "workspace.tokens.radius"
msgstr "Радіус"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "Посилання помилкове або ні на одному із активних наборів"
#: src/app/main/ui/workspace/tokens/management/forms/typography.cljs:176
msgid "workspace.tokens.reference-composite"
msgstr "Введіть псевдо токену типографіки"

View File

@ -472,9 +472,8 @@ msgstr "导出已选中"
msgid "dashboard.export-standard-multi"
msgstr "下载 %s 标准文件 (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr "你想导出的一个或多个文件用到了共享库。你想怎么处理它们的素材?"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:336
@ -7022,10 +7021,6 @@ msgstr "内边距"
msgid "workspace.tokens.radius"
msgstr "圆角半径"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "引用无效或不在任何活动集中"
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
#, unused
msgid "workspace.tokens.reference-error"

View File

@ -443,9 +443,8 @@ msgstr "匯出已選取項目"
msgid "dashboard.export-standard-multi"
msgstr "下載%s個標準檔案 (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:155
#, unused
msgid "dashboard.export.explain"
#: src/app/main/ui/exports/files.cljs:132
msgid "files-download-modal.description-1"
msgstr "你想匯出的單個或多個檔案中使用了共用資料庫,你想要如何處理它們的素材*"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:336
@ -6149,10 +6148,6 @@ msgstr "現主時您沒有任何主題。"
msgid "workspace.tokens.original-value"
msgstr "原始值:%s"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:47, src/app/main/ui/workspace/tokens/management/token_pill.cljs:130
msgid "workspace.tokens.ref-not-valid"
msgstr "參照無效或不在任何啟用的集內"
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:59, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:87, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:105, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:296, src/app/main/ui/workspace/tokens/management/forms/controls/color_input.cljs:489, src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs:298, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:189, src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs:324, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:259, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:381, src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs:505, src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
#, fuzzy
msgid "workspace.tokens.resolved-value"

View File

@ -25,6 +25,6 @@
"packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
"devDependencies": {
"concurrently": "^10.0.3",
"prettier": "^3.9.1"
"prettier": "^3.9.4"
}
}

View File

@ -18,7 +18,7 @@
"devDependencies": {
"cross-env": "^10.1.0",
"typescript": "^6.0.3",
"vite": "^8.1.0",
"vite": "^8.1.1",
"vite-live-preview": "^0.4.0"
}
}

View File

@ -29,14 +29,14 @@
"@modelcontextprotocol/sdk": "^1.29.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"express": "^5.1.0",
"express": "^5.2.1",
"ioredis": "^5.11.1",
"js-yaml": "^5.2.0",
"nrepl-client": "^0.3.0",
"penpot-mcp": "file:..",
"pino": "^10.3.1",
"pino-loki": "^3.0.0",
"pino-pretty": "^13.1.1",
"pino-pretty": "^13.1.3",
"reflect-metadata": "^0.2.2",
"sharp": "^0.35.2",
"ws": "^8.21.0",
@ -47,11 +47,11 @@
"@types/express": "^5.0.6",
"@types/js-yaml": "^4.0.9",
"@types/node": "^26.0.1",
"@types/ws": "^8.5.10",
"@types/ws": "^8.18.1",
"cross-env": "^10.1.0",
"esbuild": "^0.28.1",
"ts-node": "^10.9.2",
"tsx": "^4.22.3",
"tsx": "^4.22.4",
"typescript": "^6.0.3"
},
"ts-node": {

87
mcp/pnpm-lock.yaml generated
View File

@ -12,8 +12,8 @@ importers:
specifier: ^10.0.3
version: 10.0.3
prettier:
specifier: ^3.9.1
version: 3.9.1
specifier: ^3.9.4
version: 3.9.4
packages/common:
devDependencies:
@ -37,11 +37,11 @@ importers:
specifier: ^6.0.3
version: 6.0.3
vite:
specifier: ^8.1.0
version: 8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4)
specifier: ^8.1.1
version: 8.1.1(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4)
vite-live-preview:
specifier: ^0.4.0
version: 0.4.0(vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4))
version: 0.4.0(vite@8.1.1(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4))
packages/server:
dependencies:
@ -55,7 +55,7 @@ importers:
specifier: ^0.15.1
version: 0.15.1
express:
specifier: ^5.1.0
specifier: ^5.2.1
version: 5.2.1
ioredis:
specifier: ^5.11.1
@ -76,7 +76,7 @@ importers:
specifier: ^3.0.0
version: 3.0.0
pino-pretty:
specifier: ^13.1.1
specifier: ^13.1.3
version: 13.1.3
reflect-metadata:
specifier: ^0.2.2
@ -104,7 +104,7 @@ importers:
specifier: ^26.0.1
version: 26.0.1
'@types/ws':
specifier: ^8.5.10
specifier: ^8.18.1
version: 8.18.1
cross-env:
specifier: ^10.1.0
@ -116,7 +116,7 @@ importers:
specifier: ^10.9.2
version: 10.9.2(@types/node@26.0.1)(typescript@6.0.3)
tsx:
specifier: ^4.22.3
specifier: ^4.22.4
version: 4.22.4
typescript:
specifier: ^6.0.3
@ -835,8 +835,8 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
es-object-atoms@1.1.2:
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
engines: {node: '>= 0.4'}
esbuild@0.28.1:
@ -942,8 +942,8 @@ packages:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
help-me@5.0.0:
@ -1144,8 +1144,8 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-to-regexp@8.3.0:
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
penpot-mcp@file:packages:
resolution: {directory: packages, type: directory}
@ -1184,8 +1184,8 @@ packages:
resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==}
engines: {node: ^10 || ^12 || >=14}
prettier@3.9.1:
resolution: {integrity: sha512-ppiDo2CSwexck1eyZUwJHg/N3nf1+6IRCv7W/VJ5vaLnVCmB7+3CdRfMwoCHBBX6xTrREDTksZ4OZl5SSf4zXA==}
prettier@3.9.4:
resolution: {integrity: sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==}
engines: {node: '>=14'}
hasBin: true
@ -1202,8 +1202,8 @@ packages:
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
qs@6.14.1:
resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
qs@6.15.3:
resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
engines: {node: '>=0.6'}
quick-format-unescaped@4.0.4:
@ -1293,8 +1293,8 @@ packages:
resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==}
engines: {node: '>= 0.4'}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
side-channel-list@1.0.1:
resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
engines: {node: '>= 0.4'}
side-channel-map@1.0.1:
@ -1305,8 +1305,8 @@ packages:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'}
side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
side-channel@1.1.1:
resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
engines: {node: '>= 0.4'}
sonic-boom@4.2.0:
@ -1417,8 +1417,8 @@ packages:
peerDependencies:
vite: '>=5.4.0'
vite@8.1.0:
resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==}
vite@8.1.1:
resolution: {integrity: sha512-X/05/cT+VITy2AeDc1der6smvGWWREtL4hPbPTaVbjSBuuWkmNOjR6HP3NzqcQA2nF6VHGUPaFRJyft/2AE9Kg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@ -1927,7 +1927,7 @@ snapshots:
http-errors: 2.0.1
iconv-lite: 0.7.2
on-finished: 2.4.1
qs: 6.14.1
qs: 6.15.3
raw-body: 3.0.2
type-is: 2.0.1
transitivePeerDependencies:
@ -2034,7 +2034,7 @@ snapshots:
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
es-object-atoms@1.1.2:
dependencies:
es-errors: 1.3.0
@ -2108,7 +2108,7 @@ snapshots:
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.14.1
qs: 6.15.3
range-parser: 1.2.1
router: 2.2.0
send: 1.2.1
@ -2160,24 +2160,24 @@ snapshots:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
es-object-atoms: 1.1.2
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
hasown: 2.0.4
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
es-object-atoms: 1.1.2
gopd@1.2.0: {}
has-symbols@1.1.0: {}
hasown@2.0.2:
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@ -2327,7 +2327,7 @@ snapshots:
path-key@3.1.1: {}
path-to-regexp@8.3.0: {}
path-to-regexp@8.4.2: {}
penpot-mcp@file:packages: {}
@ -2384,7 +2384,7 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
prettier@3.9.1: {}
prettier@3.9.4: {}
process-warning@5.0.0: {}
@ -2403,9 +2403,10 @@ snapshots:
end-of-stream: 1.4.5
once: 1.4.0
qs@6.14.1:
qs@6.15.3:
dependencies:
side-channel: 1.1.0
es-define-property: 1.0.1
side-channel: 1.1.1
quick-format-unescaped@4.0.4: {}
@ -2459,7 +2460,7 @@ snapshots:
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
path-to-regexp: 8.3.0
path-to-regexp: 8.4.2
transitivePeerDependencies:
- supports-color
@ -2542,7 +2543,7 @@ snapshots:
shell-quote@1.8.4: {}
side-channel-list@1.0.0:
side-channel-list@1.0.1:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
@ -2562,11 +2563,11 @@ snapshots:
object-inspect: 1.13.4
side-channel-map: 1.0.1
side-channel@1.1.0:
side-channel@1.1.1:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
side-channel-list: 1.0.0
side-channel-list: 1.0.1
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
@ -2657,20 +2658,20 @@ snapshots:
vary@1.1.2: {}
vite-live-preview@0.4.0(vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4)):
vite-live-preview@0.4.0(vite@8.1.1(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4)):
dependencies:
'@seahax/deep-copy': 0.1.0
'@seahax/semaphore': 0.5.1
'@types/ws': 8.18.1
escape-goat: 4.0.0
strip-ansi: 7.2.0
vite: 8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4)
vite: 8.1.1(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4)
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4):
vite@8.1.1(@types/node@26.0.1)(esbuild@0.28.1)(tsx@4.22.4):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4

View File

@ -4,6 +4,10 @@ allowBuilds:
linkWorkspacePackages: true
minimumReleaseAgeExclude:
- qs@6.14.2 || 6.15.2
- path-to-regexp@8.4.0
packages:
- "./packages/common"
- "./packages/server"

View File

@ -21,6 +21,7 @@
- **plugin-types**: Added `fixedWhenScrolling` property for shapes
- **plugin-runtime:** `addToken` now resolves references against all token sets, allowing references to tokens in inactive sets
- **plugin-types:** `TokenCatalog.addSet` now accepts an optional `active` flag to create an already-active set (sets are inactive by default)
- **plugin-types:** `TokenTheme.addSet` and `TokenTheme.removeSet` now accept a token set id (`string`) in addition to a `TokenSet`
- **plugin-runtime:** A `fontFamilies` token's `resolvedValue` now returns the documented `string[]` (the resolved family list) instead of leaking the raw tokenscript list symbol
### 🩹 Fixes

View File

@ -17,6 +17,6 @@
"test:ci:mocked": "pnpm run build:headless && MOCK_BACKEND=1 tsx ci/run-ci.ts"
},
"devDependencies": {
"playwright": "^1.61.0"
"playwright": "^1.61.1"
}
}

View File

@ -4,9 +4,9 @@ import type { CommentThread, Page } from '@penpot/plugin-types';
import type { TestContext } from '../framework/types';
// Comments.
// Comment threads are created on the current page. Both thread removal APIs are
// currently broken (see the dedicated red tests), so cleanup is best-effort to
// keep the other assertions meaningful.
// Comment threads are created on the current page. Thread/comment removal is
// asynchronous (it resolves once the backend delete RPC completes), so callers
// must await it; cleanup is best-effort and swallows errors.
function page(ctx: TestContext): Page {
const p = ctx.penpot.currentPage;
@ -124,7 +124,7 @@ describe.skipIfMocked('Comments', () => {
x: 8,
y: 8,
});
thread.remove();
await thread.remove();
const threads = await p.findCommentThreads();
expect(threads.every((t) => t.seqNumber !== thread.seqNumber)).toBe(true);
});

View File

@ -83,6 +83,21 @@ describe('Interactions', () => {
expect(interaction.action.type).toBe('open-overlay');
});
// position is optional; when omitted the overlay defaults to 'center'.
test('open-overlay without a position defaults to center', (ctx) => {
const overlay = board(ctx);
const r = rect(ctx);
const interaction = r.addInteraction('click', {
type: 'open-overlay',
destination: overlay,
});
expect(interaction.action.type).toBe('open-overlay');
if (interaction.action.type === 'open-overlay') {
expect(interaction.action.destination.id).toBe(overlay.id);
expect(interaction.action.position).toBe('center');
}
});
test('toggle-overlay interaction round-trips', (ctx) => {
const overlay = board(ctx);
const r = rect(ctx);
@ -116,6 +131,23 @@ describe('Interactions', () => {
}
});
// animation is optional on close-overlay; omitting it closes with no transition.
test('close-overlay without an animation round-trips', (ctx) => {
const overlay = board(ctx);
const r = rect(ctx);
const interaction = r.addInteraction('click', {
type: 'close-overlay',
destination: overlay,
});
expect(interaction.action.type).toBe('close-overlay');
if (interaction.action.type === 'close-overlay') {
expect(
interaction.action.destination && interaction.action.destination.id,
).toBe(overlay.id);
expect(interaction.action.animation).toBeUndefined();
}
});
test('previous-screen interaction round-trips', (ctx) => {
const r = rect(ctx);
const interaction = r.addInteraction('click', { type: 'previous-screen' });
@ -134,6 +166,19 @@ describe('Interactions', () => {
expect(interaction.delay).toBeCloseTo(1000, 0);
});
// A zero delay is a valid value (fires immediately), not an error.
test('after-delay accepts a zero delay', (ctx) => {
const dest = board(ctx);
const r = rect(ctx);
const interaction = r.addInteraction(
'after-delay',
{ type: 'navigate-to', destination: dest },
0,
);
expect(interaction.trigger).toBe('after-delay');
expect(interaction.delay).toBeCloseTo(0, 0);
});
test('mouse-leave trigger is recorded', (ctx) => {
// click / mouse-enter / after-delay are covered above; mouse-leave is the
// remaining trigger.
@ -167,6 +212,20 @@ describe('Interactions', () => {
expect(interaction.action.type).toBe('previous-screen');
});
// The delay setter accepts zero (fires immediately) as a valid value.
test('delay setter accepts a zero value', (ctx) => {
const dest = board(ctx);
const r = rect(ctx);
const interaction = r.addInteraction(
'after-delay',
{ type: 'navigate-to', destination: dest },
1000,
);
interaction.delay = 0;
expect(interaction.delay).toBeCloseTo(0, 0);
});
describe('Animations', () => {
test('dissolve animation round-trips', (ctx) => {
const dest = board(ctx);

View File

@ -65,6 +65,23 @@ describe('Layout', () => {
expect(flex.leftPadding).toBeCloseTo(4, 0);
});
// Gap and padding setters accept fractional numbers, not just integers.
test('gaps and padding accept fractional values', (ctx) => {
const flex = board(ctx).addFlexLayout();
flex.rowGap = 5.5;
flex.columnGap = 10.25;
flex.topPadding = 1.5;
flex.rightPadding = 2.25;
flex.bottomPadding = 3.75;
flex.leftPadding = 4.5;
expect(flex.rowGap).toBeCloseTo(5.5, 2);
expect(flex.columnGap).toBeCloseTo(10.25, 2);
expect(flex.topPadding).toBeCloseTo(1.5, 2);
expect(flex.rightPadding).toBeCloseTo(2.25, 2);
expect(flex.bottomPadding).toBeCloseTo(3.75, 2);
expect(flex.leftPadding).toBeCloseTo(4.5, 2);
});
test('sizing round-trips', (ctx) => {
const flex = board(ctx).addFlexLayout();
flex.horizontalSizing = 'fix';
@ -176,6 +193,23 @@ describe('Layout', () => {
expect(grid.columnGap).toBeCloseTo(9, 0);
});
// Gap and padding setters accept fractional numbers, not just integers.
test('gaps and padding accept fractional values', (ctx) => {
const grid = board(ctx).addGridLayout();
grid.rowGap = 7.5;
grid.columnGap = 9.25;
grid.topPadding = 1.5;
grid.rightPadding = 2.75;
grid.bottomPadding = 3.25;
grid.leftPadding = 4.5;
expect(grid.rowGap).toBeCloseTo(7.5, 2);
expect(grid.columnGap).toBeCloseTo(9.25, 2);
expect(grid.topPadding).toBeCloseTo(1.5, 2);
expect(grid.rightPadding).toBeCloseTo(2.75, 2);
expect(grid.bottomPadding).toBeCloseTo(3.25, 2);
expect(grid.leftPadding).toBeCloseTo(4.5, 2);
});
// Index boundaries — invalid indices must be rejected.
test('addRowAtIndex with a negative index throws', (ctx) => {
const grid = board(ctx).addGridLayout();

View File

@ -237,6 +237,21 @@ describe('Shapes', () => {
expect(r.borderRadiusBottomRight).toBeCloseTo(3, 0);
expect(r.borderRadiusBottomLeft).toBeCloseTo(4, 0);
});
// Border radius setters accept fractional numbers, not just integers.
test('border radius accepts fractional values', (ctx) => {
const r = rect(ctx);
r.borderRadius = 4.5;
expect(r.borderRadius).toBeCloseTo(4.5, 2);
r.borderRadiusTopLeft = 1.25;
r.borderRadiusTopRight = 2.5;
r.borderRadiusBottomRight = 3.75;
r.borderRadiusBottomLeft = 0.5;
expect(r.borderRadiusTopLeft).toBeCloseTo(1.25, 2);
expect(r.borderRadiusTopRight).toBeCloseTo(2.5, 2);
expect(r.borderRadiusBottomRight).toBeCloseTo(3.75, 2);
expect(r.borderRadiusBottomLeft).toBeCloseTo(0.5, 2);
});
});
describe('Ordering', () => {

View File

@ -148,6 +148,19 @@ describe('Tokens', () => {
theme.removeSet(set);
});
// addSet/removeSet also accept a token set id (string), not just a TokenSet.
test('addSet and removeSet accept a set id', async (ctx) => {
const cat = catalog(ctx);
const theme = cat.addTheme({ group: '', name: unique('theme') });
const set = cat.addSet({ name: unique('set'), active: false });
theme.addSet(set.id);
await sleep(300);
expect(theme.activeSets.length).toBeGreaterThan(0);
theme.removeSet(set.id);
await sleep(300);
expect(theme.activeSets.length).toBe(0);
});
test('duplicate and remove a theme', (ctx) => {
const theme = catalog(ctx).addTheme({ group: '', name: unique('theme') });
const dup = theme.duplicate();

View File

@ -485,9 +485,9 @@ export interface CloseOverlay {
readonly destination?: Board;
/**
* Animation displayed with this interaction.
* Animation displayed with this interaction. Omit it to close with no transition.
*/
readonly animation: Animation;
readonly animation?: Animation;
}
/**
@ -5291,13 +5291,17 @@ export interface TokenTheme {
/**
* Adds a set to the list of the theme.
*
* @param tokenSet a `TokenSet` or the id of a token set.
*/
addSet(tokenSet: TokenSet): void;
addSet(tokenSet: TokenSet | string): void;
/**
* Removes a set from the list of the theme.
*
* @param tokenSet a `TokenSet` or the id of a token set.
*/
removeSet(tokenSet: TokenSet): void;
removeSet(tokenSet: TokenSet | string): void;
/**
* Adds to the catalog a new TokenTheme equal to this one but with a new id.

View File

@ -2,9 +2,9 @@
"name": "@penpot/plugins-runtime",
"version": "1.4.2",
"dependencies": {
"@penpot/plugin-types": "^1.4.2",
"@penpot/plugin-types": "workspace:^",
"ses": "^2.1.0",
"zod": "^3.22.4"
"zod": "^3.25.76"
},
"module": "./dist/index.js",
"typings": "./dist/index.d.ts",

View File

@ -45,9 +45,9 @@
"@types/feather-icons": "^4.29.4",
"@types/node": "26.0.1",
"@types/yargs": "^17.0.35",
"@typescript-eslint/eslint-plugin": "8.62.0",
"@typescript-eslint/parser": "8.62.0",
"@typescript-eslint/utils": "^8.62.0",
"@typescript-eslint/eslint-plugin": "8.62.1",
"@typescript-eslint/parser": "8.62.1",
"@typescript-eslint/utils": "^8.62.1",
"@vitest/coverage-v8": "4.1.9",
"@vitest/ui": "4.1.9",
"concurrently": "^10.0.3",
@ -60,18 +60,18 @@
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.1.1",
"eslint-plugin-unused-imports": "^4.4.1",
"fs-extra": "^11.3.5",
"fs-extra": "^11.3.6",
"globals": "^17.7.0",
"happy-dom": "^20.10.6",
"jiti": "2.7.0",
"jsdom": "~29.1.1",
"jsonc-eslint-parser": "^3.1.0",
"prettier": "^3.9.1",
"prettier": "^3.9.4",
"tsx": "^4.22.4",
"typedoc": "^0.28.19",
"typescript": "6.0.3",
"typescript-eslint": "^8.62.0",
"vite": "8.1.0",
"typescript-eslint": "^8.62.1",
"vite": "8.1.1",
"vite-plugin-checker": "^0.14.4",
"vite-plugin-dts": "5.0.3",
"vite-plugin-static-copy": "^4.1.1",

630
plugins/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,22 @@ allowBuilds:
linkWorkspacePackages: true
minimumReleaseAgeExclude:
- minimatch@10.2.1 || 10.2.3
- ajv@8.18.0
- lodash@4.17.24
- esbuild@0.28.1
- '@babel/core@7.29.1'
- undici@7.28.0
overrides:
'@babel/core@<=7.29.0': ^7.29.1
ajv@>=7.0.0-alpha.0 <8.18.0: ^8.18.0
lodash@<=4.17.23: ^4.17.24
lodash@>=4.0.0 <=4.17.23: ^4.17.24
minimatch@>=10.0.0 <10.2.1: ^10.2.1
minimatch@>=10.0.0 <10.2.3: ^10.2.3
peerDependencyRules:
allowedVersions:
"eslint-plugin-import>eslint": "10.6.0"

View File

@ -733,7 +733,11 @@ pub extern "C" fn set_shape_svg_raw_content() -> Result<()> {
.map_err(|e| Error::RecoverableError(e.to_string()))?
.trim_end_matches('\0')
.to_string();
let render_state = get_render_state();
let font_manager = skia::FontMgr::from(render_state.fonts().font_provider().clone());
shape.set_svg_raw_content(svg_raw_content);
shape.update_svg_raw_content(font_manager);
});
Ok(())

Some files were not shown because too many files have changed in this diff Show More