@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
@@ -0,0 +1,120 @@
1
+ import { nothing, type PropertyValues, type TemplateResult } from "lit";
2
+ import { PcElement } from "../../base";
3
+ import { PcPanel } from "../PcPanel";
4
+ import { type SideMode } from "../../layout";
5
+ /** Which way a pane's start, middle and end run */
6
+ export type PaneOrientation = "vertical" | "horizontal";
7
+ /** Which edge of the layout a pane is on: panels opened from its rail, and its floating box, go the other way */
8
+ export type PaneEdge = "left" | "right" | "top" | "bottom";
9
+ /** Event fired when a floating pane is shown or collapsed (detail: `{ collapsed }`) */
10
+ export declare const PANE_COLLAPSE_EVENT = "pc-collapse";
11
+ /**
12
+ * An area where buttons and panels are shown or docked, e.g. a side of the `<paint-canvas>` layout. Its content goes in
13
+ * its `start`, `middle` and `end` slots (content without a slot goes at the start), which run down a `vertical` pane
14
+ * (the default) or across a `horizontal` one; the middle takes the space left.
15
+ *
16
+ * Its `mode` sets how it shows its panels (`PcPanel`s, e.g. `<pc-panel-layers>`):
17
+ * - `docked`: as they are.
18
+ * - `rail`: as a column of buttons (with each panel's icon, name and badge) that open one panel at a time over the
19
+ * canvas, beside the pane (away from its `edge`). Focus moves into the panel; Escape, a press outside it or its
20
+ * button again closes it, and focus goes back to the button.
21
+ * - `floating`: the pane floats over the canvas as a box that collapses to a button (`collapsed`, named with its
22
+ * `label`), firing `pc-collapse` when it's shown or collapsed.
23
+ * - `overlay`: a rail over the canvas, without a background: each button has its own, and presses between them reach
24
+ * the canvas. Its panels stay solid.
25
+ *
26
+ * Panes made for one purpose extend it and put their own content in its regions (see `renderStart`, `renderMiddle`
27
+ * and `renderEnd`), e.g. `<pc-toolbar>`.
28
+ *
29
+ * @example
30
+ * html`<pc-pane edge="right" mode="rail" label="Panels"><pc-panel-layers .app=${app}></pc-panel-layers></pc-pane>`
31
+ */
32
+ export declare class PcPane extends PcElement {
33
+ static properties: {
34
+ orientation: {
35
+ type: StringConstructor;
36
+ reflect: boolean;
37
+ };
38
+ mode: {
39
+ type: StringConstructor;
40
+ reflect: boolean;
41
+ };
42
+ edge: {
43
+ type: StringConstructor;
44
+ reflect: boolean;
45
+ };
46
+ label: {
47
+ type: StringConstructor;
48
+ };
49
+ collapsed: {
50
+ type: BooleanConstructor;
51
+ reflect: boolean;
52
+ };
53
+ openPanel: {
54
+ state: boolean;
55
+ };
56
+ translations: {
57
+ attribute: boolean;
58
+ };
59
+ icons: {
60
+ attribute: boolean;
61
+ };
62
+ theme: {
63
+ type: StringConstructor;
64
+ reflect: boolean;
65
+ };
66
+ };
67
+ static styles: import("lit").CSSResultGroup[];
68
+ /** Which way its start, middle and end run (`vertical` by default) */
69
+ orientation: PaneOrientation;
70
+ /** How it shows its panels (`docked` by default) */
71
+ mode: SideMode;
72
+ /** Which edge of the layout it's on (`left` by default) */
73
+ edge: PaneEdge;
74
+ /** Names it, e.g. on its button when floating and collapsed */
75
+ label: string;
76
+ /** Whether it's collapsed to a button while floating (true until opened) */
77
+ collapsed: boolean;
78
+ /** The panel open from its rail, if any */
79
+ openPanel?: PcPanel;
80
+ /** The panels in it */
81
+ private panels;
82
+ /** The rail button that opened the open panel, to return focus to */
83
+ private opener?;
84
+ private readonly panelObserver;
85
+ private readonly onDocumentPointerDown;
86
+ private readonly onKeyDown;
87
+ constructor();
88
+ connectedCallback(): void;
89
+ disconnectedCallback(): void;
90
+ /**
91
+ * Open a panel over the canvas from its rail button (closing any other), or close it if it's open. Focus moves into
92
+ * the panel: to the first thing in it that takes focus, or the panel itself.
93
+ *
94
+ * @example
95
+ * pane.togglePanel(pane.querySelector("pc-panel-layers"));
96
+ */
97
+ togglePanel(panel: PcPanel, opener?: HTMLElement): void;
98
+ /**
99
+ * Close the panel open from the rail, moving focus back to its rail button if asked.
100
+ */
101
+ closePanel(returnFocus: boolean): void;
102
+ /** Show or collapse the floating pane, telling listeners */
103
+ private toggleCollapsed;
104
+ /** Follow the panels slotted into it */
105
+ private onSlotChange;
106
+ /** Show the panels for the mode: hidden behind the rail (except the open one, beside the pane) or as they are */
107
+ private syncPanels;
108
+ protected updated(changed: PropertyValues<this>): void;
109
+ /** Content of its own at its start, before what's slotted there */
110
+ protected renderStart(): TemplateResult | typeof nothing;
111
+ /** Content of its own in its middle, before what's slotted there */
112
+ protected renderMiddle(): TemplateResult | typeof nothing;
113
+ /** Content of its own at its end, before what's slotted there */
114
+ protected renderEnd(): TemplateResult | typeof nothing;
115
+ /** The rail: a button per panel */
116
+ private renderRail;
117
+ /** The button that shows or collapses a floating pane */
118
+ private renderCollapse;
119
+ render(): TemplateResult<1>;
120
+ }
@@ -0,0 +1,394 @@
1
+ import { PcElement, defineElement, emit } from "../../base/index.js";
2
+ import { markDismissingPress } from "../../dismiss/index.js";
3
+ import { PcPanel } from "../PcPanel/index.js";
4
+ import { showsRail } from "../../layout/index.js";
5
+ import { css, html, nothing } from "lit";
6
+ // src/ui/elements/PcPane/index.ts
7
+ /** Event fired when a floating pane is shown or collapsed (detail: `{ collapsed }`) */
8
+ var PANE_COLLAPSE_EVENT = "pc-collapse";
9
+ /** Elements that can take keyboard focus */
10
+ var FOCUSABLE = "button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])";
11
+ /**
12
+ * An area where buttons and panels are shown or docked, e.g. a side of the `<paint-canvas>` layout. Its content goes in
13
+ * its `start`, `middle` and `end` slots (content without a slot goes at the start), which run down a `vertical` pane
14
+ * (the default) or across a `horizontal` one; the middle takes the space left.
15
+ *
16
+ * Its `mode` sets how it shows its panels (`PcPanel`s, e.g. `<pc-panel-layers>`):
17
+ * - `docked`: as they are.
18
+ * - `rail`: as a column of buttons (with each panel's icon, name and badge) that open one panel at a time over the
19
+ * canvas, beside the pane (away from its `edge`). Focus moves into the panel; Escape, a press outside it or its
20
+ * button again closes it, and focus goes back to the button.
21
+ * - `floating`: the pane floats over the canvas as a box that collapses to a button (`collapsed`, named with its
22
+ * `label`), firing `pc-collapse` when it's shown or collapsed.
23
+ * - `overlay`: a rail over the canvas, without a background: each button has its own, and presses between them reach
24
+ * the canvas. Its panels stay solid.
25
+ *
26
+ * Panes made for one purpose extend it and put their own content in its regions (see `renderStart`, `renderMiddle`
27
+ * and `renderEnd`), e.g. `<pc-toolbar>`.
28
+ *
29
+ * @example
30
+ * html`<pc-pane edge="right" mode="rail" label="Panels"><pc-panel-layers .app=${app}></pc-panel-layers></pc-pane>`
31
+ */
32
+ var PcPane = class extends PcElement {
33
+ static properties = {
34
+ ...PcElement.properties,
35
+ orientation: {
36
+ type: String,
37
+ reflect: true
38
+ },
39
+ mode: {
40
+ type: String,
41
+ reflect: true
42
+ },
43
+ edge: {
44
+ type: String,
45
+ reflect: true
46
+ },
47
+ label: { type: String },
48
+ collapsed: {
49
+ type: Boolean,
50
+ reflect: true
51
+ },
52
+ openPanel: { state: true }
53
+ };
54
+ static styles = [PcElement.styles, css`
55
+ :host {
56
+ position: relative;
57
+ display: flex;
58
+ box-sizing: border-box;
59
+ min-width: 0;
60
+ min-height: 0;
61
+ font-family: var(--pc-font);
62
+ font-size: var(--pc-font-size);
63
+ }
64
+ :host([edge="right"]) {
65
+ flex-direction: row-reverse;
66
+ }
67
+ .regions {
68
+ display: flex;
69
+ flex: 1;
70
+ flex-direction: column;
71
+ min-width: 0;
72
+ min-height: 0;
73
+ }
74
+ :host([orientation="horizontal"]) .regions {
75
+ flex-direction: row;
76
+ }
77
+ .start,
78
+ .middle,
79
+ .end {
80
+ display: flex;
81
+ flex-direction: inherit;
82
+ }
83
+ /* The middle takes the space left, and gives it up first; the start and end keep their content's size */
84
+ .middle {
85
+ flex: 1;
86
+ min-width: 0;
87
+ min-height: 0;
88
+ }
89
+ .regions[hidden] {
90
+ display: none;
91
+ }
92
+
93
+ /* Rail: a column of buttons, one per panel, on the side facing the canvas */
94
+ .rail {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: var(--pc-space-1);
98
+ padding: var(--pc-space-1);
99
+ border-inline: 1px solid var(--pc-border);
100
+ background: var(--pc-bar);
101
+ }
102
+ /* The same size as the toolbars' buttons (borders included) */
103
+ .rail button {
104
+ position: relative;
105
+ display: inline-flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ box-sizing: border-box;
109
+ width: var(--pc-side-button-size);
110
+ height: var(--pc-side-button-size);
111
+ padding: 0;
112
+ border: 1px solid transparent;
113
+ border-radius: var(--pc-radius-small);
114
+ background: transparent;
115
+ cursor: pointer;
116
+ }
117
+ .rail button:hover {
118
+ background: var(--pc-surface-hover);
119
+ }
120
+ .rail button[aria-expanded="true"] {
121
+ border-color: var(--pc-accent);
122
+ background: var(--pc-accent);
123
+ }
124
+ .rail button:focus-visible {
125
+ outline: var(--pc-focus-width) solid var(--pc-focus);
126
+ }
127
+ .rail img {
128
+ width: var(--pc-side-icon-size);
129
+ height: var(--pc-side-icon-size);
130
+ filter: var(--pc-icon-filter);
131
+ }
132
+ .rail .badge {
133
+ position: absolute;
134
+ top: -2px;
135
+ right: -2px;
136
+ width: 10px;
137
+ height: 10px;
138
+ border: 2px solid var(--pc-bar);
139
+ border-radius: 50%;
140
+ background: var(--pc-red);
141
+ }
142
+
143
+ /* Overlay: the rail over the canvas without a background, so only its buttons (each with its own) take presses */
144
+ :host([mode="overlay"]) {
145
+ pointer-events: none;
146
+ }
147
+ :host([mode="overlay"]) .rail {
148
+ border-inline: 0;
149
+ background: transparent;
150
+ }
151
+ :host([mode="overlay"]) .rail button,
152
+ :host([mode="overlay"]) ::slotted(*) {
153
+ pointer-events: auto;
154
+ }
155
+ :host([mode="overlay"]) .rail button {
156
+ border-color: var(--pc-border);
157
+ background: var(--pc-surface-raised);
158
+ box-shadow: var(--pc-shadow);
159
+ }
160
+ :host([mode="overlay"]) .rail button:hover {
161
+ background: var(--pc-surface-hover);
162
+ }
163
+ :host([mode="overlay"]) .rail button[aria-expanded="true"] {
164
+ border-color: var(--pc-accent);
165
+ background: var(--pc-accent);
166
+ }
167
+
168
+ /* Floating: the pane floats over the canvas as a box, and collapses to a button */
169
+ :host([mode="floating"]) {
170
+ position: absolute;
171
+ z-index: 5;
172
+ top: var(--pc-space-2);
173
+ bottom: var(--pc-space-2);
174
+ flex-direction: column;
175
+ gap: var(--pc-space-1);
176
+ max-width: var(--pc-floating-panel-width, min(360px, 85vw));
177
+ border-radius: var(--pc-radius);
178
+ background: var(--pc-surface-raised);
179
+ box-shadow: var(--pc-shadow);
180
+ overflow: hidden;
181
+ }
182
+ :host([mode="floating"]:not([edge="right"])) {
183
+ left: var(--pc-space-2);
184
+ }
185
+ :host([mode="floating"][edge="right"]) {
186
+ right: var(--pc-space-2);
187
+ }
188
+ :host([mode="floating"][collapsed]) {
189
+ bottom: auto;
190
+ }
191
+ .collapse {
192
+ align-self: flex-end;
193
+ min-width: var(--pc-control-size);
194
+ min-height: var(--pc-control-size);
195
+ border: 0;
196
+ background: transparent;
197
+ color: var(--pc-text);
198
+ font-size: 18px;
199
+ cursor: pointer;
200
+ }
201
+ .collapse:focus-visible {
202
+ outline: var(--pc-focus-width) solid var(--pc-focus);
203
+ }
204
+ `];
205
+ /** The panels in it */
206
+ panels = [];
207
+ /** The rail button that opened the open panel, to return focus to */
208
+ opener;
209
+ panelObserver = new MutationObserver(() => this.requestUpdate());
210
+ onDocumentPointerDown = (event) => {
211
+ const panel = this.openPanel;
212
+ if (!panel) return;
213
+ const path = event.composedPath();
214
+ if (path.includes(panel) || this.opener && path.includes(this.opener)) return;
215
+ markDismissingPress(event);
216
+ this.closePanel(false);
217
+ };
218
+ onKeyDown = (event) => {
219
+ if (event.key !== "Escape" || !this.openPanel) return;
220
+ event.preventDefault();
221
+ this.closePanel(true);
222
+ };
223
+ constructor() {
224
+ super();
225
+ this.orientation = "vertical";
226
+ this.mode = "docked";
227
+ this.edge = "left";
228
+ this.label = "";
229
+ this.collapsed = true;
230
+ }
231
+ connectedCallback() {
232
+ super.connectedCallback();
233
+ this.panelObserver.observe(this, {
234
+ subtree: true,
235
+ attributes: true,
236
+ attributeFilter: [
237
+ "icon",
238
+ "label",
239
+ "badge"
240
+ ]
241
+ });
242
+ this.addEventListener("keydown", this.onKeyDown);
243
+ document.addEventListener("pointerdown", this.onDocumentPointerDown, true);
244
+ }
245
+ disconnectedCallback() {
246
+ super.disconnectedCallback();
247
+ this.panelObserver.disconnect();
248
+ this.removeEventListener("keydown", this.onKeyDown);
249
+ document.removeEventListener("pointerdown", this.onDocumentPointerDown, true);
250
+ }
251
+ /**
252
+ * Open a panel over the canvas from its rail button (closing any other), or close it if it's open. Focus moves into
253
+ * the panel: to the first thing in it that takes focus, or the panel itself.
254
+ *
255
+ * @example
256
+ * pane.togglePanel(pane.querySelector("pc-panel-layers"));
257
+ */
258
+ togglePanel(panel, opener) {
259
+ if (this.openPanel === panel) {
260
+ this.closePanel(true);
261
+ return;
262
+ }
263
+ if (this.openPanel) this.closePanel(false);
264
+ this.openPanel = panel;
265
+ this.opener = opener;
266
+ this.syncPanels();
267
+ const focusable = panel.querySelector(FOCUSABLE);
268
+ if (focusable) focusable.focus();
269
+ else {
270
+ panel.tabIndex = -1;
271
+ panel.focus();
272
+ }
273
+ }
274
+ /**
275
+ * Close the panel open from the rail, moving focus back to its rail button if asked.
276
+ */
277
+ closePanel(returnFocus) {
278
+ if (!this.openPanel) return;
279
+ this.openPanel = void 0;
280
+ this.syncPanels();
281
+ if (returnFocus) this.opener?.focus();
282
+ this.opener = void 0;
283
+ }
284
+ /** Show or collapse the floating pane, telling listeners */
285
+ toggleCollapsed() {
286
+ this.collapsed = !this.collapsed;
287
+ emit(this, PANE_COLLAPSE_EVENT, { collapsed: this.collapsed });
288
+ }
289
+ /** Follow the panels slotted into it */
290
+ onSlotChange() {
291
+ this.panels = [...this.renderRoot.querySelectorAll("slot")].flatMap((slot) => slot.assignedElements({ flatten: true }).filter((element) => element instanceof PcPanel));
292
+ if (this.openPanel && !this.panels.includes(this.openPanel)) this.closePanel(false);
293
+ this.syncPanels();
294
+ this.requestUpdate();
295
+ }
296
+ /** Show the panels for the mode: hidden behind the rail (except the open one, beside the pane) or as they are */
297
+ syncPanels() {
298
+ const rail = showsRail(this.mode);
299
+ for (const panel of this.panels) {
300
+ panel.toggleAttribute("data-pc-rail", rail);
301
+ if (rail && panel === this.openPanel) panel.setAttribute("data-pc-open", this.edge);
302
+ else panel.removeAttribute("data-pc-open");
303
+ }
304
+ }
305
+ updated(changed) {
306
+ if (!changed.has("mode") && !changed.has("edge")) return;
307
+ if (!showsRail(this.mode)) this.closePanel(false);
308
+ this.syncPanels();
309
+ }
310
+ /** Content of its own at its start, before what's slotted there */
311
+ renderStart() {
312
+ return nothing;
313
+ }
314
+ /** Content of its own in its middle, before what's slotted there */
315
+ renderMiddle() {
316
+ return nothing;
317
+ }
318
+ /** Content of its own at its end, before what's slotted there */
319
+ renderEnd() {
320
+ return nothing;
321
+ }
322
+ /** The rail: a button per panel */
323
+ renderRail() {
324
+ if (!showsRail(this.mode) || this.panels.length === 0) return nothing;
325
+ return html`<div
326
+ class="rail"
327
+ part="rail"
328
+ role="toolbar"
329
+ aria-orientation="vertical"
330
+ aria-label=${this.label || nothing}
331
+ >
332
+ ${this.panels.map((panel) => {
333
+ const label = panel.panelLabel;
334
+ return html`<button
335
+ type="button"
336
+ title=${label}
337
+ aria-label=${panel.badge ? this.t("paintCanvas.layout.new", { name: label }) : label}
338
+ aria-expanded=${String(this.openPanel === panel)}
339
+ @click=${(event) => this.togglePanel(panel, event.currentTarget)}
340
+ >
341
+ <img src=${this.iconUrl(panel.panelIcon)} alt="" />
342
+ ${panel.badge ? html`<span class="badge"></span>` : nothing}
343
+ </button>`;
344
+ })}
345
+ </div>`;
346
+ }
347
+ /** The button that shows or collapses a floating pane */
348
+ renderCollapse() {
349
+ if (this.mode !== "floating") return nothing;
350
+ const collapsed = this.collapsed;
351
+ const arrows = this.edge === "right" ? ["«", "»"] : ["»", "«"];
352
+ return html`<button
353
+ type="button"
354
+ class="collapse"
355
+ part="collapse"
356
+ aria-expanded=${String(!collapsed)}
357
+ aria-label=${this.t(collapsed ? "paintCanvas.layout.show" : "paintCanvas.layout.hide", { name: this.label })}
358
+ @click=${this.toggleCollapsed}
359
+ >
360
+ ${collapsed ? arrows[0] : arrows[1]}
361
+ </button>`;
362
+ }
363
+ render() {
364
+ return html`${this.renderCollapse()}
365
+ <div
366
+ class="regions"
367
+ part="regions"
368
+ ?hidden=${this.mode === "floating" && this.collapsed}
369
+ >
370
+ <div class="start" part="start">
371
+ ${this.renderStart()}<slot
372
+ name="start"
373
+ @slotchange=${this.onSlotChange}
374
+ ></slot
375
+ ><slot @slotchange=${this.onSlotChange}></slot>
376
+ </div>
377
+ <div class="middle" part="middle">
378
+ ${this.renderMiddle()}<slot
379
+ name="middle"
380
+ @slotchange=${this.onSlotChange}
381
+ ></slot>
382
+ </div>
383
+ <div class="end" part="end">
384
+ ${this.renderEnd()}<slot
385
+ name="end"
386
+ @slotchange=${this.onSlotChange}
387
+ ></slot>
388
+ </div>
389
+ </div>
390
+ ${this.renderRail()}`;
391
+ }
392
+ };
393
+ defineElement("pc-pane", PcPane);
394
+ export { PANE_COLLAPSE_EVENT, PcPane };
@@ -1,12 +1,20 @@
1
+ import { nothing, type TemplateResult } from "lit";
1
2
  import { PcElement } from "../../base";
