@lvl99dotcom/paintcanvas 0.3.0 → 0.4.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 +1 -1
- package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
- package/dist/lib/brushes/StrokeEngine/index.js +26 -28
- package/dist/lib/classes/BrushSettings/index.d.ts +24 -0
- package/dist/lib/classes/BrushSettings/index.js +35 -1
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +17 -6
- package/dist/lib/classes/KeyboardShortcuts/index.js +81 -18
- package/dist/lib/classes/PaintCanvas/index.d.ts +77 -27
- package/dist/lib/classes/PaintCanvas/index.js +143 -42
- 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 +52 -0
- package/dist/lib/classes/SettingsAutosave/index.js +96 -0
- package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
- package/dist/lib/classes/ShortcutSettings/index.js +110 -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 +91 -0
- package/dist/lib/settings/schema.d.ts +150 -0
- package/dist/lib/settings/schema.js +102 -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 +872 -1
- package/dist/locales/en-GB.js +87 -1
- package/dist/locales/en-US.js +87 -1
- package/dist/locales/fr.js +87 -1
- package/dist/locales/ja.js +87 -1
- package/dist/locales/ko.js +87 -1
- package/dist/locales/zh-Hans.js +87 -1
- package/dist/locales/zh-Hant.js +87 -1
- package/dist/package.js +1 -1
- package/dist/ui/elements/PaintCanvas/index.d.ts +49 -47
- package/dist/ui/elements/PaintCanvas/index.js +128 -169
- 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.js +1 -0
- package/dist/ui/elements/PcButton/index.js +1 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +1 -1
- package/dist/ui/elements/PcCanvas/index.js +7 -5
- 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 +70 -33
- package/dist/ui/elements/PcContextMenu/index.js +128 -97
- package/dist/ui/elements/PcDialog/index.js +1 -0
- package/dist/ui/elements/PcIconToggle/index.js +1 -0
- 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 +93 -0
- package/dist/ui/elements/PcMenu/index.js +204 -0
- package/dist/ui/elements/PcPane/index.d.ts +118 -0
- package/dist/ui/elements/PcPane/index.js +357 -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 +20 -6
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +20 -15
- 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/PcShortcutSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
- package/dist/ui/elements/PcThemePicker/index.js +1 -0
- package/dist/ui/elements/PcToast/index.js +1 -0
- package/dist/ui/elements/PcToolSettings/index.js +2 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +47 -12
- package/dist/ui/elements/PcToolbar/index.js +77 -56
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/index.d.ts +7 -1
- package/dist/ui/index.js +8 -2
- package/dist/ui/tokens/index.js +3 -1
- package/locales/en-GB.json +87 -1
- package/locales/en-US.json +87 -1
- package/locales/fr.json +87 -1
- package/locales/ja.json +87 -1
- package/locales/ko.json +87 -1
- package/locales/zh-Hans.json +87 -1
- package/locales/zh-Hant.json +87 -1
- package/package.json +1 -1
- package/dist/ui/elements/PcBrushMenu/style.js +0 -3
- package/dist/ui/elements/PcLayersMenu/style.js +0 -3
|
@@ -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 };
|
|
@@ -2,6 +2,8 @@ import { PcElement } from "../../base";
|
|
|
2
2
|
import { BlendMode, type default as Layer } from "../../../lib/classes/Layer";
|
|
3
3
|
import type LayerStack from "../../../lib/classes/LayerStack";
|
|
4
4
|
import "../PcDialog";
|
|
5
|
+
import "../PcContextMenu";
|
|
6
|
+
import { MENU_MARGIN, menuTop } from "../PcContextMenu";
|
|
5
7
|
import { type DialogChoice } from "../PcDialog";
|
|
6
8
|
import type BackgroundWork from "../../../lib/classes/BackgroundWork";
|
|
7
9
|
/** Blend mode options shown in the layer's select input */
|
|
@@ -56,17 +58,8 @@ export interface LayerDragDetail {
|
|
|
56
58
|
x: number;
|
|
57
59
|
y: number;
|
|
58
60
|
}
|
|
59
|
-
/**
|
|
60
|
-
export
|
|
61
|
-
/**
|
|
62
|
-
* Where the layer actions menu's top goes: level with the handle, moved up as far as needed to stay in the window
|
|
63
|
-
* (but never above its top margin).
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* menuTop(700, 300, 800); // 492 (moved up to fit)
|
|
67
|
-
* menuTop(100, 300, 800); // 100
|
|
68
|
-
*/
|
|
69
|
-
export declare const menuTop: (anchorTop: number, menuHeight: number, viewportHeight: number, margin?: number) => number;
|
|
61
|
+
/** Where the layer actions menu goes and how close to the window's edges (see `pc-context-menu`) */
|
|
62
|
+
export { MENU_MARGIN, menuTop };
|
|
70
63
|
/**
|
|
71
64
|
* One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock (padlock) and visibility (eye) icons.
|
|
72
65
|
* Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
|
|
@@ -141,28 +134,22 @@ export declare class PcLayerItem extends PcElement {
|
|
|
141
134
|
runAction(action: LayerAction): void;
|
|
142
135
|
/**
|
|
143
136
|
* Whether a context menu action can be done to the layer now: not merging down the bottom layer (or with a hidden
|
|
144
|
-
* layer), deleting the only layer,
|
|
137
|
+
* layer), deleting the only layer, moving a layer further up or down than it can go, or anything to a layer no longer
|
|
138
|
+
* in the stack (e.g. just deleted).
|
|
145
139
|
*/
|
|
146
140
|
canRun(action: LayerAction): boolean;
|
|
147
141
|
/**
|
|
148
142
|
* Do an action the user confirmed.
|
|
149
143
|
*/
|
|
150
144
|
confirmed(action: ConfirmedAction): void;
|
|
151
|
-
private readonly onOutsidePointer;
|
|
152
|
-
private readonly onKeyDown;
|
|
153
|
-
/**
|
|
154
|
-
* Open the context menu on right-click, or close it if open.
|
|
155
|
-
*/
|
|
156
|
-
private onContextMenu;
|
|
157
145
|
/**
|
|
158
|
-
*
|
|
159
|
-
* drag does nothing.
|
|
146
|
+
* Follow the context menu opening or closing, telling other rows when it opens (so they close theirs).
|
|
160
147
|
*/
|
|
161
|
-
private
|
|
148
|
+
private onMenuToggle;
|
|
162
149
|
/**
|
|
163
|
-
*
|
|
150
|
+
* A click on the handle opens the menu, unless it ends a drag (then it's stopped before reaching the menu's button).
|
|
164
151
|
*/
|
|
165
|
-
private
|
|
152
|
+
private readonly onHandleClick;
|
|
166
153
|
disconnectedCallback(): void;
|
|
167
154
|
/** Tell the layers menu about a drag of this row's layer */
|
|
168
155
|
private emitDrag;
|
|
@@ -188,16 +175,6 @@ export declare class PcLayerItem extends PcElement {
|
|
|
188
175
|
private readonly onDragKeyDown;
|
|
189
176
|
/** Stop following the pointer pressed on the handle */
|
|
190
177
|
private endPress;
|
|
191
|
-
/**
|
|
192
|
-
* Start or stop listening for clicks outside and Escape while the menu is open.
|
|
193
|
-
*/
|
|
194
|
-
private listenOutside;
|
|
195
|
-
private readonly onScroll;
|
|
196
|
-
/**
|
|
197
|
-
* Place the open layer actions menu beside the reorder handle, floating over the layers panel (so the panel's edges
|
|
198
|
-
* don't cut it off) and kept within the window.
|
|
199
|
-
*/
|
|
200
|
-
private placeMenu;
|
|
201
178
|
/**
|
|
202
179
|
* Ask for a mask following the layer's alpha (Shift adds, Alt subtracts; otherwise it replaces the mask).
|
|
203
180
|
*/
|
|
@@ -210,12 +187,14 @@ export declare class PcLayerItem extends PcElement {
|
|
|
210
187
|
* Rename a layer when the name input changes. Empty names are reverted.
|
|
211
188
|
*/
|
|
212
189
|
private onRename;
|
|
213
|
-
updated(
|
|
190
|
+
updated(): void;
|
|
214
191
|
/**
|
|
215
192
|
* Draw the layer's image, scaled to fit, on a checkerboard that shows transparency.
|
|
216
193
|
*/
|
|
217
194
|
drawThumbnail(): void;
|
|
218
195
|
/** The dialog asking the user to confirm an action, while asking */
|
|
219
196
|
private renderConfirmation;
|
|
197
|
+
/** The layer's actions, for its context menu (those it can't do now are disabled) */
|
|
198
|
+
private menuItems;
|
|
220
199
|
render(): import("lit").TemplateResult<1>;
|
|
221
200
|
}
|
|
@@ -5,6 +5,7 @@ import ChangeController from "../../ChangeController/index.js";
|
|
|
5
5
|
import { BlendMode } from "../../../lib/classes/Layer/index.js";
|
|
6
6
|
import { drawCheckerboard, fitSize } from "../../../lib/rendering/checkerboard/index.js";
|
|
7
7
|
import { CANCEL_CHOICE } from "../PcDialog/index.js";
|
|
8
|
+
import { MENU_MARGIN, menuTop } from "../PcContextMenu/index.js";
|
|
8
9
|
import { html, nothing, unsafeCSS } from "lit";
|
|
9
10
|
// src/ui/elements/PcLayerItem/index.ts
|
|
10
11
|
/** Blend mode options shown in the layer's select input */
|
|
@@ -127,17 +128,6 @@ var LAYER_ALPHA_MASK_EVENT = "pc-layer-alpha-mask";
|
|
|
127
128
|
var DRAG_THRESHOLD = 4;
|
|
128
129
|
/** Event a row dispatches while its layer is dragged by its reorder handle (detail: {@link LayerDragDetail}) */
|
|
129
130
|
var LAYER_DRAG_EVENT = "pc-layer-drag";
|
|
130
|
-
/** Space kept between the layer actions menu and the window's edges */
|
|
131
|
-
var MENU_MARGIN = 8;
|
|
132
|
-
/**
|
|
133
|
-
* Where the layer actions menu's top goes: level with the handle, moved up as far as needed to stay in the window
|
|
134
|
-
* (but never above its top margin).
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* menuTop(700, 300, 800); // 492 (moved up to fit)
|
|
138
|
-
* menuTop(100, 300, 800); // 100
|
|
139
|
-
*/
|
|
140
|
-
var menuTop = (anchorTop, menuHeight, viewportHeight, margin = 8) => Math.max(margin, Math.min(anchorTop, viewportHeight - margin - menuHeight));
|
|
141
131
|
/**
|
|
142
132
|
* One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock (padlock) and visibility (eye) icons.
|
|
143
133
|
* Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
|
|
@@ -220,11 +210,12 @@ var PcLayerItem = class extends PcElement {
|
|
|
220
210
|
}
|
|
221
211
|
/**
|
|
222
212
|
* Whether a context menu action can be done to the layer now: not merging down the bottom layer (or with a hidden
|
|
223
|
-
* layer), deleting the only layer,
|
|
213
|
+
* layer), deleting the only layer, moving a layer further up or down than it can go, or anything to a layer no longer
|
|
214
|
+
* in the stack (e.g. just deleted).
|
|
224
215
|
*/
|
|
225
216
|
canRun(action) {
|
|
226
217
|
const { stack, layer } = this;
|
|
227
|
-
if (!stack || !layer) return false;
|
|
218
|
+
if (!stack || !layer || !stack.layers.includes(layer)) return false;
|
|
228
219
|
const index = stack.indexOf(layer.id);
|
|
229
220
|
const last = stack.layers.length - 1;
|
|
230
221
|
switch (action) {
|
|
@@ -248,43 +239,30 @@ var PcLayerItem = class extends PcElement {
|
|
|
248
239
|
else if (action === "clear") stack.clear(layer.id);
|
|
249
240
|
else stack.remove(layer.id);
|
|
250
241
|
}
|
|
251
|
-
onOutsidePointer = (event) => {
|
|
252
|
-
if (!event.composedPath().includes(this)) this.menuOpen = false;
|
|
253
|
-
};
|
|
254
|
-
onKeyDown = (event) => {
|
|
255
|
-
if (event.key === "Escape") this.menuOpen = false;
|
|
256
|
-
};
|
|
257
242
|
/**
|
|
258
|
-
*
|
|
243
|
+
* Follow the context menu opening or closing, telling other rows when it opens (so they close theirs).
|
|
259
244
|
*/
|
|
260
|
-
|
|
261
|
-
event.
|
|
262
|
-
this.
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Open the context menu on a click or tap on the handle (or Enter or Space), or close it if open; a click that ends a
|
|
266
|
-
* drag does nothing.
|
|
267
|
-
*/
|
|
268
|
-
onHandleClick() {
|
|
269
|
-
if (this.suppressClick) {
|
|
270
|
-
this.suppressClick = false;
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
this.toggleMenu();
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Open the context menu (closing other rows' menus), or close it if open.
|
|
277
|
-
*/
|
|
278
|
-
toggleMenu() {
|
|
279
|
-
this.menuOpen = !this.menuOpen;
|
|
280
|
-
if (this.menuOpen) this.dispatchEvent(new CustomEvent(LAYER_MENU_OPEN_EVENT, {
|
|
245
|
+
onMenuToggle(event) {
|
|
246
|
+
this.menuOpen = event.detail.open;
|
|
247
|
+
if (!this.menuOpen) return;
|
|
248
|
+
this.dispatchEvent(new CustomEvent(LAYER_MENU_OPEN_EVENT, {
|
|
281
249
|
bubbles: true,
|
|
282
250
|
composed: true
|
|
283
251
|
}));
|
|
284
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* A click on the handle opens the menu, unless it ends a drag (then it's stopped before reaching the menu's button).
|
|
255
|
+
*/
|
|
256
|
+
onHandleClick = {
|
|
257
|
+
handleEvent: (event) => {
|
|
258
|
+
if (!this.suppressClick) return;
|
|
259
|
+
this.suppressClick = false;
|
|
260
|
+
event.stopPropagation();
|
|
261
|
+
},
|
|
262
|
+
capture: true
|
|
263
|
+
};
|
|
285
264
|
disconnectedCallback() {
|
|
286
265
|
super.disconnectedCallback();
|
|
287
|
-
this.listenOutside(false);
|
|
288
266
|
this.endPress();
|
|
289
267
|
}
|
|
290
268
|
/** Tell the layers menu about a drag of this row's layer */
|
|
@@ -313,7 +291,7 @@ var PcLayerItem = class extends PcElement {
|
|
|
313
291
|
y: event.clientY,
|
|
314
292
|
dragging: false
|
|
315
293
|
};
|
|
316
|
-
event.
|
|
294
|
+
event.composedPath()[0].setPointerCapture(event.pointerId);
|
|
317
295
|
window.addEventListener("pointermove", this.onHandlePointerMove);
|
|
318
296
|
window.addEventListener("pointerup", this.onHandlePointerUp);
|
|
319
297
|
window.addEventListener("pointercancel", this.onHandlePointerCancel);
|
|
@@ -368,29 +346,6 @@ var PcLayerItem = class extends PcElement {
|
|
|
368
346
|
window.removeEventListener("pointercancel", this.onHandlePointerCancel);
|
|
369
347
|
}
|
|
370
348
|
/**
|
|
371
|
-
* Start or stop listening for clicks outside and Escape while the menu is open.
|
|
372
|
-
*/
|
|
373
|
-
listenOutside(listen) {
|
|
374
|
-
const method = listen ? "addEventListener" : "removeEventListener";
|
|
375
|
-
window[method]("pointerdown", this.onOutsidePointer, true);
|
|
376
|
-
window[method]("keydown", this.onKeyDown);
|
|
377
|
-
window[method]("scroll", this.onScroll, true);
|
|
378
|
-
}
|
|
379
|
-
onScroll = () => {
|
|
380
|
-
this.menuOpen = false;
|
|
381
|
-
};
|
|
382
|
-
/**
|
|
383
|
-
* Place the open layer actions menu beside the reorder handle, floating over the layers panel (so the panel's edges
|
|
384
|
-
* don't cut it off) and kept within the window.
|
|
385
|
-
*/
|
|
386
|
-
placeMenu() {
|
|
387
|
-
const menu = this.renderRoot.querySelector("menu");
|
|
388
|
-
if (!menu) return;
|
|
389
|
-
const anchor = this.renderRoot.querySelector(".handle").getBoundingClientRect();
|
|
390
|
-
menu.style.left = `${anchor.right}px`;
|
|
391
|
-
menu.style.top = `${menuTop(anchor.top, menu.offsetHeight, window.innerHeight)}px`;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
349
|
* Ask for a mask following the layer's alpha (Shift adds, Alt subtracts; otherwise it replaces the mask).
|
|
395
350
|
*/
|
|
396
351
|
requestMask(layer, keys) {
|
|
@@ -426,11 +381,7 @@ var PcLayerItem = class extends PcElement {
|
|
|
426
381
|
input.value = layer.name;
|
|
427
382
|
}
|
|
428
383
|
}
|
|
429
|
-
updated(
|
|
430
|
-
if (changed.has("menuOpen")) {
|
|
431
|
-
this.listenOutside(this.menuOpen);
|
|
432
|
-
if (this.menuOpen) this.placeMenu();
|
|
433
|
-
}
|
|
384
|
+
updated() {
|
|
434
385
|
const id = this.layer?.id;
|
|
435
386
|
if (this.background && id) this.background.run(`thumbnail ${id}`, () => this.drawThumbnail());
|
|
436
387
|
else this.drawThumbnail();
|
|
@@ -472,23 +423,32 @@ var PcLayerItem = class extends PcElement {
|
|
|
472
423
|
}}
|
|
473
424
|
></pc-dialog>`;
|
|
474
425
|
}
|
|
426
|
+
/** The layer's actions, for its context menu (those it can't do now are disabled) */
|
|
427
|
+
menuItems() {
|
|
428
|
+
return LAYER_ACTIONS.map(({ action, label }) => ({
|
|
429
|
+
value: action,
|
|
430
|
+
label: this.t(label),
|
|
431
|
+
disabled: !this.canRun(action)
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
475
434
|
render() {
|
|
476
435
|
const { stack, layer } = this;
|
|
477
436
|
if (!stack || !layer) return html``;
|
|
478
437
|
const active = stack.active.id === layer.id;
|
|
479
438
|
return html`
|
|
480
|
-
<
|
|
481
|
-
type="button"
|
|
439
|
+
<pc-context-menu
|
|
482
440
|
class="handle"
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
441
|
+
placement="beside"
|
|
442
|
+
label=${this.t("paintCanvas.layers.reorder", { name: layer.name })}
|
|
443
|
+
menu-label=${this.t("paintCanvas.layers.actions")}
|
|
444
|
+
.open=${this.menuOpen}
|
|
445
|
+
.items=${this.menuItems()}
|
|
446
|
+
@pc-toggle=${this.onMenuToggle}
|
|
447
|
+
@pc-select=${(e) => this.runAction(e.detail.value)}
|
|
487
448
|
@click=${this.onHandleClick}
|
|
488
449
|
@pointerdown=${this.onHandlePointerDown}
|
|
450
|
+
><span slot="icon" aria-hidden="true">⠿</span></pc-context-menu
|
|
489
451
|
>
|
|
490
|
-
⠿
|
|
491
|
-
</button>
|
|
492
452
|
<div
|
|
493
453
|
class="details"
|
|
494
454
|
@click=${(e) => this.onRowClick(e, stack, layer)}
|
|
@@ -541,18 +501,6 @@ var PcLayerItem = class extends PcElement {
|
|
|
541
501
|
>
|
|
542
502
|
<img src=${this.iconUrl(layer.visible ? "visible" : "hidden")} alt="" />
|
|
543
503
|
</button>
|
|
544
|
-
${this.menuOpen ? html`<menu aria-label=${this.t("paintCanvas.layers.actions")}>
|
|
545
|
-
${LAYER_ACTIONS.map(({ action, label }) => html`<li>
|
|
546
|
-
<button
|
|
547
|
-
type="button"
|
|
548
|
-
data-action=${action}
|
|
549
|
-
?disabled=${!this.canRun(action)}
|
|
550
|
-
@click=${() => this.runAction(action)}
|
|
551
|
-
>
|
|
552
|
-
${this.t(label)}
|
|
553
|
-
</button>
|
|
554
|
-
</li>`)}
|
|
555
|
-
</menu>` : nothing}
|
|
556
504
|
${this.renderConfirmation(layer)}
|
|
557
505
|
`;
|
|
558
506
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PcLayerItem/style.css?inline
|
|
2
|
-
var style_default = "/* A white row with space around its parts */\n:host {\n display: grid;\n grid-template-columns: 30px 1fr 30px 30px;\n align-items: center;\n column-gap: 6px;\n position: relative;\n padding: 6px;\n border: 1px solid var(--pc-surface-active);\n background: var(--pc-surface);\n}\n.name.active {\n font-weight: bold;\n}\n.thumbnail {\n width: 30px;\n height: 30px;\n}\n/* Thumbnail on the left and name on the right; under them, opacity on the left and blend mode on the right
|
|
2
|
+
var style_default = "/* A white row with space around its parts. The middle column can shrink, so the row never grows wider than its panel */\n:host {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) 30px 30px;\n align-items: center;\n column-gap: 6px;\n position: relative;\n padding: 6px;\n border: 1px solid var(--pc-surface-active);\n background: var(--pc-surface);\n}\n.name.active {\n font-weight: bold;\n}\n.thumbnail {\n width: 30px;\n height: 30px;\n}\n/* Thumbnail on the left and name on the right; under them, opacity on the left and blend mode on the right. The blend\n mode gets a set share of the width rather than its own: browsers size dropdowns differently (Safari and Firefox as\n wide as the longest option, Chrome as the chosen one), which would otherwise push the row wider than its panel */\n.details {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) minmax(0, 2fr);\n align-items: center;\n gap: 4px 6px;\n min-width: 0;\n}\n.details .name {\n grid-column: 2 / -1;\n}\n.details .name,\n.details select {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n}\n.details input[type=\"range\"] {\n grid-column: 1 / 3;\n min-width: 0;\n margin: 0;\n}\n/* The reorder handle drags with mouse, pen or touch (touch drags it rather than scrolling the panel) */\n.handle {\n /* As tall as the row */\n align-self: stretch;\n}\n.handle::part(button) {\n min-width: 0;\n padding: 0;\n cursor: grab;\n touch-action: none;\n}\n/* While dragged: the row floats with the pointer, out of the list (the layers menu places it), and the cursor shows\n what dropping would do (\"+\" duplicates on \"New Layer\") */\n:host([dragging]) {\n position: fixed;\n z-index: 10;\n opacity: 0.9;\n background: Canvas;\n box-shadow: var(--pc-shadow);\n}\n:host([dragging]) .handle::part(button) {\n cursor: grabbing;\n}\n:host([drop-action=\"duplicate\"]) .handle::part(button) {\n cursor: copy;\n}\n:host([drop-action=\"none\"]) .handle::part(button) {\n cursor: no-drop;\n}\n/* Alpha lock and visibility: boxed icon toggles like the settings bar's, darker while on */\n.alpha-lock,\n.visibility {\n width: 30px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--pc-border);\n border-radius: 4px;\n background: var(--pc-surface);\n cursor: pointer;\n}\n.alpha-lock:hover,\n.visibility:hover {\n background: var(--pc-surface-hover);\n}\n.alpha-lock[aria-pressed=\"true\"],\n.visibility[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.alpha-lock:focus-visible,\n.visibility:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n}\n.alpha-lock img,\n.visibility img {\n display: block;\n width: 24px;\n height: 24px;\n margin: auto;\n}\n";
|
|
3
3
|
export { style_default as default };
|