@jxsuite/studio 10.2.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/dist/chunks/{2020-bx702wqa.js → 2020-906vx2dq.js} +3 -3
  2. package/dist/chunks/adapter-hnvad61x.js +4 -0
  3. package/dist/chunks/add-new-flow-v0pm96mr.js +4 -0
  4. package/dist/chunks/cf-account-picker-xb8vvt2h.js +4 -0
  5. package/dist/chunks/cf-settings-mkbcz4z2.js +4 -0
  6. package/dist/chunks/components-w5zqwva1.js +4 -0
  7. package/dist/chunks/content-source-g38w0d3f.js +4 -0
  8. package/dist/chunks/convert-to-repeater-r8sfxjjm.js +4 -0
  9. package/dist/chunks/editor-wvf5e6j9.js +4 -0
  10. package/dist/chunks/entry-commands-t13dyw3c.js +4 -0
  11. package/dist/chunks/extension-sections-vjzw46xb.js +4 -0
  12. package/dist/chunks/file-ops-1sghzgcr.js +4 -0
  13. package/dist/chunks/files-zrqtgt41.js +4 -0
  14. package/dist/chunks/format-host-9gy76erh.js +4 -0
  15. package/dist/chunks/fs-events-45rhg3me.js +4 -0
  16. package/dist/chunks/grid-open-t4fq6bbm.js +4 -0
  17. package/dist/chunks/{grid-view-tghcwrrt.js → grid-view-2f03wcgw.js} +16 -16
  18. package/dist/chunks/icons-gb967qc8.js +4 -0
  19. package/dist/chunks/iframe-host-9aq8ze3q.js +4 -0
  20. package/dist/chunks/image-bake-4afr4y6a.js +4 -0
  21. package/dist/chunks/image-editor-x7t8jbjn.js +4 -0
  22. package/dist/chunks/index-f9qr7kzh.js +4 -0
  23. package/dist/chunks/{index-vr006kbt.js → index-yjrfeks8.js} +3 -3
  24. package/dist/chunks/{javascript-z1g3s9fd.js → javascript-hy2mws8v.js} +3 -3
  25. package/dist/chunks/{jsonMode-7szwg8c4.js → jsonMode-vj30te2h.js} +6 -6
  26. package/dist/chunks/jx-validate-r4ax11e3.js +4 -0
  27. package/dist/chunks/layers-m49nmwvx.js +4 -0
  28. package/dist/chunks/layers-panel-zr92pwdp.js +4 -0
  29. package/dist/chunks/lit-html-k2mb9k0r.js +4 -0
  30. package/dist/chunks/media-library-j74rt0w4.js +4 -0
  31. package/dist/chunks/media-open-mypr0k6b.js +4 -0
  32. package/dist/chunks/media-picker-pemtbsjv.js +4 -0
  33. package/dist/chunks/{monaco-binding-v42w9c1v.js → monaco-binding-a51py33c.js} +3 -3
  34. package/dist/chunks/monaco-buffer-pa0g6ax7.js +4 -0
  35. package/dist/chunks/{monaco-setup-7bffxd6v.js → monaco-setup-0hw9f835.js} +3 -3
  36. package/dist/chunks/notify-bwehrp7q.js +4 -0
  37. package/dist/chunks/parse-hee7ne9b.js +4 -0
  38. package/dist/chunks/platform-kv0k65cy.js +4 -0
  39. package/dist/chunks/publish-panel-vg1n880b.js +4 -0
  40. package/dist/chunks/publish-quick-4fwngwrw.js +4 -0
  41. package/dist/chunks/publish-workspace-jv4w2bx2.js +4 -0
  42. package/dist/chunks/quick-search-nh29rt8a.js +4 -0
  43. package/dist/chunks/references-vqyr5s9x.js +4 -0
  44. package/dist/chunks/right-panel-3ws9f2w0.js +4 -0
  45. package/dist/chunks/source-control-h3hcs5gy.js +4 -0
  46. package/dist/chunks/{studio-j3d2fjqz.js → studio-0939t1q1.js} +4 -4
  47. package/dist/chunks/studio-0gvvqj3h.js +5 -0
  48. package/dist/chunks/studio-0khd8xtx.js +4 -0
  49. package/dist/chunks/{studio-9b7177jq.js → studio-0x6f3nk9.js} +4 -4
  50. package/dist/chunks/studio-1410q5dw.js +5 -0
  51. package/dist/chunks/studio-1ctnw3xv.js +5 -0
  52. package/dist/chunks/studio-1ks5d9y4.js +5 -0
  53. package/dist/chunks/{studio-awxwjawq.js → studio-1y9mab9m.js} +4 -4
  54. package/dist/chunks/studio-2e4hry72.js +6 -0
  55. package/dist/chunks/studio-2kpxm0eh.js +7 -0
  56. package/dist/chunks/studio-2pk7kc7z.js +5 -0
  57. package/dist/chunks/studio-2x972v58.js +5 -0
  58. package/dist/chunks/studio-2zz4qbv9.js +5 -0
  59. package/dist/chunks/{studio-bvs9kpst.js → studio-3na1r48j.js} +8 -8
  60. package/dist/chunks/studio-3pz4t5a2.js +5 -0
  61. package/dist/chunks/studio-3qr08sxq.js +4 -0
  62. package/dist/chunks/{studio-75kcdbsk.js → studio-45mj4jqe.js} +4 -4
  63. package/dist/chunks/studio-4bgvvzhv.js +5 -0
  64. package/dist/chunks/studio-4f7thq0p.js +5 -0
  65. package/dist/chunks/studio-4fnb5648.js +5 -0
  66. package/dist/chunks/studio-4zd6an2b.js +5 -0
  67. package/dist/chunks/{studio-9rbe2xpw.js → studio-5tet77de.js} +3 -3
  68. package/dist/chunks/studio-61rg2cz5.js +5 -0
  69. package/dist/chunks/{studio-ybnwymxw.js → studio-7rrdbvex.js} +4 -4
  70. package/dist/chunks/{studio-sxdb9h3q.js → studio-90zkgne5.js} +7 -7
  71. package/dist/chunks/studio-9c6cbq2c.js +4 -0
  72. package/dist/chunks/studio-9dq7rwc7.js +5 -0
  73. package/dist/chunks/studio-9gr7xs9r.js +6 -0
  74. package/dist/chunks/studio-a88ej93p.js +7 -0
  75. package/dist/chunks/{studio-er5f383a.js → studio-abh9kfdh.js} +4 -4
  76. package/dist/chunks/studio-acer0g8w.js +5 -0
  77. package/dist/chunks/{studio-qh8berdz.js → studio-amaz545p.js} +4 -4
  78. package/dist/chunks/studio-az3wvyh2.js +5 -0
  79. package/dist/chunks/studio-bg8pzprg.js +5 -0
  80. package/dist/chunks/{studio-399a25za.js → studio-bnc50f9h.js} +4 -4
  81. package/dist/chunks/studio-brh8d2pr.js +5 -0
  82. package/dist/chunks/{studio-646nn2ys.js → studio-c2fa783p.js} +5 -5
  83. package/dist/chunks/studio-cfnn70h1.js +5 -0
  84. package/dist/chunks/{studio-rzqaz21g.js → studio-cqxj4fcy.js} +4 -4
  85. package/dist/chunks/studio-cw11h078.js +5 -0
  86. package/dist/chunks/studio-daksw8kh.js +5 -0
  87. package/dist/chunks/studio-dn56z7sn.js +5 -0
  88. package/dist/chunks/studio-egnjpyde.js +9 -0
  89. package/dist/chunks/studio-ejrypvmq.js +5 -0
  90. package/dist/chunks/studio-eqcddvc2.js +928 -0
  91. package/dist/chunks/studio-evq47j2z.js +5 -0
  92. package/dist/chunks/{studio-r7pf0rkv.js → studio-frc16j8v.js} +7 -7
  93. package/dist/chunks/studio-g0hxtcga.js +5 -0
  94. package/dist/chunks/{studio-th7z3fg7.js → studio-g9brbewz.js} +4 -4
  95. package/dist/chunks/studio-gnazwtv3.js +5 -0
  96. package/dist/chunks/studio-hfqpfmpk.js +5 -0
  97. package/dist/chunks/studio-hh394phe.js +5 -0
  98. package/dist/chunks/{studio-pjtntksy.js → studio-j0zf5x2x.js} +4 -4
  99. package/dist/chunks/studio-j5fcp6gj.js +5 -0
  100. package/dist/chunks/studio-jd1qb1st.js +7 -0
  101. package/dist/chunks/studio-jma2q8r9.js +5 -0
  102. package/dist/chunks/studio-k0xzrse2.js +64 -0
  103. package/dist/chunks/studio-k1xqrgm5.js +5 -0
  104. package/dist/chunks/studio-k2evngrj.js +7 -0
  105. package/dist/chunks/studio-k3we3kqb.js +5 -0
  106. package/dist/chunks/studio-knsjyx7n.js +5 -0
  107. package/dist/chunks/studio-mrjtnm62.js +5 -0
  108. package/dist/chunks/{studio-sevbc3xv.js → studio-n9qba0gx.js} +4 -4
  109. package/dist/chunks/{studio-ea74s8c1.js → studio-nvcn2ykc.js} +4 -4
  110. package/dist/chunks/studio-nwf0c32p.js +5 -0
  111. package/dist/chunks/studio-nzemfbkk.js +14 -0
  112. package/dist/chunks/{studio-43y04qck.js → studio-pj216fmn.js} +4 -4
  113. package/dist/chunks/{studio-4g8yb6jn.js → studio-pm17wdc4.js} +4 -4
  114. package/dist/chunks/studio-pwctzka3.js +16 -0
  115. package/dist/chunks/{studio-71m92v9s.js → studio-q80fg1d6.js} +7 -7
  116. package/dist/chunks/studio-q9hx2qp0.js +5 -0
  117. package/dist/chunks/studio-qrzmfaf6.js +5 -0
  118. package/dist/chunks/{studio-86r8hp7r.js → studio-r6mx7vm5.js} +5 -5
  119. package/dist/chunks/studio-rr00ex23.js +7 -0
  120. package/dist/chunks/{studio-4jhw38b2.js → studio-s24mhq0z.js} +4 -4
  121. package/dist/chunks/studio-sce30r7b.js +4 -0
  122. package/dist/chunks/studio-swedpd82.js +5 -0
  123. package/dist/chunks/studio-tmfcn347.js +6 -0
  124. package/dist/chunks/studio-tpxcryf2.js +5 -0
  125. package/dist/chunks/{studio-wc6za3zs.js → studio-v5wktj6f.js} +4 -4
  126. package/dist/chunks/studio-v7a7yb5w.js +9 -0
  127. package/dist/chunks/{studio-kg1abnz0.js → studio-vpevkfxw.js} +4 -4
  128. package/dist/chunks/{studio-881z1xrx.js → studio-wmjmsw0p.js} +4 -4
  129. package/dist/chunks/studio-wqfaqw4m.js +5 -0
  130. package/dist/chunks/{studio-hhp4x8xf.js → studio-xpx91fby.js} +5 -5
  131. package/dist/chunks/studio-xva3hzkv.js +5 -0
  132. package/dist/chunks/{studio-6hzdjfv0.js → studio-xx3r3sb0.js} +4 -4
  133. package/dist/chunks/{studio-cg1bfyhf.js → studio-z3fztxrp.js} +8 -8
  134. package/dist/chunks/studio-z9453ypn.js +6 -0
  135. package/dist/chunks/{tsMode-f54j66ym.js → tsMode-jg1dtphj.js} +7 -7
  136. package/dist/chunks/workspace-6ztqcrsh.js +4 -0
  137. package/dist/chunks/workspace-home-rse9h8a0.js +4 -0
  138. package/dist/chunks/{ws-client-e8kj684q.js → ws-client-arz2qsy9.js} +3 -3
  139. package/dist/iframe-entry.js +13 -13
  140. package/dist/manifest.json +83 -78
  141. package/dist/studio.js +84 -84
  142. package/package.json +11 -11
  143. package/src/browse/library-pane.ts +44 -29
  144. package/src/browse/library-preview.ts +6 -2
  145. package/src/canvas/asset-refs.ts +13 -4
  146. package/src/canvas/asset-resolve.ts +54 -11
  147. package/src/canvas/canvas-render.ts +25 -0
  148. package/src/canvas/iframe-entry.ts +5 -1
  149. package/src/canvas/iframe-host.ts +16 -6
  150. package/src/canvas/iframe-protocol.ts +9 -1
  151. package/src/commands/context.ts +5 -0
  152. package/src/commands/live-context.ts +5 -2
  153. package/src/editor/shortcuts.ts +3 -0
  154. package/src/files/asset-versions.ts +108 -2
  155. package/src/files/file-ops.ts +11 -1
  156. package/src/files/files.ts +111 -48
  157. package/src/files/media-paths.ts +12 -4
  158. package/src/files/media-upload.ts +195 -21
  159. package/src/files/new-file-name.ts +39 -1
  160. package/src/media/image-bake.ts +164 -0
  161. package/src/media/image-editor.ts +846 -0
  162. package/src/media/image-format.ts +262 -0
  163. package/src/media/image-recipe.ts +651 -0
  164. package/src/media/media-commands.ts +65 -1
  165. package/src/media/media-library-model.ts +1 -1
  166. package/src/media/media-library.ts +175 -15
  167. package/src/media/media-open.ts +156 -3
  168. package/src/media/media-pane.ts +8 -4
  169. package/src/panels/pane-context.ts +6 -1
  170. package/src/panels/tab-strip.ts +12 -3
  171. package/src/studio.ts +13 -2
  172. package/src/surfaces/image-editor.json +1390 -0
  173. package/src/surfaces/image-editor.ts +272 -0
  174. package/src/surfaces/inspector-dock.json +3 -3
  175. package/src/surfaces/media-library.json +129 -1
  176. package/src/surfaces/media-library.ts +17 -2
  177. package/src/types.ts +2 -1
  178. package/src/workspace/tab-workspace.ts +2 -1
  179. package/src/workspace/workspace-switch.ts +3 -1
  180. package/src/workspace/workspace.ts +22 -1
  181. package/dist/chunks/adapter-pp011m5x.js +0 -4
  182. package/dist/chunks/add-new-flow-ecqmzz76.js +0 -4
  183. package/dist/chunks/cf-account-picker-c07znkqt.js +0 -4
  184. package/dist/chunks/cf-settings-0v2bmqy0.js +0 -4
  185. package/dist/chunks/components-yn0dn2pq.js +0 -4
  186. package/dist/chunks/content-source-g0j41yy6.js +0 -4
  187. package/dist/chunks/convert-to-repeater-sbjk7tgn.js +0 -4
  188. package/dist/chunks/editor-xz02jeh1.js +0 -4
  189. package/dist/chunks/entry-commands-kmt02crp.js +0 -4
  190. package/dist/chunks/extension-sections-0rprf12d.js +0 -4
  191. package/dist/chunks/file-ops-mexj5qed.js +0 -4
  192. package/dist/chunks/files-b2tcppdd.js +0 -4
  193. package/dist/chunks/format-host-btsevv0c.js +0 -4
  194. package/dist/chunks/fs-events-r0apejva.js +0 -4
  195. package/dist/chunks/grid-open-n2a8xkht.js +0 -4
  196. package/dist/chunks/icons-853n6qt3.js +0 -4
  197. package/dist/chunks/iframe-host-gx0ypyh3.js +0 -4
  198. package/dist/chunks/index-bs4k9tr6.js +0 -4
  199. package/dist/chunks/jx-validate-ymf10teh.js +0 -4
  200. package/dist/chunks/layers-p9nfya38.js +0 -4
  201. package/dist/chunks/layers-panel-tt1hzhhf.js +0 -4
  202. package/dist/chunks/lit-html-x18n2sfz.js +0 -4
  203. package/dist/chunks/media-library-0n45gv8r.js +0 -4
  204. package/dist/chunks/media-picker-s09at95z.js +0 -4
  205. package/dist/chunks/monaco-buffer-een2ea0v.js +0 -4
  206. package/dist/chunks/notify-qwb8radt.js +0 -4
  207. package/dist/chunks/parse-v77xtwv7.js +0 -4
  208. package/dist/chunks/platform-8jnez0wr.js +0 -4
  209. package/dist/chunks/publish-panel-aysnj611.js +0 -4
  210. package/dist/chunks/publish-quick-kpbqs9mz.js +0 -4
  211. package/dist/chunks/publish-workspace-nd0a8q99.js +0 -4
  212. package/dist/chunks/quick-search-vg0jkhk2.js +0 -4
  213. package/dist/chunks/references-a7v6a0e2.js +0 -4
  214. package/dist/chunks/right-panel-cvk1vxx5.js +0 -4
  215. package/dist/chunks/source-control-fsqzc6he.js +0 -4
  216. package/dist/chunks/studio-05jfwbmf.js +0 -5
  217. package/dist/chunks/studio-0q5jwddh.js +0 -9
  218. package/dist/chunks/studio-0w2dr0q8.js +0 -5
  219. package/dist/chunks/studio-1tzpds97.js +0 -9
  220. package/dist/chunks/studio-216b27sv.js +0 -4
  221. package/dist/chunks/studio-2ejhqns4.js +0 -5
  222. package/dist/chunks/studio-2r90cts3.js +0 -16
  223. package/dist/chunks/studio-399xknkb.js +0 -6
  224. package/dist/chunks/studio-4ncmwqw5.js +0 -5
  225. package/dist/chunks/studio-5atwjgkj.js +0 -7
  226. package/dist/chunks/studio-5frqk7c0.js +0 -5
  227. package/dist/chunks/studio-6cwj6xrx.js +0 -5
  228. package/dist/chunks/studio-78pywnp2.js +0 -6
  229. package/dist/chunks/studio-886t6ztg.js +0 -5
  230. package/dist/chunks/studio-8wnvtr4z.js +0 -5
  231. package/dist/chunks/studio-8z66jk4z.js +0 -5
  232. package/dist/chunks/studio-b2qpt0xy.js +0 -14
  233. package/dist/chunks/studio-c71806xz.js +0 -7
  234. package/dist/chunks/studio-cbwks69x.js +0 -928
  235. package/dist/chunks/studio-cg710sk9.js +0 -5
  236. package/dist/chunks/studio-cw0wb9qk.js +0 -7
  237. package/dist/chunks/studio-cyheys7k.js +0 -5
  238. package/dist/chunks/studio-ea5m9kg5.js +0 -5
  239. package/dist/chunks/studio-efdxjwyp.js +0 -64
  240. package/dist/chunks/studio-egmw2837.js +0 -5
  241. package/dist/chunks/studio-f746039f.js +0 -5
  242. package/dist/chunks/studio-fmdymd9v.js +0 -5
  243. package/dist/chunks/studio-gcm12z00.js +0 -6
  244. package/dist/chunks/studio-gj98m8gy.js +0 -7
  245. package/dist/chunks/studio-hasbgjah.js +0 -5
  246. package/dist/chunks/studio-hz5n7x27.js +0 -5
  247. package/dist/chunks/studio-jeraxdgv.js +0 -7
  248. package/dist/chunks/studio-jvgek9xt.js +0 -5
  249. package/dist/chunks/studio-jws1a87n.js +0 -5
  250. package/dist/chunks/studio-k2sz13zg.js +0 -5
  251. package/dist/chunks/studio-k3m4jkdk.js +0 -5
  252. package/dist/chunks/studio-kg5ya5r2.js +0 -5
  253. package/dist/chunks/studio-khwbgrtg.js +0 -5
  254. package/dist/chunks/studio-mx6wzh25.js +0 -5
  255. package/dist/chunks/studio-mxs3ar9t.js +0 -5
  256. package/dist/chunks/studio-nm4szey4.js +0 -5
  257. package/dist/chunks/studio-nqxqf8p2.js +0 -4
  258. package/dist/chunks/studio-qn7z4krm.js +0 -5
  259. package/dist/chunks/studio-qyed0ah6.js +0 -5
  260. package/dist/chunks/studio-r4bcdwhq.js +0 -5
  261. package/dist/chunks/studio-rtfnhrdg.js +0 -5
  262. package/dist/chunks/studio-s5tzw84t.js +0 -5
  263. package/dist/chunks/studio-t51y8v1d.js +0 -5
  264. package/dist/chunks/studio-t98vq4r6.js +0 -5
  265. package/dist/chunks/studio-tsxpqvh0.js +0 -6
  266. package/dist/chunks/studio-v1yascv2.js +0 -5
  267. package/dist/chunks/studio-wxq6v5sv.js +0 -5
  268. package/dist/chunks/studio-y60sts62.js +0 -4
  269. package/dist/chunks/studio-zndrqawb.js +0 -5
  270. package/dist/chunks/studio-zwaxn5s2.js +0 -5
  271. package/dist/chunks/workspace-home-x1787wpv.js +0 -4
  272. package/dist/chunks/workspace-zpe1np43.js +0 -4