2
3
  /**
3
- * Wraps content docked at a side of the `<paint-canvas>` layout (e.g. the layers menu or a chat window). Its `icon`,
4
- * `label` and `badge` are what the layout shows for it when the side narrows to a rail of buttons; the badge is a dot
5
- * meaning something new (e.g. unread messages). It names its region for screen readers. It's at most 290px wide
6
- * (`--pc-panel-max-width`).
4
+ * A panel: a box of settings or content for one purpose, docked in a `<pc-pane>` or floating over the canvas. It has
5
+ * `header`, `body` and `footer` slots; content without a slot goes in the body. It names its region with its `label`
6
+ * for screen readers.
7
+ *
8
+ * Its `icon`, `label` and `badge` are what a pane shows for it when the pane narrows to a rail of buttons; the badge is
9
+ * a dot meaning something new (e.g. unread messages). It's 290px wide (`--pc-panel-width`), and in every layout it's
10
+ * never wider than 290px (`--pc-panel-max-width`) or the space it has.
11
+ *
12
+ * Panels made for one purpose extend `PcPanel`, so they share its look: e.g. `<pc-panel-layers>`,
13
+ * `<pc-panel-brushes>` and `<pc-panel-colours>`. They put their own content in the body (see `renderBody`), and give
14
+ * their own `defaultLabel` and `defaultIcon`.
7
15
  *
8
16
  * @example
9
- * html`<pc-panel slot="right" icon="layers" label="Layers"><my-layers></my-layers></pc-panel>`
17
+ * html`<pc-panel icon="chat" label="Chat"><my-chat></my-chat></pc-panel>`
10
18
  */
