@lvl99dotcom/paintcanvas 0.0.0-stage → 0.3.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 (284) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +131 -2
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +5 -0
  5. package/dist/lib/brushes/BrushTipCache/index.d.ts +32 -0
  6. package/dist/lib/brushes/BrushTipCache/index.js +60 -0
  7. package/dist/lib/brushes/StrokeEngine/index.d.ts +171 -0
  8. package/dist/lib/brushes/StrokeEngine/index.js +324 -0
  9. package/dist/lib/brushes/StrokePath/index.d.ts +53 -0
  10. package/dist/lib/brushes/StrokePath/index.js +161 -0
  11. package/dist/lib/brushes/StrokeSampler/index.d.ts +55 -0
  12. package/dist/lib/brushes/StrokeSampler/index.js +75 -0
  13. package/dist/lib/brushes/authors/index.d.ts +55 -0
  14. package/dist/lib/brushes/authors/index.js +60 -0
  15. package/dist/lib/brushes/configRanges/index.d.ts +52 -0
  16. package/dist/lib/brushes/configRanges/index.js +206 -0
  17. package/dist/lib/brushes/index.d.ts +5 -0
  18. package/dist/lib/brushes/index.js +19 -0
  19. package/dist/lib/brushes/params/index.d.ts +108 -0
  20. package/dist/lib/brushes/params/index.js +139 -0
  21. package/dist/lib/brushes/presetFile/index.d.ts +32 -0
  22. package/dist/lib/brushes/presetFile/index.js +55 -0
  23. package/dist/lib/brushes/presets/drawing/brush-pen.js +3 -0
  24. package/dist/lib/brushes/presets/drawing/felt-pen.js +3 -0
  25. package/dist/lib/brushes/presets/drawing/index.d.ts +13 -0
  26. package/dist/lib/brushes/presets/drawing/index.js +88 -0
  27. package/dist/lib/brushes/presets/drawing/pencil-2b.js +3 -0
  28. package/dist/lib/brushes/presets/drawing/pencil-hb.js +3 -0
  29. package/dist/lib/brushes/presets/painting/grass.js +3 -0
  30. package/dist/lib/brushes/presets/painting/hair.js +3 -0
  31. package/dist/lib/brushes/presets/painting/index.d.ts +18 -0
  32. package/dist/lib/brushes/presets/painting/index.js +118 -0
  33. package/dist/lib/brushes/presets/painting/splatter.js +3 -0
  34. package/dist/lib/brushes/presets/painting/textured-paint.js +3 -0
  35. package/dist/lib/brushes/presets/painting/watery-paint.js +3 -0
  36. package/dist/lib/brushes/presets/round/index.d.ts +9 -0
  37. package/dist/lib/brushes/presets/round/index.js +44 -0
  38. package/dist/lib/brushes/presets/round/round.js +3 -0
  39. package/dist/lib/brushes/presets/round/soft-round.js +3 -0
  40. package/dist/lib/brushes/presets/sized/index.d.ts +20 -0
  41. package/dist/lib/brushes/presets/sized/index.js +30 -0
  42. package/dist/lib/brushes/presets/sketching/index.d.ts +14 -0
  43. package/dist/lib/brushes/presets/sketching/index.js +107 -0
  44. package/dist/lib/brushes/presets/square/index.d.ts +7 -0
  45. package/dist/lib/brushes/presets/square/index.js +22 -0
  46. package/dist/lib/brushes/presets/square/square.js +3 -0
  47. package/dist/lib/brushes/shapeImage/index.d.ts +40 -0
  48. package/dist/lib/brushes/shapeImage/index.js +61 -0
  49. package/dist/lib/brushes/types/index.d.ts +243 -0
  50. package/dist/lib/brushes/types/index.js +20 -0
  51. package/dist/lib/classes/BackgroundWork/index.d.ts +55 -0
  52. package/dist/lib/classes/BackgroundWork/index.js +97 -0
  53. package/dist/lib/classes/BrushSettings/index.d.ts +91 -0
  54. package/dist/lib/classes/BrushSettings/index.js +152 -0
  55. package/dist/lib/classes/CustomPresets/index.d.ts +53 -0
  56. package/dist/lib/classes/CustomPresets/index.js +71 -0
  57. package/dist/lib/classes/ExternalSheets/index.d.ts +64 -0
  58. package/dist/lib/classes/ExternalSheets/index.js +112 -0
  59. package/dist/lib/classes/GestureTracker/index.d.ts +62 -0
  60. package/dist/lib/classes/GestureTracker/index.js +122 -0
  61. package/dist/lib/classes/History/index.d.ts +83 -0
  62. package/dist/lib/classes/History/index.js +126 -0
  63. package/dist/lib/classes/HistoryGuard/index.d.ts +58 -0
  64. package/dist/lib/classes/HistoryGuard/index.js +97 -0
  65. package/dist/lib/classes/InputSettings/index.d.ts +74 -0
  66. package/dist/lib/classes/InputSettings/index.js +101 -0
  67. package/dist/lib/classes/KeyboardShortcuts/index.d.ts +48 -0
  68. package/dist/lib/classes/KeyboardShortcuts/index.js +137 -0
  69. package/dist/lib/classes/Layer/index.d.ts +86 -0
  70. package/dist/lib/classes/Layer/index.js +111 -0
  71. package/dist/lib/classes/LayerAutosave/index.d.ts +180 -0
  72. package/dist/lib/classes/LayerAutosave/index.js +346 -0
  73. package/dist/lib/classes/LayerStack/index.d.ts +177 -0
  74. package/dist/lib/classes/LayerStack/index.js +394 -0
  75. package/dist/lib/classes/PaintCanvas/index.d.ts +537 -0
  76. package/dist/lib/classes/PaintCanvas/index.js +965 -0
  77. package/dist/lib/classes/StrokeLog/index.d.ts +149 -0
  78. package/dist/lib/classes/StrokeLog/index.js +257 -0
  79. package/dist/lib/classes/Viewport/index.d.ts +108 -0
  80. package/dist/lib/classes/Viewport/index.js +194 -0
  81. package/dist/lib/document/index.d.ts +17 -0
  82. package/dist/lib/document/index.js +25 -0
  83. package/dist/lib/document/schema.d.ts +132 -0
  84. package/dist/lib/document/schema.js +110 -0
  85. package/dist/lib/i18n/index.d.ts +131 -0
  86. package/dist/lib/i18n/index.js +277 -0
  87. package/dist/lib/image/index.d.ts +90 -0
  88. package/dist/lib/image/index.js +147 -0
  89. package/dist/lib/messages/PatchSender/index.d.ts +75 -0
  90. package/dist/lib/messages/PatchSender/index.js +125 -0
  91. package/dist/lib/messages/index.d.ts +26 -0
  92. package/dist/lib/messages/index.js +59 -0
  93. package/dist/lib/messages/schema.d.ts +1775 -0
  94. package/dist/lib/messages/schema.js +192 -0
  95. package/dist/lib/presets/index.d.ts +32 -0
  96. package/dist/lib/presets/index.js +35 -0
  97. package/dist/lib/presets/schema.d.ts +753 -0
  98. package/dist/lib/presets/schema.js +181 -0
  99. package/dist/lib/rendering/BrushTip/index.d.ts +63 -0
  100. package/dist/lib/rendering/BrushTip/index.js +112 -0
  101. package/dist/lib/rendering/checkerboard/index.d.ts +22 -0
  102. package/dist/lib/rendering/checkerboard/index.js +31 -0
  103. package/dist/lib/rendering/compositing/index.d.ts +64 -0
  104. package/dist/lib/rendering/compositing/index.js +124 -0
  105. package/dist/lib/rendering/cursor/index.d.ts +14 -0
  106. package/dist/lib/rendering/cursor/index.js +39 -0
  107. package/dist/lib/rendering/cursors/index.d.ts +18 -0
  108. package/dist/lib/rendering/cursors/index.js +23 -0
  109. package/dist/lib/rendering/maskOutline/index.d.ts +22 -0
  110. package/dist/lib/rendering/maskOutline/index.js +78 -0
  111. package/dist/lib/rendering/outline/index.d.ts +36 -0
  112. package/dist/lib/rendering/outline/index.js +120 -0
  113. package/dist/lib/rendering/sample/index.d.ts +55 -0
  114. package/dist/lib/rendering/sample/index.js +83 -0
  115. package/dist/lib/rendering/screenshot/index.d.ts +67 -0
  116. package/dist/lib/rendering/screenshot/index.js +96 -0
  117. package/dist/lib/rendering/transformBox/index.d.ts +10 -0
  118. package/dist/lib/rendering/transformBox/index.js +57 -0
  119. package/dist/lib/selection/Lasso/index.d.ts +70 -0
  120. package/dist/lib/selection/Lasso/index.js +156 -0
  121. package/dist/lib/selection/Selection/index.d.ts +86 -0
  122. package/dist/lib/selection/Selection/index.js +228 -0
  123. package/dist/lib/selection/geometry/index.d.ts +37 -0
  124. package/dist/lib/selection/geometry/index.js +57 -0
  125. package/dist/lib/storage/index.d.ts +63 -0
  126. package/dist/lib/storage/index.js +114 -0
  127. package/dist/lib/transform/Transformer/index.d.ts +124 -0
  128. package/dist/lib/transform/Transformer/index.js +286 -0
  129. package/dist/lib/transform/geometry/index.d.ts +95 -0
  130. package/dist/lib/transform/geometry/index.js +258 -0
  131. package/dist/lib/utils/array/index.d.ts +10 -0
  132. package/dist/lib/utils/array/index.js +21 -0
  133. package/dist/lib/utils/gestures/index.d.ts +35 -0
  134. package/dist/lib/utils/gestures/index.js +81 -0
  135. package/dist/lib/utils/navigation/index.d.ts +51 -0
  136. package/dist/lib/utils/navigation/index.js +59 -0
  137. package/dist/lib/utils/random/index.d.ts +18 -0
  138. package/dist/lib/utils/random/index.js +27 -0
  139. package/dist/lib/utils/shortcuts/index.d.ts +74 -0
  140. package/dist/lib/utils/shortcuts/index.js +189 -0
  141. package/dist/lib/validation/validators.generated.d.ts +27 -0
  142. package/dist/lib/validation/validators.generated.js +19826 -0
  143. package/dist/locales/en-GB.js +181 -0
  144. package/dist/locales/en-US.js +181 -0
  145. package/dist/locales/fr.js +181 -0
  146. package/dist/locales/ja.js +181 -0
  147. package/dist/locales/ko.js +181 -0
  148. package/dist/locales/zh-Hans.js +181 -0
  149. package/dist/locales/zh-Hant.js +181 -0
  150. package/dist/package.js +3 -0
  151. package/dist/testing/app.d.ts +7 -0
  152. package/dist/testing/app.js +16 -0
  153. package/dist/testing/elements.d.ts +42 -0
  154. package/dist/testing/elements.js +58 -0
  155. package/dist/testing/fakeCanvas.d.ts +21 -0
  156. package/dist/testing/fakeCanvas.js +26 -0
  157. package/dist/testing/layerStore.d.ts +26 -0
  158. package/dist/testing/layerStore.js +43 -0
  159. package/dist/testing/locks.d.ts +15 -0
  160. package/dist/testing/locks.js +41 -0
  161. package/dist/testing/recordingCanvas.d.ts +14 -0
  162. package/dist/testing/recordingCanvas.js +28 -0
  163. package/dist/testing/recordingContext.d.ts +10 -0
  164. package/dist/testing/recordingContext.js +19 -0
  165. package/dist/ui/ChangeController/index.d.ts +40 -0
  166. package/dist/ui/ChangeController/index.js +62 -0
  167. package/dist/ui/base/index.d.ts +69 -0
  168. package/dist/ui/base/index.js +94 -0
  169. package/dist/ui/elements/PaintCanvas/index.d.ts +249 -0
  170. package/dist/ui/elements/PaintCanvas/index.js +584 -0
  171. package/dist/ui/elements/PaintCanvas/style.js +3 -0
  172. package/dist/ui/elements/PcBrushConfig/index.d.ts +130 -0
  173. package/dist/ui/elements/PcBrushConfig/index.js +465 -0
  174. package/dist/ui/elements/PcBrushConfig/style.js +3 -0
  175. package/dist/ui/elements/PcBrushMenu/index.d.ts +121 -0
  176. package/dist/ui/elements/PcBrushMenu/index.js +362 -0
  177. package/dist/ui/elements/PcBrushMenu/style.js +3 -0
  178. package/dist/ui/elements/PcBrushTest/index.d.ts +71 -0
  179. package/dist/ui/elements/PcBrushTest/index.js +136 -0
  180. package/dist/ui/elements/PcBrushTest/style.js +3 -0
  181. package/dist/ui/elements/PcButton/index.d.ts +57 -0
  182. package/dist/ui/elements/PcButton/index.js +124 -0
  183. package/dist/ui/elements/PcButtonGroup/index.d.ts +44 -0
  184. package/dist/ui/elements/PcButtonGroup/index.js +93 -0
  185. package/dist/ui/elements/PcCanvas/index.d.ts +356 -0
  186. package/dist/ui/elements/PcCanvas/index.js +1004 -0
  187. package/dist/ui/elements/PcCanvas/style.js +3 -0
  188. package/dist/ui/elements/PcCanvasMap/index.d.ts +96 -0
  189. package/dist/ui/elements/PcCanvasMap/index.js +187 -0
  190. package/dist/ui/elements/PcCanvasMap/style.js +3 -0
  191. package/dist/ui/elements/PcContextMenu/index.d.ts +72 -0
  192. package/dist/ui/elements/PcContextMenu/index.js +148 -0
  193. package/dist/ui/elements/PcDialog/index.d.ts +63 -0
  194. package/dist/ui/elements/PcDialog/index.js +114 -0
  195. package/dist/ui/elements/PcIcon/index.d.ts +36 -0
  196. package/dist/ui/elements/PcIcon/index.js +43 -0
  197. package/dist/ui/elements/PcIconToggle/index.d.ts +46 -0
  198. package/dist/ui/elements/PcIconToggle/index.js +87 -0
  199. package/dist/ui/elements/PcInfoTip/index.d.ts +40 -0
  200. package/dist/ui/elements/PcInfoTip/index.js +95 -0
  201. package/dist/ui/elements/PcInputSettings/index.d.ts +69 -0
  202. package/dist/ui/elements/PcInputSettings/index.js +154 -0
  203. package/dist/ui/elements/PcLayerItem/index.d.ts +221 -0
  204. package/dist/ui/elements/PcLayerItem/index.js +561 -0
  205. package/dist/ui/elements/PcLayerItem/style.js +3 -0
  206. package/dist/ui/elements/PcLayersMenu/index.d.ts +108 -0
  207. package/dist/ui/elements/PcLayersMenu/index.js +208 -0
  208. package/dist/ui/elements/PcLayersMenu/style.js +3 -0
  209. package/dist/ui/elements/PcMatch/index.d.ts +31 -0
  210. package/dist/ui/elements/PcMatch/index.js +58 -0
  211. package/dist/ui/elements/PcPanel/index.d.ts +43 -0
  212. package/dist/ui/elements/PcPanel/index.js +51 -0
  213. package/dist/ui/elements/PcPopover/index.d.ts +48 -0
  214. package/dist/ui/elements/PcPopover/index.js +86 -0
  215. package/dist/ui/elements/PcSlider/index.d.ts +57 -0
  216. package/dist/ui/elements/PcSlider/index.js +88 -0
  217. package/dist/ui/elements/PcStats/index.d.ts +54 -0
  218. package/dist/ui/elements/PcStats/index.js +144 -0
  219. package/dist/ui/elements/PcThemePicker/index.d.ts +56 -0
  220. package/dist/ui/elements/PcThemePicker/index.js +134 -0
  221. package/dist/ui/elements/PcToast/index.d.ts +58 -0
  222. package/dist/ui/elements/PcToast/index.js +110 -0
  223. package/dist/ui/elements/PcToggleCheckbox/index.d.ts +37 -0
  224. package/dist/ui/elements/PcToggleCheckbox/index.js +105 -0
  225. package/dist/ui/elements/PcToolSettings/index.d.ts +128 -0
  226. package/dist/ui/elements/PcToolSettings/index.js +549 -0
  227. package/dist/ui/elements/PcToolSettings/style.js +3 -0
  228. package/dist/ui/elements/PcToolbar/index.d.ts +71 -0
  229. package/dist/ui/elements/PcToolbar/index.js +190 -0
  230. package/dist/ui/elements/PcToolbar/style.js +3 -0
  231. package/dist/ui/icons/images/brush.js +3 -0
  232. package/dist/ui/icons/images/circle-mask.js +3 -0
  233. package/dist/ui/icons/images/clipboard.js +3 -0
  234. package/dist/ui/icons/images/cursor.js +3 -0
  235. package/dist/ui/icons/images/eraser.js +3 -0
  236. package/dist/ui/icons/images/eyedropper.js +3 -0
  237. package/dist/ui/icons/images/flip-horizontal.js +3 -0
  238. package/dist/ui/icons/images/flip-vertical.js +3 -0
  239. package/dist/ui/icons/images/focus-pen.js +3 -0
  240. package/dist/ui/icons/images/freehand-mask.js +3 -0
  241. package/dist/ui/icons/images/hidden.js +3 -0
  242. package/dist/ui/icons/images/layers.js +3 -0
  243. package/dist/ui/icons/images/linked.js +3 -0
  244. package/dist/ui/icons/images/locked.js +3 -0
  245. package/dist/ui/icons/images/moon.js +3 -0
  246. package/dist/ui/icons/images/pan.js +3 -0
  247. package/dist/ui/icons/images/polygon-mask.js +3 -0
  248. package/dist/ui/icons/images/pressure.js +3 -0
  249. package/dist/ui/icons/images/randomise.js +3 -0
  250. package/dist/ui/icons/images/rectangle-mask.js +3 -0
  251. package/dist/ui/icons/images/redo.js +3 -0
  252. package/dist/ui/icons/images/rotate-90-left.js +3 -0
  253. package/dist/ui/icons/images/rotate-90-right.js +3 -0
  254. package/dist/ui/icons/images/smoothing-bezier-cubic.js +3 -0
  255. package/dist/ui/icons/images/smoothing-bezier-quad.js +3 -0
  256. package/dist/ui/icons/images/smoothing-linear.js +3 -0
  257. package/dist/ui/icons/images/star.js +3 -0
  258. package/dist/ui/icons/images/sun.js +3 -0
  259. package/dist/ui/icons/images/tilt.js +3 -0
  260. package/dist/ui/icons/images/undo.js +3 -0
  261. package/dist/ui/icons/images/unlinked.js +3 -0
  262. package/dist/ui/icons/images/unlocked.js +3 -0
  263. package/dist/ui/icons/images/visible.js +3 -0
  264. package/dist/ui/icons/images/zoom-in.js +3 -0
  265. package/dist/ui/icons/images/zoom-out.js +3 -0
  266. package/dist/ui/icons/images/zoom.js +3 -0
  267. package/dist/ui/icons/index.d.ts +42 -0
  268. package/dist/ui/icons/index.js +130 -0
  269. package/dist/ui/index.d.ts +21 -0
  270. package/dist/ui/index.js +22 -0
  271. package/dist/ui/layout/index.d.ts +88 -0
  272. package/dist/ui/layout/index.js +96 -0
  273. package/dist/ui/styles/index.d.ts +13 -0
  274. package/dist/ui/styles/index.js +195 -0
  275. package/dist/ui/tokens/index.d.ts +29 -0
  276. package/dist/ui/tokens/index.js +114 -0
  277. package/locales/en-GB.json +179 -0
  278. package/locales/en-US.json +179 -0
  279. package/locales/fr.json +179 -0
  280. package/locales/ja.json +179 -0
  281. package/locales/ko.json +179 -0
  282. package/locales/zh-Hans.json +179 -0
  283. package/locales/zh-Hant.json +179 -0
  284. package/package.json +97 -3
