@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.
- package/README.md +30 -15
- package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
- package/dist/lib/brushes/StrokeEngine/index.js +26 -28
- package/dist/lib/brushes/presets/round/index.d.ts +1 -1
- package/dist/lib/brushes/presets/round/index.js +12 -7
- package/dist/lib/brushes/presets/square/index.d.ts +1 -1
- package/dist/lib/brushes/presets/square/index.js +7 -3
- package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
- package/dist/lib/brushes/shapeImage/index.js +15 -9
- package/dist/lib/brushes/shapes/index.d.ts +18 -0
- package/dist/lib/brushes/shapes/index.js +86 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +53 -2
- package/dist/lib/classes/BrushSettings/index.js +85 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +47 -7
- package/dist/lib/classes/KeyboardShortcuts/index.js +145 -20
- package/dist/lib/classes/PaintCanvas/index.d.ts +123 -27
- package/dist/lib/classes/PaintCanvas/index.js +208 -41
- package/dist/lib/classes/PaintCanvas/tool.d.ts +15 -0
- package/dist/lib/classes/PaintCanvas/tool.js +18 -0
- package/dist/lib/classes/Palette/index.d.ts +58 -0
- package/dist/lib/classes/Palette/index.js +92 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +53 -0
- package/dist/lib/classes/SettingsAutosave/index.js +98 -0
- package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
- package/dist/lib/classes/ShortcutSettings/index.js +110 -0
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/palette/index.d.ts +49 -0
- package/dist/lib/palette/index.js +82 -0
- package/dist/lib/palette/schema.d.ts +45 -0
- package/dist/lib/palette/schema.js +41 -0
- package/dist/lib/rendering/cursors/index.d.ts +2 -0
- package/dist/lib/rendering/cursors/index.js +3 -1
- package/dist/lib/rendering/sample/index.js +3 -2
- package/dist/lib/settings/index.d.ts +63 -0
- package/dist/lib/settings/index.js +93 -0
- package/dist/lib/settings/schema.d.ts +168 -0
- package/dist/lib/settings/schema.js +116 -0
- package/dist/lib/storage/index.d.ts +36 -5
- package/dist/lib/storage/index.js +87 -29
- package/dist/lib/utils/colour/index.d.ts +72 -0
- package/dist/lib/utils/colour/index.js +154 -0
- package/dist/lib/utils/shortcuts/index.d.ts +174 -11
- package/dist/lib/utils/shortcuts/index.js +391 -145
- package/dist/lib/validation/validators.generated.d.ts +5 -0
- package/dist/lib/validation/validators.generated.js +1040 -1
- package/dist/locales/en-GB.js +96 -3
- package/dist/locales/en-US.js +96 -3
- package/dist/locales/fr.js +96 -3
- package/dist/locales/ja.js +96 -3
- package/dist/locales/ko.js +96 -3
- package/dist/locales/zh-Hans.js +96 -3
- package/dist/locales/zh-Hant.js +96 -3
- package/dist/package.js +1 -1
- package/dist/testing/recordingCanvas.js +2 -1
- package/dist/ui/base/index.d.ts +2 -2
- package/dist/ui/dismiss/index.d.ts +21 -0
- package/dist/ui/dismiss/index.js +27 -0
- package/dist/ui/drag/index.d.ts +51 -0
- package/dist/ui/drag/index.js +81 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +57 -50
- package/dist/ui/elements/PaintCanvas/index.js +141 -174
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +59 -0
- package/dist/ui/elements/PcAppSettings/index.js +222 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +9 -2
- package/dist/ui/elements/PcBrushConfig/index.js +47 -7
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
- package/dist/ui/elements/PcBrushSize/index.js +66 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
- package/dist/ui/elements/PcBrushTest/index.js +73 -5
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcCanvas/index.d.ts +22 -2
- package/dist/ui/elements/PcCanvas/index.js +60 -15
- package/dist/ui/elements/PcCanvasMap/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +48 -0
- package/dist/ui/elements/PcClipboardTools/index.js +118 -0
- package/dist/ui/elements/PcColourPair/index.d.ts +43 -0
- package/dist/ui/elements/PcColourPair/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +71 -34
- package/dist/ui/elements/PcContextMenu/index.js +128 -97
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcLayerItem/index.d.ts +13 -34
- package/dist/ui/elements/PcLayerItem/index.js +39 -91
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcMenu/index.d.ts +138 -0
- package/dist/ui/elements/PcMenu/index.js +296 -0
- package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
- package/dist/ui/elements/PcNumberSlider/index.js +247 -0
- package/dist/ui/elements/PcPane/index.d.ts +120 -0
- package/dist/ui/elements/PcPane/index.js +394 -0
- package/dist/ui/elements/PcPanel/index.d.ts +26 -8
- package/dist/ui/elements/PcPanel/index.js +76 -8
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.d.ts +22 -21
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +24 -57
- package/dist/ui/elements/PcPanelBrushes/style.js +3 -0
- package/dist/ui/elements/PcPanelColours/index.d.ts +171 -0
- package/dist/ui/elements/PcPanelColours/index.js +650 -0
- package/dist/ui/elements/PcPanelColours/style.js +3 -0
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.d.ts +17 -4
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.js +17 -10
- package/dist/ui/elements/PcPanelLayers/style.js +3 -0
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- package/dist/ui/elements/PcShortcutSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
- package/dist/ui/elements/PcThemePicker/index.js +22 -21
- package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
- package/dist/ui/elements/PcToolSettings/index.js +384 -319
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +62 -12
- package/dist/ui/elements/PcToolbar/index.js +119 -57
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/icons/images/icon-brush@2x.js +3 -0
- package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
- package/dist/ui/icons/images/icon-cog@2x.js +3 -0
- package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
- package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
- package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
- package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
- package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-layers@2x.js +3 -0
- package/dist/ui/icons/images/icon-linked@2x.js +3 -0
- package/dist/ui/icons/images/icon-locked@2x.js +3 -0
- package/dist/ui/icons/images/icon-moon@2x.js +3 -0
- package/dist/ui/icons/images/icon-pan@2x.js +3 -0
- package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
- package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
- package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-redo@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
- package/dist/ui/icons/images/icon-star@2x.js +3 -0
- package/dist/ui/icons/images/icon-sun@2x.js +3 -0
- package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
- package/dist/ui/icons/images/icon-undo@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
- package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-visible@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +84 -72
- package/dist/ui/index.d.ts +13 -2
- package/dist/ui/index.js +16 -4
- package/dist/ui/layout/index.d.ts +9 -1
- package/dist/ui/layout/index.js +11 -2
- package/dist/ui/tokens/index.d.ts +5 -4
- package/dist/ui/tokens/index.js +15 -8
- package/locales/en-GB.json +96 -3
- package/locales/en-US.json +96 -3
- package/locales/fr.json +96 -3
- package/locales/ja.json +96 -3
- package/locales/ko.json +96 -3
- package/locales/zh-Hans.json +96 -3
- package/locales/zh-Hant.json +96 -3
- package/package.json +11 -4
- package/dist/ui/elements/PcBrushMenu/style.js +0 -3
- package/dist/ui/elements/PcLayersMenu/style.js +0 -3
- package/dist/ui/icons/images/brush.js +0 -3
- package/dist/ui/icons/images/circle-mask.js +0 -3
- package/dist/ui/icons/images/clipboard.js +0 -3
- package/dist/ui/icons/images/cursor.js +0 -3
- package/dist/ui/icons/images/eraser.js +0 -3
- package/dist/ui/icons/images/eyedropper.js +0 -3
- package/dist/ui/icons/images/flip-horizontal.js +0 -3
- package/dist/ui/icons/images/flip-vertical.js +0 -3
- package/dist/ui/icons/images/focus-pen.js +0 -3
- package/dist/ui/icons/images/freehand-mask.js +0 -3
- package/dist/ui/icons/images/hidden.js +0 -3
- package/dist/ui/icons/images/layers.js +0 -3
- package/dist/ui/icons/images/linked.js +0 -3
- package/dist/ui/icons/images/locked.js +0 -3
- package/dist/ui/icons/images/moon.js +0 -3
- package/dist/ui/icons/images/pan.js +0 -3
- package/dist/ui/icons/images/polygon-mask.js +0 -3
- package/dist/ui/icons/images/pressure.js +0 -3
- package/dist/ui/icons/images/randomise.js +0 -3
- package/dist/ui/icons/images/rectangle-mask.js +0 -3
- package/dist/ui/icons/images/redo.js +0 -3
- package/dist/ui/icons/images/rotate-90-left.js +0 -3
- package/dist/ui/icons/images/rotate-90-right.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
- package/dist/ui/icons/images/smoothing-linear.js +0 -3
- package/dist/ui/icons/images/star.js +0 -3
- package/dist/ui/icons/images/sun.js +0 -3
- package/dist/ui/icons/images/tilt.js +0 -3
- package/dist/ui/icons/images/undo.js +0 -3
- package/dist/ui/icons/images/unlinked.js +0 -3
- package/dist/ui/icons/images/unlocked.js +0 -3
- package/dist/ui/icons/images/visible.js +0 -3
- package/dist/ui/icons/images/zoom-in.js +0 -3
- package/dist/ui/icons/images/zoom-out.js +0 -3
- package/dist/ui/icons/images/zoom.js +0 -3
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { PcElement, defineElement, emit } from "../../base/index.js";
|
|
2
|
+
import ChangeController from "../../ChangeController/index.js";
|
|
3
|
+
import { css, html } from "lit";
|
|
4
|
+
import { live } from "lit/directives/live.js";
|
|
5
|
+
// src/ui/elements/PcColourPair/index.ts
|
|
6
|
+
/** Event fired when the foreground or background colour is pressed (detail: `{ slot }`) */
|
|
7
|
+
var COLOUR_CLICK_EVENT = "pc-colour-click";
|
|
8
|
+
/** The two colours, foreground first */
|
|
9
|
+
var SLOTS = ["foreground", "background"];
|
|
10
|
+
/**
|
|
11
|
+
* The foreground and background colours as two overlapping circles in a square (the foreground at the top left), the
|
|
12
|
+
* one selected for configuration outlined and on top. Pressing either fires `pc-colour-click` with its `slot`; the
|
|
13
|
+
* implementer decides what it does (e.g. select it and open a colour palette). `openPicker()` opens the browser's colour
|
|
14
|
+
* picker for the selected colour, which sets it. Sized by `--pc-colour-pair-size` (50px by default).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* html`<pc-colour-pair .app=${app} @pc-colour-click=${(e) => app.selectColour(e.detail.slot)}></pc-colour-pair>`
|
|
18
|
+
*/
|
|
19
|
+
var PcColourPair = class extends PcElement {
|
|
20
|
+
static properties = {
|
|
21
|
+
...PcElement.properties,
|
|
22
|
+
app: { attribute: false }
|
|
23
|
+
};
|
|
24
|
+
static styles = [PcElement.styles, css`
|
|
25
|
+
:host {
|
|
26
|
+
--size: var(--pc-colour-pair-size, 50px);
|
|
27
|
+
position: relative;
|
|
28
|
+
display: block;
|
|
29
|
+
flex: none;
|
|
30
|
+
width: var(--size);
|
|
31
|
+
height: var(--size);
|
|
32
|
+
background: var(--pc-text-muted);
|
|
33
|
+
}
|
|
34
|
+
button {
|
|
35
|
+
position: absolute;
|
|
36
|
+
width: 56%;
|
|
37
|
+
height: 56%;
|
|
38
|
+
padding: 0;
|
|
39
|
+
border: 1px solid var(--pc-text);
|
|
40
|
+
border-radius: 50%;
|
|
41
|
+
background: var(--colour);
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
.foreground {
|
|
45
|
+
top: 10%;
|
|
46
|
+
left: 10%;
|
|
47
|
+
z-index: 1;
|
|
48
|
+
}
|
|
49
|
+
.background {
|
|
50
|
+
right: 10%;
|
|
51
|
+
bottom: 10%;
|
|
52
|
+
}
|
|
53
|
+
/* The selected colour: on top, with a white ring outlined in black */
|
|
54
|
+
button[aria-pressed="true"] {
|
|
55
|
+
z-index: 2;
|
|
56
|
+
box-shadow:
|
|
57
|
+
0 0 0 2px #ffffff,
|
|
58
|
+
0 0 0 3px #000000;
|
|
59
|
+
}
|
|
60
|
+
button:focus-visible {
|
|
61
|
+
outline: var(--pc-focus-width) solid var(--pc-focus);
|
|
62
|
+
outline-offset: 4px;
|
|
63
|
+
}
|
|
64
|
+
/* Kept in place (invisible) so the browser's colour picker opens beside the colours */
|
|
65
|
+
input {
|
|
66
|
+
position: absolute;
|
|
67
|
+
bottom: 0;
|
|
68
|
+
left: 0;
|
|
69
|
+
width: 1px;
|
|
70
|
+
height: 1px;
|
|
71
|
+
padding: 0;
|
|
72
|
+
border: 0;
|
|
73
|
+
opacity: 0;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
}
|
|
76
|
+
`];
|
|
77
|
+
changes = new ChangeController(this, () => [this.app]);
|
|
78
|
+
/**
|
|
79
|
+
* Open the browser's colour picker for the selected colour (call it while handling a press, as browsers only open
|
|
80
|
+
* pickers for one).
|
|
81
|
+
*/
|
|
82
|
+
openPicker() {
|
|
83
|
+
const input = this.renderRoot.querySelector("input");
|
|
84
|
+
if (!input) return;
|
|
85
|
+
try {
|
|
86
|
+
input.showPicker();
|
|
87
|
+
} catch {
|
|
88
|
+
input.click();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Tell listeners a colour was pressed */
|
|
92
|
+
onClick(slot) {
|
|
93
|
+
emit(this, COLOUR_CLICK_EVENT, { slot });
|
|
94
|
+
}
|
|
95
|
+
/** Set the selected colour from the browser's colour picker */
|
|
96
|
+
onPick(event) {
|
|
97
|
+
const app = this.app;
|
|
98
|
+
app?.setColour(event.target.value, app.selectedColour);
|
|
99
|
+
}
|
|
100
|
+
render() {
|
|
101
|
+
const app = this.app;
|
|
102
|
+
if (!app) return html``;
|
|
103
|
+
return html`${SLOTS.map((slot) => {
|
|
104
|
+
const colour = app.colours[slot];
|
|
105
|
+
return html`<button
|
|
106
|
+
type="button"
|
|
107
|
+
part=${slot}
|
|
108
|
+
class=${slot}
|
|
109
|
+
style="--colour: ${colour}"
|
|
110
|
+
title=${this.t(`paintCanvas.colours.${slot}`, { colour })}
|
|
111
|
+
aria-label=${this.t(`paintCanvas.colours.${slot}`, { colour })}
|
|
112
|
+
aria-pressed=${app.selectedColour === slot}
|
|
113
|
+
@click=${() => this.onClick(slot)}
|
|
114
|
+
></button>`;
|
|
115
|
+
})}<input
|
|
116
|
+
type="color"
|
|
117
|
+
tabindex="-1"
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
.value=${live(app.colours[app.selectedColour])}
|
|
120
|
+
@input=${this.onPick}
|
|
121
|
+
/>`;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
defineElement("pc-colour-pair", PcColourPair);
|
|
125
|
+
export { COLOUR_CLICK_EVENT, PcColourPair };
|
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
import { type PropertyValues } from "lit";
|
|
2
2
|
import { PcElement } from "../../base";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
import { PcMenu, type MenuEntry } from "../PcMenu";
|
|
4
|
+
import "../PcMenu";
|
|
5
|
+
/** Space kept between a context menu's menu and the window's edges */
|
|
6
|
+
export declare const MENU_MARGIN = 8;
|
|
7
|
+
/** Event fired when a context menu opens or closes (detail: `{ open }`) */
|
|
8
|
+
export declare const CONTEXT_MENU_TOGGLE_EVENT = "pc-toggle";
|
|
9
|
+
/** Where a context menu's menu opens: under its button, or beside it */
|
|
10
|
+
export type MenuPlacement = "below" | "beside";
|
|
11
|
+
/**
|
|
12
|
+
* Where a menu's top goes: level with its anchor, moved up as far as needed to stay in the window (but never above its
|
|
13
|
+
* top margin).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* menuTop(700, 300, 800); // 492 (moved up to fit)
|
|
17
|
+
* menuTop(100, 300, 800); // 100
|
|
18
|
+
*/
|
|
19
|
+
export declare const menuTop: (anchorTop: number, menuHeight: number, viewportHeight: number, margin?: number) => number;
|
|
20
|
+
/**
|
|
21
|
+
* Where a menu's left edge goes: at `left`, moved left as far as needed to stay in the window (but never past its left
|
|
22
|
+
* margin).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* menuLeft(900, 200, 1000); // 792 (moved left to fit)
|
|
26
|
+
*/
|
|
27
|
+
export declare const menuLeft: (left: number, menuWidth: number, viewportWidth: number, margin?: number) => number;
|
|
12
28
|
/**
|
|
13
|
-
* A
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
29
|
+
* A button that opens a menu (`pc-menu`): its `icon` slot is what the button shows (⋮ by default), and its `menu` slot
|
|
30
|
+
* takes the `pc-menu` to open. Without one, it opens its own menu of `items`. Pressing the button (or right-clicking
|
|
31
|
+
* it) opens the menu under it (or beside it, with `placement="beside"`), kept within the window; choosing an item,
|
|
32
|
+
* pressing outside, Escape, Tab, scrolling or pressing the button again closes it, and focus returns to the button.
|
|
33
|
+
*
|
|
34
|
+
* Fires `pc-toggle` (detail: `{ open }`) when it opens or closes. The menu's `pc-select` (detail: `{ value }`) reaches
|
|
35
|
+
* the context menu's listeners.
|
|
17
36
|
*
|
|
18
37
|
* @example
|
|
19
|
-
* html`<pc-context-menu
|
|
20
|
-
*
|
|
38
|
+
* html`<pc-context-menu label="Layer actions" .items=${items} @pc-select=${(e) => act(e.detail.value)}>
|
|
39
|
+
* <pc-icon slot="icon" name="menu"></pc-icon>
|
|
40
|
+
* </pc-context-menu>`
|
|
21
41
|
*/
|
|
22
42
|
export declare class PcContextMenu extends PcElement {
|
|
23
43
|
static properties: {
|
|
@@ -31,11 +51,16 @@ export declare class PcContextMenu extends PcElement {
|
|
|
31
51
|
label: {
|
|
32
52
|
type: StringConstructor;
|
|
33
53
|
};
|
|
34
|
-
|
|
35
|
-
type:
|
|
54
|
+
menuLabel: {
|
|
55
|
+
type: StringConstructor;
|
|
56
|
+
attribute: string;
|
|
57
|
+
};
|
|
58
|
+
placement: {
|
|
59
|
+
type: StringConstructor;
|
|
36
60
|
};
|
|
37
|
-
|
|
38
|
-
type:
|
|
61
|
+
disabled: {
|
|
62
|
+
type: BooleanConstructor;
|
|
63
|
+
reflect: boolean;
|
|
39
64
|
};
|
|
40
65
|
translations: {
|
|
41
66
|
attribute: boolean;
|
|
@@ -49,24 +74,36 @@ export declare class PcContextMenu extends PcElement {
|
|
|
49
74
|
};
|
|
50
75
|
};
|
|
51
76
|
static styles: import("lit").CSSResultGroup[];
|
|
77
|
+
/** Whether the menu is open */
|
|
52
78
|
open: boolean;
|
|
53
|
-
items
|
|
54
|
-
|
|
79
|
+
/** The items of its own menu, when no `pc-menu` is slotted */
|
|
80
|
+
items: readonly MenuEntry[];
|
|
81
|
+
/** Names the button for screen readers */
|
|
55
82
|
label: string;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
83
|
+
/** Names the menu for screen readers (the button's `label` by default) */
|
|
84
|
+
menuLabel: string;
|
|
85
|
+
/** Where the menu opens: under the button (default) or beside it */
|
|
86
|
+
placement: MenuPlacement;
|
|
87
|
+
disabled: boolean;
|
|
88
|
+
private readonly onScroll;
|
|
59
89
|
constructor();
|
|
60
|
-
connectedCallback(): void;
|
|
61
90
|
disconnectedCallback(): void;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
/** The menu it opens: the slotted `pc-menu`, or its own */
|
|
92
|
+
get menu(): PcMenu | undefined;
|
|
93
|
+
/** The button that opens the menu */
|
|
94
|
+
private get button();
|
|
95
|
+
/**
|
|
96
|
+
* Open or close the menu, telling listeners.
|
|
97
|
+
*/
|
|
98
|
+
setOpen(open: boolean): void;
|
|
99
|
+
/** Open the menu on a right-click (instead of the browser's), or close it */
|
|
100
|
+
private onContextMenu;
|
|
101
|
+
/** Close the menu when it asks to, and return focus to the button */
|
|
102
|
+
private onClose;
|
|
103
|
+
/** Set up a newly slotted menu like the current one (open or closed) */
|
|
104
|
+
private onMenuSlotChange;
|
|
105
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
106
|
+
/** Put the open menu under or beside the button, within the window */
|
|
107
|
+
private place;
|
|
71
108
|
render(): import("lit").TemplateResult<1>;
|
|
72
109
|
}
|
|
@@ -1,15 +1,41 @@
|
|
|
1
1
|
import { PcElement, defineElement, emit } from "../../base/index.js";
|
|
2
|
+
import { PcMenu } from "../PcMenu/index.js";
|
|
2
3
|
import { css, html, nothing } from "lit";
|
|
3
4
|
// src/ui/elements/PcContextMenu/index.ts
|
|
5
|
+
/** Space kept between a context menu's menu and the window's edges */
|
|
6
|
+
var MENU_MARGIN = 8;
|
|
7
|
+
/** Event fired when a context menu opens or closes (detail: `{ open }`) */
|
|
8
|
+
var CONTEXT_MENU_TOGGLE_EVENT = "pc-toggle";
|
|
4
9
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Fires `pc-select` with the chosen item's value, and `pc-close` when it should close (after choosing, Escape, Tab, or a
|
|
8
|
-
* press outside it). The implementer sets `open` to false in response.
|
|
10
|
+
* Where a menu's top goes: level with its anchor, moved up as far as needed to stay in the window (but never above its
|
|
11
|
+
* top margin).
|
|
9
12
|
*
|
|
10
13
|
* @example
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* menuTop(700, 300, 800); // 492 (moved up to fit)
|
|
15
|
+
* menuTop(100, 300, 800); // 100
|
|
16
|
+
*/
|
|
17
|
+
var menuTop = (anchorTop, menuHeight, viewportHeight, margin = 8) => Math.max(margin, Math.min(anchorTop, viewportHeight - margin - menuHeight));
|
|
18
|
+
/**
|
|
19
|
+
* Where a menu's left edge goes: at `left`, moved left as far as needed to stay in the window (but never past its left
|
|
20
|
+
* margin).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* menuLeft(900, 200, 1000); // 792 (moved left to fit)
|
|
24
|
+
*/
|
|
25
|
+
var menuLeft = (left, menuWidth, viewportWidth, margin = 8) => Math.max(margin, Math.min(left, viewportWidth - margin - menuWidth));
|
|
26
|
+
/**
|
|
27
|
+
* A button that opens a menu (`pc-menu`): its `icon` slot is what the button shows (⋮ by default), and its `menu` slot
|
|
28
|
+
* takes the `pc-menu` to open. Without one, it opens its own menu of `items`. Pressing the button (or right-clicking
|
|
29
|
+
* it) opens the menu under it (or beside it, with `placement="beside"`), kept within the window; choosing an item,
|
|
30
|
+
* pressing outside, Escape, Tab, scrolling or pressing the button again closes it, and focus returns to the button.
|
|
31
|
+
*
|
|
32
|
+
* Fires `pc-toggle` (detail: `{ open }`) when it opens or closes. The menu's `pc-select` (detail: `{ value }`) reaches
|
|
33
|
+
* the context menu's listeners.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* html`<pc-context-menu label="Layer actions" .items=${items} @pc-select=${(e) => act(e.detail.value)}>
|
|
37
|
+
* <pc-icon slot="icon" name="menu"></pc-icon>
|
|
38
|
+
* </pc-context-menu>`
|
|
13
39
|
*/
|
|
14
40
|
var PcContextMenu = class extends PcElement {
|
|
15
41
|
static properties = {
|
|
@@ -20,129 +46,134 @@ var PcContextMenu = class extends PcElement {
|
|
|
20
46
|
},
|
|
21
47
|
items: { attribute: false },
|
|
22
48
|
label: { type: String },
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
menuLabel: {
|
|
50
|
+
type: String,
|
|
51
|
+
attribute: "menu-label"
|
|
52
|
+
},
|
|
53
|
+
placement: { type: String },
|
|
54
|
+
disabled: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
reflect: true
|
|
57
|
+
}
|
|
25
58
|
};
|
|
26
59
|
static styles = [PcElement.styles, css`
|
|
27
60
|
:host {
|
|
28
|
-
|
|
29
|
-
z-index: 10;
|
|
30
|
-
display: none;
|
|
31
|
-
}
|
|
32
|
-
:host([open]) {
|
|
33
|
-
display: block;
|
|
34
|
-
}
|
|
35
|
-
[role="menu"] {
|
|
36
|
-
display: flex;
|
|
37
|
-
min-width: 160px;
|
|
38
|
-
flex-direction: column;
|
|
39
|
-
padding: var(--pc-space-1) 0;
|
|
40
|
-
border-radius: var(--pc-radius);
|
|
41
|
-
background: var(--pc-surface-raised);
|
|
42
|
-
box-shadow: var(--pc-shadow);
|
|
61
|
+
display: inline-flex;
|
|
43
62
|
}
|
|
44
63
|
button {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
display: inline-flex;
|
|
65
|
+
flex: 1;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
min-width: var(--pc-control-size);
|
|
69
|
+
min-height: var(--pc-control-size);
|
|
70
|
+
padding: 0 var(--pc-space-1);
|
|
71
|
+
border: 1px solid var(--pc-border);
|
|
72
|
+
border-radius: var(--pc-radius-small);
|
|
73
|
+
background: var(--pc-surface);
|
|
48
74
|
color: var(--pc-text);
|
|
49
|
-
font
|
|
50
|
-
font-size: var(--pc-font-size);
|
|
51
|
-
text-align: start;
|
|
75
|
+
font: inherit;
|
|
52
76
|
cursor: pointer;
|
|
53
77
|
}
|
|
54
78
|
button:hover:not(:disabled),
|
|
55
|
-
|
|
79
|
+
:host([open]) button {
|
|
56
80
|
background: var(--pc-surface-hover);
|
|
57
|
-
outline: none;
|
|
58
81
|
}
|
|
59
82
|
button:focus-visible {
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
button.danger {
|
|
63
|
-
color: var(--pc-dismiss-text);
|
|
83
|
+
outline: var(--pc-focus-width) solid var(--pc-focus);
|
|
64
84
|
}
|
|
65
85
|
button:disabled {
|
|
66
|
-
|
|
86
|
+
opacity: 0.4;
|
|
67
87
|
cursor: default;
|
|
68
88
|
}
|
|
69
89
|
`];
|
|
70
|
-
|
|
71
|
-
if (this.open && !event.composedPath().includes(this)) this.close();
|
|
72
|
-
};
|
|
90
|
+
onScroll = () => this.setOpen(false);
|
|
73
91
|
constructor() {
|
|
74
92
|
super();
|
|
75
93
|
this.open = false;
|
|
76
94
|
this.items = [];
|
|
77
95
|
this.label = "";
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
80
|
-
|
|
81
|
-
connectedCallback() {
|
|
82
|
-
super.connectedCallback();
|
|
83
|
-
document.addEventListener("pointerdown", this.onPointerDown, true);
|
|
96
|
+
this.menuLabel = "";
|
|
97
|
+
this.placement = "below";
|
|
98
|
+
this.disabled = false;
|
|
84
99
|
}
|
|
85
100
|
disconnectedCallback() {
|
|
86
101
|
super.disconnectedCallback();
|
|
87
|
-
|
|
102
|
+
window.removeEventListener("scroll", this.onScroll, true);
|
|
88
103
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.
|
|
92
|
-
if (changed.has("open") && this.open) this.buttons()[0]?.focus();
|
|
104
|
+
/** The menu it opens: the slotted `pc-menu`, or its own */
|
|
105
|
+
get menu() {
|
|
106
|
+
return this.renderRoot.querySelector("slot[name=\"menu\"]")?.assignedElements({ flatten: true }).find((element) => element instanceof PcMenu) ?? void 0;
|
|
93
107
|
}
|
|
94
|
-
/** The
|
|
95
|
-
|
|
96
|
-
return
|
|
108
|
+
/** The button that opens the menu */
|
|
109
|
+
get button() {
|
|
110
|
+
return this.renderRoot.querySelector("button");
|
|
97
111
|
}
|
|
98
|
-
/**
|
|
99
|
-
close
|
|
100
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Open or close the menu, telling listeners.
|
|
114
|
+
*/
|
|
115
|
+
setOpen(open) {
|
|
116
|
+
if (open === this.open || open && this.disabled) return;
|
|
117
|
+
this.open = open;
|
|
118
|
+
emit(this, CONTEXT_MENU_TOGGLE_EVENT, { open });
|
|
101
119
|
}
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.
|
|
120
|
+
/** Open the menu on a right-click (instead of the browser's), or close it */
|
|
121
|
+
onContextMenu(event) {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
this.setOpen(!this.open);
|
|
106
124
|
}
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
/** Close the menu when it asks to, and return focus to the button */
|
|
126
|
+
onClose() {
|
|
127
|
+
const focusWasInside = this.menu.matches(":focus-within");
|
|
128
|
+
this.setOpen(false);
|
|
129
|
+
if (focusWasInside) this.button.focus();
|
|
130
|
+
}
|
|
131
|
+
/** Set up a newly slotted menu like the current one (open or closed) */
|
|
132
|
+
onMenuSlotChange() {
|
|
133
|
+
this.requestUpdate("open", !this.open);
|
|
134
|
+
}
|
|
135
|
+
updated(changed) {
|
|
136
|
+
const menu = this.menu;
|
|
137
|
+
if (!menu) return;
|
|
138
|
+
menu.anchor = this;
|
|
139
|
+
menu.label ||= this.menuLabel || this.label;
|
|
140
|
+
if (!changed.has("open")) return;
|
|
141
|
+
window[this.open ? "addEventListener" : "removeEventListener"]("scroll", this.onScroll, true);
|
|
142
|
+
menu.open = this.open;
|
|
143
|
+
if (this.open) this.place(menu);
|
|
144
|
+
}
|
|
145
|
+
/** Put the open menu under or beside the button, within the window */
|
|
146
|
+
async place(menu) {
|
|
147
|
+
const anchor = this.button.getBoundingClientRect();
|
|
148
|
+
const beside = this.placement === "beside";
|
|
149
|
+
menu.x = beside ? anchor.right : anchor.left;
|
|
150
|
+
menu.y = beside ? anchor.top : anchor.bottom;
|
|
151
|
+
await menu.updateComplete;
|
|
152
|
+
menu.x = menuLeft(menu.x, menu.offsetWidth, window.innerWidth);
|
|
153
|
+
menu.y = menuTop(menu.y, menu.offsetHeight, window.innerHeight);
|
|
125
154
|
}
|
|
126
155
|
render() {
|
|
127
|
-
return html`<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
return html`<button
|
|
157
|
+
type="button"
|
|
158
|
+
part="button"
|
|
159
|
+
aria-haspopup="menu"
|
|
160
|
+
aria-expanded=${String(this.open)}
|
|
161
|
+
aria-label=${this.label || nothing}
|
|
162
|
+
?disabled=${this.disabled}
|
|
163
|
+
@click=${() => this.setOpen(!this.open)}
|
|
164
|
+
@contextmenu=${this.onContextMenu}
|
|
165
|
+
>
|
|
166
|
+
<slot name="icon">⋮</slot></button
|
|
167
|
+
><slot
|
|
168
|
+
name="menu"
|
|
169
|
+
@slotchange=${this.onMenuSlotChange}
|
|
170
|
+
@pc-close=${this.onClose}
|
|
171
|
+
>${this.items.length ? html`<pc-menu
|
|
172
|
+
.items=${this.items}
|
|
173
|
+
label=${this.menuLabel || this.label}
|
|
174
|
+
></pc-menu>` : nothing}</slot
|
|
175
|
+
>`;
|
|
145
176
|
}
|
|
146
177
|
};
|
|
147
178
|
defineElement("pc-context-menu", PcContextMenu);
|
|
148
|
-
export { PcContextMenu };
|
|
179
|
+
export { CONTEXT_MENU_TOGGLE_EVENT, MENU_MARGIN, PcContextMenu, menuLeft, menuTop };
|
|
@@ -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
|
>
|