@lvl99dotcom/paintcanvas 0.3.1 → 0.5.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 (212) hide show
  1. package/README.md +30 -15
  2. package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
  3. package/dist/lib/brushes/StrokeEngine/index.js +26 -28
  4. package/dist/lib/brushes/presets/round/index.d.ts +1 -1
  5. package/dist/lib/brushes/presets/round/index.js +12 -7
  6. package/dist/lib/brushes/presets/square/index.d.ts +1 -1
  7. package/dist/lib/brushes/presets/square/index.js +7 -3
  8. package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
  9. package/dist/lib/brushes/shapeImage/index.js +15 -9
  10. package/dist/lib/brushes/shapes/index.d.ts +18 -0
  11. package/dist/lib/brushes/shapes/index.js +86 -0
  12. package/dist/lib/classes/BrushSettings/index.d.ts +53 -2
  13. package/dist/lib/classes/BrushSettings/index.js +85 -14
  14. package/dist/lib/classes/KeyboardShortcuts/index.d.ts +47 -7
  15. package/dist/lib/classes/KeyboardShortcuts/index.js +145 -20
  16. package/dist/lib/classes/PaintCanvas/index.d.ts +123 -27
  17. package/dist/lib/classes/PaintCanvas/index.js +208 -41
  18. package/dist/lib/classes/PaintCanvas/tool.d.ts +15 -0
  19. package/dist/lib/classes/PaintCanvas/tool.js +18 -0
  20. package/dist/lib/classes/Palette/index.d.ts +58 -0
  21. package/dist/lib/classes/Palette/index.js +92 -0
  22. package/dist/lib/classes/SettingsAutosave/index.d.ts +53 -0
  23. package/dist/lib/classes/SettingsAutosave/index.js +98 -0
  24. package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
  25. package/dist/lib/classes/ShortcutSettings/index.js +110 -0
  26. package/dist/lib/classes/UiSettings/index.d.ts +68 -0
  27. package/dist/lib/classes/UiSettings/index.js +82 -0
  28. package/dist/lib/palette/index.d.ts +49 -0
  29. package/dist/lib/palette/index.js +82 -0
  30. package/dist/lib/palette/schema.d.ts +45 -0
  31. package/dist/lib/palette/schema.js +41 -0
  32. package/dist/lib/rendering/cursors/index.d.ts +2 -0
  33. package/dist/lib/rendering/cursors/index.js +3 -1
  34. package/dist/lib/rendering/sample/index.js +3 -2
  35. package/dist/lib/settings/index.d.ts +63 -0
  36. package/dist/lib/settings/index.js +93 -0
  37. package/dist/lib/settings/schema.d.ts +168 -0
  38. package/dist/lib/settings/schema.js +116 -0
  39. package/dist/lib/storage/index.d.ts +36 -5
  40. package/dist/lib/storage/index.js +87 -29
  41. package/dist/lib/utils/colour/index.d.ts +72 -0
  42. package/dist/lib/utils/colour/index.js +154 -0
  43. package/dist/lib/utils/shortcuts/index.d.ts +174 -11
  44. package/dist/lib/utils/shortcuts/index.js +391 -145
  45. package/dist/lib/validation/validators.generated.d.ts +5 -0
  46. package/dist/lib/validation/validators.generated.js +1040 -1
  47. package/dist/locales/en-GB.js +96 -3
  48. package/dist/locales/en-US.js +96 -3
  49. package/dist/locales/fr.js +96 -3
  50. package/dist/locales/ja.js +96 -3
  51. package/dist/locales/ko.js +96 -3
  52. package/dist/locales/zh-Hans.js +96 -3
  53. package/dist/locales/zh-Hant.js +96 -3
  54. package/dist/package.js +1 -1
  55. package/dist/testing/recordingCanvas.js +2 -1
  56. package/dist/ui/base/index.d.ts +2 -2
  57. package/dist/ui/dismiss/index.d.ts +21 -0
  58. package/dist/ui/dismiss/index.js +27 -0
  59. package/dist/ui/drag/index.d.ts +51 -0
  60. package/dist/ui/drag/index.js +81 -0
  61. package/dist/ui/elements/PaintCanvas/index.d.ts +57 -50
  62. package/dist/ui/elements/PaintCanvas/index.js +141 -174
  63. package/dist/ui/elements/PaintCanvas/style.js +1 -1
  64. package/dist/ui/elements/PcAppSettings/index.d.ts +59 -0
  65. package/dist/ui/elements/PcAppSettings/index.js +222 -0
  66. package/dist/ui/elements/PcBrushConfig/index.d.ts +9 -2
  67. package/dist/ui/elements/PcBrushConfig/index.js +47 -7
  68. package/dist/ui/elements/PcBrushConfig/style.js +1 -1
  69. package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
  70. package/dist/ui/elements/PcBrushSize/index.js +66 -0
  71. package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
  72. package/dist/ui/elements/PcBrushTest/index.js +73 -5
  73. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  74. package/dist/ui/elements/PcCanvas/index.d.ts +22 -2
  75. package/dist/ui/elements/PcCanvas/index.js +60 -15
  76. package/dist/ui/elements/PcCanvasMap/style.js +1 -1
  77. package/dist/ui/elements/PcClipboardTools/index.d.ts +48 -0
  78. package/dist/ui/elements/PcClipboardTools/index.js +118 -0
  79. package/dist/ui/elements/PcColourPair/index.d.ts +43 -0
  80. package/dist/ui/elements/PcColourPair/index.js +125 -0
  81. package/dist/ui/elements/PcContextMenu/index.d.ts +71 -34
  82. package/dist/ui/elements/PcContextMenu/index.js +128 -97
  83. package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
  84. package/dist/ui/elements/PcInputSettings/index.js +16 -1
  85. package/dist/ui/elements/PcLayerItem/index.d.ts +13 -34
  86. package/dist/ui/elements/PcLayerItem/index.js +39 -91
  87. package/dist/ui/elements/PcLayerItem/style.js +1 -1
  88. package/dist/ui/elements/PcMenu/index.d.ts +138 -0
  89. package/dist/ui/elements/PcMenu/index.js +296 -0
  90. package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
  91. package/dist/ui/elements/PcNumberSlider/index.js +247 -0
  92. package/dist/ui/elements/PcPane/index.d.ts +120 -0
  93. package/dist/ui/elements/PcPane/index.js +394 -0
  94. package/dist/ui/elements/PcPanel/index.d.ts +26 -8
  95. package/dist/ui/elements/PcPanel/index.js +76 -8
  96. package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.d.ts +22 -21
  97. package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +24 -57
  98. package/dist/ui/elements/PcPanelBrushes/style.js +3 -0
  99. package/dist/ui/elements/PcPanelColours/index.d.ts +171 -0
  100. package/dist/ui/elements/PcPanelColours/index.js +650 -0
  101. package/dist/ui/elements/PcPanelColours/style.js +3 -0
  102. package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.d.ts +17 -4
  103. package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.js +17 -10
  104. package/dist/ui/elements/PcPanelLayers/style.js +3 -0
  105. package/dist/ui/elements/PcPopover/index.d.ts +39 -1
  106. package/dist/ui/elements/PcPopover/index.js +72 -4
  107. package/dist/ui/elements/PcShortcutSettings/index.d.ts +69 -0
  108. package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
  109. package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
  110. package/dist/ui/elements/PcThemePicker/index.js +22 -21
  111. package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
  112. package/dist/ui/elements/PcToolSettings/index.js +384 -319
  113. package/dist/ui/elements/PcToolSettings/style.js +1 -1
  114. package/dist/ui/elements/PcToolbar/index.d.ts +62 -12
  115. package/dist/ui/elements/PcToolbar/index.js +119 -57
  116. package/dist/ui/elements/PcToolbar/style.js +1 -1
  117. package/dist/ui/icons/images/icon-brush@2x.js +3 -0
  118. package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
  119. package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
  120. package/dist/ui/icons/images/icon-cog@2x.js +3 -0
  121. package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
  122. package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
  123. package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
  124. package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
  125. package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
  126. package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
  127. package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
  128. package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
  129. package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
  130. package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
  131. package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
  132. package/dist/ui/icons/images/icon-layers@2x.js +3 -0
  133. package/dist/ui/icons/images/icon-linked@2x.js +3 -0
  134. package/dist/ui/icons/images/icon-locked@2x.js +3 -0
  135. package/dist/ui/icons/images/icon-moon@2x.js +3 -0
  136. package/dist/ui/icons/images/icon-pan@2x.js +3 -0
  137. package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
  138. package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
  139. package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
  140. package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
  141. package/dist/ui/icons/images/icon-redo@2x.js +3 -0
  142. package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
  143. package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
  144. package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
  145. package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
  146. package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
  147. package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
  148. package/dist/ui/icons/images/icon-star@2x.js +3 -0
  149. package/dist/ui/icons/images/icon-sun@2x.js +3 -0
  150. package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
  151. package/dist/ui/icons/images/icon-undo@2x.js +3 -0
  152. package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
  153. package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
  154. package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
  155. package/dist/ui/icons/images/icon-visible@2x.js +3 -0
  156. package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
  157. package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
  158. package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
  159. package/dist/ui/icons/index.d.ts +1 -1
  160. package/dist/ui/icons/index.js +84 -72
  161. package/dist/ui/index.d.ts +13 -2
  162. package/dist/ui/index.js +16 -4
  163. package/dist/ui/layout/index.d.ts +9 -1
  164. package/dist/ui/layout/index.js +11 -2
  165. package/dist/ui/tokens/index.d.ts +5 -4
  166. package/dist/ui/tokens/index.js +15 -8
  167. package/locales/en-GB.json +96 -3
  168. package/locales/en-US.json +96 -3
  169. package/locales/fr.json +96 -3
  170. package/locales/ja.json +96 -3
  171. package/locales/ko.json +96 -3
  172. package/locales/zh-Hans.json +96 -3
  173. package/locales/zh-Hant.json +96 -3
  174. package/package.json +11 -4
  175. package/dist/ui/elements/PcBrushMenu/style.js +0 -3
  176. package/dist/ui/elements/PcLayersMenu/style.js +0 -3
  177. package/dist/ui/icons/images/brush.js +0 -3
  178. package/dist/ui/icons/images/circle-mask.js +0 -3
  179. package/dist/ui/icons/images/clipboard.js +0 -3
  180. package/dist/ui/icons/images/cursor.js +0 -3
  181. package/dist/ui/icons/images/eraser.js +0 -3
  182. package/dist/ui/icons/images/eyedropper.js +0 -3
  183. package/dist/ui/icons/images/flip-horizontal.js +0 -3
  184. package/dist/ui/icons/images/flip-vertical.js +0 -3
  185. package/dist/ui/icons/images/focus-pen.js +0 -3
  186. package/dist/ui/icons/images/freehand-mask.js +0 -3
  187. package/dist/ui/icons/images/hidden.js +0 -3
  188. package/dist/ui/icons/images/layers.js +0 -3
  189. package/dist/ui/icons/images/linked.js +0 -3
  190. package/dist/ui/icons/images/locked.js +0 -3
  191. package/dist/ui/icons/images/moon.js +0 -3
  192. package/dist/ui/icons/images/pan.js +0 -3
  193. package/dist/ui/icons/images/polygon-mask.js +0 -3
  194. package/dist/ui/icons/images/pressure.js +0 -3
  195. package/dist/ui/icons/images/randomise.js +0 -3
  196. package/dist/ui/icons/images/rectangle-mask.js +0 -3
  197. package/dist/ui/icons/images/redo.js +0 -3
  198. package/dist/ui/icons/images/rotate-90-left.js +0 -3
  199. package/dist/ui/icons/images/rotate-90-right.js +0 -3
  200. package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
  201. package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
  202. package/dist/ui/icons/images/smoothing-linear.js +0 -3
  203. package/dist/ui/icons/images/star.js +0 -3
  204. package/dist/ui/icons/images/sun.js +0 -3
  205. package/dist/ui/icons/images/tilt.js +0 -3
  206. package/dist/ui/icons/images/undo.js +0 -3
  207. package/dist/ui/icons/images/unlinked.js +0 -3
  208. package/dist/ui/icons/images/unlocked.js +0 -3
  209. package/dist/ui/icons/images/visible.js +0 -3
  210. package/dist/ui/icons/images/zoom-in.js +0 -3
  211. package/dist/ui/icons/images/zoom-out.js +0 -3
  212. package/dist/ui/icons/images/zoom.js +0 -3
