@lvl99dotcom/paintcanvas 0.4.0 → 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 (174) hide show
  1. package/README.md +30 -15
  2. package/dist/lib/brushes/presets/round/index.d.ts +1 -1
  3. package/dist/lib/brushes/presets/round/index.js +12 -7
  4. package/dist/lib/brushes/presets/square/index.d.ts +1 -1
  5. package/dist/lib/brushes/presets/square/index.js +7 -3
  6. package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
  7. package/dist/lib/brushes/shapeImage/index.js +15 -9
  8. package/dist/lib/brushes/shapes/index.d.ts +18 -0
  9. package/dist/lib/brushes/shapes/index.js +86 -0
  10. package/dist/lib/classes/BrushSettings/index.d.ts +29 -2
  11. package/dist/lib/classes/BrushSettings/index.js +51 -14
  12. package/dist/lib/classes/KeyboardShortcuts/index.d.ts +31 -2
  13. package/dist/lib/classes/KeyboardShortcuts/index.js +66 -4
  14. package/dist/lib/classes/PaintCanvas/index.d.ts +46 -0
  15. package/dist/lib/classes/PaintCanvas/index.js +67 -0
  16. package/dist/lib/classes/SettingsAutosave/index.d.ts +3 -2
  17. package/dist/lib/classes/SettingsAutosave/index.js +5 -3
  18. package/dist/lib/classes/UiSettings/index.d.ts +68 -0
  19. package/dist/lib/classes/UiSettings/index.js +82 -0
  20. package/dist/lib/settings/index.d.ts +1 -1
  21. package/dist/lib/settings/index.js +4 -2
  22. package/dist/lib/settings/schema.d.ts +18 -0
  23. package/dist/lib/settings/schema.js +14 -0
  24. package/dist/lib/validation/validators.generated.js +169 -1
  25. package/dist/locales/en-GB.js +9 -2
  26. package/dist/locales/en-US.js +9 -2
  27. package/dist/locales/fr.js +9 -2
  28. package/dist/locales/ja.js +9 -2
  29. package/dist/locales/ko.js +9 -2
  30. package/dist/locales/zh-Hans.js +9 -2
  31. package/dist/locales/zh-Hant.js +9 -2
  32. package/dist/package.js +1 -1
  33. package/dist/testing/recordingCanvas.js +2 -1
  34. package/dist/ui/base/index.d.ts +2 -2
  35. package/dist/ui/dismiss/index.d.ts +21 -0
  36. package/dist/ui/dismiss/index.js +27 -0
  37. package/dist/ui/drag/index.d.ts +51 -0
  38. package/dist/ui/drag/index.js +81 -0
  39. package/dist/ui/elements/PaintCanvas/index.d.ts +17 -12
  40. package/dist/ui/elements/PaintCanvas/index.js +34 -17
  41. package/dist/ui/elements/PaintCanvas/style.js +1 -1
  42. package/dist/ui/elements/PcAppSettings/index.d.ts +1 -1
  43. package/dist/ui/elements/PcAppSettings/index.js +1 -1
  44. package/dist/ui/elements/PcBrushConfig/index.d.ts +9 -2
  45. package/dist/ui/elements/PcBrushConfig/index.js +47 -7
  46. package/dist/ui/elements/PcBrushConfig/style.js +1 -1
  47. package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
  48. package/dist/ui/elements/PcBrushSize/index.js +66 -0
  49. package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
  50. package/dist/ui/elements/PcBrushTest/index.js +73 -5
  51. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  52. package/dist/ui/elements/PcCanvas/index.d.ts +21 -1
  53. package/dist/ui/elements/PcCanvas/index.js +53 -10
  54. package/dist/ui/elements/PcCanvasMap/style.js +1 -1
  55. package/dist/ui/elements/PcClipboardTools/index.d.ts +48 -0
  56. package/dist/ui/elements/PcClipboardTools/index.js +118 -0
  57. package/dist/ui/elements/PcContextMenu/index.d.ts +2 -2
  58. package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
  59. package/dist/ui/elements/PcInputSettings/index.js +16 -1
  60. package/dist/ui/elements/PcMenu/index.d.ts +48 -3
  61. package/dist/ui/elements/PcMenu/index.js +111 -19
  62. package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
  63. package/dist/ui/elements/PcNumberSlider/index.js +247 -0
  64. package/dist/ui/elements/PcPane/index.d.ts +3 -1
  65. package/dist/ui/elements/PcPane/index.js +44 -7
  66. package/dist/ui/elements/PcPanel/index.d.ts +2 -2
  67. package/dist/ui/elements/PcPanel/index.js +8 -8
  68. package/dist/ui/elements/PcPanelBrushes/index.d.ts +2 -15
  69. package/dist/ui/elements/PcPanelBrushes/index.js +6 -43
  70. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  71. package/dist/ui/elements/PcPopover/index.d.ts +39 -1
  72. package/dist/ui/elements/PcPopover/index.js +72 -4
  73. package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
  74. package/dist/ui/elements/PcThemePicker/index.js +22 -21
  75. package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
  76. package/dist/ui/elements/PcToolSettings/index.js +382 -318
  77. package/dist/ui/elements/PcToolSettings/style.js +1 -1
  78. package/dist/ui/elements/PcToolbar/index.d.ts +15 -0
  79. package/dist/ui/elements/PcToolbar/index.js +55 -14
  80. package/dist/ui/elements/PcToolbar/style.js +1 -1
  81. package/dist/ui/icons/images/icon-brush@2x.js +3 -0
  82. package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
  83. package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
  84. package/dist/ui/icons/images/icon-cog@2x.js +3 -0
  85. package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
  86. package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
  87. package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
  88. package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
  89. package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
  90. package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
  91. package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
  92. package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
  93. package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
  94. package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
  95. package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
  96. package/dist/ui/icons/images/icon-layers@2x.js +3 -0
  97. package/dist/ui/icons/images/icon-linked@2x.js +3 -0
  98. package/dist/ui/icons/images/icon-locked@2x.js +3 -0
  99. package/dist/ui/icons/images/icon-moon@2x.js +3 -0
  100. package/dist/ui/icons/images/icon-pan@2x.js +3 -0
  101. package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
  102. package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
  103. package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
  104. package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
  105. package/dist/ui/icons/images/icon-redo@2x.js +3 -0
  106. package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
  107. package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
  108. package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
  109. package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
  110. package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
  111. package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
  112. package/dist/ui/icons/images/icon-star@2x.js +3 -0
  113. package/dist/ui/icons/images/icon-sun@2x.js +3 -0
  114. package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
  115. package/dist/ui/icons/images/icon-undo@2x.js +3 -0
  116. package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
  117. package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
  118. package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
  119. package/dist/ui/icons/images/icon-visible@2x.js +3 -0
  120. package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
  121. package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
  122. package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
  123. package/dist/ui/icons/index.d.ts +1 -1
  124. package/dist/ui/icons/index.js +84 -72
  125. package/dist/ui/index.d.ts +7 -2
  126. package/dist/ui/index.js +10 -4
  127. package/dist/ui/layout/index.d.ts +9 -1
  128. package/dist/ui/layout/index.js +11 -2
  129. package/dist/ui/tokens/index.d.ts +5 -4
  130. package/dist/ui/tokens/index.js +12 -7
  131. package/locales/en-GB.json +9 -2
  132. package/locales/en-US.json +9 -2
  133. package/locales/fr.json +9 -2
  134. package/locales/ja.json +9 -2
  135. package/locales/ko.json +9 -2
  136. package/locales/zh-Hans.json +9 -2
  137. package/locales/zh-Hant.json +9 -2
  138. package/package.json +11 -4
  139. package/dist/ui/icons/images/brush.js +0 -3
  140. package/dist/ui/icons/images/circle-mask.js +0 -3
  141. package/dist/ui/icons/images/clipboard.js +0 -3
  142. package/dist/ui/icons/images/cursor.js +0 -3
  143. package/dist/ui/icons/images/eraser.js +0 -3
  144. package/dist/ui/icons/images/eyedropper.js +0 -3
  145. package/dist/ui/icons/images/flip-horizontal.js +0 -3
  146. package/dist/ui/icons/images/flip-vertical.js +0 -3
  147. package/dist/ui/icons/images/focus-pen.js +0 -3
  148. package/dist/ui/icons/images/freehand-mask.js +0 -3
  149. package/dist/ui/icons/images/hidden.js +0 -3
  150. package/dist/ui/icons/images/layers.js +0 -3
  151. package/dist/ui/icons/images/linked.js +0 -3
  152. package/dist/ui/icons/images/locked.js +0 -3
  153. package/dist/ui/icons/images/moon.js +0 -3
  154. package/dist/ui/icons/images/pan.js +0 -3
  155. package/dist/ui/icons/images/polygon-mask.js +0 -3
  156. package/dist/ui/icons/images/pressure.js +0 -3
  157. package/dist/ui/icons/images/randomise.js +0 -3
  158. package/dist/ui/icons/images/rectangle-mask.js +0 -3
  159. package/dist/ui/icons/images/redo.js +0 -3
  160. package/dist/ui/icons/images/rotate-90-left.js +0 -3
  161. package/dist/ui/icons/images/rotate-90-right.js +0 -3
  162. package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
  163. package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
  164. package/dist/ui/icons/images/smoothing-linear.js +0 -3
  165. package/dist/ui/icons/images/star.js +0 -3
  166. package/dist/ui/icons/images/sun.js +0 -3
  167. package/dist/ui/icons/images/tilt.js +0 -3
  168. package/dist/ui/icons/images/undo.js +0 -3
  169. package/dist/ui/icons/images/unlinked.js +0 -3
  170. package/dist/ui/icons/images/unlocked.js +0 -3
  171. package/dist/ui/icons/images/visible.js +0 -3
  172. package/dist/ui/icons/images/zoom-in.js +0 -3
  173. package/dist/ui/icons/images/zoom-out.js +0 -3
  174. package/dist/ui/icons/images/zoom.js +0 -3
