@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
@@ -2,6 +2,8 @@ import { PcElement } from "../../base";
2
2
  import { BlendMode, type default as Layer } from "../../../lib/classes/Layer";
3
3
  import type LayerStack from "../../../lib/classes/LayerStack";
4
4
  import "../PcDialog";
5
+ import "../PcContextMenu";
6
+ import { MENU_MARGIN, menuTop } from "../PcContextMenu";
5
7
  import { type DialogChoice } from "../PcDialog";
6
8
  import type BackgroundWork from "../../../lib/classes/BackgroundWork";
7
9
  /** Blend mode options shown in the layer's select input */
@@ -56,17 +58,8 @@ export interface LayerDragDetail {
56
58
  x: number;
57
59
  y: number;
58
60
  }
59
- /** Space kept between the layer actions menu and the window's edges */
60
- export declare const MENU_MARGIN = 8;
61
- /**
62
- * Where the layer actions menu's top goes: level with the handle, moved up as far as needed to stay in the window
63
- * (but never above its top margin).
64
- *
65
- * @example
66
- * menuTop(700, 300, 800); // 492 (moved up to fit)
67
- * menuTop(100, 300, 800); // 100
68
- */
69
- export declare const menuTop: (anchorTop: number, menuHeight: number, viewportHeight: number, margin?: number) => number;
61
+ /** Where the layer actions menu goes and how close to the window's edges (see `pc-context-menu`) */
62
+ export { MENU_MARGIN, menuTop };
70
63
  /**
71
64
  * One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock (padlock) and visibility (eye) icons.
72
65
  * Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
@@ -141,28 +134,22 @@ export declare class PcLayerItem extends PcElement {
141
134
  runAction(action: LayerAction): void;
142
135
  /**
143
136
  * Whether a context menu action can be done to the layer now: not merging down the bottom layer (or with a hidden
144
- * layer), deleting the only layer, or moving a layer further up or down than it can go.
137
+ * layer), deleting the only layer, moving a layer further up or down than it can go, or anything to a layer no longer
138
+ * in the stack (e.g. just deleted).
145
139
  */
146
140
  canRun(action: LayerAction): boolean;
147
141
  /**
148
142
  * Do an action the user confirmed.
149
143
  */
150
144
  confirmed(action: ConfirmedAction): void;
151
- private readonly onOutsidePointer;
152
- private readonly onKeyDown;
153
- /**
154
- * Open the context menu on right-click, or close it if open.
155
- */
156
- private onContextMenu;
157
145
  /**
158
- * Open the context menu on a click or tap on the handle (or Enter or Space), or close it if open; a click that ends a
159
- * drag does nothing.
146
+ * Follow the context menu opening or closing, telling other rows when it opens (so they close theirs).
160
147
  */
161
- private onHandleClick;
148
+ private onMenuToggle;
162
149
  /**
163
- * Open the context menu (closing other rows' menus), or close it if open.
150
+ * A click on the handle opens the menu, unless it ends a drag (then it's stopped before reaching the menu's button).
164
151
  */
165
- private toggleMenu;
152
+ private readonly onHandleClick;
166
153
  disconnectedCallback(): void;
167
154
  /** Tell the layers menu about a drag of this row's layer */
168
155
  private emitDrag;