@@ -1,11 +1,15 @@
1
+ import { Tool } from "../../../lib/classes/PaintCanvas/tool.js";
1
2
  import style_default from "./style.js";
2
3
  import { ICON_CONTROLS_CSS, PILLS_CSS } from "../../styles/index.js";
3
4
  import { PcElement, defineElement } from "../../base/index.js";
4
5
  import ChangeController from "../../ChangeController/index.js";
5
- import { Panel, Tool } from "../../../lib/classes/PaintCanvas/index.js";
6
+ import { Panel } from "../../../lib/classes/PaintCanvas/index.js";
6
7
  import "../../../lib/classes/BrushSettings/index.js";
7
8
  import { MAX_ZOOM, MIN_ZOOM } from "../../../lib/classes/Viewport/index.js";
8
9
  import { iconLabel } from "../../icons/index.js";
10
+ import "../PcBrushSize/index.js";
11
+ import "../PcNumberSlider/index.js";
12
+ import "../PcPopover/index.js";
9
13
  import { html, nothing, svg, unsafeCSS } from "lit";
10
14
  import { live } from "lit/directives/live.js";
11
15
  import { styleMap } from "lit/directives/style-map.js";
@@ -50,6 +54,10 @@ var withSource = (preset, params, source, on) => Object.fromEntries(params.map((
50
54
  * roundAngle(12.3456); // 12.35
51
55
  */
52
56
  var roundAngle = (degrees) => Math.round(degrees * 100) / 100;
57
+ /** Largest width or height the move tool's fields go to (pixels or percent) */
58
+ var MAX_MOVE_SIZE = 1e4;
59
+ /** Shown in a move tool field while there's nothing to move */
60
+ var NO_VALUE = "–";
53
61
  /** Steps of the zoom slider, across the whole zoom range */
54
62
  var ZOOM_SLIDER_STEPS = 1e3;
55
63
  /** How many times bigger the largest zoom is than the smallest */
@@ -80,19 +88,21 @@ var zoomPercent = (zoom) => {
80
88
  const percent = zoom * 100;
81
89
  return `${percent < 10 ? Math.round(percent * 10) / 10 : Math.round(percent)}%`;
82
90
  };
83
- /** Read a number input, or undefined if it isn't a number */
84
- var numberValue = (event) => {
85
- const value = event.target.valueAsNumber;
86
- return Number.isFinite(value) ? value : void 0;
87
- };
88
91
  /**
89
- * The bar of settings for the current tool, shown at the top of the canvas when the tool has any:
92
+ * The bar of settings for the current tool, shown at the top of the canvas when the tool has any. The tool's settings
93
+ * are at the start (part `start`); the end, on the right (part `end`), is for your own items. The `start` and `end`
94
+ * slots add your own items to each. (Cut, copy and paste are `<pc-clipboard-tools>`.)
90
95
  *
91
- * - Brush and eraser: the preset (its name and shape; shows or hides the preset menu), width, height, constrain proportions, pressure, tilt, opacity and "Configure" (fires
92
- * `pc-configure-brush` with the tool, "brush" or "eraser").
96
+ * - Brush and eraser: the preset (its name and shape; shows or hides the preset menu), the size (`pc-brush-size`), the
97
+ * size settings (a cog button that shows or hides a popover under it with pressure, tilt and focus pen mode above the
98
+ * width, constrain proportions and height), opacity and "Configure" (fires `pc-configure-brush` with the tool,
99
+ * "brush" or "eraser"). The size, width, height and opacity are `pc-number-slider`s: pressed and dragged right or up
100
+ * to raise them, left or down to lower them, or tapped to type a value. Focus pen mode: only pens draw, and touches
101
+ * are ignored while a pen is down or just lifted.
93
102
  * - Lassos: select all, and invert selection and deselect (only while there is a mask).
94
103
  * - Move, scale and rotate: width and height (in pixels or percent), constrain proportions, flip horizontal and vertical,
95
- * angle (to 2 decimal places), rotate 90° left and right, and reset (undoes the whole transform).
104
+ * angle (to 2 decimal places), rotate 90° left and right, and reset (undoes the whole transform). The width, height
105
+ * and angle are `pc-number-slider`s.
96
106
  * - Colour sample: point sample (the pixel under the pointer) or area sample (the average of a dragged rectangle).
97
107
  * - Zoom: zoom in and out, a slider across the zoom range (logarithmic) with the zoom as a percentage, and reset (100%),
98
108
  * all around the centre of the view.
@@ -100,13 +110,16 @@ var numberValue = (event) => {
100
110
  * Changes to the brush or eraser apply to the current settings only, not the preset. Styling is structural only.
101
111
  *
102
112
  * @example
103
- * html`<pc-tool-settings .app=${app}></pc-tool-settings>`
113
+ * html`<pc-tool-settings .app=${app}>
114
+ * <button slot="end" @click=${share}>Share</button>
115
+ * </pc-tool-settings>`
104
116
  */
105
117
  var PcToolSettings = class extends PcElement {
106
118
  static properties = {
107
119
  ...PcElement.properties,
108
120
  app: { attribute: false },
109
- percent: { state: true }
121
+ percent: { state: true },
122
+ sizeOpen: { state: true }
110
123
  };
111
124
  static styles = [
112
125
  PcElement.styles,
@@ -114,6 +127,7 @@ var PcToolSettings = class extends PcElement {
114
127
  unsafeCSS(PILLS_CSS),
115
128
  unsafeCSS(style_default)
116
129
  ];
130
+ placeSizeSettings = () => this.sizePopover?.placeBelow();
117
131
  changes = new ChangeController(this, () => {
118
132
  const app = this.app;
119
133
  return app ? [
@@ -129,6 +143,47 @@ var PcToolSettings = class extends PcElement {
129
143
  constructor() {
130
144
  super();
131
145
  this.percent = false;
146
+ this.sizeOpen = false;
147
+ }
148
+ disconnectedCallback() {
149
+ super.disconnectedCallback();
150
+ this.listenWhileSizeOpen(false);
151
+ }
152
+ /** The size settings' popover, while a brush or eraser's settings are shown */
153
+ get sizePopover() {
154
+ return this.renderRoot.querySelector("pc-popover.size-popover");
155
+ }
156
+ /** Follow the window's scrolling and resizing (or stop) */
157
+ listenWhileSizeOpen(open) {
158
+ const method = open ? "addEventListener" : "removeEventListener";
159
+ window[method]("scroll", this.placeSizeSettings, true);
160
+ window[method]("resize", this.placeSizeSettings);
161
+ }
162
+ /**
163
+ * Hide the size settings (e.g. on Escape or a press outside them), returning focus to their button if it was in them.
164
+ */
165
+ closeSizeSettings() {
166
+ const focusWasInside = this.sizePopover.matches(":focus-within");
167
+ this.sizeOpen = false;
168
+ if (focusWasInside) this.renderRoot.querySelector(".size-settings")?.focus();
169
+ }
170
+ willUpdate(changed) {
171
+ super.willUpdate(changed);
172
+ const tool = this.app?.tool;
173
+ if (tool !== Tool.Brush && tool !== Tool.Eraser) this.sizeOpen = false;
174
+ }
175
+ updated(changed) {
176
+ super.updated(changed);
177
+ if (!changed.has("sizeOpen")) return;
178
+ this.listenWhileSizeOpen(this.sizeOpen);
179
+ if (this.sizeOpen) this.showSizeSettings();
180
+ }
181
+ /** Put the opened size settings under their button, once they can be measured */
182
+ async showSizeSettings() {
183
+ const popover = this.sizePopover;
184
+ popover.anchor = this.renderRoot.querySelector(".size-settings");
185
+ await popover.updateComplete;
186
+ popover.placeBelow();
132
187
  }
133
188
  /** Fire an event that crosses shadow DOM boundaries */
134
189
  emit(type, detail) {
@@ -149,21 +204,45 @@ var PcToolSettings = class extends PcElement {
149
204
  });
150
205
  }
151
206
  /**
152
- * Render the focus pen mode toggle, on the right of every tool's settings: while on, only pens draw, and touches are
153
- * ignored while a pen is down or just lifted.
207
+ * Render the focus pen mode toggle, for the brush and eraser: while on, only pens draw, and touches are ignored while a
208
+ * pen is down or just lifted.
154
209
  */
155
210
  renderFocusPen(app) {
156
- return html`<label
157
- class="icon-toggle focus-pen"
211
+ const on = app.input.data.focusPen;
212
+ return html`<button
213
+ type="button"
214
+ class="focus-pen icon-button"
158
215
  title=${this.t("paintCanvas.toolSettings.focusPenHint")}
216
+ aria-label=${this.t("paintCanvas.toolSettings.focusPen")}
217
+ aria-pressed=${String(on)}
218
+ @click=${() => app.input.update({ focusPen: !on })}
159
219
  >
160
- <input
161
- type="checkbox"
162
- .checked=${live(app.input.data.focusPen)}
163
- @change=${(e) => app.input.update({ focusPen: e.target.checked })}
164
- />
165
- ${iconLabel(this.iconUrl("focusPen"), this.t("paintCanvas.toolSettings.focusPen"))}
166
- </label>`;
220
+ <img src=${this.iconUrl("focusPen")} alt="" />
221
+ </button>`;
222
+ }
223
+ /**
224
+ * Render a setting as a named `pc-number-slider` (dragged or typed), from 1 to 256 pixels unless given another range.
225
+ */
226
+ renderNumberField(name, label, value, change, { min = 1, max = 256, unit = "", step = 1, dragStep = step, text = "", disabled = false } = {}) {
227
+ return html`<div class="field">
228
+ <span aria-hidden="true">${label}</span>
229
+ <pc-number-slider
230
+ class=${name}
231
+ min=${min}
232
+ max=${max}
233
+ step=${step}
234
+ drag-step=${dragStep}
235
+ unit=${unit}
236
+ label=${label}
237
+ .text=${text}
238
+ ?disabled=${disabled}
239
+ .value=${live(value)}
240
+ @pc-change=${(e) => {
241
+ change(e.detail.value);
242
+ this.requestUpdate();
243
+ }}
244
+ ></pc-number-slider>
245
+ </div>`;
167
246
  }
168
247
  /** Render the brush or eraser settings */
169
248
  renderBrush(app, settings, kind) {
@@ -172,143 +251,136 @@ var PcToolSettings = class extends PcElement {
172
251
  const tilt = TILT_PARAMS.some((param) => preset[param]?.useTilt);
173
252
  const label = this.t(kind === "eraser" ? "paintCanvas.tool.eraser" : "paintCanvas.tool.brush");
174
253
  const panel = kind === "eraser" ? Panel.Eraser : Panel.Brush;
175
- return html`<div
176
- role="toolbar"
177
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: label })}
178
- >
179
- <button
180
- type="button"
181
- class="preset"
182
- title=${this.t("paintCanvas.toolSettings.presets", { tool: label })}
183
- aria-expanded=${app.panel === panel ? "true" : "false"}
184
- @click=${() => app.togglePanel(panel)}
185
- >
186
- <span
187
- class=${preset.image ? "shape-preview image" : "shape-preview"}
188
- style=${styleMap({ maskImage: typeof preset.image === "string" ? `url("${preset.image}")` : void 0 })}
189
- ></span>
190
- ${preset.name ?? preset.id}
191
- </button>
192
- <label>
193
- ${this.t("paintCanvas.common.width")}
194
- <input
195
- type="number"
196
- class="width"
197
- min=${1}
198
- max=${256}
199
- .value=${String(settings.width)}
200
- @change=${(e) => {
201
- const value = numberValue(e);
202
- if (value !== void 0) settings.setSize(value, settings.height);
203
- }}
204
- />
205
- </label>
206
- <label
207
- class="icon-toggle"
208
- title=${this.t("paintCanvas.toolSettings.constrainProportions")}
254
+ const sizeSettings = this.t("paintCanvas.toolSettings.sizeSettings");
255
+ return {
256
+ label: this.t("paintCanvas.toolSettings.settings", { tool: label }),
257
+ start: html`<button
258
+ type="button"
259
+ class="preset"
260
+ data-panel=${panel}
261
+ title=${this.t("paintCanvas.toolSettings.presets", { tool: label })}
262
+ aria-expanded=${app.panel === panel ? "true" : "false"}
263
+ @click=${() => app.togglePanel(panel)}
264
+ >
265
+ <span
266
+ class=${preset.image ? "shape-preview image" : "shape-preview"}
267
+ style=${styleMap({ maskImage: typeof preset.image === "string" ? `url("${preset.image}")` : void 0 })}
268
+ ></span>
269
+ ${preset.name ?? preset.id}
270
+ </button>
271
+ <div class="field">
272
+ <span aria-hidden="true">${this.t("paintCanvas.common.size")}</span>
273
+ <pc-brush-size
274
+ class="size"
275
+ .settings=${settings}
276
+ label=${this.t("paintCanvas.brushMenu.size", { tool: label })}
277
+ ></pc-brush-size>
278
+ </div>
279
+ <button
280
+ type="button"
281
+ class="size-settings"
282
+ title=${sizeSettings}
283
+ aria-label=${sizeSettings}
284
+ aria-haspopup="dialog"
285
+ aria-expanded=${this.sizeOpen ? "true" : "false"}
286
+ @click=${() => this.sizeOpen = !this.sizeOpen}
287
+ >
288
+ <img src=${this.iconUrl("cog")} alt="" />
289
+ </button>
290
+
291
+ ${this.renderNumberField("opacity", this.t("paintCanvas.common.opacity"), Math.round((preset.alpha ?? 1) * 100), (value) => settings.update({ alpha: value / 100 }), {
292
+ min: 0,
293
+ max: 100,
294
+ unit: "%"
295
+ })}
296
+ <button
297
+ type="button"
298
+ class="configure"
299
+ @click=${() => this.emit("pc-configure-brush", kind)}
300
+ >
301
+ ${this.t("paintCanvas.toolSettings.configure")}
302
+ </button>`,
303
+ after: html`<pc-popover
304
+ class="size-popover"
305
+ arrow
306
+ label=${sizeSettings}
307
+ ?open=${this.sizeOpen}
308
+ @pc-close=${this.closeSizeSettings}
209
309
  >
210
- <input
211
- type="checkbox"
212
- class="constrain"
213
- .checked=${preset.constrainProportions ?? false}
214
- @change=${(e) => settings.update({ constrainProportions: e.target.checked })}
215
- />
216
- ${iconLabel(this.iconUrl(preset.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
217
- </label>
218
- <label>
219
- ${this.t("paintCanvas.common.height")}
220
- <input
221
- type="number"
222
- class="height"
223
- min=${1}
224
- max=${256}
225
- .value=${String(settings.height)}
226
- @change=${(e) => {
227
- const value = numberValue(e);
228
- if (value !== void 0) settings.setHeight(value);
229
- }}
230
- />
231
- </label>
232
- <label
233
- class="icon-toggle"
234
- title=${this.t("paintCanvas.toolSettings.pressure")}
235
- >
236
- <input
237
- type="checkbox"
238
- class="pressure"
239
- .checked=${pressure}
240
- @change=${(e) => settings.update(withSource(preset, PRESSURE_PARAMS, "usePressure", e.target.checked))}
241
- />
242
- ${iconLabel(this.iconUrl("pressure"), this.t("paintCanvas.toolSettings.pressure"))}
243
- </label>
244
- <label
245
- class="icon-toggle"
246
- title=${this.t("paintCanvas.toolSettings.tilt")}
247
- >
248
- <input
249
- type="checkbox"
250
- class="tilt"
251
- .checked=${tilt}
252
- @change=${(e) => settings.update(withSource(preset, TILT_PARAMS, "useTilt", e.target.checked))}
253
- />
254
- ${iconLabel(this.iconUrl("tilt"), this.t("paintCanvas.toolSettings.tilt"))}
255
- </label>
256
- <label>
257
- ${this.t("paintCanvas.common.opacity")}
258
- <input
259
- type="number"
260
- class="opacity"
261
- min="0"
262
- max="100"
263
- .value=${String(Math.round((preset.alpha ?? 1) * 100))}
264
- @change=${(e) => {
265
- const value = numberValue(e);
266
- if (value !== void 0) settings.update({ alpha: Math.max(0, Math.min(100, value)) / 100 });
267
- }}
268
- />
269
- <span class="unit">%</span>
270
- </label>
271
- <button
272
- type="button"
273
- class="configure"
274
- @click=${() => this.emit("pc-configure-brush", kind)}
275
- >
276
- ${this.t("paintCanvas.toolSettings.configure")}
277
- </button>
278
- ${this.renderFocusPen(app)}
279
- </div>`;
310
+ <div class="size-toggles">
311
+ <label
312
+ class="icon-toggle"
313
+ title=${this.t("paintCanvas.toolSettings.pressure")}
314
+ >
315
+ <input
316
+ type="checkbox"
317
+ class="pressure"
318
+ .checked=${pressure}
319
+ @change=${(e) => settings.update(withSource(preset, PRESSURE_PARAMS, "usePressure", e.target.checked))}
320
+ />
321
+ ${iconLabel(this.iconUrl("pressure"), this.t("paintCanvas.toolSettings.pressure"))}
322
+ </label>
323
+ <label
324
+ class="icon-toggle"
325
+ title=${this.t("paintCanvas.toolSettings.tilt")}
326
+ >
327
+ <input
328
+ type="checkbox"
329
+ class="tilt"
330
+ .checked=${tilt}
331
+ @change=${(e) => settings.update(withSource(preset, TILT_PARAMS, "useTilt", e.target.checked))}
332
+ />
333
+ ${iconLabel(this.iconUrl("tilt"), this.t("paintCanvas.toolSettings.tilt"))}
334
+ </label>
335
+ ${this.renderFocusPen(app)}
336
+ </div>
337
+ <div class="size-fields">
338
+ ${this.renderNumberField("width", this.t("paintCanvas.common.width"), settings.width, (value) => settings.setSize(value, settings.height))}
339
+ <label
340
+ class="icon-toggle"
341
+ title=${this.t("paintCanvas.toolSettings.constrainProportions")}
342
+ >
343
+ <input
344
+ type="checkbox"
345
+ class="constrain"
346
+ .checked=${preset.constrainProportions ?? false}
347
+ @change=${(e) => settings.update({ constrainProportions: e.target.checked })}
348
+ />
349
+ ${iconLabel(this.iconUrl(preset.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
350
+ </label>
351
+ ${this.renderNumberField("height", this.t("paintCanvas.common.height"), settings.height, (value) => settings.setHeight(value))}
352
+ </div>
353
+ </pc-popover>`
354
+ };
280
355
  }
281
356
  /** Render the mask settings */
282
357
  renderMask(app) {
283
- return html`<div
284
- role="toolbar"
285
- aria-label=${this.t("paintCanvas.toolSettings.maskSettings")}
286
- >
287
- <button
288
- type="button"
289
- class="pill select-all"
290
- @click=${() => app.selection.selectAll()}
291
- >
292
- ${this.t("paintCanvas.toolSettings.selectAll")}
293
- </button>
294
- <button
295
- type="button"
296
- class="pill invert"
297
- ?disabled=${!app.selection.active}
298
- @click=${() => app.selection.invert()}
299
- >
300
- ${this.t("paintCanvas.toolSettings.invertSelection")}
301
- </button>
302
- <button
303
- type="button"
304
- class="pill deselect"
305
- ?disabled=${!app.selection.active}
306
- @click=${() => app.selection.clear()}
307
- >
308
- ${this.t("paintCanvas.toolSettings.deselect")}
309
- </button>
310
- ${this.renderFocusPen(app)}
311
- </div>`;
358
+ return {
359
+ label: this.t("paintCanvas.toolSettings.maskSettings"),
360
+ start: html`<button
361
+ type="button"
362
+ class="pill select-all"
363
+ @click=${() => app.selection.selectAll()}
364
+ >
365
+ ${this.t("paintCanvas.toolSettings.selectAll")}
366
+ </button>
367
+ <button
368
+ type="button"
369
+ class="pill invert"
370
+ ?disabled=${!app.selection.active}
371
+ @click=${() => app.selection.invert()}
372
+ >
373
+ ${this.t("paintCanvas.toolSettings.invertSelection")}
374
+ </button>
375
+ <button
376
+ type="button"
377
+ class="pill deselect"
378
+ ?disabled=${!app.selection.active}
379
+ @click=${() => app.selection.clear()}
380
+ >
381
+ ${this.t("paintCanvas.toolSettings.deselect")}
382
+ </button>`
383
+ };
312
384
  }
313
385
  /**
314
386
  * Resize the move tool's box to a width or height (in the current unit), keeping flips, and the other side in
@@ -318,7 +390,6 @@ var PcToolSettings = class extends PcElement {
318
390
  const { box, source } = app.transform;
319
391
  if (!box || !source) return;
320
392
  const pixels = this.percent ? source[side] * value / 100 : value;
321
- if (pixels <= 0) return;
322
393
  const other = side === "width" ? "height" : "width";
323
394
  const ratio = Math.abs(box[other] / box[side]);
324
395
  app.transform.setBox({
@@ -327,118 +398,101 @@ var PcToolSettings = class extends PcElement {
327
398
  ...app.constrainProportions ? { [other]: Math.sign(box[other]) * pixels * ratio } : {}
328
399
  });
329
400
  }
330
- /** A box side in the current unit, for an input */
401
+ /** A box side in the current unit (pixels or percent) */
331
402
  sideValue(box, source, side) {
332
403
  const pixels = Math.abs(box[side]);
333
- return String(Math.round(this.percent ? pixels / source[side] * 100 : pixels));
404
+ return Math.round(this.percent ? pixels / source[side] * 100 : pixels);
334
405
  }
335
406
  /** Render the move, scale and rotate settings */
336
407
  renderMove(app) {
337
408
  const { box, source } = app.transform;
338
409
  const disabled = !box || !source;
339
- const side = (name, label) => html`<label>
340
- ${label}
341
- <input
342
- type="number"
343
- class=${name}
344
- min="1"
345
- ?disabled=${disabled}
346
- .value=${box && source ? this.sideValue(box, source, name) : ""}
347
- @change=${(e) => {
348
- const value = numberValue(e);
349
- if (value !== void 0) this.resizeBox(app, name, value);
350
- }}
351
- />
352
- </label>`;
410
+ const side = (name, label) => this.renderNumberField(name, label, box && source ? this.sideValue(box, source, name) : 0, (value) => this.resizeBox(app, name, value), {
411
+ max: MAX_MOVE_SIZE,
412
+ unit: this.percent ? "%" : "",
413
+ text: disabled ? NO_VALUE : "",
414
+ disabled
415
+ });
353
416
  const rotate = (by) => this.changeBox(app, (current) => ({ rotation: by(current.rotation) % 360 }));
354
- return html`<div
355
- role="toolbar"
356
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.move") })}
357
- >
358
- ${side("width", this.t("paintCanvas.common.width"))}
359
- <label
360
- class="icon-toggle"
361
- title=${this.t("paintCanvas.toolSettings.constrainProportions")}
362
- >
363
- <input
364
- type="checkbox"
365
- class="constrain"
366
- .checked=${live(app.constrainProportions)}
367
- @change=${(e) => app.setConstrainProportions(e.target.checked)}
368
- />
369
- ${iconLabel(this.iconUrl(app.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
370
- </label>
371
- ${side("height", this.t("paintCanvas.common.height"))}
372
- <label>
373
- <input
374
- type="checkbox"
375
- class="percent"
376
- .checked=${this.percent}
377
- @change=${(e) => this.percent = e.target.checked}
378
- />
379
- ${this.t("paintCanvas.toolSettings.percent")}
380
- </label>
381
- <button
382
- type="button"
383
- class="icon-button flip-horizontal"
384
- title=${this.t("paintCanvas.toolSettings.flipHorizontal")}
385
- ?disabled=${disabled}
386
- @click=${() => this.changeBox(app, (current) => ({ width: -current.width }))}
387
- >
388
- ${iconLabel(this.iconUrl("flipHorizontal"), this.t("paintCanvas.toolSettings.flipHorizontal"))}
389
- </button>
390
- <button
391
- type="button"
392
- class="icon-button flip-vertical"
393
- title=${this.t("paintCanvas.toolSettings.flipVertical")}
394
- ?disabled=${disabled}
395
- @click=${() => this.changeBox(app, (current) => ({ height: -current.height }))}
396
- >
397
- ${iconLabel(this.iconUrl("flipVertical"), this.t("paintCanvas.toolSettings.flipVertical"))}
398
- </button>
399
- <label>
400
- ${this.t("paintCanvas.toolSettings.angle")}
401
- <input
402
- type="number"
403
- class="angle"
404
- step="0.01"
417
+ return {
418
+ label: this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.move") }),
419
+ start: html`${side("width", this.t("paintCanvas.common.width"))}
420
+ <label
421
+ class="icon-toggle"
422
+ title=${this.t("paintCanvas.toolSettings.constrainProportions")}
423
+ >
424
+ <input
425
+ type="checkbox"
426
+ class="constrain"
427
+ .checked=${live(app.constrainProportions)}
428
+ @change=${(e) => app.setConstrainProportions(e.target.checked)}
429
+ />
430
+ ${iconLabel(this.iconUrl(app.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
431
+ </label>
432
+ ${side("height", this.t("paintCanvas.common.height"))}
433
+ <label>
434
+ <input
435
+ type="checkbox"
436
+ class="percent"
437
+ .checked=${this.percent}
438
+ @change=${(e) => this.percent = e.target.checked}
439
+ />
440
+ ${this.t("paintCanvas.toolSettings.percent")}
441
+ </label>
442
+ <button
443
+ type="button"
444
+ class="icon-button flip-horizontal"
445
+ title=${this.t("paintCanvas.toolSettings.flipHorizontal")}
405
446
  ?disabled=${disabled}
406
- .value=${box ? String(roundAngle(box.rotation)) : ""}
407
- @change=${(e) => {
408
- const value = numberValue(e);
409
- if (value !== void 0) rotate(() => roundAngle(value));
410
- }}
411
- />
412
- <span class="unit">°</span>
413
- </label>
414
- <button
415
- type="button"
416
- class="icon-button rotate-left"
417
- title=${this.t("paintCanvas.toolSettings.rotateLeft")}
418
- ?disabled=${disabled}
419
- @click=${() => rotate((rotation) => rotation - 90)}
420
- >
421
- ${iconLabel(this.iconUrl("rotate90Left"), this.t("paintCanvas.toolSettings.rotateLeft"))}
422
- </button>
423
- <button
424
- type="button"
425
- class="icon-button rotate-right"
426
- title=${this.t("paintCanvas.toolSettings.rotateRight")}
427
- ?disabled=${disabled}
428
- @click=${() => rotate((rotation) => rotation + 90)}
429
- >
430
- ${iconLabel(this.iconUrl("rotate90Right"), this.t("paintCanvas.toolSettings.rotateRight"))}
431
- </button>
432
- <button
433
- type="button"
434
- class="pill reset"
435
- ?disabled=${disabled}
436
- @click=${() => app.cancelTransform()}
437
- >
438
- ${this.t("paintCanvas.toolSettings.reset")}
439
- </button>
440
- ${this.renderFocusPen(app)}
441
- </div>`;
447
+ @click=${() => this.changeBox(app, (current) => ({ width: -current.width }))}
448
+ >
449
+ ${iconLabel(this.iconUrl("flipHorizontal"), this.t("paintCanvas.toolSettings.flipHorizontal"))}
450
+ </button>
451
+ <button
452
+ type="button"
453
+ class="icon-button flip-vertical"
454
+ title=${this.t("paintCanvas.toolSettings.flipVertical")}
455
+ ?disabled=${disabled}
456
+ @click=${() => this.changeBox(app, (current) => ({ height: -current.height }))}
457
+ >
458
+ ${iconLabel(this.iconUrl("flipVertical"), this.t("paintCanvas.toolSettings.flipVertical"))}
459
+ </button>
460
+ ${this.renderNumberField("angle", this.t("paintCanvas.toolSettings.angle"), box ? roundAngle(box.rotation) : 0, (value) => rotate(() => roundAngle(value)), {
461
+ min: -360,
462
+ max: 360,
463
+ step: .01,
464
+ dragStep: 1,
465
+ unit: "°",
466
+ text: disabled ? NO_VALUE : "",
467
+ disabled
468
+ })}
469
+ <button
470
+ type="button"
471
+ class="icon-button rotate-left"
472
+ title=${this.t("paintCanvas.toolSettings.rotateLeft")}
473
+ ?disabled=${disabled}
474
+ @click=${() => rotate((rotation) => rotation - 90)}
475
+ >
476
+ ${iconLabel(this.iconUrl("rotate90Left"), this.t("paintCanvas.toolSettings.rotateLeft"))}
477
+ </button>
478
+ <button
479
+ type="button"
480
+ class="icon-button rotate-right"
481
+ title=${this.t("paintCanvas.toolSettings.rotateRight")}
482
+ ?disabled=${disabled}
483
+ @click=${() => rotate((rotation) => rotation + 90)}
484
+ >
485
+ ${iconLabel(this.iconUrl("rotate90Right"), this.t("paintCanvas.toolSettings.rotateRight"))}
486
+ </button>
487
+ <button
488
+ type="button"
489
+ class="pill reset"
490
+ ?disabled=${disabled}
491
+ @click=${() => app.cancelTransform()}
492
+ >
493
+ ${this.t("paintCanvas.toolSettings.reset")}
494
+ </button>`
495
+ };
442
496
  }
443
497
  /** Render the zoom settings */
444
498
  renderZoom(app) {
@@ -447,52 +501,49 @@ var PcToolSettings = class extends PcElement {
447
501
  x: view.width / 2,
448
502
  y: view.height / 2
449
503
  };
450
- return html`<div
451
- role="toolbar"
452
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.zoom") })}
453
- >
454
- <button
455
- type="button"
456
- class="zoom-in"
457
- title=${this.t("paintCanvas.toolSettings.zoomIn")}
458
- aria-label=${this.t("paintCanvas.toolSettings.zoomIn")}
459
- ?disabled=${view.zoom >= MAX_ZOOM}
460
- @click=${() => view.zoomIn(centre)}
461
- >
462
- <img src=${this.iconUrl("zoomIn")} alt="" />
463
- </button>
464
- <button
465
- type="button"
466
- class="zoom-out"
467
- title=${this.t("paintCanvas.toolSettings.zoomOut")}
468
- aria-label=${this.t("paintCanvas.toolSettings.zoomOut")}
469
- ?disabled=${view.zoom <= MIN_ZOOM}
470
- @click=${() => view.zoomOut(centre)}
471
- >
472
- <img src=${this.iconUrl("zoomOut")} alt="" />
473
- </button>
474
- <label>
475
- ${this.t("paintCanvas.tool.zoom")}
476
- <input
477
- type="range"
478
- class="zoom"
479
- min="0"
480
- max=${ZOOM_SLIDER_STEPS}
481
- step="1"
482
- .value=${String(zoomToSlider(view.zoom))}
483
- @input=${(e) => view.zoomAt(centre, sliderToZoom(e.target.valueAsNumber))}
484
- />
485
- <output>${zoomPercent(view.zoom)}</output>
486
- </label>
487
- <button
488
- type="button"
489
- class="pill reset"
490
- @click=${() => view.zoomAt(centre, 1)}
491
- >
492
- ${this.t("paintCanvas.toolSettings.reset")}
493
- </button>
494
- ${this.renderFocusPen(app)}
495
- </div>`;
504
+ return {
505
+ label: this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.zoom") }),
506
+ start: html`<button
507
+ type="button"
508
+ class="zoom-in"
509
+ title=${this.t("paintCanvas.toolSettings.zoomIn")}
510
+ aria-label=${this.t("paintCanvas.toolSettings.zoomIn")}
511
+ ?disabled=${view.zoom >= MAX_ZOOM}
512
+ @click=${() => view.zoomIn(centre)}
513
+ >
514
+ <img src=${this.iconUrl("zoomIn")} alt="" />
515
+ </button>
516
+ <button
517
+ type="button"
518
+ class="zoom-out"
519
+ title=${this.t("paintCanvas.toolSettings.zoomOut")}
520
+ aria-label=${this.t("paintCanvas.toolSettings.zoomOut")}
521
+ ?disabled=${view.zoom <= MIN_ZOOM}
522
+ @click=${() => view.zoomOut(centre)}
523
+ >
524
+ <img src=${this.iconUrl("zoomOut")} alt="" />
525
+ </button>
526
+ <label>
527
+ ${this.t("paintCanvas.tool.zoom")}
528
+ <input
529
+ type="range"
530
+ class="zoom"
531
+ min="0"
532
+ max=${ZOOM_SLIDER_STEPS}
533
+ step="1"
534
+ .value=${String(zoomToSlider(view.zoom))}
535
+ @input=${(e) => view.zoomAt(centre, sliderToZoom(e.target.valueAsNumber))}
536
+ />
537
+ <output>${zoomPercent(view.zoom)}</output>
538
+ </label>
539
+ <button
540
+ type="button"
541
+ class="pill reset"
542
+ @click=${() => view.zoomAt(centre, 1)}
543
+ >
544
+ ${this.t("paintCanvas.toolSettings.reset")}
545
+ </button>`
546
+ };
496
547
  }
497
548
  /** Render the colour sample tool's settings: point or area sampling */
498
549
  renderColourSample(app) {
@@ -505,11 +556,9 @@ var PcToolSettings = class extends PcElement {
505
556
  this.t("paintCanvas.toolSettings.areaSample"),
506
557
  svg`<defs><linearGradient id="area-gradient"><stop offset="0" stop-color="currentColor" stop-opacity="0" /><stop offset="1" stop-color="currentColor" /></linearGradient></defs><rect x="2" y="4" width="16" height="12" fill="url(#area-gradient)" />`
507
558
  ]];
508
- return html`<div
509
- role="toolbar"
510
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.colourSample") })}
511
- >
512
- ${modes.map(([mode, label, icon]) => html`<button
559
+ return {
560
+ label: this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.colourSample") }),
561
+ start: html`${modes.map(([mode, label, icon]) => html`<button
513
562
  type="button"
514
563
  class="sample-${mode}"
515
564
  title=${label}
@@ -528,22 +577,38 @@ var PcToolSettings = class extends PcElement {
528
577
  >
529
578
  ${icon}
530
579
  </svg>
531
- </button>`)}
532
- ${this.renderFocusPen(app)}
533
- </div>`;
580
+ </button>`)}`
581
+ };
534
582
  }
535
- render() {
536
- const app = this.app;
537
- if (!app) return nothing;
583
+ /** The current tool's settings, if it has any */
584
+ toolView(app) {
538
585
  switch (app.tool) {
539
586
  case Tool.Brush: return this.renderBrush(app, app.brush, "brush");
540
587
  case Tool.Eraser: return this.renderBrush(app, app.eraser, "eraser");
541
588
  case Tool.Move: return this.renderMove(app);
542
589
  case Tool.Zoom: return this.renderZoom(app);
543
590
  case Tool.ColourSample: return this.renderColourSample(app);
544
- default: return MASK_TOOLS.includes(app.tool) ? this.renderMask(app) : nothing;
591
+ default: return MASK_TOOLS.includes(app.tool) ? this.renderMask(app) : void 0;
545
592
  }
546
593
  }
594
+ render() {
595
+ const app = this.app;
596
+ const view = app && this.toolView(app);
597
+ if (!view) return nothing;
598
+ return html`<div
599
+ role="toolbar"
600
+ aria-label=${view.label}
601
+ @scroll=${this.placeSizeSettings}
602
+ >
603
+ <div class="start" part="start">
604
+ ${view.start}<slot name="start"></slot>
605
+ </div>
606
+ <div class="end" part="end">
607
+ <slot name="end"></slot>
608
+ </div>
609
+ </div>
610
+ ${view.after ?? nothing}`;
611
+ }
547
612
  };
548
613
  defineElement("pc-tool-settings", PcToolSettings);
549
- export { PcToolSettings, ZOOM_SLIDER_STEPS, hasToolSettings, roundAngle, sliderToZoom, withSource, zoomPercent, zoomToSlider };
614
+ export { MAX_MOVE_SIZE, PcToolSettings, ZOOM_SLIDER_STEPS, hasToolSettings, roundAngle, sliderToZoom, withSource, zoomPercent, zoomToSlider };