@@ -0,0 +1,121 @@
1
+ import { PcElement } from "../../base";
2
+ import type PaintCanvas from "../../../lib/classes/PaintCanvas";
3
+ import type BrushSettings from "../../../lib/classes/BrushSettings";
4
+ import type { BrushConfig } from "../../../lib/brushes/types";
5
+ import { type SaveMethod } from "../../../lib/rendering/screenshot";
6
+ /** Cursor over a preset that a click deletes (Alt/Option held) */
7
+ export declare const DELETE_CURSOR: string;
8
+ /** Asks the user to confirm; replaceable for tests */
9
+ export type ConfirmFn = (message: string) => boolean;
10
+ /** Largest shape preview in a preset tile, in CSS pixels */
11
+ export declare const MAX_PREVIEW_SIZE = 30;
12
+ /** Smallest shape preview in a preset tile, in CSS pixels */
13
+ export declare const MIN_PREVIEW_SIZE = 3;
14
+ /**
15
+ * The size of each preset's shape preview, relative to the largest preset of its family (`group`), so a family's sizes
16
+ * are easy to tell apart (the largest fills the tile). Presets without a family fill the tile from 50 pixels.
17
+ *
18
+ * @example
19
+ * previewSizes([{ id: "a", group: "Round", width: 10, height: 10 }, { id: "b", group: "Round", width: 50, height: 50 }]);
20
+ * // Map { "a" => { width: 6, height: 6 }, "b" => { width: 30, height: 30 } }
21
+ */
22
+ export declare const previewSizes: (presets: BrushConfig[]) => Map<string, {
23
+ width: number;
24
+ height: number;
25
+ }>;
26
+ /**
27
+ * Brush or eraser menu: current size, a drag control to change size, a scrollable grid of presets grouped by author
28
+ * ("My presets", "PaintCanvas", then other authors), and "New preset", "Import" and "Export" buttons. Each preset is a
29
+ * tile showing its shape at its size relative to the rest of its family, five to a row. Picking a preset closes the
30
+ * menu; holding Alt/Option shows an "X" over custom presets, and clicking one asks before deleting it.
31
+ *
32
+ * Dragging the size control up or right makes the brush bigger; down or left makes it smaller.
33
+ *
34
+ * Fires `pc-new-preset` (detail: "brush" or "eraser") for "New preset", and `pc-toast` (detail: the message) after
35
+ * importing, exporting or deleting presets, or when that fails.
36
+ *
37
+ * @example
38
+ * html`<pc-brush-menu kind="eraser" .app=${app}></pc-brush-menu>`
39
+ */
40
+ export declare class PcBrushMenu extends PcElement {
41
+ static properties: {
42
+ app: {
43
+ attribute: boolean;
44
+ };
45
+ kind: {
46
+ type: StringConstructor;
47
+ };
48
+ translations: {
49
+ attribute: boolean;
50
+ };
51
+ icons: {
52
+ attribute: boolean;
53
+ };
54
+ theme: {
55
+ type: StringConstructor;
56
+ reflect: boolean;
57
+ };
58
+ };
59
+ static styles: import("lit").CSSResultGroup[];
60
+ app?: PaintCanvas;
61
+ /** Which settings this menu controls */
62
+ kind: "brush" | "eraser";
63
+ confirm: ConfirmFn;
64
+ /** Saves exported presets (replaceable for tests) */
65
+ saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
66
+ /** Last pointer position while dragging the size control */
67
+ private dragPoint;
68
+ private changes;
69
+ private readonly onKey;
70
+ private readonly onBlur;
71
+ constructor();
72
+ connectedCallback(): void;
73
+ disconnectedCallback(): void;
74
+ /** Fire an event that crosses shadow DOM boundaries */
75
+ private emit;
76
+ /**
77
+ * Select a preset, or with Alt/Option held, delete a custom preset after confirming.
78
+ */
79
+ private onPresetClick;
80
+ /**
81
+ * Delete a custom preset after confirming.
82
+ */
83
+ deletePreset(preset: BrushConfig): Promise<void>;
84
+ /**
85
+ * Import the presets in a brush preset file, adding them to the custom presets (replacing any with the same ID).
86
+ */
87
+ importFile(file: File): Promise<void>;
88
+ /**
89
+ * Export the custom presets (not the built-in ones) as a brush preset file.
90
+ */
91
+ exportFile(): Promise<void>;
92
+ /**
93
+ * A preset file error's message in the chosen language (or the fallback's, for any other error).
94
+ */
95
+ private presetFileError;
96
+ /**
97
+ * A preset group's heading in the chosen language ("My presets" and "Unknown" are translated; author names aren't).
98
+ */
99
+ private groupLabel;
100
+ /** The brush or eraser settings this menu controls */
101
+ get settings(): BrushSettings | undefined;
102
+ /**
103
+ * Select a preset and close the menu.
104
+ */
105
+ selectPreset(id: string): void;
106
+ /**
107
+ * Start dragging the size control.
108
+ */
109
+ private onPointerDown;
110
+ /**
111
+ * Resize while dragging: right and up are positive, left and down are negative.
112
+ */
113
+ private onPointerMove;
114
+ /**
115
+ * Stop dragging the size control.
116
+ */
117
+ private onPointerUp;
118
+ /** Render a preset's tile */
119
+ private renderTile;
120
+ render(): import("lit").TemplateResult<1>;
121
+ }
@@ -0,0 +1,362 @@
1
+ import style_default from "./style.js";
2
+ import { NO_TEXT_SELECTION_CSS, PILLS_CSS, WINDOWS_CSS } from "../../styles/index.js";
3
+ import { PcElement, defineElement } from "../../base/index.js";
4
+ import ChangeController from "../../ChangeController/index.js";
5
+ import { groupPresetsByAuthor } from "../../../lib/brushes/authors/index.js";
6
+ import { BRUSH_PRESET_FILE_FORMAT } from "../../../lib/presets/schema.js";
7
+ import { PRESET_FILE_ERRORS, PresetFileError, exportPresetFile, parsePresetFile, presetFileName } from "../../../lib/brushes/presetFile/index.js";
8
+ import { svgCursor } from "../../../lib/rendering/cursors/index.js";
9
+ import { JSON_FILE_TYPE, saveFile } from "../../../lib/rendering/screenshot/index.js";
10
+ import { html, nothing, unsafeCSS } from "lit";
11
+ import { styleMap } from "lit/directives/style-map.js";
12
+ // src/ui/elements/PcBrushMenu/index.ts
13
+ /** Translation keys of the preset file error messages */
14
+ var PRESET_FILE_ERROR_KEYS = {
15
+ [PRESET_FILE_ERRORS.incompatible]: "paintCanvas.presets.fileIncompatible",
16
+ [PRESET_FILE_ERRORS.read]: "paintCanvas.presets.fileReadFailed",
17
+ [PRESET_FILE_ERRORS.export]: "paintCanvas.presets.exportFailed",
18
+ [PRESET_FILE_ERRORS.empty]: "paintCanvas.presets.nothingToExport"
19
+ };
20
+ /** Cursor over a preset that a click deletes (Alt/Option held) */
21
+ var DELETE_CURSOR = svgCursor("M6 6 L18 18 M18 6 L6 18", [12, 12], "not-allowed");
22
+ /** Size the previews of presets without a family are measured against, so a 50px preset fills its tile */
23
+ var UNGROUPED_FULL_SIZE = 50;
24
+ /** Largest shape preview in a preset tile, in CSS pixels */
25
+ var MAX_PREVIEW_SIZE = 30;
26
+ /** Smallest shape preview in a preset tile, in CSS pixels */
27
+ var MIN_PREVIEW_SIZE = 3;
28
+ /**
29
+ * The size of each preset's shape preview, relative to the largest preset of its family (`group`), so a family's sizes
30
+ * are easy to tell apart (the largest fills the tile). Presets without a family fill the tile from 50 pixels.
31
+ *
32
+ * @example
33
+ * previewSizes([{ id: "a", group: "Round", width: 10, height: 10 }, { id: "b", group: "Round", width: 50, height: 50 }]);
34
+ * // Map { "a" => { width: 6, height: 6 }, "b" => { width: 30, height: 30 } }
35
+ */
36
+ var previewSizes = (presets) => {
37
+ const largest = /* @__PURE__ */ new Map();
38
+ for (const preset of presets) {
39
+ const group = preset.group ?? preset.id;
40
+ const size = Math.max(preset.width ?? 1, preset.height ?? 1);
41
+ largest.set(group, Math.max(largest.get(group) ?? (preset.group ? 0 : UNGROUPED_FULL_SIZE), size));
42
+ }
43
+ return new Map(presets.map((preset) => {
44
+ const scale = 30 / largest.get(preset.group ?? preset.id);
45
+ const size = (value = 1) => Math.max(3, Math.round(value * scale));
46
+ return [preset.id, {
47
+ width: size(preset.width),
48
+ height: size(preset.height)
49
+ }];
50
+ }));
51
+ };
52
+ /**
53
+ * Brush or eraser menu: current size, a drag control to change size, a scrollable grid of presets grouped by author
54
+ * ("My presets", "PaintCanvas", then other authors), and "New preset", "Import" and "Export" buttons. Each preset is a
55
+ * tile showing its shape at its size relative to the rest of its family, five to a row. Picking a preset closes the
56
+ * menu; holding Alt/Option shows an "X" over custom presets, and clicking one asks before deleting it.
57
+ *
58
+ * Dragging the size control up or right makes the brush bigger; down or left makes it smaller.
59
+ *
60
+ * Fires `pc-new-preset` (detail: "brush" or "eraser") for "New preset", and `pc-toast` (detail: the message) after
61
+ * importing, exporting or deleting presets, or when that fails.
62
+ *
63
+ * @example
64
+ * html`<pc-brush-menu kind="eraser" .app=${app}></pc-brush-menu>`
65
+ */
66
+ var PcBrushMenu = class extends PcElement {
67
+ static properties = {
68
+ ...PcElement.properties,
69
+ app: { attribute: false },
70
+ kind: { type: String }
71
+ };
72
+ static styles = [
73
+ PcElement.styles,
74
+ unsafeCSS(NO_TEXT_SELECTION_CSS),
75
+ unsafeCSS(style_default),
76
+ unsafeCSS(WINDOWS_CSS),
77
+ unsafeCSS(PILLS_CSS)
78
+ ];
79
+ confirm = (message) => window.confirm(message);
80
+ /** Saves exported presets (replaceable for tests) */
81
+ saveFile = (blob, name) => saveFile(blob, name, void 0, JSON_FILE_TYPE);
82
+ /** Last pointer position while dragging the size control */
83
+ dragPoint = null;
84
+ changes = new ChangeController(this, () => [this.app, this.settings]);
85
+ onKey = (event) => {
86
+ if (event.key === "Alt") this.toggleAttribute("deleting", event.type === "keydown");
87
+ };
88
+ onBlur = () => this.removeAttribute("deleting");
89
+ constructor() {
90
+ super();
91
+ this.kind = "brush";
92
+ }
93
+ connectedCallback() {
94
+ super.connectedCallback();
95
+ this.style.setProperty("--delete-cursor", DELETE_CURSOR);
96
+ window.addEventListener("keydown", this.onKey);
97
+ window.addEventListener("keyup", this.onKey);
98
+ window.addEventListener("blur", this.onBlur);
99
+ }
100
+ disconnectedCallback() {
101
+ super.disconnectedCallback();
102
+ window.removeEventListener("keydown", this.onKey);
103
+ window.removeEventListener("keyup", this.onKey);
104
+ window.removeEventListener("blur", this.onBlur);
105
+ }
106
+ /** Fire an event that crosses shadow DOM boundaries */
107
+ emit(type, detail) {
108
+ this.dispatchEvent(new CustomEvent(type, {
109
+ detail,
110
+ bubbles: true,
111
+ composed: true
112
+ }));
113
+ }
114
+ /**
115
+ * Select a preset, or with Alt/Option held, delete a custom preset after confirming.
116
+ */
117
+ onPresetClick(event, preset) {
118
+ const app = this.app;
119
+ if (event.altKey && !app.isBuiltInPreset(preset.id)) {
120
+ this.deletePreset(preset);
121
+ return;
122
+ }
123
+ this.selectPreset(preset.id);
124
+ }
125
+ /**
126
+ * Delete a custom preset after confirming.
127
+ */
128
+ async deletePreset(preset) {
129
+ const app = this.app;
130
+ if (!app) return;
131
+ const name = preset.name ?? preset.id;
132
+ if (!this.confirm(this.t("paintCanvas.presets.deleteConfirm", { name }))) return;
133
+ try {
134
+ await app.customPresets.remove(preset.id);
135
+ this.emit("pc-toast", {
136
+ message: this.t("paintCanvas.presets.deleted", { name }),
137
+ kind: "success"
138
+ });
139
+ } catch {
140
+ this.emit("pc-toast", {
141
+ message: this.t("paintCanvas.presets.deleteFailed", { name }),
142
+ kind: "error"
143
+ });
144
+ }
145
+ }
146
+ /**
147
+ * Import the presets in a brush preset file, adding them to the custom presets (replacing any with the same ID).
148
+ */
149
+ async importFile(file) {
150
+ const app = this.app;
151
+ if (!app) return;
152
+ let text;
153
+ try {
154
+ text = await file.text();
155
+ } catch {
156
+ this.emit("pc-toast", {
157
+ message: this.presetFileError(void 0, PRESET_FILE_ERRORS.read),
158
+ kind: "error"
159
+ });
160
+ return;
161
+ }
162
+ try {
163
+ const presets = parsePresetFile(text, [app.names.presetFileFormat, BRUSH_PRESET_FILE_FORMAT]);
164
+ await app.customPresets.save(...presets);
165
+ this.emit("pc-toast", {
166
+ message: this.t("paintCanvas.presets.imported", { count: presets.length }),
167
+ kind: "success"
168
+ });
169
+ } catch (error) {
170
+ this.emit("pc-toast", {
171
+ message: this.presetFileError(error, PRESET_FILE_ERRORS.read),
172
+ kind: "error"
173
+ });
174
+ }
175
+ }
176
+ /**
177
+ * Export the custom presets (not the built-in ones) as a brush preset file.
178
+ */
179
+ async exportFile() {
180
+ const app = this.app;
181
+ if (!app) return;
182
+ try {
183
+ const json = exportPresetFile(app.customPresets.presets, app.names.presetFileFormat);
184
+ await this.saveFile(new Blob([json], { type: "application/json" }), presetFileName(void 0, app.names.filePrefix));
185
+ } catch (error) {
186
+ this.emit("pc-toast", {
187
+ message: this.presetFileError(error, PRESET_FILE_ERRORS.export),
188
+ kind: "error"
189
+ });
190
+ }
191
+ }
192
+ /**
193
+ * A preset file error's message in the chosen language (or the fallback's, for any other error).
194
+ */
195
+ presetFileError(error, fallback) {
196
+ const message = error instanceof PresetFileError ? error.message : fallback;
197
+ const key = PRESET_FILE_ERROR_KEYS[message];
198
+ return key ? this.t(key) : message;
199
+ }
200
+ /**
201
+ * A preset group's heading in the chosen language ("My presets" and "Unknown" are translated; author names aren't).
202
+ */
203
+ groupLabel(label) {
204
+ if (label === "My presets") return this.t("paintCanvas.presets.myPresets");
205
+ if (label === "Unknown") return this.t("paintCanvas.presets.unknownAuthor");
206
+ return label;
207
+ }
208
+ /** The brush or eraser settings this menu controls */
209
+ get settings() {
210
+ return this.kind === "eraser" ? this.app?.eraser : this.app?.brush;
211
+ }
212
+ /**
213
+ * Select a preset and close the menu.
214
+ */
215
+ selectPreset(id) {
216
+ this.settings?.select(id);
217
+ this.app?.closePanel();
218
+ }
219
+ /**
220
+ * Start dragging the size control.
221
+ */
222
+ onPointerDown(event) {
223
+ this.dragPoint = {
224
+ x: event.clientX,
225
+ y: event.clientY
226
+ };
227
+ event.currentTarget.setPointerCapture(event.pointerId);
228
+ }
229
+ /**
230
+ * Resize while dragging: right and up are positive, left and down are negative.
231
+ */
232
+ onPointerMove(event) {
233
+ if (!this.dragPoint || !this.settings) return;
234
+ const delta = event.clientX - this.dragPoint.x + (this.dragPoint.y - event.clientY);
235
+ this.dragPoint = {
236
+ x: event.clientX,
237
+ y: event.clientY
238
+ };
239
+ if (delta !== 0) this.settings.resize(delta);
240
+ }
241
+ /**
242
+ * Stop dragging the size control.
243
+ */
244
+ onPointerUp() {
245
+ this.dragPoint = null;
246
+ }
247
+ /** Render a preset's tile */
248
+ renderTile(settings, preset, sizes) {
249
+ const size = sizes.get(preset.id);
250
+ const custom = !this.app?.isBuiltInPreset(preset.id);
251
+ return html`<li>
252
+ <button
253
+ type="button"
254
+ class=${custom ? "custom" : nothing}
255
+ data-preset=${preset.id}
256
+ title=${preset.name ?? preset.id}
257
+ aria-label=${preset.name ?? preset.id}
258
+ aria-pressed=${settings.presetId === preset.id}
259
+ @click=${(e) => this.onPresetClick(e, preset)}
260
+ >
261
+ <span
262
+ class=${preset.image ? "shape image" : "shape"}
263
+ style=${styleMap({
264
+ width: `${size.width}px`,
265
+ height: `${size.height}px`,
266
+ maskImage: typeof preset.image === "string" ? `url("${preset.image}")` : void 0,
267
+ transform: preset.rotation ? `rotate(${preset.rotation}deg)` : void 0
268
+ })}
269
+ ></span>
270
+ </button>
271
+ </li>`;
272
+ }
273
+ render() {
274
+ const settings = this.settings;
275
+ if (!settings) return html``;
276
+ const title = this.t(this.kind === "eraser" ? "paintCanvas.tool.eraser" : "paintCanvas.tool.brush");
277
+ const presets = Object.values(settings.presets);
278
+ const sizes = previewSizes(presets);
279
+ const app = this.app;
280
+ const groups = groupPresetsByAuthor(presets, (id) => app.isBuiltInPreset(id), app.authorHandle, {
281
+ builtIn: app.names.app,
282
+ unknown: app.names.unknownAuthor
283
+ });
284
+ const preset = settings.preset;
285
+ return html`
286
+ <h2 class="visually-hidden">${title}</h2>
287
+ <!-- The current preset: its shape, name and size (drag sideways to resize) -->
288
+ <div class="current">
289
+ <span
290
+ class=${preset.image ? "current-shape image" : "current-shape"}
291
+ style=${styleMap({ maskImage: typeof preset.image === "string" ? `url("${preset.image}")` : void 0 })}
292
+ ></span>
293
+ <span class="name">${preset.name ?? preset.id}</span>
294
+ <div
295
+ class="size"
296
+ role="slider"
297
+ aria-label=${this.t("paintCanvas.brushMenu.size", { tool: title })}
298
+ aria-valuenow=${settings.width}
299
+ title=${this.t("paintCanvas.brushMenu.dragToResize")}
300
+ @pointerdown=${this.onPointerDown}
301
+ @pointermove=${this.onPointerMove}
302
+ @pointerup=${this.onPointerUp}
303
+ @pointercancel=${this.onPointerUp}
304
+ >
305
+ ${settings.width === settings.height ? settings.width : `${settings.width} × ${settings.height}`}
306
+ </div>
307
+ </div>
308
+ <div
309
+ class="presets"
310
+ aria-label=${this.t("paintCanvas.brushMenu.presets", { tool: title })}
311
+ >
312
+ ${groups.map((group) => html`
313
+ <h3>${this.groupLabel(group.label)}</h3>
314
+ <ul aria-label=${this.groupLabel(group.label)}>
315
+ ${group.presets.map((preset) => this.renderTile(settings, preset, sizes))}
316
+ </ul>
317
+ `)}
318
+ </div>
319
+ <div class="actions">
320
+ <button
321
+ type="button"
322
+ class="pill new-preset"
323
+ @click=${() => this.emit("pc-new-preset", this.kind)}
324
+ >
325
+ ${this.t("paintCanvas.brushMenu.create")}
326
+ </button>
327
+ <button
328
+ type="button"
329
+ class="pill import"
330
+ @click=${() => this.renderRoot.querySelector(".import-file")?.click()}
331
+ >
332
+ ${this.t("paintCanvas.brushMenu.import")}
333
+ </button>
334
+ <!-- Chosen through "Import" -->
335
+ <label class="visually-hidden">
336
+ ${this.t("paintCanvas.brushMenu.importFile")}
337
+ <input
338
+ type="file"
339
+ class="import-file"
340
+ tabindex="-1"
341
+ accept="application/json,.json"
342
+ @change=${(e) => {
343
+ const input = e.target;
344
+ const file = input.files?.[0];
345
+ input.value = "";
346
+ if (file) this.importFile(file);
347
+ }}
348
+ />
349
+ </label>
350
+ <button
351
+ type="button"
352
+ class="pill export"
353
+ @click=${() => void this.exportFile()}
354
+ >
355
+ ${this.t("paintCanvas.brushMenu.export")}
356
+ </button>
357
+ </div>
358
+ `;
359
+ }
360
+ };
361
+ defineElement("pc-brush-menu", PcBrushMenu);
362
+ export { DELETE_CURSOR, MAX_PREVIEW_SIZE, MIN_PREVIEW_SIZE, PcBrushMenu, previewSizes };
@@ -0,0 +1,3 @@
1
+ // src/ui/elements/PcBrushMenu/style.css?inline
2
+ var style_default = ":host {\n display: flex;\n flex-direction: column;\n gap: 8px;\n box-sizing: border-box;\n width: 240px;\n max-height: 100%;\n padding: 8px;\n /* Light grey, like the bars */\n background: var(--pc-bar);\n box-shadow: var(--pc-shadow);\n}\n/* The current preset on one row: its shape, name and size */\n.current {\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 44px;\n padding: 4px 8px;\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n}\n.current-shape {\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: var(--pc-text);\n}\n.current-shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n.name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.size {\n flex: none;\n align-self: stretch;\n display: flex;\n align-items: center;\n padding: 0 0 0 8px;\n border-left: 1px solid var(--pc-border);\n cursor: ew-resize;\n touch-action: none;\n}\n/* The presets scroll, at most 400px tall, or less so the whole menu fits under the settings bar in a shorter window */\n.presets {\n flex: 1 1 auto;\n min-height: 0;\n max-height: min(400px, calc(100vh - 180px));\n overflow-y: auto;\n}\nh3 {\n margin: 4px 0;\n font-size: 14px;\n}\nul {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n align-content: start;\n margin: 0;\n padding: 0;\n list-style: none;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n:host([deleting]) li button.custom {\n cursor: var(--delete-cursor);\n}\n/* Create, Import and Export side by side */\n.actions {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 6px;\n}\n.actions button.pill {\n padding: 4px 8px;\n}\n.visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\nli button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n aspect-ratio: 1;\n padding: 0;\n border: none;\n border-right: 1px solid var(--pc-border);\n border-bottom: 1px solid var(--pc-border);\n background: var(--pc-bar);\n cursor: pointer;\n}\nli button:hover {\n background: var(--pc-surface-hover);\n}\nli button[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.shape {\n background: var(--pc-text);\n border-radius: 50%;\n}\n.shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: 100% 100%;\n}\n";
3
+ export { style_default as default };
@@ -0,0 +1,71 @@
1
+ import { PcElement } from "../../base";
2
+ import type { BrushConfig } from "../../../lib/brushes/types";
3
+ import type BrushTipCache from "../../../lib/brushes/BrushTipCache";
4
+ import { type CanvasFactory } from "../../../lib/classes/Layer";
5
+ /** Width of the testing area in pixels (double its displayed width, for a sharper picture) */
6
+ export declare const TEST_AREA_WIDTH = 800;
7
+ /** Height of the testing area in pixels (double its displayed height) */
8
+ export declare const TEST_AREA_HEIGHT = 320;
9
+ /** The colour test strokes are drawn in, whatever the current colour */
10
+ export declare const TEST_COLOUR = "#000000";
11
+ /**
12
+ * A small area to try brush settings before saving them: strokes are drawn in black, at the settings' own size, on a
13
+ * white background at double resolution (800 × 320 pixels shown in the window's width). "Clear" (a red pill) empties it.
14
+ *
15
+ * @example
16
+ * html`<pc-brush-test .config=${config} .tips=${app.tips}></pc-brush-test>`
17
+ */
18
+ export declare class PcBrushTest extends PcElement {
19
+ static properties: {
20
+ config: {
21
+ attribute: boolean;
22
+ };
23
+ tips: {
24
+ attribute: boolean;
25
+ };
26
+ translations: {
27
+ attribute: boolean;
28
+ };
29
+ icons: {
30
+ attribute: boolean;
31
+ };
32
+ theme: {
33
+ type: StringConstructor;
34
+ reflect: boolean;
35
+ };
36
+ };
37
+ static styles: import("lit").CSSResultGroup[];
38
+ /** The settings to try */
39
+ config?: BrushConfig;
40
+ /** Brush shapes */
41
+ tips?: BrushTipCache;
42
+ /** Creates the layer and stroke canvases (replaceable for tests) */
43
+ createCanvas: CanvasFactory;
44
+ private layer?;
45
+ private engine?;
46
+ /** The pointer drawing, if any */
47
+ private pointerId?;
48
+ private readonly onEngineChange;
49
+ disconnectedCallback(): void;
50
+ /** The visible canvas */
51
+ private get canvas();
52
+ /** The layer strokes are drawn on, and the engine drawing them (created on first use) */
53
+ private get drawing();
54
+ /** A pointer's position in the area's pixels, with pen pressure and tilt */
55
+ private point;
56
+ /**
57
+ * Start a test stroke.
58
+ */
59
+ private onPointerDown;
60
+ /** Continue the test stroke */
61
+ private onPointerMove;
62
+ /** Finish the test stroke */
63
+ private onPointerUp;
64
+ /**
65
+ * Empty the testing area.
66
+ */
67
+ clear(): void;
68
+ /** Show the layer (with any stroke in progress) on the visible canvas */
69
+ private paint;
70
+ render(): import("lit").TemplateResult<1>;
71
+ }