@kubohiroya/turbowarp-plus-editor-layout 0.1.1 → 0.1.2
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.ja.md +2 -0
- package/README.md +2 -0
- package/dist/sprite-asset-pane.d.ts +37 -0
- package/dist/sprite-asset-pane.js +109 -0
- package/dist/sprite-editor-toolbar.d.ts +25 -0
- package/dist/sprite-editor-toolbar.js +72 -0
- package/dist/sprite-layout-menu.d.ts +11 -0
- package/dist/sprite-layout-menu.js +114 -0
- package/dist/sprite-tabs.d.ts +113 -20
- package/dist/sprite-tabs.js +496 -76
- package/package.json +12 -1
- package/src/editor.css +74 -5
- package/src/sprite-asset-pane.css +169 -0
- package/src/sprite-asset-pane.tsx +168 -0
- package/src/sprite-editor-toolbar.css +108 -0
- package/src/sprite-editor-toolbar.tsx +124 -0
- package/src/sprite-layout-menu.css +54 -0
- package/src/sprite-layout-menu.ts +119 -0
- package/src/sprite-tabs.ts +593 -95
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
.tw-plus-sprite-layout-menu {
|
|
2
|
+
align-self: center;
|
|
3
|
+
padding-inline: 4px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.tw-plus-sprite-layout-menu-trigger {
|
|
7
|
+
display: grid;
|
|
8
|
+
place-items: center;
|
|
9
|
+
width: 28px;
|
|
10
|
+
height: 28px;
|
|
11
|
+
padding: 4px;
|
|
12
|
+
color: var(--text-primary, #263247);
|
|
13
|
+
background: transparent;
|
|
14
|
+
border: 1px solid transparent;
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tw-plus-sprite-layout-menu-trigger svg { width: 16px; height: 16px; }
|
|
20
|
+
.tw-plus-sprite-layout-menu-trigger:hover { background: var(--ui-black-transparent, #e2e7f0); }
|
|
21
|
+
|
|
22
|
+
.tw-plus-sprite-layout-menu-popup {
|
|
23
|
+
position: fixed;
|
|
24
|
+
z-index: 1000;
|
|
25
|
+
inset: auto;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
max-width: calc(100vw - 8px);
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 4px;
|
|
30
|
+
color: var(--text-primary, #263247);
|
|
31
|
+
background: var(--popover-background, var(--ui-white, #fff));
|
|
32
|
+
border: 1px solid var(--ui-black-transparent, #c6cfda);
|
|
33
|
+
border-radius: 6px;
|
|
34
|
+
box-shadow: 0 4px 12px #0003;
|
|
35
|
+
font: 13px system-ui, sans-serif;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.tw-plus-sprite-layout-menu-popup button {
|
|
39
|
+
display: block;
|
|
40
|
+
width: 100%;
|
|
41
|
+
padding: 6px 8px;
|
|
42
|
+
color: inherit;
|
|
43
|
+
background: transparent;
|
|
44
|
+
border: 0;
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
text-align: start;
|
|
47
|
+
font: inherit;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.tw-plus-sprite-layout-menu-popup button:disabled { opacity: 0.55; cursor: default; }
|
|
52
|
+
.tw-plus-sprite-layout-menu-popup button:enabled:is(:hover, :focus-visible) {
|
|
53
|
+
background: var(--looks-light-transparent, #e6dff3);
|
|
54
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
interface SpriteLayoutCommands {
|
|
2
|
+
splitRight(): void;
|
|
3
|
+
splitBelow(): void;
|
|
4
|
+
close(): void;
|
|
5
|
+
canClose(): boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const labels = {
|
|
9
|
+
ja: ['編集パネルのレイアウト', 'パネルを横に分割', 'パネルを縦に分割', 'この分割パネルを閉じる'],
|
|
10
|
+
en: ['Editor panel layout', 'Split right', 'Split below', 'Close this split']
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createSpriteLayoutMenu(document: Document, locale: string, commands: SpriteLayoutCommands) {
|
|
14
|
+
const [title, right, below, closeLabel] = /^ja(?:-|$)/i.test(locale) ? labels.ja : labels.en;
|
|
15
|
+
const element = document.createElement('div');
|
|
16
|
+
element.className = 'tw-plus-sprite-layout-menu';
|
|
17
|
+
const trigger = document.createElement('button');
|
|
18
|
+
trigger.type = 'button';
|
|
19
|
+
trigger.className = 'tw-plus-sprite-layout-menu-trigger';
|
|
20
|
+
trigger.title = title;
|
|
21
|
+
trigger.setAttribute('aria-label', title);
|
|
22
|
+
trigger.setAttribute('aria-haspopup', 'menu');
|
|
23
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
24
|
+
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
25
|
+
icon.setAttribute('viewBox', '0 0 16 16');
|
|
26
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
27
|
+
icon.setAttribute('fill', 'none');
|
|
28
|
+
icon.setAttribute('stroke', 'currentColor');
|
|
29
|
+
icon.setAttribute('stroke-width', '1.4');
|
|
30
|
+
const shape = document.createElementNS(icon.namespaceURI, 'path');
|
|
31
|
+
shape.setAttribute('d', 'M2.5 3.5h11v9h-11zM2.5 6.5h11m-7 0v6');
|
|
32
|
+
icon.append(shape);
|
|
33
|
+
trigger.append(icon);
|
|
34
|
+
element.append(trigger);
|
|
35
|
+
|
|
36
|
+
const popup = document.createElement('div');
|
|
37
|
+
popup.className = 'tw-plus-sprite-layout-menu-popup';
|
|
38
|
+
popup.setAttribute('role', 'menu');
|
|
39
|
+
popup.setAttribute('aria-label', title);
|
|
40
|
+
const buttons = [[right, commands.splitRight], [below, commands.splitBelow], [closeLabel, commands.close]].map(
|
|
41
|
+
([label, run]: [string, () => void]) => {
|
|
42
|
+
const item = document.createElement('button');
|
|
43
|
+
item.type = 'button';
|
|
44
|
+
item.textContent = label;
|
|
45
|
+
item.tabIndex = -1;
|
|
46
|
+
item.setAttribute('role', 'menuitem');
|
|
47
|
+
item.addEventListener('click', () => {
|
|
48
|
+
close(true);
|
|
49
|
+
run();
|
|
50
|
+
});
|
|
51
|
+
return item;
|
|
52
|
+
});
|
|
53
|
+
popup.append(...buttons);
|
|
54
|
+
let openedIn: Document | undefined;
|
|
55
|
+
let topLayer = false;
|
|
56
|
+
|
|
57
|
+
function close(restoreFocus = false) {
|
|
58
|
+
if (!openedIn) return;
|
|
59
|
+
openedIn.removeEventListener('pointerdown', dismiss, true);
|
|
60
|
+
openedIn = undefined;
|
|
61
|
+
if (topLayer && popup.matches(':popover-open')) popup.hidePopover();
|
|
62
|
+
popup.remove();
|
|
63
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
64
|
+
if (restoreFocus) trigger.focus({preventScroll: true});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function dismiss(event: PointerEvent) {
|
|
68
|
+
const node = event.target as Node;
|
|
69
|
+
if (!trigger.contains(node) && !popup.contains(node)) close();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function open(last = false) {
|
|
73
|
+
if (openedIn) return;
|
|
74
|
+
openedIn = trigger.ownerDocument;
|
|
75
|
+
buttons[2].disabled = !commands.canClose();
|
|
76
|
+
topLayer = typeof popup.showPopover === 'function';
|
|
77
|
+
if (topLayer) {
|
|
78
|
+
popup.setAttribute('popover', 'manual');
|
|
79
|
+
element.append(popup);
|
|
80
|
+
popup.showPopover();
|
|
81
|
+
} else {
|
|
82
|
+
openedIn.body.append(popup);
|
|
83
|
+
}
|
|
84
|
+
const anchor = trigger.getBoundingClientRect();
|
|
85
|
+
const viewport = openedIn.documentElement;
|
|
86
|
+
const {width, height} = popup.getBoundingClientRect();
|
|
87
|
+
popup.style.left = Math.max(4, Math.min(anchor.right - width, viewport.clientWidth - width - 4)) + 'px';
|
|
88
|
+
popup.style.top = (anchor.bottom + height + 8 <= viewport.clientHeight ? anchor.bottom + 4 :
|
|
89
|
+
Math.max(4, anchor.top - height - 4)) + 'px';
|
|
90
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
91
|
+
openedIn.addEventListener('pointerdown', dismiss, true);
|
|
92
|
+
const enabled = buttons.filter(button => !button.disabled);
|
|
93
|
+
enabled[last ? enabled.length - 1 : 0]?.focus({preventScroll: true});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
trigger.addEventListener('click', () => openedIn ? close(true) : open());
|
|
97
|
+
trigger.addEventListener('keydown', event => {
|
|
98
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
open(event.key === 'ArrowUp');
|
|
101
|
+
});
|
|
102
|
+
popup.addEventListener('focusout', event => {
|
|
103
|
+
if (!popup.contains(event.relatedTarget as Node)) close();
|
|
104
|
+
});
|
|
105
|
+
popup.addEventListener('keydown', event => {
|
|
106
|
+
if (event.key === 'Escape') {
|
|
107
|
+
event.stopPropagation();
|
|
108
|
+
close(true);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const enabled = buttons.filter(button => !button.disabled);
|
|
112
|
+
const index = enabled.indexOf(event.target as HTMLButtonElement);
|
|
113
|
+
const next = {ArrowDown: index + 1, ArrowUp: index - 1, Home: 0, End: enabled.length - 1}[event.key];
|
|
114
|
+
if (next === undefined) return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
enabled[(next + enabled.length) % enabled.length]?.focus({preventScroll: true});
|
|
117
|
+
});
|
|
118
|
+
return {element, dispose: () => close()};
|
|
119
|
+
}
|