11
19
  export declare class PcPanel extends PcElement {
12
20
  static properties: {
@@ -32,12 +40,22 @@ export declare class PcPanel extends PcElement {
32
40
  };
33
41
  };
34
42
  static styles: import("lit").CSSResultGroup[];
35
- /** Name of the icon for its rail button */
43
+ /** Name of the icon for its rail button (`defaultIcon` if not given) */
36
44
  icon: string;
37
- /** Names the panel (its rail button and region) */
45
+ /** Names the panel: its region and rail button (`defaultLabel` if not given) */
38
46
  label: string;
39
47
  /** Whether there's something new in it */
40
48
  badge: boolean;
41
49
  constructor();
42
- render(): import("lit").TemplateResult<1>;
50
+ /** The icon a panel made for a purpose shows on its rail button when none is given */
51
+ protected get defaultIcon(): string;
52
+ /** The name a panel made for a purpose has when none is given, in the chosen language */
53
+ protected get defaultLabel(): string;
54
+ /** The icon shown on its rail button */
55
+ get panelIcon(): string;
56
+ /** The panel's name, for its region and rail button */
57
+ get panelLabel(): string;
58
+ /** The panel's own body content, before what's slotted into its body (none for a plain `<pc-panel>`) */
59
+ protected renderBody(): TemplateResult | typeof nothing;
60
+ render(): TemplateResult<1>;
43
61
  }
