@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,296 @@
|
|
|
1
|
+
import { PcElement, defineElement, emit } from "../../base/index.js";
|
|
2
|
+
import { markDismissingPress } from "../../dismiss/index.js";
|
|
3
|
+
import { ariaKeyShortcut, shortcutLabel } from "../../../lib/utils/shortcuts/index.js";
|
|
4
|
+
import "../PcIcon/index.js";
|
|
5
|
+
import { css, html, nothing } from "lit";
|
|
6
|
+
// src/ui/elements/PcMenu/index.ts
|
|
7
|
+
/** Longest shortcut label shown on a menu item (characters); longer ones are left out */
|
|
8
|
+
var MAX_SHORTCUT_LABEL_LENGTH = 3;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a menu entry is an item that can be chosen.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* isMenuItem({ value: "grid", label: "Grid" }); // true
|
|
14
|
+
* isMenuItem({ type: "divider" }); // false
|
|
15
|
+
*/
|
|
16
|
+
var isMenuItem = (entry) => entry.type === void 0 || entry.type === "item";
|
|
17
|
+
/**
|
|
18
|
+
* Whether a menu item has something to show on its left: a checkmark or an icon.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* hasStart({ value: "grid", label: "Grid", checked: false }); // true
|
|
22
|
+
*/
|
|
23
|
+
var hasStart = (item) => item.checked !== void 0 || item.icon !== void 0;
|
|
24
|
+
/**
|
|
25
|
+
* A menu item's classes: `danger` and `selected`, when it is.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* itemClasses({ value: "brush", label: "Brush", selected: true }); // "selected"
|
|
29
|
+
*/
|
|
30
|
+
var itemClasses = (item) => [item.danger && "danger", item.selected && "selected"].filter(Boolean).join(" ");
|
|
31
|
+
/**
|
|
32
|
+
* Split menu entries into sections: a label or divider starts a new one.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* menuSections([{ type: "label", label: "Tools" }, { value: "brush", label: "Brush" }]);
|
|
36
|
+
* // [{ label: "Tools", items: [{ value: "brush", label: "Brush" }], divided: false }]
|
|
37
|
+
*/
|
|
38
|
+
var menuSections = (entries) => {
|
|
39
|
+
const sections = [{
|
|
40
|
+
items: [],
|
|
41
|
+
divided: false
|
|
42
|
+
}];
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const current = sections[sections.length - 1];
|
|
45
|
+
if (isMenuItem(entry)) current.items.push(entry);
|
|
46
|
+
else if (entry.type === "divider") sections.push({
|
|
47
|
+
items: [],
|
|
48
|
+
divided: true
|
|
49
|
+
});
|
|
50
|
+
else if (current.items.length === 0 && current.label === void 0) current.label = entry.label;
|
|
51
|
+
else sections.push({
|
|
52
|
+
label: entry.label,
|
|
53
|
+
items: [],
|
|
54
|
+
divided: false
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return sections.filter((section) => section.divided || section.label !== void 0 || section.items.length);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* A list of menu items, e.g. a layer's actions (see `pc-context-menu` for a button that opens one). While `open`, it shows its items at `x`, `y` (in the page's viewport) and takes
|
|
61
|
+
* keyboard focus: arrow keys, Home and End move between items, Enter or Space chooses one, and Escape or Tab closes it.
|
|
62
|
+
* Items can show a checkmark (toggles) or an icon on their left, and their keyboard shortcut on their right; long
|
|
63
|
+
* labels wrap. Non-interactive labels (`{ type: "label", label }`, small bold text naming the items after them) and
|
|
64
|
+
* dividers (`{ type: "divider" }`) can go between items; keyboard focus skips them.
|
|
65
|
+
* Fires `pc-select` with the chosen item's value, and `pc-close` when it should close (after choosing, Escape, Tab, or a
|
|
66
|
+
* press outside it and its `anchor`). The implementer sets `open` to false in response.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* html`<pc-menu open .x=${10} .y=${20} label="Layer actions" .items=${items}
|
|
70
|
+
* @pc-select=${(e) => act(e.detail.value)} @pc-close=${close}></pc-menu>`
|
|
71
|
+
*/
|
|
72
|
+
var PcMenu = class extends PcElement {
|
|
73
|
+
static properties = {
|
|
74
|
+
...PcElement.properties,
|
|
75
|
+
open: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
reflect: true
|
|
78
|
+
},
|
|
79
|
+
items: { attribute: false },
|
|
80
|
+
label: { type: String },
|
|
81
|
+
x: { type: Number },
|
|
82
|
+
y: { type: Number },
|
|
83
|
+
anchor: { attribute: false }
|
|
84
|
+
};
|
|
85
|
+
static styles = [PcElement.styles, css`
|
|
86
|
+
:host {
|
|
87
|
+
position: fixed;
|
|
88
|
+
z-index: 10;
|
|
89
|
+
display: none;
|
|
90
|
+
user-select: none;
|
|
91
|
+
-webkit-user-select: none;
|
|
92
|
+
}
|
|
93
|
+
:host([open]) {
|
|
94
|
+
display: block;
|
|
95
|
+
}
|
|
96
|
+
[role="menu"] {
|
|
97
|
+
display: flex;
|
|
98
|
+
min-width: 160px;
|
|
99
|
+
max-width: min(320px, calc(100vw - 2 * var(--pc-space-3)));
|
|
100
|
+
flex-direction: column;
|
|
101
|
+
padding: var(--pc-space-1) 0;
|
|
102
|
+
border-radius: var(--pc-radius);
|
|
103
|
+
background: var(--pc-surface-raised);
|
|
104
|
+
box-shadow: var(--pc-shadow);
|
|
105
|
+
}
|
|
106
|
+
button {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: baseline;
|
|
109
|
+
gap: var(--pc-space-2);
|
|
110
|
+
padding: var(--pc-space-2) var(--pc-space-3);
|
|
111
|
+
border: 0;
|
|
112
|
+
background: transparent;
|
|
113
|
+
color: var(--pc-text);
|
|
114
|
+
font-family: var(--pc-font);
|
|
115
|
+
font-size: var(--pc-font-size);
|
|
116
|
+
text-align: start;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
}
|
|
119
|
+
button:hover:not(:disabled),
|
|
120
|
+
button:focus-visible {
|
|
121
|
+
background: var(--pc-surface-hover);
|
|
122
|
+
outline: none;
|
|
123
|
+
}
|
|
124
|
+
button:focus-visible {
|
|
125
|
+
box-shadow: inset 0 0 0 var(--pc-focus-width) var(--pc-focus);
|
|
126
|
+
}
|
|
127
|
+
button.danger {
|
|
128
|
+
color: var(--pc-dismiss-text);
|
|
129
|
+
}
|
|
130
|
+
/* The current choice, e.g. the selected tool, like other selected buttons */
|
|
131
|
+
button.selected,
|
|
132
|
+
button.selected:hover:not(:disabled),
|
|
133
|
+
button.selected:focus-visible {
|
|
134
|
+
background: var(--pc-accent);
|
|
135
|
+
color: var(--pc-accent-text);
|
|
136
|
+
}
|
|
137
|
+
button.selected .end {
|
|
138
|
+
color: inherit;
|
|
139
|
+
}
|
|
140
|
+
.start {
|
|
141
|
+
/* Icons as tall as the text, so items with icons are no taller */
|
|
142
|
+
--pc-icon-size: 1.2em;
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
width: var(--pc-icon-size);
|
|
145
|
+
flex: none;
|
|
146
|
+
align-self: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
}
|
|
149
|
+
.label {
|
|
150
|
+
flex: 1;
|
|
151
|
+
overflow-wrap: anywhere;
|
|
152
|
+
}
|
|
153
|
+
.end {
|
|
154
|
+
flex: none;
|
|
155
|
+
color: var(--pc-text-muted);
|
|
156
|
+
white-space: nowrap;
|
|
157
|
+
}
|
|
158
|
+
button:disabled {
|
|
159
|
+
color: var(--pc-text-muted);
|
|
160
|
+
cursor: default;
|
|
161
|
+
}
|
|
162
|
+
[role="group"] {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
}
|
|
166
|
+
.heading {
|
|
167
|
+
padding: var(--pc-space-2) var(--pc-space-3) var(--pc-space-1);
|
|
168
|
+
color: var(--pc-text-muted);
|
|
169
|
+
font-family: var(--pc-font);
|
|
170
|
+
font-size: var(--pc-font-size-small);
|
|
171
|
+
font-weight: bold;
|
|
172
|
+
cursor: default;
|
|
173
|
+
}
|
|
174
|
+
[role="separator"] {
|
|
175
|
+
height: 1px;
|
|
176
|
+
margin: var(--pc-space-1) 0;
|
|
177
|
+
background: var(--pc-border);
|
|
178
|
+
}
|
|
179
|
+
`];
|
|
180
|
+
onPointerDown = (event) => {
|
|
181
|
+
const path = event.composedPath();
|
|
182
|
+
if (!this.open || path.includes(this) || this.anchor && path.includes(this.anchor)) return;
|
|
183
|
+
markDismissingPress(event);
|
|
184
|
+
this.close();
|
|
185
|
+
};
|
|
186
|
+
constructor() {
|
|
187
|
+
super();
|
|
188
|
+
this.open = false;
|
|
189
|
+
this.items = [];
|
|
190
|
+
this.label = "";
|
|
191
|
+
this.x = 0;
|
|
192
|
+
this.y = 0;
|
|
193
|
+
}
|
|
194
|
+
connectedCallback() {
|
|
195
|
+
super.connectedCallback();
|
|
196
|
+
document.addEventListener("pointerdown", this.onPointerDown, true);
|
|
197
|
+
}
|
|
198
|
+
disconnectedCallback() {
|
|
199
|
+
super.disconnectedCallback();
|
|
200
|
+
document.removeEventListener("pointerdown", this.onPointerDown, true);
|
|
201
|
+
}
|
|
202
|
+
updated(changed) {
|
|
203
|
+
this.style.left = `${this.x}px`;
|
|
204
|
+
this.style.top = `${this.y}px`;
|
|
205
|
+
if (changed.has("open") && this.open) this.buttons()[0]?.focus();
|
|
206
|
+
}
|
|
207
|
+
/** The enabled items' buttons */
|
|
208
|
+
buttons() {
|
|
209
|
+
return [...this.renderRoot.querySelectorAll("button")].filter((button) => !button.disabled);
|
|
210
|
+
}
|
|
211
|
+
/** Tell listeners the menu should close */
|
|
212
|
+
close() {
|
|
213
|
+
emit(this, "pc-close");
|
|
214
|
+
}
|
|
215
|
+
/** Choose an item, then close */
|
|
216
|
+
choose(item) {
|
|
217
|
+
emit(this, "pc-select", { value: item.value });
|
|
218
|
+
this.close();
|
|
219
|
+
}
|
|
220
|
+
/** Move focus between items, or close */
|
|
221
|
+
onKeyDown(event) {
|
|
222
|
+
const buttons = this.buttons();
|
|
223
|
+
const index = buttons.indexOf(this.renderRoot.activeElement);
|
|
224
|
+
const last = buttons.length - 1;
|
|
225
|
+
const target = {
|
|
226
|
+
ArrowDown: index >= last ? 0 : index + 1,
|
|
227
|
+
ArrowUp: index <= 0 ? last : index - 1,
|
|
228
|
+
Home: 0,
|
|
229
|
+
End: last
|
|
230
|
+
}[event.key];
|
|
231
|
+
if (target !== void 0) {
|
|
232
|
+
event.preventDefault();
|
|
233
|
+
buttons[target]?.focus();
|
|
234
|
+
} else if (event.key === "Escape" || event.key === "Tab") {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
this.close();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** An item's left side: a checkmark while a toggle is on, its icon, or empty space */
|
|
240
|
+
renderStart(item) {
|
|
241
|
+
const content = item.checked !== void 0 ? item.checked ? "✔" : nothing : item.icon ? html`<pc-icon name=${item.icon}></pc-icon>` : nothing;
|
|
242
|
+
return html`<span part="item-start" class="start" aria-hidden="true"
|
|
243
|
+
>${content}</span
|
|
244
|
+
>`;
|
|
245
|
+
}
|
|
246
|
+
/** An item's right side: its shortcut's label, if short enough */
|
|
247
|
+
renderEnd(item) {
|
|
248
|
+
const label = item.shortcut && shortcutLabel(item.shortcut);
|
|
249
|
+
return label && [...label].length <= 3 ? html`<span part="item-end" class="end" aria-hidden="true"
|
|
250
|
+
>${label}</span
|
|
251
|
+
>` : nothing;
|
|
252
|
+
}
|
|
253
|
+
/** An item's button */
|
|
254
|
+
renderItem(item, showStart) {
|
|
255
|
+
return html`<button
|
|
256
|
+
part="item"
|
|
257
|
+
type="button"
|
|
258
|
+
role=${item.checked === void 0 ? "menuitem" : "menuitemcheckbox"}
|
|
259
|
+
aria-checked=${item.checked === void 0 ? nothing : String(item.checked)}
|
|
260
|
+
aria-keyshortcuts=${item.shortcut ? ariaKeyShortcut(item.shortcut) : nothing}
|
|
261
|
+
class=${itemClasses(item) || nothing}
|
|
262
|
+
aria-current=${item.selected ? "true" : nothing}
|
|
263
|
+
?disabled=${item.disabled}
|
|
264
|
+
data-value=${item.value}
|
|
265
|
+
@click=${() => this.choose(item)}
|
|
266
|
+
>
|
|
267
|
+
${showStart ? this.renderStart(item) : nothing}<span class="label"
|
|
268
|
+
>${item.label}</span
|
|
269
|
+
>${this.renderEnd(item)}
|
|
270
|
+
</button>`;
|
|
271
|
+
}
|
|
272
|
+
/** A section: its divider and label, if any, and its items (grouped under the label for screen readers) */
|
|
273
|
+
renderSection(section, index, showStart) {
|
|
274
|
+
const items = section.items.map((item) => this.renderItem(item, showStart));
|
|
275
|
+
const id = `menu-label-${index}`;
|
|
276
|
+
return html`${section.divided ? html`<div part="divider" role="separator"></div>` : nothing}${section.label === void 0 ? items : html`<div role="group" aria-labelledby=${id}>
|
|
277
|
+
<div part="label" class="heading" id=${id} role="presentation">
|
|
278
|
+
${section.label}
|
|
279
|
+
</div>
|
|
280
|
+
${items}
|
|
281
|
+
</div>`}`;
|
|
282
|
+
}
|
|
283
|
+
render() {
|
|
284
|
+
const showStart = this.items.some((entry) => isMenuItem(entry) && hasStart(entry));
|
|
285
|
+
return html`<div
|
|
286
|
+
part="menu"
|
|
287
|
+
role="menu"
|
|
288
|
+
aria-label=${this.label}
|
|
289
|
+
@keydown=${this.onKeyDown}
|
|
290
|
+
>
|
|
291
|
+
${menuSections(this.items).map((section, index) => this.renderSection(section, index, showStart))}
|
|
292
|
+
</div>`;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
defineElement("pc-menu", PcMenu);
|
|
296
|
+
export { MAX_SHORTCUT_LABEL_LENGTH, PcMenu, isMenuItem, menuSections };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type PropertyValues } from "lit";
|
|
2
|
+
import { PcElement } from "../../base";
|
|
3
|
+
/**
|
|
4
|
+
* A number kept within `min` and `max` and rounded to a multiple of `step` (from `min`).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* fitNumber(300, 1, 256, 1); // 256
|
|
8
|
+
* fitNumber(12.4, 0, 100, 5); // 10
|
|
9
|
+
* fitNumber(45.678, -360, 360, 0.01); // 45.68
|
|
10
|
+
*/
|
|
11
|
+
export declare const fitNumber: (value: number, min: number, max: number, step: number) => number;
|
|
12
|
+
/**
|
|
13
|
+
* A number shown as text that changes when pressed and dragged: right or up raises it, left or down lowers it, a step
|
|
14
|
+
* per pixel (or `drag-step`), within `min` and `max`. A press that barely moves (a tap or click) turns it into a field to type a value
|
|
15
|
+
* in: Enter or leaving the field keeps the value, Escape cancels.
|
|
16
|
+
*
|
|
17
|
+
* With keyboard focus, the arrow keys change it by `step`, Page Up and Page Down by `pageStep`, Home and End go to
|
|
18
|
+
* `min` and `max`, and Enter or F2 starts typing.
|
|
19
|
+
*
|
|
20
|
+
* Fires `pc-change` (detail: `{ value }`) when the value changes, after changing `value` itself. Bind a value that
|
|
21
|
+
* may be changed by others with `live()`, e.g. `.value=${live(brush.width)}`, so it's shown again if a listener keeps
|
|
22
|
+
* it the same.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* html`<pc-number-slider label="Opacity" min="0" max="100" unit="%" .value=${live(50)}
|
|
26
|
+
* @pc-change=${(e) => setOpacity(e.detail.value)}></pc-number-slider>`
|
|
27
|
+
*/
|
|
28
|
+
export declare class PcNumberSlider extends PcElement {
|
|
29
|
+
static properties: {
|
|
30
|
+
value: {
|
|
31
|
+
type: NumberConstructor;
|
|
32
|
+
};
|
|
33
|
+
min: {
|
|
34
|
+
type: NumberConstructor;
|
|
35
|
+
};
|
|
36
|
+
max: {
|
|
37
|
+
type: NumberConstructor;
|
|
38
|
+
};
|
|
39
|
+
step: {
|
|
40
|
+
type: NumberConstructor;
|
|
41
|
+
};
|
|
42
|
+
pageStep: {
|
|
43
|
+
type: NumberConstructor;
|
|
44
|
+
attribute: string;
|
|
45
|
+
};
|
|
46
|
+
dragStep: {
|
|
47
|
+
type: NumberConstructor;
|
|
48
|
+
attribute: string;
|
|
49
|
+
};
|
|
50
|
+
unit: {
|
|
51
|
+
type: StringConstructor;
|
|
52
|
+
};
|
|
53
|
+
text: {
|
|
54
|
+
type: StringConstructor;
|
|
55
|
+
};
|
|
56
|
+
label: {
|
|
57
|
+
type: StringConstructor;
|
|
58
|
+
};
|
|
59
|
+
disabled: {
|
|
60
|
+
type: BooleanConstructor;
|
|
61
|
+
reflect: boolean;
|
|
62
|
+
};
|
|
63
|
+
editing: {
|
|
64
|
+
type: BooleanConstructor;
|
|
65
|
+
reflect: boolean;
|
|
66
|
+
};
|
|
67
|
+
translations: {
|
|
68
|
+
attribute: boolean;
|
|
69
|
+
};
|
|
70
|
+
icons: {
|
|
71
|
+
attribute: boolean;
|
|
72
|
+
};
|
|
73
|
+
theme: {
|
|
74
|
+
type: StringConstructor;
|
|
75
|
+
reflect: boolean;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
static styles: import("lit").CSSResultGroup[];
|
|
79
|
+
/** The number */
|
|
80
|
+
value: number;
|
|
81
|
+
/** Lowest value (0 by default) */
|
|
82
|
+
min: number;
|
|
83
|
+
/** Highest value (100 by default) */
|
|
84
|
+
max: number;
|
|
85
|
+
/** How much a pixel of dragging or an arrow key changes it (1 by default) */
|
|
86
|
+
step: number;
|
|
87
|
+
/** How much Page Up and Page Down change it (10 steps by default) */
|
|
88
|
+
pageStep?: number;
|
|
89
|
+
/** How much a pixel of dragging changes it (`step` by default), e.g. 1 for an angle typed to 0.01 */
|
|
90
|
+
dragStep?: number;
|
|
91
|
+
/** Shown after the value, e.g. "%" */
|
|
92
|
+
unit: string;
|
|
93
|
+
/** Shown instead of the value and unit, e.g. "30 × 10" */
|
|
94
|
+
text: string;
|
|
95
|
+
/** Its name for screen readers, e.g. "Opacity" */
|
|
96
|
+
label: string;
|
|
97
|
+
disabled: boolean;
|
|
98
|
+
/** Whether it's a field being typed in */
|
|
99
|
+
editing: boolean;
|
|
100
|
+
/** Whether focus goes back to the slider when typing ends (it doesn't when the field lost focus) */
|
|
101
|
+
private refocus;
|
|
102
|
+
private readonly drag;
|
|
103
|
+
constructor();
|
|
104
|
+
/** The value as shown: `text`, or the value and unit */
|
|
105
|
+
get displayText(): string;
|
|
106
|
+
/**
|
|
107
|
+
* Set the value (fitted within `min` and `max`, to a multiple of `step`) and tell listeners if it changed.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* slider.change(40); // fires pc-change with { value: 40 }
|
|
111
|
+
*/
|
|
112
|
+
change(value: number): void;
|
|
113
|
+
/**
|
|
114
|
+
* Turn into a field to type a value in, with its text selected.
|
|
115
|
+
*/
|
|
116
|
+
startEditing(): void;
|
|
117
|
+
/**
|
|
118
|
+
* Stop typing, keeping the typed value (if it's a number) unless cancelled.
|
|
119
|
+
*/
|
|
120
|
+
private stopEditing;
|
|
121
|
+
/** Start a press, unless disabled */
|
|
122
|
+
private onPointerDown;
|
|
123
|
+
/** End a press: one that didn't become a drag starts typing */
|
|
124
|
+
private onPointerUp;
|
|
125
|
+
/** Change the value with the arrow keys, Page Up and Page Down, Home and End, or start typing (Enter, F2) */
|
|
126
|
+
private onSliderKeyDown;
|
|
127
|
+
/** Keep the typed value with Enter, or cancel with Escape (neither reaches the rest of the page) */
|
|
128
|
+
private onInputKeyDown;
|
|
129
|
+
/**
|
|
130
|
+
* Keep the typed value when the field loses focus, leaving focus where it went. Ignored once typing has ended (some
|
|
131
|
+
* browsers blur the field as it's removed).
|
|
132
|
+
*/
|
|
133
|
+
private onInputBlur;
|
|
134
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
135
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
136
|
+
render(): import("lit").TemplateResult<1>;
|
|
137
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { PcElement, defineElement, emit } from "../../base/index.js";
|
|
2
|
+
import { PointerDrag } from "../../drag/index.js";
|
|
3
|
+
import { css, html, nothing } from "lit";
|
|
4
|
+
// src/ui/elements/PcNumberSlider/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* A number kept within `min` and `max` and rounded to a multiple of `step` (from `min`).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* fitNumber(300, 1, 256, 1); // 256
|
|
10
|
+
* fitNumber(12.4, 0, 100, 5); // 10
|
|
11
|
+
* fitNumber(45.678, -360, 360, 0.01); // 45.68
|
|
12
|
+
*/
|
|
13
|
+
var fitNumber = (value, min, max, step) => {
|
|
14
|
+
const decimals = String(step).split(".")[1]?.length ?? 0;
|
|
15
|
+
const stepped = Number((min + Math.round((value - min) / step) * step).toFixed(decimals));
|
|
16
|
+
return Math.min(max, Math.max(min, stepped));
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A number shown as text that changes when pressed and dragged: right or up raises it, left or down lowers it, a step
|
|
20
|
+
* per pixel (or `drag-step`), within `min` and `max`. A press that barely moves (a tap or click) turns it into a field to type a value
|
|
21
|
+
* in: Enter or leaving the field keeps the value, Escape cancels.
|
|
22
|
+
*
|
|
23
|
+
* With keyboard focus, the arrow keys change it by `step`, Page Up and Page Down by `pageStep`, Home and End go to
|
|
24
|
+
* `min` and `max`, and Enter or F2 starts typing.
|
|
25
|
+
*
|
|
26
|
+
* Fires `pc-change` (detail: `{ value }`) when the value changes, after changing `value` itself. Bind a value that
|
|
27
|
+
* may be changed by others with `live()`, e.g. `.value=${live(brush.width)}`, so it's shown again if a listener keeps
|
|
28
|
+
* it the same.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* html`<pc-number-slider label="Opacity" min="0" max="100" unit="%" .value=${live(50)}
|
|
32
|
+
* @pc-change=${(e) => setOpacity(e.detail.value)}></pc-number-slider>`
|
|
33
|
+
*/
|
|
34
|
+
var PcNumberSlider = class extends PcElement {
|
|
35
|
+
static properties = {
|
|
36
|
+
...PcElement.properties,
|
|
37
|
+
value: { type: Number },
|
|
38
|
+
min: { type: Number },
|
|
39
|
+
max: { type: Number },
|
|
40
|
+
step: { type: Number },
|
|
41
|
+
pageStep: {
|
|
42
|
+
type: Number,
|
|
43
|
+
attribute: "page-step"
|
|
44
|
+
},
|
|
45
|
+
dragStep: {
|
|
46
|
+
type: Number,
|
|
47
|
+
attribute: "drag-step"
|
|
48
|
+
},
|
|
49
|
+
unit: { type: String },
|
|
50
|
+
text: { type: String },
|
|
51
|
+
label: { type: String },
|
|
52
|
+
disabled: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
reflect: true
|
|
55
|
+
},
|
|
56
|
+
editing: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
reflect: true
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
static styles = [PcElement.styles, css`
|
|
62
|
+
:host {
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
min-width: 44px;
|
|
65
|
+
user-select: none;
|
|
66
|
+
-webkit-user-select: none;
|
|
67
|
+
}
|
|
68
|
+
[role="slider"],
|
|
69
|
+
input {
|
|
70
|
+
display: flex;
|
|
71
|
+
flex: 1;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
width: 100%;
|
|
75
|
+
min-width: 0;
|
|
76
|
+
min-height: 30px;
|
|
77
|
+
margin: 0;
|
|
78
|
+
padding: 0 var(--pc-space-2);
|
|
79
|
+
border: 1px solid var(--pc-border);
|
|
80
|
+
border-radius: var(--pc-radius-small);
|
|
81
|
+
background: var(--pc-surface);
|
|
82
|
+
color: var(--pc-text);
|
|
83
|
+
font-family: var(--pc-font);
|
|
84
|
+
font-size: var(--pc-font-size);
|
|
85
|
+
font-variant-numeric: tabular-nums;
|
|
86
|
+
text-align: center;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
}
|
|
89
|
+
[role="slider"] {
|
|
90
|
+
cursor: ew-resize;
|
|
91
|
+
touch-action: none;
|
|
92
|
+
}
|
|
93
|
+
[role="slider"]:hover {
|
|
94
|
+
background: var(--pc-surface-hover);
|
|
95
|
+
}
|
|
96
|
+
[role="slider"]:focus-visible,
|
|
97
|
+
input:focus-visible {
|
|
98
|
+
outline: var(--pc-focus-width) solid var(--pc-focus);
|
|
99
|
+
outline-offset: -2px;
|
|
100
|
+
}
|
|
101
|
+
input {
|
|
102
|
+
user-select: text;
|
|
103
|
+
-webkit-user-select: text;
|
|
104
|
+
}
|
|
105
|
+
:host([disabled]) [role="slider"] {
|
|
106
|
+
opacity: 0.4;
|
|
107
|
+
cursor: default;
|
|
108
|
+
}
|
|
109
|
+
`];
|
|
110
|
+
/** Whether focus goes back to the slider when typing ends (it doesn't when the field lost focus) */
|
|
111
|
+
refocus = false;
|
|
112
|
+
drag = new PointerDrag((delta) => this.change(this.value + delta * (this.dragStep ?? this.step)), 4);
|
|
113
|
+
constructor() {
|
|
114
|
+
super();
|
|
115
|
+
this.value = 0;
|
|
116
|
+
this.min = 0;
|
|
117
|
+
this.max = 100;
|
|
118
|
+
this.step = 1;
|
|
119
|
+
this.unit = "";
|
|
120
|
+
this.text = "";
|
|
121
|
+
this.label = "";
|
|
122
|
+
this.disabled = false;
|
|
123
|
+
this.editing = false;
|
|
124
|
+
}
|
|
125
|
+
/** The value as shown: `text`, or the value and unit */
|
|
126
|
+
get displayText() {
|
|
127
|
+
return this.text || `${this.value}${this.unit}`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Set the value (fitted within `min` and `max`, to a multiple of `step`) and tell listeners if it changed.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* slider.change(40); // fires pc-change with { value: 40 }
|
|
134
|
+
*/
|
|
135
|
+
change(value) {
|
|
136
|
+
const next = fitNumber(value, this.min, this.max, this.step);
|
|
137
|
+
if (next === this.value) return;
|
|
138
|
+
this.value = next;
|
|
139
|
+
emit(this, "pc-change", { value: next });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Turn into a field to type a value in, with its text selected.
|
|
143
|
+
*/
|
|
144
|
+
startEditing() {
|
|
145
|
+
if (this.disabled) return;
|
|
146
|
+
this.refocus = true;
|
|
147
|
+
this.editing = true;
|
|
148
|
+
this.performUpdate();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Stop typing, keeping the typed value (if it's a number) unless cancelled.
|
|
152
|
+
*/
|
|
153
|
+
stopEditing(keep) {
|
|
154
|
+
const typed = this.renderRoot.querySelector("input").value.trim();
|
|
155
|
+
this.editing = false;
|
|
156
|
+
if (keep && typed !== "" && Number.isFinite(Number(typed))) this.change(Number(typed));
|
|
157
|
+
}
|
|
158
|
+
/** Start a press, unless disabled */
|
|
159
|
+
onPointerDown(event) {
|
|
160
|
+
if (!this.disabled) this.drag.start(event);
|
|
161
|
+
}
|
|
162
|
+
/** End a press: one that didn't become a drag starts typing */
|
|
163
|
+
onPointerUp() {
|
|
164
|
+
if (this.disabled) return;
|
|
165
|
+
if (!this.drag.end()) this.startEditing();
|
|
166
|
+
}
|
|
167
|
+
/** Change the value with the arrow keys, Page Up and Page Down, Home and End, or start typing (Enter, F2) */
|
|
168
|
+
onSliderKeyDown(event) {
|
|
169
|
+
if (this.disabled) return;
|
|
170
|
+
const page = this.pageStep ?? this.step * 10;
|
|
171
|
+
const action = {
|
|
172
|
+
ArrowUp: () => this.change(this.value + this.step),
|
|
173
|
+
ArrowRight: () => this.change(this.value + this.step),
|
|
174
|
+
ArrowDown: () => this.change(this.value - this.step),
|
|
175
|
+
ArrowLeft: () => this.change(this.value - this.step),
|
|
176
|
+
PageUp: () => this.change(this.value + page),
|
|
177
|
+
PageDown: () => this.change(this.value - page),
|
|
178
|
+
Home: () => this.change(this.min),
|
|
179
|
+
End: () => this.change(this.max),
|
|
180
|
+
Enter: () => this.startEditing(),
|
|
181
|
+
F2: () => this.startEditing()
|
|
182
|
+
}[event.key];
|
|
183
|
+
if (!action) return;
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
action();
|
|
186
|
+
}
|
|
187
|
+
/** Keep the typed value with Enter, or cancel with Escape (neither reaches the rest of the page) */
|
|
188
|
+
onInputKeyDown(event) {
|
|
189
|
+
if (event.key !== "Enter" && event.key !== "Escape") return;
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
event.stopPropagation();
|
|
192
|
+
this.stopEditing(event.key === "Enter");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Keep the typed value when the field loses focus, leaving focus where it went. Ignored once typing has ended (some
|
|
196
|
+
* browsers blur the field as it's removed).
|
|
197
|
+
*/
|
|
198
|
+
onInputBlur() {
|
|
199
|
+
if (!this.editing) return;
|
|
200
|
+
this.refocus = false;
|
|
201
|
+
this.stopEditing(true);
|
|
202
|
+
}
|
|
203
|
+
willUpdate(changed) {
|
|
204
|
+
if (changed.has("disabled") && this.disabled) this.editing = false;
|
|
205
|
+
}
|
|
206
|
+
updated(changed) {
|
|
207
|
+
if (!changed.has("editing")) return;
|
|
208
|
+
if (this.editing) {
|
|
209
|
+
const input = this.renderRoot.querySelector("input");
|
|
210
|
+
input.focus();
|
|
211
|
+
input.select();
|
|
212
|
+
} else if (changed.get("editing") && this.refocus) this.renderRoot.querySelector("[role=slider]").focus();
|
|
213
|
+
}
|
|
214
|
+
render() {
|
|
215
|
+
const text = this.displayText;
|
|
216
|
+
if (this.editing) return html`<input
|
|
217
|
+
part="field"
|
|
218
|
+
type="text"
|
|
219
|
+
inputmode="decimal"
|
|
220
|
+
aria-label=${this.label || nothing}
|
|
221
|
+
.value=${String(this.value)}
|
|
222
|
+
@keydown=${this.onInputKeyDown}
|
|
223
|
+
@blur=${this.onInputBlur}
|
|
224
|
+
/>`;
|
|
225
|
+
return html`<div
|
|
226
|
+
part="control"
|
|
227
|
+
role="slider"
|
|
228
|
+
tabindex=${this.disabled ? -1 : 0}
|
|
229
|
+
aria-label=${this.label || nothing}
|
|
230
|
+
aria-valuemin=${this.min}
|
|
231
|
+
aria-valuemax=${this.max}
|
|
232
|
+
aria-valuenow=${this.value}
|
|
233
|
+
aria-valuetext=${text}
|
|
234
|
+
aria-disabled=${this.disabled ? "true" : nothing}
|
|
235
|
+
title=${this.t("paintCanvas.numberSlider.hint")}
|
|
236
|
+
@pointerdown=${this.onPointerDown}
|
|
237
|
+
@pointermove=${this.drag.move}
|
|
238
|
+
@pointerup=${this.onPointerUp}
|
|
239
|
+
@pointercancel=${this.drag.end}
|
|
240
|
+
@keydown=${this.onSliderKeyDown}
|
|
241
|
+
>
|
|
242
|
+
${text}
|
|
243
|
+
</div>`;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
defineElement("pc-number-slider", PcNumberSlider);
|
|
247
|
+
export { PcNumberSlider, fitNumber };
|