@@ -1,6 +1,7 @@
1
1
  import { nothing } from "lit";
2
2
  import { PcElement } from "../../base";
3
3
  import { type InputSettingsData } from "../../../lib/classes/InputSettings";
4
+ import type { UiSettingsLike } from "../../../lib/classes/UiSettings";
4
5
  /** Settings `pc-input-settings` can change: PaintCanvas's `InputSettings`, or anything with the same data and `update` */
5
6
  export interface InputSettingsLike extends EventTarget {
6
7
  readonly data: Readonly<InputSettingsData>;
@@ -12,6 +13,9 @@ export interface InputSettingsLike extends EventTarget {
12
13
  * and the language. Changes apply straight away. Hide focus pen mode or the language with `focusPen` and `languages`
13
14
  * set to false. Fires `pc-language-change` (with the locale) when the user picks a language.
14
15
  *
16
+ * With `ui` (e.g. the canvas's `ui`), the picked language is kept there, and used whenever it changes (e.g. when
17
+ * restored), so it's kept with the other app settings.
18
+ *
15
19
  * @example
16
20
  * html`<pc-input-settings .settings=${canvas.input}></pc-input-settings>`
17
21
  */
@@ -20,6 +24,9 @@ export declare class PcInputSettings extends PcElement {
20
24
  settings: {
21
25
  attribute: boolean;
22
26
  };
27
+ ui: {
28
+ attribute: boolean;
29
+ };
23
30
  focusPen: {
24
31
  type: BooleanConstructor;
25
32
  };
@@ -46,6 +53,8 @@ export declare class PcInputSettings extends PcElement {
46
53
  static styles: import("lit").CSSResultGroup[];
47
54
  /** The settings shown and changed */
48
55
  settings?: InputSettingsLike;
56
+ /** Where the language is kept, if anywhere (e.g. the canvas's `ui`) */
57
+ ui?: UiSettingsLike;
49
58
  /** Whether to show focus pen mode (default true) */
50
59
  focusPen: boolean;
51
60
  /** Whether to show the language (default true) */
@@ -55,7 +64,11 @@ export declare class PcInputSettings extends PcElement {
55
64
  /** Name of the zoom modifier key ("Cmd" or "Ctrl", detected by default) */
56
65
  modifier: string;
57
66
  private changes;
67
+ /** The kept language last used, so it's only switched to once */
68
+ private usedLocale?;
58
69
  constructor();
70
+ /** Use the kept language when it changes */
71
+ protected willUpdate(): void;
59
72
  /** A checkbox for an on/off setting */
60
73
  private toggle;
61
74
  /**
@@ -18,6 +18,9 @@ var ACTION_KEYS = {
18
18
  * and the language. Changes apply straight away. Hide focus pen mode or the language with `focusPen` and `languages`
19
19
  * set to false. Fires `pc-language-change` (with the locale) when the user picks a language.
20
20
  *
21
+ * With `ui` (e.g. the canvas's `ui`), the picked language is kept there, and used whenever it changes (e.g. when
22
+ * restored), so it's kept with the other app settings.
23
+ *
21
24
  * @example
22
25
  * html`<pc-input-settings .settings=${canvas.input}></pc-input-settings>`
23
26
  */
@@ -25,6 +28,7 @@ var PcInputSettings = class extends PcElement {
25
28
  static properties = {
26
29
  ...PcElement.properties,
27
30
  settings: { attribute: false },
31
+ ui: { attribute: false },
28
32
  focusPen: { type: Boolean },
29
33
  languages: { type: Boolean },
30
34
  touch: { type: Boolean },
@@ -67,7 +71,9 @@ var PcInputSettings = class extends PcElement {
67
71
  outline-offset: 2px;
68
72
  }
69
73
  `];
70
- changes = new ChangeController(this, () => [this.settings]);
74
+ changes = new ChangeController(this, () => [this.settings, this.ui]);
75
+ /** The kept language last used, so it's only switched to once */
76
+ usedLocale;
71
77
  constructor() {
72
78
  super();
73
79
  this.focusPen = true;
@@ -75,6 +81,13 @@ var PcInputSettings = class extends PcElement {
75
81
  this.touch = isTouchDevice();
76
82
  this.modifier = zoomModifierName();
77
83
  }
84
+ /** Use the kept language when it changes */
85
+ willUpdate() {
86
+ const locale = this.ui?.data.locale;
87
+ if (locale === void 0 || locale === this.usedLocale) return;
88
+ this.usedLocale = locale;
89
+ if (locale !== this.translations.locale) this.translations.setLocale(locale);
90
+ }
78
91
  /** A checkbox for an on/off setting */
79
92
  toggle(data, key, label) {
80
93
  return html`<label class="toggle">
@@ -123,6 +136,8 @@ var PcInputSettings = class extends PcElement {
123
136
  name="language"
124
137
  @change=${(e) => {
125
138
  const locale = e.target.value;
139
+ this.usedLocale = locale;
140
+ this.ui?.update({ locale });
126
141
  translations.setLocale(locale).then(() => emit(this, "pc-language-change", { locale }));
127
142
  }}
128
143
  >
@@ -4,8 +4,10 @@ import { type KeyCombo } from "../../../lib/utils/shortcuts";
4
4
  import "../PcIcon";
5
5
  /** Longest shortcut label shown on a menu item (characters); longer ones are left out */
6
6
  export declare const MAX_SHORTCUT_LABEL_LENGTH = 3;
7
- /** An item in a context menu */
7
+ /** An item in a menu that can be chosen */
8
8
  export interface MenuItem {
9
+ /** Optional; items are the default kind of menu entry */
10
+ type?: "item";
9
11
  /** What `pc-select` gives when it's chosen */
10
12
  value: string;
11
13
  label: string;
@@ -18,12 +20,49 @@ export interface MenuItem {
18
20
  icon?: string;
19
21
  /** The action's keyboard shortcut, shown on its right as symbols (e.g. ⌘S) when 3 characters or fewer */
20
22
  shortcut?: KeyCombo;
23
+ /** Marks the current choice (e.g. the selected tool) with the `selected` class and a purple background */
24
+ selected?: boolean;
21
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[];
22
60
  /**
23
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
24
62
  * keyboard focus: arrow keys, Home and End move between items, Enter or Space chooses one, and Escape or Tab closes it.
25
63
  * Items can show a checkmark (toggles) or an icon on their left, and their keyboard shortcut on their right; long
26
- * labels wrap.
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.
27
66
  * Fires `pc-select` with the chosen item's value, and `pc-close` when it should close (after choosing, Escape, Tab, or a
28
67
  * press outside it and its `anchor`). The implementer sets `open` to false in response.
29
68
  *
@@ -65,7 +104,8 @@ export declare class PcMenu extends PcElement {
65
104
  };
66
105
  static styles: import("lit").CSSResultGroup[];
67
106
  open: boolean;
68
- items: readonly MenuItem[];
107
+ /** Items, labels and dividers, in order */
108
+ items: readonly MenuEntry[];
69
109
  /** Names the menu for screen readers */
70
110
  label: string;
71
111
  x: number;
@@ -89,5 +129,10 @@ export declare class PcMenu extends PcElement {
89
129
  private renderStart;
90
130
  /** An item's right side: its shortcut's label, if short enough */
91
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;
92
136
  render(): import("lit").TemplateResult<1>;
93
137
  }
138
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { PcElement, defineElement, emit } from "../../base/index.js";
2
+ import { markDismissingPress } from "../../dismiss/index.js";
2
3
  import { ariaKeyShortcut, shortcutLabel } from "../../../lib/utils/shortcuts/index.js";
3
4
  import "../PcIcon/index.js";
4
5
  import { css, html, nothing } from "lit";
@@ -6,6 +7,14 @@ import { css, html, nothing } from "lit";
6
7
  /** Longest shortcut label shown on a menu item (characters); longer ones are left out */
7
8
  var MAX_SHORTCUT_LABEL_LENGTH = 3;
8
9
  /**
10
+ * Whether a menu entry is an item that can be chosen.
11
+ *
12
+ * @example
13
+ * isMenuItem({ value: "grid", label: "Grid" }); // true
14
+ * isMenuItem({ type: "divider" }); // false
15
+ */
16
+ var isMenuItem = (entry) => entry.type === void 0 || entry.type === "item";
17
+ /**
9
18
  * Whether a menu item has something to show on its left: a checkmark or an icon.
10
19
  *
11
20
  * @example
@@ -13,10 +22,46 @@ var MAX_SHORTCUT_LABEL_LENGTH = 3;
13
22
  */
14
23
  var hasStart = (item) => item.checked !== void 0 || item.icon !== void 0;
15
24
  /**
25
+ * A menu item's classes: `danger` and `selected`, when it is.
26
+ *
27
+ * @example
28
+ * itemClasses({ value: "brush", label: "Brush", selected: true }); // "selected"
29
+ */
30
+ var itemClasses = (item) => [item.danger && "danger", item.selected && "selected"].filter(Boolean).join(" ");
31
+ /**
32
+ * Split menu entries into sections: a label or divider starts a new one.
33
+ *
34
+ * @example
35
+ * menuSections([{ type: "label", label: "Tools" }, { value: "brush", label: "Brush" }]);
36
+ * // [{ label: "Tools", items: [{ value: "brush", label: "Brush" }], divided: false }]
37
+ */
38
+ var menuSections = (entries) => {
39
+ const sections = [{
40
+ items: [],
41
+ divided: false
42
+ }];
43
+ for (const entry of entries) {
44
+ const current = sections[sections.length - 1];
45
+ if (isMenuItem(entry)) current.items.push(entry);
46
+ else if (entry.type === "divider") sections.push({
47
+ items: [],
48
+ divided: true
49
+ });
50
+ else if (current.items.length === 0 && current.label === void 0) current.label = entry.label;
51
+ else sections.push({
52
+ label: entry.label,
53
+ items: [],
54
+ divided: false
55
+ });
56
+ }
57
+ return sections.filter((section) => section.divided || section.label !== void 0 || section.items.length);
58
+ };
59
+ /**
16
60
  * 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
17
61
  * keyboard focus: arrow keys, Home and End move between items, Enter or Space chooses one, and Escape or Tab closes it.
18
62
  * Items can show a checkmark (toggles) or an icon on their left, and their keyboard shortcut on their right; long
19
- * labels wrap.
63
+ * labels wrap. Non-interactive labels (`{ type: "label", label }`, small bold text naming the items after them) and
64
+ * dividers (`{ type: "divider" }`) can go between items; keyboard focus skips them.
20
65
  * Fires `pc-select` with the chosen item's value, and `pc-close` when it should close (after choosing, Escape, Tab, or a
21
66
  * press outside it and its `anchor`). The implementer sets `open` to false in response.
22
67
  *
@@ -42,6 +87,8 @@ var PcMenu = class extends PcElement {
42
87
  position: fixed;
43
88
  z-index: 10;
44
89
  display: none;
90
+ user-select: none;
91
+ -webkit-user-select: none;
45
92
  }
46
93
  :host([open]) {
47
94
  display: block;
@@ -80,6 +127,16 @@ var PcMenu = class extends PcElement {
80
127
  button.danger {
81
128
  color: var(--pc-dismiss-text);
82
129
  }
130
+ /* The current choice, e.g. the selected tool, like other selected buttons */
131
+ button.selected,
132
+ button.selected:hover:not(:disabled),
133
+ button.selected:focus-visible {
134
+ background: var(--pc-accent);
135
+ color: var(--pc-accent-text);
136
+ }
137
+ button.selected .end {
138
+ color: inherit;
139
+ }
83
140
  .start {
84
141
  /* Icons as tall as the text, so items with icons are no taller */
85
142
  --pc-icon-size: 1.2em;
@@ -102,10 +159,29 @@ var PcMenu = class extends PcElement {
102
159
  color: var(--pc-text-muted);
103
160
  cursor: default;
104
161
  }
162
+ [role="group"] {
163
+ display: flex;
164
+ flex-direction: column;
165
+ }
166
+ .heading {
167
+ padding: var(--pc-space-2) var(--pc-space-3) var(--pc-space-1);
168
+ color: var(--pc-text-muted);
169
+ font-family: var(--pc-font);
170
+ font-size: var(--pc-font-size-small);
171
+ font-weight: bold;
172
+ cursor: default;
173
+ }
174
+ [role="separator"] {
175
+ height: 1px;
176
+ margin: var(--pc-space-1) 0;
177
+ background: var(--pc-border);
178
+ }
105
179
  `];
106
180
  onPointerDown = (event) => {
107
181
  const path = event.composedPath();
108
- if (this.open && !path.includes(this) && !(this.anchor && path.includes(this.anchor))) this.close();
182
+ if (!this.open || path.includes(this) || this.anchor && path.includes(this.anchor)) return;
183
+ markDismissingPress(event);
184
+ this.close();
109
185
  };
110
186
  constructor() {
111
187
  super();
@@ -174,31 +250,47 @@ var PcMenu = class extends PcElement {
174
250
  >${label}</span
175
251
  >` : nothing;
176
252
  }
253
+ /** An item's button */
254
+ renderItem(item, showStart) {
255
+ return html`<button
256
+ part="item"
257
+ type="button"
258
+ role=${item.checked === void 0 ? "menuitem" : "menuitemcheckbox"}
259
+ aria-checked=${item.checked === void 0 ? nothing : String(item.checked)}
260
+ aria-keyshortcuts=${item.shortcut ? ariaKeyShortcut(item.shortcut) : nothing}
261
+ class=${itemClasses(item) || nothing}
262
+ aria-current=${item.selected ? "true" : nothing}
263
+ ?disabled=${item.disabled}
264
+ data-value=${item.value}
265
+ @click=${() => this.choose(item)}
266
+ >
267
+ ${showStart ? this.renderStart(item) : nothing}<span class="label"
268
+ >${item.label}</span
269
+ >${this.renderEnd(item)}
270
+ </button>`;
271
+ }
272
+ /** A section: its divider and label, if any, and its items (grouped under the label for screen readers) */
273
+ renderSection(section, index, showStart) {
274
+ const items = section.items.map((item) => this.renderItem(item, showStart));
275
+ const id = `menu-label-${index}`;
276
+ return html`${section.divided ? html`<div part="divider" role="separator"></div>` : nothing}${section.label === void 0 ? items : html`<div role="group" aria-labelledby=${id}>
277
+ <div part="label" class="heading" id=${id} role="presentation">
278
+ ${section.label}
279
+ </div>
280
+ ${items}
281
+ </div>`}`;
282
+ }
177
283
  render() {
178
- const showStart = this.items.some(hasStart);
284
+ const showStart = this.items.some((entry) => isMenuItem(entry) && hasStart(entry));
179
285
  return html`<div
180
286
  part="menu"
181
287
  role="menu"
182
288
  aria-label=${this.label}
183
289
  @keydown=${this.onKeyDown}
184
290
  >
185
- ${this.items.map((item) => html`<button
186
- part="item"
187
- type="button"
188
- role=${item.checked === void 0 ? "menuitem" : "menuitemcheckbox"}
189
- aria-checked=${item.checked === void 0 ? nothing : String(item.checked)}
190
- aria-keyshortcuts=${item.shortcut ? ariaKeyShortcut(item.shortcut) : nothing}
191
- class=${item.danger ? "danger" : nothing}
192
- ?disabled=${item.disabled}
193
- data-value=${item.value}
194
- @click=${() => this.choose(item)}
195
- >
196
- ${showStart ? this.renderStart(item) : nothing}<span class="label"
197
- >${item.label}</span
198
- >${this.renderEnd(item)}
199
- </button>`)}
291
+ ${menuSections(this.items).map((section, index) => this.renderSection(section, index, showStart))}
200
292
  </div>`;
201
293
  }
202
294
  };
203
295
  defineElement("pc-menu", PcMenu);
204
- export { MAX_SHORTCUT_LABEL_LENGTH, PcMenu };
296
+ export { MAX_SHORTCUT_LABEL_LENGTH, PcMenu, isMenuItem, menuSections };
@@ -0,0 +1,137 @@
1
+ import { type PropertyValues } from "lit";
2
+ import { PcElement } from "../../base";
3
+ /**
4
+ * A number kept within `min` and `max` and rounded to a multiple of `step` (from `min`).
5
+ *
6
+ * @example
7
+ * fitNumber(300, 1, 256, 1); // 256
8
+ * fitNumber(12.4, 0, 100, 5); // 10
9
+ * fitNumber(45.678, -360, 360, 0.01); // 45.68
10
+ */
11
+ export declare const fitNumber: (value: number, min: number, max: number, step: number) => number;
12
+ /**
13
+ * A number shown as text that changes when pressed and dragged: right or up raises it, left or down lowers it, a step
14
+ * per pixel (or `drag-step`), within `min` and `max`. A press that barely moves (a tap or click) turns it into a field to type a value
15
+ * in: Enter or leaving the field keeps the value, Escape cancels.
16
+ *
17
+ * With keyboard focus, the arrow keys change it by `step`, Page Up and Page Down by `pageStep`, Home and End go to
18
+ * `min` and `max`, and Enter or F2 starts typing.
19
+ *
20
+ * Fires `pc-change` (detail: `{ value }`) when the value changes, after changing `value` itself. Bind a value that
21
+ * may be changed by others with `live()`, e.g. `.value=${live(brush.width)}`, so it's shown again if a listener keeps
22
+ * it the same.
23
+ *
24
+ * @example
25
+ * html`<pc-number-slider label="Opacity" min="0" max="100" unit="%" .value=${live(50)}
26
+ * @pc-change=${(e) => setOpacity(e.detail.value)}></pc-number-slider>`
27
+ */
28
+ export declare class PcNumberSlider extends PcElement {
29
+ static properties: {
30
+ value: {
31
+ type: NumberConstructor;
32
+ };
33
+ min: {
34
+ type: NumberConstructor;
35
+ };
36
+ max: {
37
+ type: NumberConstructor;
38
+ };
39
+ step: {
40
+ type: NumberConstructor;
41
+ };
42
+ pageStep: {
43
+ type: NumberConstructor;
44
+ attribute: string;
45
+ };
46
+ dragStep: {
47
+ type: NumberConstructor;
48
+ attribute: string;
49
+ };
50
+ unit: {
51
+ type: StringConstructor;
52
+ };
53
+ text: {
54
+ type: StringConstructor;
55
+ };
56
+ label: {
57
+ type: StringConstructor;
58
+ };
59
+ disabled: {
60
+ type: BooleanConstructor;
61
+ reflect: boolean;
62
+ };
63
+ editing: {
64
+ type: BooleanConstructor;
65
+ reflect: boolean;
66
+ };
67
+ translations: {
68
+ attribute: boolean;
69
+ };
70
+ icons: {
71
+ attribute: boolean;
72
+ };
73
+ theme: {
74
+ type: StringConstructor;
75
+ reflect: boolean;
76
+ };
77
+ };
78
+ static styles: import("lit").CSSResultGroup[];
79
+ /** The number */
80
+ value: number;
81
+ /** Lowest value (0 by default) */
82
+ min: number;
83
+ /** Highest value (100 by default) */
84
+ max: number;
85
+ /** How much a pixel of dragging or an arrow key changes it (1 by default) */
86
+ step: number;
87
+ /** How much Page Up and Page Down change it (10 steps by default) */
88
+ pageStep?: number;
89
+ /** How much a pixel of dragging changes it (`step` by default), e.g. 1 for an angle typed to 0.01 */
90
+ dragStep?: number;
91
+ /** Shown after the value, e.g. "%" */
92
+ unit: string;
93
+ /** Shown instead of the value and unit, e.g. "30 × 10" */
94
+ text: string;
95
+ /** Its name for screen readers, e.g. "Opacity" */
96
+ label: string;
97
+ disabled: boolean;
98
+ /** Whether it's a field being typed in */
99
+ editing: boolean;
100
+ /** Whether focus goes back to the slider when typing ends (it doesn't when the field lost focus) */
101
+ private refocus;
102
+ private readonly drag;
103
+ constructor();
104
+ /** The value as shown: `text`, or the value and unit */
105
+ get displayText(): string;
106
+ /**
107
+ * Set the value (fitted within `min` and `max`, to a multiple of `step`) and tell listeners if it changed.
108
+ *
109
+ * @example
110
+ * slider.change(40); // fires pc-change with { value: 40 }
111
+ */
112
+ change(value: number): void;
113
+ /**
114
+ * Turn into a field to type a value in, with its text selected.
115
+ */
116
+ startEditing(): void;
117
+ /**
118
+ * Stop typing, keeping the typed value (if it's a number) unless cancelled.
119
+ */
120
+ private stopEditing;
121
+ /** Start a press, unless disabled */
122
+ private onPointerDown;
123
+ /** End a press: one that didn't become a drag starts typing */
124
+ private onPointerUp;
125
+ /** Change the value with the arrow keys, Page Up and Page Down, Home and End, or start typing (Enter, F2) */
126
+ private onSliderKeyDown;
127
+ /** Keep the typed value with Enter, or cancel with Escape (neither reaches the rest of the page) */
128
+ private onInputKeyDown;
129
+ /**
130
+ * Keep the typed value when the field loses focus, leaving focus where it went. Ignored once typing has ended (some
131
+ * browsers blur the field as it's removed).
132
+ */
133
+ private onInputBlur;
134
+ protected willUpdate(changed: PropertyValues<this>): void;
135
+ protected updated(changed: PropertyValues<this>): void;
136
+ render(): import("lit").TemplateResult<1>;
137
+ }