@@ -2,13 +2,20 @@ import { PcElement, defineElement } from "../../base/index.js";
2
2
  import { css, html, nothing } from "lit";
3
3
  // src/ui/elements/PcPanel/index.ts
4
4
  /**
5
- * Wraps content docked at a side of the `<paint-canvas>` layout (e.g. the layers menu or a chat window). Its `icon`,
6
- * `label` and `badge` are what the layout shows for it when the side narrows to a rail of buttons; the badge is a dot
7
- * meaning something new (e.g. unread messages). It names its region for screen readers. It's at most 290px wide
8
- * (`--pc-panel-max-width`).
5
+ * A panel: a box of settings or content for one purpose, docked in a `<pc-pane>` or floating over the canvas. It has
6
+ * `header`, `body` and `footer` slots; content without a slot goes in the body. It names its region with its `label`
7
+ * for screen readers.
8
+ *
9
+ * Its `icon`, `label` and `badge` are what a pane shows for it when the pane narrows to a rail of buttons; the badge is
10
+ * a dot meaning something new (e.g. unread messages). It's 290px wide (`--pc-panel-width`), and in every layout it's
11
+ * never wider than 290px (`--pc-panel-max-width`) or the space it has.
12
+ *
13
+ * Panels made for one purpose extend `PcPanel`, so they share its look: e.g. `<pc-panel-layers>`,
14
+ * `<pc-panel-brushes>` and `<pc-panel-colours>`. They put their own content in the body (see `renderBody`), and give
15
+ * their own `defaultLabel` and `defaultIcon`.
9
16
  *
10
17
  * @example
11
- * html`<pc-panel slot="right" icon="layers" label="Layers"><my-layers></my-layers></pc-panel>`
18
+ * html`<pc-panel icon="chat" label="Chat"><my-chat></my-chat></pc-panel>`
12
19
  */