@@ -188,16 +175,6 @@ export declare class PcLayerItem extends PcElement {
188
175
  private readonly onDragKeyDown;
189
176
  /** Stop following the pointer pressed on the handle */
190
177
  private endPress;
191
- /**
192
- * Start or stop listening for clicks outside and Escape while the menu is open.
193
- */
194
- private listenOutside;
195
- private readonly onScroll;
196
- /**
197
- * Place the open layer actions menu beside the reorder handle, floating over the layers panel (so the panel's edges
198
- * don't cut it off) and kept within the window.
199
- */
200
- private placeMenu;
201
178
  /**
202
179
  * Ask for a mask following the layer's alpha (Shift adds, Alt subtracts; otherwise it replaces the mask).
203
180
  */
@@ -210,12 +187,14 @@ export declare class PcLayerItem extends PcElement {
210
187
  * Rename a layer when the name input changes. Empty names are reverted.
211
188
  */
212
189
  private onRename;
213
- updated(changed: Map<PropertyKey, unknown>): void;
190
+ updated(): void;
214
191
  /**
215
192
  * Draw the layer's image, scaled to fit, on a checkerboard that shows transparency.
216
193
  */
217
194
  drawThumbnail(): void;
218
195
  /** The dialog asking the user to confirm an action, while asking */
219
196
  private renderConfirmation;
197
+ /** The layer's actions, for its context menu (those it can't do now are disabled) */
198
+ private menuItems;
220
199
  render(): import("lit").TemplateResult<1>;
221
200
  }
@@ -5,6 +5,7 @@ import ChangeController from "../../ChangeController/index.js";
5
5
  import { BlendMode } from "../../../lib/classes/Layer/index.js";
6
6
  import { drawCheckerboard, fitSize } from "../../../lib/rendering/checkerboard/index.js";
7
7
  import { CANCEL_CHOICE } from "../PcDialog/index.js";
8
+ import { MENU_MARGIN, menuTop } from "../PcContextMenu/index.js";
8
9
  import { html, nothing, unsafeCSS } from "lit";
9
10
  // src/ui/elements/PcLayerItem/index.ts
10
11
  /** Blend mode options shown in the layer's select input */
@@ -127,17 +128,6 @@ var LAYER_ALPHA_MASK_EVENT = "pc-layer-alpha-mask";
127
128
  var DRAG_THRESHOLD = 4;
128
129
  /** Event a row dispatches while its layer is dragged by its reorder handle (detail: {@link LayerDragDetail}) */
129
130
  var LAYER_DRAG_EVENT = "pc-layer-drag";
130
- /** Space kept between the layer actions menu and the window's edges */
131
- var MENU_MARGIN = 8;
132
- /**
133
- * Where the layer actions menu's top goes: level with the handle, moved up as far as needed to stay in the window
134
- * (but never above its top margin).
135
- *
136
- * @example
137
- * menuTop(700, 300, 800); // 492 (moved up to fit)
138
- * menuTop(100, 300, 800); // 100
139
- */
140
- var menuTop = (anchorTop, menuHeight, viewportHeight, margin = 8) => Math.max(margin, Math.min(anchorTop, viewportHeight - margin - menuHeight));
141
131
  /**
142
132
  * One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock (padlock) and visibility (eye) icons.
143
133
  * Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
@@ -220,11 +210,12 @@ var PcLayerItem = class extends PcElement {
220
210
  }
221
211
  /**
222
212
  * Whether a context menu action can be done to the layer now: not merging down the bottom layer (or with a hidden
223
- * layer), deleting the only layer, or moving a layer further up or down than it can go.
213
+ * layer), deleting the only layer, moving a layer further up or down than it can go, or anything to a layer no longer
214
+ * in the stack (e.g. just deleted).
224
215
  */
225
216
  canRun(action) {
226
217
  const { stack, layer } = this;
227
- if (!stack || !layer) return false;
218
+ if (!stack || !layer || !stack.layers.includes(layer)) return false;
228
219
  const index = stack.indexOf(layer.id);
229
220
  const last = stack.layers.length - 1;
230
221
  switch (action) {
@@ -248,43 +239,30 @@ var PcLayerItem = class extends PcElement {
248
239
  else if (action === "clear") stack.clear(layer.id);
249
240
  else stack.remove(layer.id);
250
241
  }
251
- onOutsidePointer = (event) => {
252
- if (!event.composedPath().includes(this)) this.menuOpen = false;
253
- };
254
- onKeyDown = (event) => {
255
- if (event.key === "Escape") this.menuOpen = false;
256
- };
257
242
  /**
258
- * Open the context menu on right-click, or close it if open.
243
+ * Follow the context menu opening or closing, telling other rows when it opens (so they close theirs).
259
244
  */
260
- onContextMenu(event) {
261
- event.preventDefault();
262
- this.toggleMenu();
263
- }
264
- /**
265
- * Open the context menu on a click or tap on the handle (or Enter or Space), or close it if open; a click that ends a
266
- * drag does nothing.
267
- */
268
- onHandleClick() {
269
- if (this.suppressClick) {
270
- this.suppressClick = false;
271
- return;
272
- }
273
- this.toggleMenu();
274
- }
275
- /**
276
- * Open the context menu (closing other rows' menus), or close it if open.
277
- */
278
- toggleMenu() {
279
- this.menuOpen = !this.menuOpen;
280
- if (this.menuOpen) this.dispatchEvent(new CustomEvent(LAYER_MENU_OPEN_EVENT, {
245
+ onMenuToggle(event) {
246
+ this.menuOpen = event.detail.open;
247
+ if (!this.menuOpen) return;
248
+ this.dispatchEvent(new CustomEvent(LAYER_MENU_OPEN_EVENT, {
281
249
  bubbles: true,
282
250
  composed: true
283
251
  }));
284
252
  }
253
+ /**
254
+ * A click on the handle opens the menu, unless it ends a drag (then it's stopped before reaching the menu's button).
255
+ */
256
+ onHandleClick = {
257
+ handleEvent: (event) => {
258
+ if (!this.suppressClick) return;
259
+ this.suppressClick = false;
260
+ event.stopPropagation();
261
+ },
262
+ capture: true
263
+ };
285
264
  disconnectedCallback() {
286
265
  super.disconnectedCallback();
287
- this.listenOutside(false);
288
266
  this.endPress();
289
267
  }
290
268
  /** Tell the layers menu about a drag of this row's layer */
@@ -313,7 +291,7 @@ var PcLayerItem = class extends PcElement {
313
291
  y: event.clientY,
314
292
  dragging: false
315
293
  };
316
- event.currentTarget.setPointerCapture(event.pointerId);
294
+ event.composedPath()[0].setPointerCapture(event.pointerId);
317
295
  window.addEventListener("pointermove", this.onHandlePointerMove);
318
296
  window.addEventListener("pointerup", this.onHandlePointerUp);
319
297
  window.addEventListener("pointercancel", this.onHandlePointerCancel);
@@ -368,29 +346,6 @@ var PcLayerItem = class extends PcElement {
368
346
  window.removeEventListener("pointercancel", this.onHandlePointerCancel);
369
347
  }
370
348
  /**
371
- * Start or stop listening for clicks outside and Escape while the menu is open.
372
- */
373
- listenOutside(listen) {
374
- const method = listen ? "addEventListener" : "removeEventListener";
375
- window[method]("pointerdown", this.onOutsidePointer, true);
376
- window[method]("keydown", this.onKeyDown);
377
- window[method]("scroll", this.onScroll, true);
378
- }
379
- onScroll = () => {
380
- this.menuOpen = false;
381
- };
382
- /**
383
- * Place the open layer actions menu beside the reorder handle, floating over the layers panel (so the panel's edges
384
- * don't cut it off) and kept within the window.
385
- */
386
- placeMenu() {
387
- const menu = this.renderRoot.querySelector("menu");
388
- if (!menu) return;
389
- const anchor = this.renderRoot.querySelector(".handle").getBoundingClientRect();
390
- menu.style.left = `${anchor.right}px`;
391
- menu.style.top = `${menuTop(anchor.top, menu.offsetHeight, window.innerHeight)}px`;
392
- }
393
- /**
394
349
  * Ask for a mask following the layer's alpha (Shift adds, Alt subtracts; otherwise it replaces the mask).
395
350
  */
396
351
  requestMask(layer, keys) {
@@ -426,11 +381,7 @@ var PcLayerItem = class extends PcElement {
426
381
  input.value = layer.name;
427
382
  }
428
383
  }
429
- updated(changed) {
430
- if (changed.has("menuOpen")) {
431
- this.listenOutside(this.menuOpen);
432
- if (this.menuOpen) this.placeMenu();
433
- }
384
+ updated() {
434
385
  const id = this.layer?.id;
435
386
  if (this.background && id) this.background.run(`thumbnail ${id}`, () => this.drawThumbnail());
436
387
  else this.drawThumbnail();
@@ -472,23 +423,32 @@ var PcLayerItem = class extends PcElement {
472
423
  }}
473
424
  ></pc-dialog>`;
474
425
  }
426
+ /** The layer's actions, for its context menu (those it can't do now are disabled) */
427
+ menuItems() {
428
+ return LAYER_ACTIONS.map(({ action, label }) => ({
429
+ value: action,
430
+ label: this.t(label),
431
+ disabled: !this.canRun(action)
432
+ }));
433
+ }
475
434
  render() {
476
435
  const { stack, layer } = this;
477
436
  if (!stack || !layer) return html``;
478
437
  const active = stack.active.id === layer.id;
479
438
  return html`
480
- <button
481
- type="button"
439
+ <pc-context-menu
482
440
  class="handle"
483
- aria-label=${this.t("paintCanvas.layers.reorder", { name: layer.name })}
484
- aria-haspopup="menu"
485
- aria-expanded=${this.menuOpen ? "true" : "false"}
486
- @contextmenu=${this.onContextMenu}
441
+ placement="beside"
442
+ label=${this.t("paintCanvas.layers.reorder", { name: layer.name })}
443
+ menu-label=${this.t("paintCanvas.layers.actions")}
444
+ .open=${this.menuOpen}
445
+ .items=${this.menuItems()}
446
+ @pc-toggle=${this.onMenuToggle}
447
+ @pc-select=${(e) => this.runAction(e.detail.value)}
487
448
  @click=${this.onHandleClick}
488
449
  @pointerdown=${this.onHandlePointerDown}
450
+ ><span slot="icon" aria-hidden="true">⠿</span></pc-context-menu
489
451
  >
490
- ⠿
491
- </button>
492
452
  <div
493
453
  class="details"
494
454
  @click=${(e) => this.onRowClick(e, stack, layer)}
@@ -541,18 +501,6 @@ var PcLayerItem = class extends PcElement {
541
501
  >
542
502
  <img src=${this.iconUrl(layer.visible ? "visible" : "hidden")} alt="" />
543
503
  </button>
544
- ${this.menuOpen ? html`<menu aria-label=${this.t("paintCanvas.layers.actions")}>
545
- ${LAYER_ACTIONS.map(({ action, label }) => html`<li>
546
- <button
547
- type="button"
548
- data-action=${action}
549
- ?disabled=${!this.canRun(action)}
550
- @click=${() => this.runAction(action)}
551
- >
552
- ${this.t(label)}
553
- </button>
554
- </li>`)}
555
- </menu>` : nothing}
556
504
  ${this.renderConfirmation(layer)}
557
505
  `;
558
506
  }
@@ -1,3 +1,3 @@
1
1
  // src/ui/elements/PcLayerItem/style.css?inline
2
- var style_default = "/* A white row with space around its parts */\n:host {\n display: grid;\n grid-template-columns: 30px 1fr 30px 30px;\n align-items: center;\n column-gap: 6px;\n position: relative;\n padding: 6px;\n border: 1px solid var(--pc-surface-active);\n background: var(--pc-surface);\n}\n.name.active {\n font-weight: bold;\n}\n.thumbnail {\n width: 30px;\n height: 30px;\n}\n/* Thumbnail on the left and name on the right; under them, opacity on the left and blend mode on the right */\n.details {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) auto;\n align-items: center;\n gap: 4px 6px;\n}\n.details .name {\n grid-column: 2 / -1;\n min-width: 0;\n}\n.details input[type=\"range\"] {\n grid-column: 1 / 3;\n min-width: 0;\n margin: 0;\n}\n/* Like the chat's ⋮ menu: white and rounded, with plain items highlighted on hover. It floats beside the handle (placed\n by `placeMenu`), so the layers panel's edges don't cut it off */\nmenu {\n position: fixed;\n z-index: 20;\n min-width: 160px;\n margin: 0;\n padding: 4px 0;\n border-radius: 12px;\n list-style: none;\n background: var(--pc-surface);\n color: var(--pc-text);\n box-shadow: var(--pc-shadow);\n}\nmenu button {\n width: 100%;\n min-height: 32px;\n padding: 4px 16px;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n text-align: left;\n white-space: nowrap;\n cursor: pointer;\n}\nmenu button:hover:not(:disabled),\nmenu button:focus-visible {\n background: var(--pc-bar);\n}\n/* Actions the layer can't take (e.g. moving the top layer up) */\nmenu button:disabled {\n opacity: 0.4;\n cursor: default;\n}\n/* The reorder handle drags with mouse, pen or touch (touch drags it rather than scrolling the panel) */\n.handle {\n /* As tall as the row */\n align-self: stretch;\n cursor: grab;\n touch-action: none;\n}\n/* While dragged: the row floats with the pointer, out of the list (the layers menu places it), and the cursor shows\n what dropping would do (\"+\" duplicates on \"New Layer\") */\n:host([dragging]) {\n position: fixed;\n z-index: 10;\n opacity: 0.9;\n background: Canvas;\n box-shadow: var(--pc-shadow);\n}\n:host([dragging]) .handle {\n cursor: grabbing;\n}\n:host([drop-action=\"duplicate\"]) .handle {\n cursor: copy;\n}\n:host([drop-action=\"none\"]) .handle {\n cursor: no-drop;\n}\n/* Alpha lock and visibility: boxed icon toggles like the settings bar's, darker while on */\n.alpha-lock,\n.visibility {\n width: 30px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--pc-border);\n border-radius: 4px;\n background: var(--pc-surface);\n cursor: pointer;\n}\n.alpha-lock:hover,\n.visibility:hover {\n background: var(--pc-surface-hover);\n}\n.alpha-lock[aria-pressed=\"true\"],\n.visibility[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.alpha-lock:focus-visible,\n.visibility:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n}\n.alpha-lock img,\n.visibility img {\n display: block;\n width: 24px;\n height: 24px;\n margin: auto;\n}\n";
2
+ var style_default = "/* A white row with space around its parts. The middle column can shrink, so the row never grows wider than its panel */\n:host {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) 30px 30px;\n align-items: center;\n column-gap: 6px;\n position: relative;\n padding: 6px;\n border: 1px solid var(--pc-surface-active);\n background: var(--pc-surface);\n}\n.name.active {\n font-weight: bold;\n}\n.thumbnail {\n width: 30px;\n height: 30px;\n}\n/* Thumbnail on the left and name on the right; under them, opacity on the left and blend mode on the right. The blend\n mode gets a set share of the width rather than its own: browsers size dropdowns differently (Safari and Firefox as\n wide as the longest option, Chrome as the chosen one), which would otherwise push the row wider than its panel */\n.details {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) minmax(0, 2fr);\n align-items: center;\n gap: 4px 6px;\n min-width: 0;\n}\n.details .name {\n grid-column: 2 / -1;\n}\n.details .name,\n.details select {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n}\n.details input[type=\"range\"] {\n grid-column: 1 / 3;\n min-width: 0;\n margin: 0;\n}\n/* The reorder handle drags with mouse, pen or touch (touch drags it rather than scrolling the panel) */\n.handle {\n /* As tall as the row */\n align-self: stretch;\n}\n.handle::part(button) {\n min-width: 0;\n padding: 0;\n cursor: grab;\n touch-action: none;\n}\n/* While dragged: the row floats with the pointer, out of the list (the layers menu places it), and the cursor shows\n what dropping would do (\"+\" duplicates on \"New Layer\") */\n:host([dragging]) {\n position: fixed;\n z-index: 10;\n opacity: 0.9;\n background: Canvas;\n box-shadow: var(--pc-shadow);\n}\n:host([dragging]) .handle::part(button) {\n cursor: grabbing;\n}\n:host([drop-action=\"duplicate\"]) .handle::part(button) {\n cursor: copy;\n}\n:host([drop-action=\"none\"]) .handle::part(button) {\n cursor: no-drop;\n}\n/* Alpha lock and visibility: boxed icon toggles like the settings bar's, darker while on */\n.alpha-lock,\n.visibility {\n width: 30px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--pc-border);\n border-radius: 4px;\n background: var(--pc-surface);\n cursor: pointer;\n}\n.alpha-lock:hover,\n.visibility:hover {\n background: var(--pc-surface-hover);\n}\n.alpha-lock[aria-pressed=\"true\"],\n.visibility[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.alpha-lock:focus-visible,\n.visibility:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n}\n.alpha-lock img,\n.visibility img {\n display: block;\n width: 24px;\n height: 24px;\n margin: auto;\n}\n";
3
3
  export { style_default as default };
@@ -0,0 +1,138 @@
1
+ import { type PropertyValues } from "lit";
2
+ import { PcElement } from "../../base";
3
+ import { type KeyCombo } from "../../../lib/utils/shortcuts";
4
+ import "../PcIcon";
5
+ /** Longest shortcut label shown on a menu item (characters); longer ones are left out */
6
+ export declare const MAX_SHORTCUT_LABEL_LENGTH = 3;
7
+ /** An item in a menu that can be chosen */
8
+ export interface MenuItem {
9
+ /** Optional; items are the default kind of menu entry */
10
+ type?: "item";
11
+ /** What `pc-select` gives when it's chosen */
12
+ value: string;
13
+ label: string;
14
+ disabled?: boolean;
15
+ /** Shown in the cancelling colour, e.g. for deleting */
16
+ danger?: boolean;
17
+ /** Makes the item a toggle, showing a checkmark on its left while on */
18
+ checked?: boolean;
19
+ /** Name of an icon to show on its left (see `icons`), when it isn't a toggle */
20
+ icon?: string;
21
+ /** The action's keyboard shortcut, shown on its right as symbols (e.g. ⌘S) when 3 characters or fewer */
22
+ shortcut?: KeyCombo;
23
+ /** Marks the current choice (e.g. the selected tool) with the `selected` class and a purple background */
24
+ selected?: boolean;
25
+ }
26
+ /** A non-interactive label naming the items after it (up to the next label or divider), e.g. "Tools" */
27
+ export interface MenuLabel {
28
+ type: "label";
29
+ label: string;
30
+ }
31
+ /** A non-interactive line between items */
32
+ export interface MenuDivider {
33
+ type: "divider";
34
+ }
35
+ /** Anything a menu can show: an item, a label or a divider */
36
+ export type MenuEntry = MenuItem | MenuLabel | MenuDivider;
37
+ /** A run of items, with the label before them if there is one */
38
+ interface MenuSection {
39
+ label?: string;
40
+ items: MenuItem[];
41
+ /** Whether a divider comes before it */
42
+ divided: boolean;
43
+ }
44
+ /**
45
+ * Whether a menu entry is an item that can be chosen.
46
+ *
47
+ * @example
48
+ * isMenuItem({ value: "grid", label: "Grid" }); // true
49
+ * isMenuItem({ type: "divider" }); // false
50
+ */
51
+ export declare const isMenuItem: (entry: MenuEntry) => entry is MenuItem;
52
+ /**
53
+ * Split menu entries into sections: a label or divider starts a new one.
54
+ *
55
+ * @example
56
+ * menuSections([{ type: "label", label: "Tools" }, { value: "brush", label: "Brush" }]);
57
+ * // [{ label: "Tools", items: [{ value: "brush", label: "Brush" }], divided: false }]
58
+ */
59
+ export declare const menuSections: (entries: readonly MenuEntry[]) => MenuSection[];
60
+ /**
61
+ * A list of menu items, e.g. a layer's actions (see `pc-context-menu` for a button that opens one). While `open`, it shows its items at `x`, `y` (in the page's viewport) and takes
62
+ * keyboard focus: arrow keys, Home and End move between items, Enter or Space chooses one, and Escape or Tab closes it.
63
+ * Items can show a checkmark (toggles) or an icon on their left, and their keyboard shortcut on their right; long
64
+ * labels wrap. Non-interactive labels (`{ type: "label", label }`, small bold text naming the items after them) and
65
+ * dividers (`{ type: "divider" }`) can go between items; keyboard focus skips them.
66
+ * Fires `pc-select` with the chosen item's value, and `pc-close` when it should close (after choosing, Escape, Tab, or a
67
+ * press outside it and its `anchor`). The implementer sets `open` to false in response.
68
+ *
69
+ * @example
70
+ * html`<pc-menu open .x=${10} .y=${20} label="Layer actions" .items=${items}
71
+ * @pc-select=${(e) => act(e.detail.value)} @pc-close=${close}></pc-menu>`
72
+ */
73
+ export declare class PcMenu extends PcElement {
74
+ static properties: {
75
+ open: {
76
+ type: BooleanConstructor;
77
+ reflect: boolean;
78
+ };
79
+ items: {
80
+ attribute: boolean;
81
+ };
82
+ label: {
83
+ type: StringConstructor;
84
+ };
85
+ x: {
86
+ type: NumberConstructor;
87
+ };
88
+ y: {
89
+ type: NumberConstructor;
90
+ };
91
+ anchor: {
92
+ attribute: boolean;
93
+ };
94
+ translations: {
95
+ attribute: boolean;
96
+ };
97
+ icons: {
98
+ attribute: boolean;
99
+ };
100
+ theme: {
101
+ type: StringConstructor;
102
+ reflect: boolean;
103
+ };
104
+ };
105
+ static styles: import("lit").CSSResultGroup[];
106
+ open: boolean;
107
+ /** Items, labels and dividers, in order */
108
+ items: readonly MenuEntry[];
109
+ /** Names the menu for screen readers */
110
+ label: string;
111
+ x: number;
112
+ y: number;
113
+ /** The element that opens it (e.g. a `pc-context-menu`'s button): pressing it doesn't count as pressing outside */
114
+ anchor?: Element;
115
+ private readonly onPointerDown;
116
+ constructor();
117
+ connectedCallback(): void;
118
+ disconnectedCallback(): void;
119
+ updated(changed: PropertyValues<this>): void;
120
+ /** The enabled items' buttons */
121
+ private buttons;
122
+ /** Tell listeners the menu should close */
123
+ close(): void;
124
+ /** Choose an item, then close */
125
+ private choose;
126
+ /** Move focus between items, or close */
127
+ private onKeyDown;
128
+ /** An item's left side: a checkmark while a toggle is on, its icon, or empty space */
129
+ private renderStart;
130
+ /** An item's right side: its shortcut's label, if short enough */
131
+ private renderEnd;
132
+ /** An item's button */
133
+ private renderItem;
134
+ /** A section: its divider and label, if any, and its items (grouped under the label for screen readers) */
135
+ private renderSection;
136
+ render(): import("lit").TemplateResult<1>;
137
+ }
138
+ export {};