@@ -0,0 +1,1390 @@
1
+ {
2
+ "$schema": "https://jxsuite.com/schema/v1",
3
+ "$id": "StudioImageEditor",
4
+ "$description": "The Image editor (studio.md §9.5): one image asset, edited where it is opened, as a recipe previewed in CSS over the picture already on screen and baked once at save. `media/image-editor.ts` owns the recipe, the undo stack, the decoded source and the one write door, and hands this document a flat projection of pre-formatted strings and discriminant words; nothing here computes geometry. The preview is the real image under a `transform` for the turns and flips and a `filter` for the tone pass, with the crop drawn as a frame over it, so there is no `<canvas>` in this document at all. The crop geometry and the oriented aspect travel as custom properties bound to state in this document's own `style` block, which is how a document binds a style declaration to state (spec.md §9.1). The overwrite sentence is permanent: the path, the absence of a backup and the recovery path are in view whenever the button that writes is.",
5
+ "tagName": "div",
6
+ "attributes": {
7
+ "part": "editor",
8
+ "data-jx-region": "${state.region}"
9
+ },
10
+ "style": {
11
+ "$description": "Keyed on `part`, re-valued onto the studio token layer like every other pane document. The stage is a size container so the frame can fit the picture inside it with container units and an `aspect-ratio`, never larger than the picture's own pixels; the crop scrim is a spread shadow on the crop box clipped by the frame, written as `rgba(0, 0, 0, 0.5)` because `check-styles.ts` allow-lists colour by value.",
12
+ "--jx-image-ratio": "${state.ratio}",
13
+ "--jx-image-max": "${state.maxWidth}",
14
+ "--jx-image-w": "${state.imageWidth}",
15
+ "--jx-image-h": "${state.imageHeight}",
16
+ "--jx-image-transform": "${state.imageTransform}",
17
+ "--jx-image-filter": "${state.filter}",
18
+ "--jx-crop-x": "${state.cropX}",
19
+ "--jx-crop-y": "${state.cropY}",
20
+ "--jx-crop-w": "${state.cropW}",
21
+ "--jx-crop-h": "${state.cropH}",
22
+ "--jx-crop-cursor": "${state.moveCursor}",
23
+ "height": "100%",
24
+ "display": "flex",
25
+ "gap": "var(--jx-space-5)",
26
+ "padding": "var(--jx-space-5)",
27
+ "boxSizing": "border-box",
28
+ "background": "var(--bg-panel)",
29
+ "fontFamily": "var(--jx-font-sans)",
30
+ "color": "var(--fg)",
31
+ "& [part=\"stage-slot\"], & [part=\"quality-slot\"]": {
32
+ "$description": "A `$switch` renders a container of its own, and neither of these is a box: each case stands where the switch does.",
33
+ "display": "contents"
34
+ },
35
+ "& [part=\"stage\"]": {
36
+ "flex": "1",
37
+ "minWidth": "0",
38
+ "minHeight": "0",
39
+ "display": "flex",
40
+ "alignItems": "center",
41
+ "justifyContent": "center",
42
+ "containerType": "size"
43
+ },
44
+ "& [part=\"frame\"]": {
45
+ "$description": "The whole picture as the author sees it, turned and mirrored: the largest box of the oriented aspect that fits the stage, and never wider than the picture's own pixels, because an upscaled preview is a lie about the asset. It clips nothing itself, so a handle on the picture's edge can stand half outside it.",
46
+ "position": "relative",
47
+ "aspectRatio": "var(--jx-image-ratio, 1)",
48
+ "width": "min(100cqw, calc(100cqh * var(--jx-image-ratio, 1)), var(--jx-image-max, 100cqw))"
49
+ },
50
+ "& [part=\"picture\"]": {
51
+ "$description": "The clipped layer: the turned image and the crop scrim, which is a spread shadow that has to stop at the picture.",
52
+ "position": "absolute",
53
+ "inset": "0",
54
+ "overflow": "hidden",
55
+ "background": "var(--bg-input)"
56
+ },
57
+ "& [part=\"image\"]": {
58
+ "$description": "The image in its own, unturned frame, centred in the oriented one and turned into it. On an odd turn its box is the frame's with the sides swapped, which is why the two sizes are percentages the host computes rather than 100%.",
59
+ "position": "absolute",
60
+ "left": "50%",
61
+ "top": "50%",
62
+ "width": "var(--jx-image-w, 100%)",
63
+ "height": "var(--jx-image-h, 100%)",
64
+ "maxWidth": "none",
65
+ "transform": "var(--jx-image-transform, translate(-50%, -50%))",
66
+ "filter": "var(--jx-image-filter, none)",
67
+ "userSelect": "none",
68
+ "pointerEvents": "none"
69
+ },
70
+ "& [part=\"scrim\"], & [part=\"crop\"]": {
71
+ "position": "absolute",
72
+ "left": "var(--jx-crop-x, 0%)",
73
+ "top": "var(--jx-crop-y, 0%)",
74
+ "width": "var(--jx-crop-w, 100%)",
75
+ "height": "var(--jx-crop-h, 100%)",
76
+ "boxSizing": "border-box"
77
+ },
78
+ "& [part=\"scrim\"]": {
79
+ "boxShadow": "0 0 0 100vmax rgba(0, 0, 0, 0.5)"
80
+ },
81
+ "& [part=\"crop\"]": {
82
+ "$description": "The kept region's outline, in white with a dark halo so it reads over a picture of any colour. Above the clipped layer, not in it, so the handles are whole at every edge.",
83
+ "outline": "1px solid white",
84
+ "boxShadow": "0 0 0 1px rgba(0, 0, 0, 0.6)"
85
+ },
86
+ "& [part=\"handle\"]": {
87
+ "$description": "A handle is a real button, so it has a name and a focus ring; drawn as a white square with a dark edge, centred on the point it moves, with `touch-action: none` so a touch drag moves the edge rather than scrolling the pane.",
88
+ "position": "absolute",
89
+ "width": "14px",
90
+ "height": "14px",
91
+ "margin": "-7px 0 0 -7px",
92
+ "padding": "0",
93
+ "border": "1px solid rgba(0, 0, 0, 0.7)",
94
+ "borderRadius": "var(--jx-radius-xs)",
95
+ "background": "white",
96
+ "touchAction": "none",
97
+ ":focus-visible": {
98
+ "outline": "var(--jx-focus-ring)",
99
+ "outlineOffset": "2px"
100
+ }
101
+ },
102
+ "& [part=\"handle\"][data-handle=\"move\"]": {
103
+ "$description": "The kept area itself, the one stop that moves the crop rather than resizing it. Drawn first, so the eight handles paint over it, and a button like them, so it can be focused and moved from the keyboard as well as dragged.",
104
+ "inset": "0",
105
+ "width": "auto",
106
+ "height": "auto",
107
+ "margin": "0",
108
+ "border": "none",
109
+ "borderRadius": "0",
110
+ "background": "transparent",
111
+ "cursor": "var(--jx-crop-cursor, move)"
112
+ },
113
+ "& [part=\"handle\"][data-handle=\"n\"]": {
114
+ "left": "50%",
115
+ "top": "0%",
116
+ "cursor": "ns-resize"
117
+ },
118
+ "& [part=\"handle\"][data-handle=\"ne\"]": {
119
+ "left": "100%",
120
+ "top": "0%",
121
+ "cursor": "nesw-resize"
122
+ },
123
+ "& [part=\"handle\"][data-handle=\"e\"]": {
124
+ "left": "100%",
125
+ "top": "50%",
126
+ "cursor": "ew-resize"
127
+ },
128
+ "& [part=\"handle\"][data-handle=\"se\"]": {
129
+ "left": "100%",
130
+ "top": "100%",
131
+ "cursor": "nwse-resize"
132
+ },
133
+ "& [part=\"handle\"][data-handle=\"s\"]": {
134
+ "left": "50%",
135
+ "top": "100%",
136
+ "cursor": "ns-resize"
137
+ },
138
+ "& [part=\"handle\"][data-handle=\"sw\"]": {
139
+ "left": "0%",
140
+ "top": "100%",
141
+ "cursor": "nesw-resize"
142
+ },
143
+ "& [part=\"handle\"][data-handle=\"w\"]": {
144
+ "left": "0%",
145
+ "top": "50%",
146
+ "cursor": "ew-resize"
147
+ },
148
+ "& [part=\"handle\"][data-handle=\"nw\"]": {
149
+ "left": "0%",
150
+ "top": "0%",
151
+ "cursor": "nwse-resize"
152
+ },
153
+ "& [part=\"note\"], & [part=\"refused\"]": {
154
+ "maxWidth": "48ch",
155
+ "margin": "0",
156
+ "padding": "var(--jx-space-6)",
157
+ "fontSize": "var(--jx-text-md)",
158
+ "color": "var(--fg-dim)",
159
+ "textAlign": "center"
160
+ },
161
+ "& [part=\"refused\"]": {
162
+ "border": "1px dashed var(--border)",
163
+ "borderRadius": "var(--radius)"
164
+ },
165
+ "& [part=\"controls\"]": {
166
+ "flex": "0 0 260px",
167
+ "display": "flex",
168
+ "flexDirection": "column",
169
+ "gap": "var(--jx-space-4)",
170
+ "overflowY": "auto"
171
+ },
172
+ "& [part=\"title\"]": {
173
+ "margin": "0",
174
+ "fontSize": "var(--jx-text-lg)",
175
+ "overflowWrap": "anywhere"
176
+ },
177
+ "& [part=\"facts\"], & [part=\"help\"]": {
178
+ "margin": "0",
179
+ "fontSize": "var(--jx-text-sm)",
180
+ "color": "var(--fg-dim)"
181
+ },
182
+ "& [part=\"group\"]": {
183
+ "display": "flex",
184
+ "flexDirection": "column",
185
+ "gap": "var(--jx-space-2)"
186
+ },
187
+ "& [part=\"group-title\"]": {
188
+ "margin": "0",
189
+ "fontSize": "var(--jx-text-md)",
190
+ "fontWeight": "500",
191
+ "color": "var(--fg-dim)"
192
+ },
193
+ "& [part=\"tools\"]": {
194
+ "display": "flex",
195
+ "flexWrap": "wrap",
196
+ "gap": "2px"
197
+ },
198
+ "& [part=\"field-row\"]": {
199
+ "display": "flex",
200
+ "alignItems": "center",
201
+ "justifyContent": "space-between",
202
+ "gap": "var(--jx-space-3)",
203
+ "fontSize": "var(--jx-text-md)"
204
+ },
205
+ "& [part=\"field-label\"]": {
206
+ "flex": "1"
207
+ },
208
+ "& [part=\"actions\"]": {
209
+ "display": "flex",
210
+ "gap": "var(--jx-space-2)",
211
+ "justifyContent": "flex-end"
212
+ },
213
+ "& [part=\"footer\"]": {
214
+ "$description": "The button that writes and the sentence about it stay together and stay in view: a column too short for its controls scrolls behind them rather than taking the sentence away.",
215
+ "position": "sticky",
216
+ "bottom": "0",
217
+ "display": "flex",
218
+ "flexDirection": "column",
219
+ "gap": "var(--jx-space-3)",
220
+ "marginTop": "auto",
221
+ "paddingTop": "var(--jx-space-2)",
222
+ "background": "var(--bg-panel)"
223
+ },
224
+ "& [part=\"warning\"]": {
225
+ "$description": "The permanent sentence, beside the button that writes. Not a dialog, because a confirmation on every save is a tax the author learns to dismiss unread.",
226
+ "margin": "0",
227
+ "padding": "var(--jx-space-3)",
228
+ "fontSize": "var(--jx-text-sm)",
229
+ "color": "var(--fg-dim)",
230
+ "border": "1px solid var(--border)",
231
+ "borderRadius": "var(--radius)",
232
+ "overflowWrap": "anywhere"
233
+ },
234
+ "& [part=\"discards\"]": {
235
+ "margin": "0",
236
+ "fontSize": "var(--jx-text-sm)",
237
+ "color": "var(--fg-dim)"
238
+ }
239
+ },
240
+ "children": [
241
+ {
242
+ "tagName": "div",
243
+ "attributes": {
244
+ "part": "stage"
245
+ },
246
+ "children": [
247
+ {
248
+ "tagName": "div",
249
+ "attributes": {
250
+ "part": "stage-slot",
251
+ "role": "none"
252
+ },
253
+ "$switch": {
254
+ "$ref": "#/state/status"
255
+ },
256
+ "cases": {
257
+ "loading": {
258
+ "tagName": "p",
259
+ "attributes": {
260
+ "part": "note"
261
+ },
262
+ "textContent": "${state.message}"
263
+ },
264
+ "refused": {
265
+ "tagName": "p",
266
+ "attributes": {
267
+ "part": "refused",
268
+ "role": "status"
269
+ },
270
+ "textContent": "${state.message}"
271
+ },
272
+ "ready": {
273
+ "tagName": "div",
274
+ "attributes": {
275
+ "part": "frame"
276
+ },
277
+ "children": [
278
+ {
279
+ "tagName": "div",
280
+ "attributes": {
281
+ "part": "picture"
282
+ },
283
+ "children": [
284
+ {
285
+ "tagName": "img",
286
+ "attributes": {
287
+ "part": "image",
288
+ "src": "${state.src}",
289
+ "alt": "${state.alt}"
290
+ },
291
+ "onload": {
292
+ "$prototype": "Function",
293
+ "body": [
294
+ {
295
+ "operator": "call",
296
+ "target": {
297
+ "$ref": "#/state/imageLoaded"
298
+ },
299
+ "value": [
300
+ {
301
+ "$ref": "event#/target/naturalWidth"
302
+ },
303
+ {
304
+ "$ref": "event#/target/naturalHeight"
305
+ }
306
+ ]
307
+ }
308
+ ],
309
+ "description": "The decoded size, with any orientation tag applied, which is the frame the crop is stored in."
310
+ },
311
+ "onerror": {
312
+ "$prototype": "Function",
313
+ "body": [
314
+ {
315
+ "operator": "call",
316
+ "target": {
317
+ "$ref": "#/state/imageFailed"
318
+ },
319
+ "value": [
320
+ {
321
+ "$ref": "event#/target/src"
322
+ }
323
+ ]
324
+ }
325
+ ],
326
+ "description": "A header can parse over a body that does not decode. The host decides what that means."
327
+ }
328
+ },
329
+ {
330
+ "tagName": "div",
331
+ "attributes": {
332
+ "part": "scrim"
333
+ }
334
+ }
335
+ ]
336
+ },
337
+ {
338
+ "tagName": "div",
339
+ "attributes": {
340
+ "part": "crop",
341
+ "role": "group",
342
+ "aria-label": "Crop"
343
+ },
344
+ "children": [
345
+ {
346
+ "$prototype": "Array",
347
+ "items": {
348
+ "$ref": "#/state/handles"
349
+ },
350
+ "key": {
351
+ "$ref": "$map/item/id"
352
+ },
353
+ "map": {
354
+ "tagName": "button",
355
+ "attributes": {
356
+ "part": "handle",
357
+ "type": "button",
358
+ "data-handle": "${$map.item.id}",
359
+ "aria-label": "${$map.item.label}",
360
+ "aria-describedby": "${state.handleHelpId}",
361
+ "tabindex": "${$map.item.tabindex}"
362
+ },
363
+ "onpointerdown": {
364
+ "$prototype": "Function",
365
+ "body": [
366
+ {
367
+ "operator": "call",
368
+ "target": {
369
+ "$ref": "#/state/handleDown"
370
+ },
371
+ "value": [
372
+ {
373
+ "$ref": "$map/item/id"
374
+ },
375
+ {
376
+ "$ref": "event#/pointerId"
377
+ },
378
+ {
379
+ "$ref": "event#/clientX"
380
+ },
381
+ {
382
+ "$ref": "event#/clientY"
383
+ },
384
+ {
385
+ "$ref": "event#/currentTarget"
386
+ }
387
+ ]
388
+ }
389
+ ],
390
+ "description": "A drag starts on the handle and captures the pointer to it, so every move and the release arrive here however far the pointer travels. The host decides what a pixel on screen is in the image's own frame."
391
+ },
392
+ "onpointermove": {
393
+ "$prototype": "Function",
394
+ "body": [
395
+ {
396
+ "operator": "call",
397
+ "target": {
398
+ "$ref": "#/state/handleMove"
399
+ },
400
+ "value": [
401
+ {
402
+ "$ref": "event#/pointerId"
403
+ },
404
+ {
405
+ "$ref": "event#/clientX"
406
+ },
407
+ {
408
+ "$ref": "event#/clientY"
409
+ }
410
+ ]
411
+ }
412
+ ]
413
+ },
414
+ "onpointerup": {
415
+ "$prototype": "Function",
416
+ "body": [
417
+ {
418
+ "operator": "call",
419
+ "target": {
420
+ "$ref": "#/state/handleUp"
421
+ },
422
+ "value": [
423
+ {
424
+ "$ref": "event#/pointerId"
425
+ }
426
+ ]
427
+ }
428
+ ]
429
+ },
430
+ "onpointercancel": {
431
+ "$prototype": "Function",
432
+ "body": [
433
+ {
434
+ "operator": "call",
435
+ "target": {
436
+ "$ref": "#/state/handleUp"
437
+ },
438
+ "value": [
439
+ {
440
+ "$ref": "event#/pointerId"
441
+ }
442
+ ]
443
+ }
444
+ ]
445
+ },
446
+ "onfocus": {
447
+ "$prototype": "Function",
448
+ "body": [
449
+ {
450
+ "operator": "call",
451
+ "target": {
452
+ "$ref": "#/state/handleFocused"
453
+ },
454
+ "value": [
455
+ {
456
+ "$ref": "$map/item/id"
457
+ }
458
+ ]
459
+ }
460
+ ]
461
+ },
462
+ "onkeydown": {
463
+ "$prototype": "Function",
464
+ "body": [
465
+ {
466
+ "$switch": {
467
+ "$ref": "event#/key"
468
+ },
469
+ "cases": {
470
+ "ArrowLeft": [
471
+ {
472
+ "preventDefault": true
473
+ },
474
+ {
475
+ "operator": "call",
476
+ "target": {
477
+ "$ref": "#/state/nudge"
478
+ },
479
+ "value": [
480
+ {
481
+ "$ref": "$map/item/id"
482
+ },
483
+ -1,
484
+ 0,
485
+ {
486
+ "$ref": "event#/shiftKey"
487
+ }
488
+ ]
489
+ }
490
+ ],
491
+ "ArrowRight": [
492
+ {
493
+ "preventDefault": true
494
+ },
495
+ {
496
+ "operator": "call",
497
+ "target": {
498
+ "$ref": "#/state/nudge"
499
+ },
500
+ "value": [
501
+ {
502
+ "$ref": "$map/item/id"
503
+ },
504
+ 1,
505
+ 0,
506
+ {
507
+ "$ref": "event#/shiftKey"
508
+ }
509
+ ]
510
+ }
511
+ ],
512
+ "ArrowUp": [
513
+ {
514
+ "preventDefault": true
515
+ },
516
+ {
517
+ "operator": "call",
518
+ "target": {
519
+ "$ref": "#/state/nudge"
520
+ },
521
+ "value": [
522
+ {
523
+ "$ref": "$map/item/id"
524
+ },
525
+ 0,
526
+ -1,
527
+ {
528
+ "$ref": "event#/shiftKey"
529
+ }
530
+ ]
531
+ }
532
+ ],
533
+ "ArrowDown": [
534
+ {
535
+ "preventDefault": true
536
+ },
537
+ {
538
+ "operator": "call",
539
+ "target": {
540
+ "$ref": "#/state/nudge"
541
+ },
542
+ "value": [
543
+ {
544
+ "$ref": "$map/item/id"
545
+ },
546
+ 0,
547
+ 1,
548
+ {
549
+ "$ref": "event#/shiftKey"
550
+ }
551
+ ]
552
+ }
553
+ ],
554
+ "Home": [
555
+ {
556
+ "preventDefault": true
557
+ },
558
+ {
559
+ "operator": "call",
560
+ "target": {
561
+ "$ref": "#/state/cycleHandle"
562
+ },
563
+ "value": [
564
+ {
565
+ "$ref": "$map/item/id"
566
+ },
567
+ -1
568
+ ]
569
+ }
570
+ ],
571
+ "End": [
572
+ {
573
+ "preventDefault": true
574
+ },
575
+ {
576
+ "operator": "call",
577
+ "target": {
578
+ "$ref": "#/state/cycleHandle"
579
+ },
580
+ "value": [
581
+ {
582
+ "$ref": "$map/item/id"
583
+ },
584
+ 1
585
+ ]
586
+ }
587
+ ]
588
+ }
589
+ }
590
+ ],
591
+ "description": "One roving tab stop for all eight handles: the arrows move the focused handle's edge one pixel (ten with Shift), and Home and End move to the previous and next handle clockwise. Tab leaves the group, so the handles are not eight stops between the image and the controls."
592
+ }
593
+ }
594
+ }
595
+ ]
596
+ }
597
+ ]
598
+ }
599
+ }
600
+ }
601
+ ]
602
+ },
603
+ {
604
+ "tagName": "div",
605
+ "attributes": {
606
+ "part": "controls"
607
+ },
608
+ "children": [
609
+ {
610
+ "tagName": "h2",
611
+ "attributes": {
612
+ "part": "title"
613
+ },
614
+ "textContent": "${state.name}"
615
+ },
616
+ {
617
+ "tagName": "p",
618
+ "attributes": {
619
+ "part": "facts",
620
+ "aria-live": "polite"
621
+ },
622
+ "textContent": "${state.facts}"
623
+ },
624
+ {
625
+ "tagName": "p",
626
+ "attributes": {
627
+ "part": "help",
628
+ "id": "${state.handleHelpId}"
629
+ },
630
+ "textContent": "Drag inside the crop to move it, or drag a handle to resize it. From the keyboard, the arrows move whichever is focused by one pixel, Shift by ten; Home and End step between them."
631
+ },
632
+ {
633
+ "tagName": "section",
634
+ "attributes": {
635
+ "part": "group"
636
+ },
637
+ "children": [
638
+ {
639
+ "tagName": "h3",
640
+ "attributes": {
641
+ "part": "group-title"
642
+ },
643
+ "textContent": "Crop"
644
+ },
645
+ {
646
+ "tagName": "label",
647
+ "attributes": {
648
+ "part": "field-row"
649
+ },
650
+ "children": [
651
+ {
652
+ "tagName": "span",
653
+ "attributes": {
654
+ "part": "field-label"
655
+ },
656
+ "textContent": "Ratio"
657
+ },
658
+ {
659
+ "tagName": "jx-select",
660
+ "attributes": {
661
+ "part": "aspect"
662
+ },
663
+ "$props": {
664
+ "label": "Ratio",
665
+ "size": "sm",
666
+ "options": {
667
+ "$ref": "#/state/aspectOptions"
668
+ },
669
+ "value": {
670
+ "$ref": "#/state/aspect"
671
+ },
672
+ "disabled": {
673
+ "$ref": "#/state/undecoded"
674
+ }
675
+ },
676
+ "onchange": {
677
+ "$prototype": "Function",
678
+ "body": [
679
+ {
680
+ "operator": "call",
681
+ "target": {
682
+ "$ref": "#/state/setAspect"
683
+ },
684
+ "value": [
685
+ {
686
+ "$ref": "event#/target/value"
687
+ }
688
+ ]
689
+ }
690
+ ]
691
+ }
692
+ }
693
+ ]
694
+ }
695
+ ]
696
+ },
697
+ {
698
+ "tagName": "section",
699
+ "attributes": {
700
+ "part": "group"
701
+ },
702
+ "children": [
703
+ {
704
+ "tagName": "h3",
705
+ "attributes": {
706
+ "part": "group-title"
707
+ },
708
+ "textContent": "Orientation"
709
+ },
710
+ {
711
+ "tagName": "div",
712
+ "attributes": {
713
+ "part": "tools",
714
+ "role": "toolbar",
715
+ "aria-label": "Orientation and history"
716
+ },
717
+ "children": [
718
+ {
719
+ "tagName": "jx-action-button",
720
+ "attributes": {
721
+ "part": "rotate-left"
722
+ },
723
+ "$props": {
724
+ "size": "sm",
725
+ "quiet": true,
726
+ "icon": "arrows-clockwise",
727
+ "label": "Rotate left",
728
+ "hint": "Rotate left",
729
+ "mirror": true,
730
+ "disabled": {
731
+ "$ref": "#/state/busy"
732
+ }
733
+ },
734
+ "onclick": {
735
+ "$prototype": "Function",
736
+ "body": [
737
+ {
738
+ "operator": "call",
739
+ "target": {
740
+ "$ref": "#/state/rotate"
741
+ },
742
+ "value": [-1]
743
+ }
744
+ ]
745
+ }
746
+ },
747
+ {
748
+ "tagName": "jx-action-button",
749
+ "attributes": {
750
+ "part": "rotate-right"
751
+ },
752
+ "$props": {
753
+ "size": "sm",
754
+ "quiet": true,
755
+ "icon": "arrows-clockwise",
756
+ "label": "Rotate right",
757
+ "hint": "Rotate right",
758
+ "disabled": {
759
+ "$ref": "#/state/busy"
760
+ }
761
+ },
762
+ "onclick": {
763
+ "$prototype": "Function",
764
+ "body": [
765
+ {
766
+ "operator": "call",
767
+ "target": {
768
+ "$ref": "#/state/rotate"
769
+ },
770
+ "value": [1]
771
+ }
772
+ ]
773
+ }
774
+ },
775
+ {
776
+ "tagName": "jx-action-button",
777
+ "attributes": {
778
+ "part": "flip-h"
779
+ },
780
+ "$props": {
781
+ "size": "sm",
782
+ "quiet": true,
783
+ "icon": "flip-horizontal",
784
+ "label": "Flip horizontally",
785
+ "hint": "Flip horizontally",
786
+ "disabled": {
787
+ "$ref": "#/state/busy"
788
+ }
789
+ },
790
+ "onclick": {
791
+ "$prototype": "Function",
792
+ "body": [
793
+ {
794
+ "operator": "call",
795
+ "target": {
796
+ "$ref": "#/state/flip"
797
+ },
798
+ "value": ["h"]
799
+ }
800
+ ]
801
+ }
802
+ },
803
+ {
804
+ "tagName": "jx-action-button",
805
+ "attributes": {
806
+ "part": "flip-v"
807
+ },
808
+ "$props": {
809
+ "size": "sm",
810
+ "quiet": true,
811
+ "icon": "flip-vertical",
812
+ "label": "Flip vertically",
813
+ "hint": "Flip vertically",
814
+ "disabled": {
815
+ "$ref": "#/state/busy"
816
+ }
817
+ },
818
+ "onclick": {
819
+ "$prototype": "Function",
820
+ "body": [
821
+ {
822
+ "operator": "call",
823
+ "target": {
824
+ "$ref": "#/state/flip"
825
+ },
826
+ "value": ["v"]
827
+ }
828
+ ]
829
+ }
830
+ },
831
+ {
832
+ "tagName": "jx-action-button",
833
+ "attributes": {
834
+ "part": "undo"
835
+ },
836
+ "$props": {
837
+ "size": "sm",
838
+ "quiet": true,
839
+ "icon": "arrow-u-up-left",
840
+ "label": "Undo",
841
+ "hint": "Undo",
842
+ "disabled": {
843
+ "$ref": "#/state/cannotUndo"
844
+ }
845
+ },
846
+ "onclick": {
847
+ "$prototype": "Function",
848
+ "body": [
849
+ {
850
+ "operator": "call",
851
+ "target": {
852
+ "$ref": "#/state/undo"
853
+ },
854
+ "value": []
855
+ }
856
+ ]
857
+ }
858
+ },
859
+ {
860
+ "tagName": "jx-action-button",
861
+ "attributes": {
862
+ "part": "redo"
863
+ },
864
+ "$props": {
865
+ "size": "sm",
866
+ "quiet": true,
867
+ "icon": "arrow-u-up-right",
868
+ "label": "Redo",
869
+ "hint": "Redo",
870
+ "disabled": {
871
+ "$ref": "#/state/cannotRedo"
872
+ }
873
+ },
874
+ "onclick": {
875
+ "$prototype": "Function",
876
+ "body": [
877
+ {
878
+ "operator": "call",
879
+ "target": {
880
+ "$ref": "#/state/redo"
881
+ },
882
+ "value": []
883
+ }
884
+ ]
885
+ }
886
+ }
887
+ ]
888
+ }
889
+ ]
890
+ },
891
+ {
892
+ "tagName": "section",
893
+ "attributes": {
894
+ "part": "group"
895
+ },
896
+ "children": [
897
+ {
898
+ "tagName": "h3",
899
+ "attributes": {
900
+ "part": "group-title"
901
+ },
902
+ "textContent": "Size"
903
+ },
904
+ {
905
+ "tagName": "label",
906
+ "attributes": {
907
+ "part": "field-row"
908
+ },
909
+ "children": [
910
+ {
911
+ "tagName": "span",
912
+ "attributes": {
913
+ "part": "field-label"
914
+ },
915
+ "textContent": "Width"
916
+ },
917
+ {
918
+ "tagName": "jx-number-field",
919
+ "attributes": {
920
+ "part": "width"
921
+ },
922
+ "$props": {
923
+ "size": "sm",
924
+ "label": "Width",
925
+ "value": {
926
+ "$ref": "#/state/width"
927
+ },
928
+ "stepper": true,
929
+ "max": {
930
+ "$ref": "#/state/maxOutputWidth"
931
+ },
932
+ "min": "1",
933
+ "disabled": {
934
+ "$ref": "#/state/undecoded"
935
+ }
936
+ },
937
+ "onchange": {
938
+ "$prototype": "Function",
939
+ "body": [
940
+ {
941
+ "operator": "call",
942
+ "target": {
943
+ "$ref": "#/state/setWidth"
944
+ },
945
+ "value": [
946
+ {
947
+ "$ref": "event#/target/value"
948
+ }
949
+ ]
950
+ }
951
+ ]
952
+ },
953
+ "oninput": {
954
+ "$prototype": "Function",
955
+ "body": [
956
+ {
957
+ "operator": "call",
958
+ "target": {
959
+ "$ref": "#/state/fieldEdited"
960
+ },
961
+ "value": []
962
+ }
963
+ ],
964
+ "description": "Text not yet committed is unsaved work, so the Save chord is not refused over it."
965
+ }
966
+ }
967
+ ]
968
+ },
969
+ {
970
+ "tagName": "jx-button",
971
+ "attributes": {
972
+ "part": "fit"
973
+ },
974
+ "$props": {
975
+ "size": "sm",
976
+ "variant": "secondary",
977
+ "disabled": {
978
+ "$ref": "#/state/cannotFit"
979
+ },
980
+ "hint": "${state.fitHint}"
981
+ },
982
+ "onclick": {
983
+ "$prototype": "Function",
984
+ "body": [
985
+ {
986
+ "operator": "call",
987
+ "target": {
988
+ "$ref": "#/state/fitForWeb"
989
+ },
990
+ "value": []
991
+ }
992
+ ]
993
+ },
994
+ "children": [
995
+ {
996
+ "tagName": "span",
997
+ "textContent": "${state.fitLabel}"
998
+ }
999
+ ]
1000
+ },
1001
+ {
1002
+ "tagName": "div",
1003
+ "attributes": {
1004
+ "part": "quality-slot",
1005
+ "role": "none"
1006
+ },
1007
+ "$switch": {
1008
+ "$ref": "#/state/quality"
1009
+ },
1010
+ "cases": {
1011
+ "shown": {
1012
+ "tagName": "label",
1013
+ "attributes": {
1014
+ "part": "field-row"
1015
+ },
1016
+ "children": [
1017
+ {
1018
+ "tagName": "span",
1019
+ "attributes": {
1020
+ "part": "field-label"
1021
+ },
1022
+ "textContent": "Quality"
1023
+ },
1024
+ {
1025
+ "tagName": "jx-number-field",
1026
+ "attributes": {
1027
+ "part": "quality"
1028
+ },
1029
+ "$props": {
1030
+ "size": "sm",
1031
+ "label": "Quality",
1032
+ "value": {
1033
+ "$ref": "#/state/qualityValue"
1034
+ },
1035
+ "stepper": true,
1036
+ "min": "1",
1037
+ "max": "100",
1038
+ "disabled": {
1039
+ "$ref": "#/state/undecoded"
1040
+ }
1041
+ },
1042
+ "onchange": {
1043
+ "$prototype": "Function",
1044
+ "body": [
1045
+ {
1046
+ "operator": "call",
1047
+ "target": {
1048
+ "$ref": "#/state/setValue"
1049
+ },
1050
+ "value": [
1051
+ "quality",
1052
+ {
1053
+ "$ref": "event#/target/value"
1054
+ }
1055
+ ]
1056
+ }
1057
+ ]
1058
+ },
1059
+ "oninput": {
1060
+ "$prototype": "Function",
1061
+ "body": [
1062
+ {
1063
+ "operator": "call",
1064
+ "target": {
1065
+ "$ref": "#/state/fieldEdited"
1066
+ },
1067
+ "value": []
1068
+ }
1069
+ ],
1070
+ "description": "Text not yet committed is unsaved work, so the Save chord is not refused over it."
1071
+ }
1072
+ }
1073
+ ]
1074
+ },
1075
+ "hidden": {
1076
+ "tagName": "span",
1077
+ "attributes": {
1078
+ "hidden": true
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+ ]
1084
+ },
1085
+ {
1086
+ "tagName": "section",
1087
+ "attributes": {
1088
+ "part": "group"
1089
+ },
1090
+ "children": [
1091
+ {
1092
+ "tagName": "h3",
1093
+ "attributes": {
1094
+ "part": "group-title"
1095
+ },
1096
+ "textContent": "Tone"
1097
+ },
1098
+ {
1099
+ "tagName": "label",
1100
+ "attributes": {
1101
+ "part": "field-row"
1102
+ },
1103
+ "children": [
1104
+ {
1105
+ "tagName": "span",
1106
+ "attributes": {
1107
+ "part": "field-label"
1108
+ },
1109
+ "textContent": "Brightness"
1110
+ },
1111
+ {
1112
+ "tagName": "jx-number-field",
1113
+ "attributes": {
1114
+ "part": "brightness"
1115
+ },
1116
+ "$props": {
1117
+ "size": "sm",
1118
+ "label": "Brightness",
1119
+ "value": {
1120
+ "$ref": "#/state/brightness"
1121
+ },
1122
+ "stepper": true,
1123
+ "min": "0",
1124
+ "max": "200",
1125
+ "disabled": {
1126
+ "$ref": "#/state/undecoded"
1127
+ }
1128
+ },
1129
+ "onchange": {
1130
+ "$prototype": "Function",
1131
+ "body": [
1132
+ {
1133
+ "operator": "call",
1134
+ "target": {
1135
+ "$ref": "#/state/setValue"
1136
+ },
1137
+ "value": [
1138
+ "brightness",
1139
+ {
1140
+ "$ref": "event#/target/value"
1141
+ }
1142
+ ]
1143
+ }
1144
+ ]
1145
+ },
1146
+ "oninput": {
1147
+ "$prototype": "Function",
1148
+ "body": [
1149
+ {
1150
+ "operator": "call",
1151
+ "target": {
1152
+ "$ref": "#/state/fieldEdited"
1153
+ },
1154
+ "value": []
1155
+ }
1156
+ ],
1157
+ "description": "Text not yet committed is unsaved work, so the Save chord is not refused over it."
1158
+ }
1159
+ }
1160
+ ]
1161
+ },
1162
+ {
1163
+ "tagName": "label",
1164
+ "attributes": {
1165
+ "part": "field-row"
1166
+ },
1167
+ "children": [
1168
+ {
1169
+ "tagName": "span",
1170
+ "attributes": {
1171
+ "part": "field-label"
1172
+ },
1173
+ "textContent": "Contrast"
1174
+ },
1175
+ {
1176
+ "tagName": "jx-number-field",
1177
+ "attributes": {
1178
+ "part": "contrast"
1179
+ },
1180
+ "$props": {
1181
+ "size": "sm",
1182
+ "label": "Contrast",
1183
+ "value": {
1184
+ "$ref": "#/state/contrast"
1185
+ },
1186
+ "stepper": true,
1187
+ "min": "0",
1188
+ "max": "200",
1189
+ "disabled": {
1190
+ "$ref": "#/state/undecoded"
1191
+ }
1192
+ },
1193
+ "onchange": {
1194
+ "$prototype": "Function",
1195
+ "body": [
1196
+ {
1197
+ "operator": "call",
1198
+ "target": {
1199
+ "$ref": "#/state/setValue"
1200
+ },
1201
+ "value": [
1202
+ "contrast",
1203
+ {
1204
+ "$ref": "event#/target/value"
1205
+ }
1206
+ ]
1207
+ }
1208
+ ]
1209
+ },
1210
+ "oninput": {
1211
+ "$prototype": "Function",
1212
+ "body": [
1213
+ {
1214
+ "operator": "call",
1215
+ "target": {
1216
+ "$ref": "#/state/fieldEdited"
1217
+ },
1218
+ "value": []
1219
+ }
1220
+ ],
1221
+ "description": "Text not yet committed is unsaved work, so the Save chord is not refused over it."
1222
+ }
1223
+ }
1224
+ ]
1225
+ },
1226
+ {
1227
+ "tagName": "label",
1228
+ "attributes": {
1229
+ "part": "field-row"
1230
+ },
1231
+ "children": [
1232
+ {
1233
+ "tagName": "span",
1234
+ "attributes": {
1235
+ "part": "field-label"
1236
+ },
1237
+ "textContent": "Saturation"
1238
+ },
1239
+ {
1240
+ "tagName": "jx-number-field",
1241
+ "attributes": {
1242
+ "part": "saturation"
1243
+ },
1244
+ "$props": {
1245
+ "size": "sm",
1246
+ "label": "Saturation",
1247
+ "value": {
1248
+ "$ref": "#/state/saturation"
1249
+ },
1250
+ "stepper": true,
1251
+ "min": "0",
1252
+ "max": "200",
1253
+ "disabled": {
1254
+ "$ref": "#/state/undecoded"
1255
+ }
1256
+ },
1257
+ "onchange": {
1258
+ "$prototype": "Function",
1259
+ "body": [
1260
+ {
1261
+ "operator": "call",
1262
+ "target": {
1263
+ "$ref": "#/state/setValue"
1264
+ },
1265
+ "value": [
1266
+ "saturation",
1267
+ {
1268
+ "$ref": "event#/target/value"
1269
+ }
1270
+ ]
1271
+ }
1272
+ ]
1273
+ },
1274
+ "oninput": {
1275
+ "$prototype": "Function",
1276
+ "body": [
1277
+ {
1278
+ "operator": "call",
1279
+ "target": {
1280
+ "$ref": "#/state/fieldEdited"
1281
+ },
1282
+ "value": []
1283
+ }
1284
+ ],
1285
+ "description": "Text not yet committed is unsaved work, so the Save chord is not refused over it."
1286
+ }
1287
+ }
1288
+ ]
1289
+ }
1290
+ ]
1291
+ },
1292
+ {
1293
+ "tagName": "div",
1294
+ "attributes": {
1295
+ "part": "footer"
1296
+ },
1297
+ "children": [
1298
+ {
1299
+ "tagName": "div",
1300
+ "attributes": {
1301
+ "part": "actions"
1302
+ },
1303
+ "children": [
1304
+ {
1305
+ "tagName": "jx-button",
1306
+ "attributes": {
1307
+ "part": "reset"
1308
+ },
1309
+ "$props": {
1310
+ "size": "sm",
1311
+ "variant": "secondary",
1312
+ "disabled": {
1313
+ "$ref": "#/state/clean"
1314
+ }
1315
+ },
1316
+ "onclick": {
1317
+ "$prototype": "Function",
1318
+ "body": [
1319
+ {
1320
+ "operator": "call",
1321
+ "target": {
1322
+ "$ref": "#/state/reset"
1323
+ },
1324
+ "value": []
1325
+ }
1326
+ ]
1327
+ },
1328
+ "children": [
1329
+ {
1330
+ "tagName": "span",
1331
+ "textContent": "Reset"
1332
+ }
1333
+ ]
1334
+ },
1335
+ {
1336
+ "tagName": "jx-button",
1337
+ "attributes": {
1338
+ "part": "save"
1339
+ },
1340
+ "$props": {
1341
+ "size": "sm",
1342
+ "variant": "accent",
1343
+ "disabled": {
1344
+ "$ref": "#/state/cannotSave"
1345
+ },
1346
+ "loading": {
1347
+ "$ref": "#/state/saving"
1348
+ }
1349
+ },
1350
+ "onclick": {
1351
+ "$prototype": "Function",
1352
+ "body": [
1353
+ {
1354
+ "operator": "call",
1355
+ "target": {
1356
+ "$ref": "#/state/save"
1357
+ },
1358
+ "value": []
1359
+ }
1360
+ ]
1361
+ },
1362
+ "children": [
1363
+ {
1364
+ "tagName": "span",
1365
+ "textContent": "Save"
1366
+ }
1367
+ ]
1368
+ }
1369
+ ]
1370
+ },
1371
+ {
1372
+ "tagName": "p",
1373
+ "attributes": {
1374
+ "part": "warning"
1375
+ },
1376
+ "textContent": "${state.warning}"
1377
+ },
1378
+ {
1379
+ "tagName": "p",
1380
+ "attributes": {
1381
+ "part": "discards"
1382
+ },
1383
+ "textContent": "Saving writes pixels only: a rotation tag is applied to them, and any color profile, high bit depth and other metadata are dropped."
1384
+ }
1385
+ ]
1386
+ }
1387
+ ]
1388
+ }
1389
+ ]
1390
+ }