13
20
  var PcPanel = class extends PcElement {
14
21
  static properties = {
@@ -23,10 +30,38 @@ var PcPanel = class extends PcElement {
23
30
  static styles = [PcElement.styles, css`
24
31
  :host {
25
32
  display: flex;
33
+ box-sizing: border-box;
34
+ width: var(--pc-panel-width, var(--pc-panel-default-width, 290px));
26
35
  min-width: 0;
27
- max-width: var(--pc-panel-max-width, 290px);
36
+ max-width: min(100%, var(--pc-panel-max-width, 290px));
28
37
  min-height: 0;
38
+ max-height: 100%;
29
39
  flex-direction: column;
40
+ /* Light grey, like the bars */
41
+ background: var(--pc-bar);
42
+ color: var(--pc-text);
43
+ box-shadow: var(--pc-shadow);
44
+ font-family: var(--pc-font);
45
+ font-size: var(--pc-font-size);
46
+ }
47
+ /* In a pane's rail (set by the pane): hidden until opened from its button, then over the canvas beside the pane,
48
+ as tall as its content needs (at most 90% of the window's height, and never taller than the pane) */
49
+ :host([data-pc-rail]:not([data-pc-open])) {
50
+ display: none;
51
+ }
52
+ :host([data-pc-open]) {
53
+ position: absolute;
54
+ z-index: 5;
55
+ top: 0;
56
+ /* Its usual width, kept within the window (the pane it opens from is narrower than it) */
57
+ max-width: min(var(--pc-panel-max-width, 290px), 85vw);
58
+ max-height: min(90vh, 100%);
59
+ }
60
+ :host([data-pc-open="left"]) {
61
+ left: 100%;
62
+ }
63
+ :host([data-pc-open="right"]) {
64
+ right: 100%;
30
65
  }
31
66
  section {
32
67
  display: flex;
@@ -34,6 +69,15 @@ var PcPanel = class extends PcElement {
34
69
  min-height: 0;
35
70
  flex-direction: column;
36
71
  }
72
+ .body {
73
+ display: flex;
74
+ flex: 1;
75
+ min-height: 0;
76
+ flex-direction: column;
77
+ gap: var(--pc-space-2);
78
+ padding: var(--pc-space-2);
79
+ overflow: auto;
80
+ }
37
81
  `];
38
82
  constructor() {
39
83
  super();
@@ -41,9 +85,33 @@ var PcPanel = class extends PcElement {
41
85
  this.label = "";
42
86
  this.badge = false;
43
87
  }
88
+ /** The icon a panel made for a purpose shows on its rail button when none is given */
89
+ get defaultIcon() {
90
+ return "";
91
+ }
92
+ /** The name a panel made for a purpose has when none is given, in the chosen language */
93
+ get defaultLabel() {
94
+ return "";
95
+ }
96
+ /** The icon shown on its rail button */
97
+ get panelIcon() {
98
+ return this.icon || this.defaultIcon;
99
+ }
100
+ /** The panel's name, for its region and rail button */
101
+ get panelLabel() {
102
+ return this.label || this.defaultLabel;
103
+ }
104
+ /** The panel's own body content, before what's slotted into its body (none for a plain `<pc-panel>`) */
105
+ renderBody() {
106
+ return nothing;
107
+ }
44
108
  render() {
45
- return html`<section part="panel" aria-label=${this.label || nothing}>
46
- <slot></slot>
109
+ return html`<section part="panel" aria-label=${this.panelLabel || nothing}>
110
+ <slot name="header"></slot>
111
+ <div class="body" part="body">
112
+ ${this.renderBody()}<slot name="body"><slot></slot></slot>
113
+ </div>
114
+ <slot name="footer"></slot>
47
115
  </section>`;
48
116
  }
49
117
  };