xterm-input-panel 1.0.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/.storybook/main.ts +11 -0
- package/.storybook/preview.ts +15 -0
- package/.storybook/vitest.setup.ts +8 -0
- package/CHANGELOG.md +7 -0
- package/package.json +37 -0
- package/src/brand-icons/claude.png +0 -0
- package/src/brand-icons/codex.png +0 -0
- package/src/brand-icons/gemini.png +0 -0
- package/src/icons.ts +77 -0
- package/src/index.ts +16 -0
- package/src/input-method-tab.stories.ts +131 -0
- package/src/input-method-tab.ts +142 -0
- package/src/input-panel-settings.stories.ts +73 -0
- package/src/input-panel-settings.ts +245 -0
- package/src/input-panel.stories.ts +241 -0
- package/src/input-panel.ts +815 -0
- package/src/pixi-theme.test.ts +58 -0
- package/src/pixi-theme.ts +179 -0
- package/src/platform.ts +14 -0
- package/src/shortcut-pages.ts +204 -0
- package/src/shortcut-tab.ts +543 -0
- package/src/virtual-keyboard-layouts.ts +150 -0
- package/src/virtual-keyboard-tab.stories.ts +390 -0
- package/src/virtual-keyboard-tab.ts +642 -0
- package/src/virtual-trackpad-tab.stories.ts +476 -0
- package/src/virtual-trackpad-tab.ts +556 -0
- package/src/xterm-addon.stories.ts +413 -0
- package/src/xterm-addon.ts +947 -0
- package/tsconfig.json +8 -0
- package/vite.config.ts +13 -0
- package/vitest.storybook.config.ts +23 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit'
|
|
2
|
+
|
|
3
|
+
const SETTINGS_KEY = 'xtermInputPanelSettings'
|
|
4
|
+
|
|
5
|
+
function mergeSettings(updates: Record<string, unknown>) {
|
|
6
|
+
try {
|
|
7
|
+
const existing = JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}')
|
|
8
|
+
localStorage.setItem(SETTINGS_KEY, JSON.stringify({ ...existing, ...updates }))
|
|
9
|
+
} catch {
|
|
10
|
+
/* ignore */
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* InputPanel settings — height/width sliders and vibration intensity.
|
|
16
|
+
*
|
|
17
|
+
* Dispatches `input-panel:settings-change` CustomEvent with updated values.
|
|
18
|
+
*/
|
|
19
|
+
export class InputPanelSettings extends LitElement {
|
|
20
|
+
static get properties() {
|
|
21
|
+
return {
|
|
22
|
+
fixedHeight: { type: Number, attribute: 'fixed-height' },
|
|
23
|
+
floatingWidth: { type: Number, attribute: 'floating-width' },
|
|
24
|
+
floatingHeight: { type: Number, attribute: 'floating-height' },
|
|
25
|
+
vibrationIntensity: { type: Number, attribute: 'vibration-intensity' },
|
|
26
|
+
historyLimit: { type: Number, attribute: 'history-limit' },
|
|
27
|
+
visible: { type: Boolean },
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static styles = css`
|
|
32
|
+
:host {
|
|
33
|
+
display: block;
|
|
34
|
+
height: 100%;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.overlay {
|
|
39
|
+
display: none;
|
|
40
|
+
position: relative;
|
|
41
|
+
height: 100%;
|
|
42
|
+
min-height: 0;
|
|
43
|
+
background: var(--terminal, #1a1a1a);
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
padding: 12px;
|
|
46
|
+
gap: 16px;
|
|
47
|
+
overflow-y: auto;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:host([visible]) .overlay {
|
|
51
|
+
display: flex;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.setting {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 6px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.setting-label {
|
|
61
|
+
font-size: 11px;
|
|
62
|
+
color: var(--muted-foreground, #888);
|
|
63
|
+
display: flex;
|
|
64
|
+
justify-content: space-between;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.setting-value {
|
|
68
|
+
color: var(--foreground, #fff);
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
input[type='range'] {
|
|
73
|
+
width: 100%;
|
|
74
|
+
accent-color: var(--primary, #e04a2f);
|
|
75
|
+
}
|
|
76
|
+
`
|
|
77
|
+
|
|
78
|
+
declare fixedHeight: number
|
|
79
|
+
declare floatingWidth: number
|
|
80
|
+
declare floatingHeight: number
|
|
81
|
+
declare vibrationIntensity: number
|
|
82
|
+
declare historyLimit: number
|
|
83
|
+
declare visible: boolean
|
|
84
|
+
|
|
85
|
+
constructor() {
|
|
86
|
+
super()
|
|
87
|
+
this.fixedHeight = 250
|
|
88
|
+
this.floatingWidth = 60
|
|
89
|
+
this.floatingHeight = 30
|
|
90
|
+
this.vibrationIntensity = 50
|
|
91
|
+
this.historyLimit = 50
|
|
92
|
+
this.visible = false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
connectedCallback() {
|
|
96
|
+
super.connectedCallback()
|
|
97
|
+
try {
|
|
98
|
+
const raw = localStorage.getItem(SETTINGS_KEY)
|
|
99
|
+
if (raw) {
|
|
100
|
+
const data = JSON.parse(raw)
|
|
101
|
+
if (typeof data.fixedHeight === 'number') this.fixedHeight = data.fixedHeight
|
|
102
|
+
if (typeof data.floatingWidth === 'number') this.floatingWidth = data.floatingWidth
|
|
103
|
+
if (typeof data.floatingHeight === 'number') {
|
|
104
|
+
// Backward compat: if > 100, treat as px and convert to vh%
|
|
105
|
+
if (data.floatingHeight > 100) {
|
|
106
|
+
this.floatingHeight = Math.round((data.floatingHeight / window.innerHeight) * 100)
|
|
107
|
+
} else {
|
|
108
|
+
this.floatingHeight = data.floatingHeight
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (typeof data.vibrationIntensity === 'number')
|
|
112
|
+
this.vibrationIntensity = data.vibrationIntensity
|
|
113
|
+
if (typeof data.historyLimit === 'number') this.historyLimit = data.historyLimit
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
/* ignore */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private _emit() {
|
|
121
|
+
mergeSettings({
|
|
122
|
+
fixedHeight: this.fixedHeight,
|
|
123
|
+
floatingWidth: this.floatingWidth,
|
|
124
|
+
floatingHeight: this.floatingHeight,
|
|
125
|
+
vibrationIntensity: this.vibrationIntensity,
|
|
126
|
+
historyLimit: this.historyLimit,
|
|
127
|
+
})
|
|
128
|
+
this.dispatchEvent(
|
|
129
|
+
new CustomEvent('input-panel:settings-change', {
|
|
130
|
+
detail: {
|
|
131
|
+
fixedHeight: this.fixedHeight,
|
|
132
|
+
floatingWidth: this.floatingWidth,
|
|
133
|
+
floatingHeight: this.floatingHeight,
|
|
134
|
+
vibrationIntensity: this.vibrationIntensity,
|
|
135
|
+
historyLimit: this.historyLimit,
|
|
136
|
+
},
|
|
137
|
+
bubbles: true,
|
|
138
|
+
composed: true,
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private _onFixedHeight(e: Event) {
|
|
144
|
+
this.fixedHeight = Number((e.target as HTMLInputElement).value)
|
|
145
|
+
this._emit()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private _onFloatingWidth(e: Event) {
|
|
149
|
+
this.floatingWidth = Number((e.target as HTMLInputElement).value)
|
|
150
|
+
this._emit()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private _onFloatingHeight(e: Event) {
|
|
154
|
+
this.floatingHeight = Number((e.target as HTMLInputElement).value)
|
|
155
|
+
this._emit()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private _onVibration(e: Event) {
|
|
159
|
+
this.vibrationIntensity = Number((e.target as HTMLInputElement).value)
|
|
160
|
+
this._emit()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private _onHistoryLimit(e: Event) {
|
|
164
|
+
this.historyLimit = Number((e.target as HTMLInputElement).value)
|
|
165
|
+
this._emit()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
render() {
|
|
169
|
+
return html`
|
|
170
|
+
<div class="overlay" part="settings-overlay">
|
|
171
|
+
<div class="setting">
|
|
172
|
+
<label class="setting-label">
|
|
173
|
+
Fixed mode height
|
|
174
|
+
<span class="setting-value">${this.fixedHeight}px</span>
|
|
175
|
+
</label>
|
|
176
|
+
<input
|
|
177
|
+
type="range"
|
|
178
|
+
min="150"
|
|
179
|
+
max="500"
|
|
180
|
+
.value=${String(this.fixedHeight)}
|
|
181
|
+
@input=${this._onFixedHeight}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="setting">
|
|
186
|
+
<label class="setting-label">
|
|
187
|
+
Floating mode width
|
|
188
|
+
<span class="setting-value">${this.floatingWidth}%</span>
|
|
189
|
+
</label>
|
|
190
|
+
<input
|
|
191
|
+
type="range"
|
|
192
|
+
min="20"
|
|
193
|
+
max="95"
|
|
194
|
+
.value=${String(this.floatingWidth)}
|
|
195
|
+
@input=${this._onFloatingWidth}
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="setting">
|
|
200
|
+
<label class="setting-label">
|
|
201
|
+
Floating mode height
|
|
202
|
+
<span class="setting-value">${this.floatingHeight}%</span>
|
|
203
|
+
</label>
|
|
204
|
+
<input
|
|
205
|
+
type="range"
|
|
206
|
+
min="15"
|
|
207
|
+
max="85"
|
|
208
|
+
.value=${String(this.floatingHeight)}
|
|
209
|
+
@input=${this._onFloatingHeight}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div class="setting">
|
|
214
|
+
<label class="setting-label">
|
|
215
|
+
Vibration intensity
|
|
216
|
+
<span class="setting-value">${this.vibrationIntensity}%</span>
|
|
217
|
+
</label>
|
|
218
|
+
<input
|
|
219
|
+
type="range"
|
|
220
|
+
min="0"
|
|
221
|
+
max="100"
|
|
222
|
+
.value=${String(this.vibrationIntensity)}
|
|
223
|
+
@input=${this._onVibration}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="setting">
|
|
228
|
+
<label class="setting-label">
|
|
229
|
+
History limit
|
|
230
|
+
<span class="setting-value">${this.historyLimit}</span>
|
|
231
|
+
</label>
|
|
232
|
+
<input
|
|
233
|
+
type="range"
|
|
234
|
+
min="1"
|
|
235
|
+
max="1000"
|
|
236
|
+
.value=${String(this.historyLimit)}
|
|
237
|
+
@input=${this._onHistoryLimit}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
`
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
customElements.define('input-panel-settings', InputPanelSettings)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/web-components-vite'
|
|
2
|
+
import type { LitElement } from 'lit'
|
|
3
|
+
import { html } from 'lit'
|
|
4
|
+
import { expect, fn } from 'storybook/test'
|
|
5
|
+
|
|
6
|
+
// Register all custom elements
|
|
7
|
+
import './index.js'
|
|
8
|
+
|
|
9
|
+
/** Helper to get a Lit element and wait for it to be ready */
|
|
10
|
+
async function getLitElement(container: HTMLElement, selector: string) {
|
|
11
|
+
const el = container.querySelector(selector) as LitElement
|
|
12
|
+
await el.updateComplete
|
|
13
|
+
return el
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const meta: Meta = {
|
|
17
|
+
title: 'InputPanel',
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
decorators: [
|
|
20
|
+
(story) => html`
|
|
21
|
+
<div
|
|
22
|
+
style="width: 400px; height: 300px; background: #1a1a1a; color: #fff; font-family: monospace;"
|
|
23
|
+
>
|
|
24
|
+
${story()}
|
|
25
|
+
</div>
|
|
26
|
+
`,
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default meta
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The main InputPanel container with toolbar tabs and content area.
|
|
34
|
+
* Default tab is "input" (Input Method).
|
|
35
|
+
*/
|
|
36
|
+
export const Default: StoryObj = {
|
|
37
|
+
render: () => html`
|
|
38
|
+
<input-panel layout="fixed" style="height: 100%;">
|
|
39
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
40
|
+
<virtual-keyboard-tab slot="keys"></virtual-keyboard-tab>
|
|
41
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
42
|
+
<virtual-trackpad-tab slot="trackpad"></virtual-trackpad-tab>
|
|
43
|
+
</input-panel>
|
|
44
|
+
`,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* InputPanel in floating layout mode (renders as dialog).
|
|
49
|
+
*/
|
|
50
|
+
export const FloatingLayout: StoryObj = {
|
|
51
|
+
render: () => html`
|
|
52
|
+
<input-panel layout="floating" style="height: 100%;">
|
|
53
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
54
|
+
<virtual-keyboard-tab slot="keys" floating></virtual-keyboard-tab>
|
|
55
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
56
|
+
<virtual-trackpad-tab slot="trackpad" floating></virtual-trackpad-tab>
|
|
57
|
+
</input-panel>
|
|
58
|
+
`,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* InputPanel starts on the "keys" tab (Virtual Keyboard).
|
|
63
|
+
*/
|
|
64
|
+
export const KeysTab: StoryObj = {
|
|
65
|
+
render: () => html`
|
|
66
|
+
<input-panel layout="fixed" active-tab="keys" style="height: 100%;">
|
|
67
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
68
|
+
<virtual-keyboard-tab slot="keys"></virtual-keyboard-tab>
|
|
69
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
70
|
+
<virtual-trackpad-tab slot="trackpad"></virtual-trackpad-tab>
|
|
71
|
+
</input-panel>
|
|
72
|
+
`,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* InputPanel starts on the "trackpad" tab (Virtual Trackpad).
|
|
77
|
+
*/
|
|
78
|
+
export const TrackpadTab: StoryObj = {
|
|
79
|
+
render: () => html`
|
|
80
|
+
<input-panel layout="fixed" active-tab="trackpad" style="height: 100%;">
|
|
81
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
82
|
+
<virtual-keyboard-tab slot="keys"></virtual-keyboard-tab>
|
|
83
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
84
|
+
<virtual-trackpad-tab slot="trackpad"></virtual-trackpad-tab>
|
|
85
|
+
</input-panel>
|
|
86
|
+
`,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* InputPanel starts on the "settings" tab.
|
|
91
|
+
*/
|
|
92
|
+
export const SettingsTab: StoryObj = {
|
|
93
|
+
render: () => html`
|
|
94
|
+
<input-panel layout="fixed" active-tab="settings" style="height: 100%;">
|
|
95
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
96
|
+
<virtual-keyboard-tab slot="keys"></virtual-keyboard-tab>
|
|
97
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
98
|
+
<virtual-trackpad-tab slot="trackpad"></virtual-trackpad-tab>
|
|
99
|
+
</input-panel>
|
|
100
|
+
`,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Verifies that tab switching works by clicking the "Keys" tab button.
|
|
105
|
+
* Now expects 5 tab buttons (Input, Keys, Shortcuts, Trackpad, Settings).
|
|
106
|
+
*/
|
|
107
|
+
export const TabSwitching: StoryObj = {
|
|
108
|
+
render: () => html`
|
|
109
|
+
<input-panel layout="fixed" style="height: 100%;">
|
|
110
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
111
|
+
<virtual-keyboard-tab slot="keys"></virtual-keyboard-tab>
|
|
112
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
113
|
+
<virtual-trackpad-tab slot="trackpad"></virtual-trackpad-tab>
|
|
114
|
+
</input-panel>
|
|
115
|
+
`,
|
|
116
|
+
play: async ({ canvasElement }) => {
|
|
117
|
+
const panel = await getLitElement(canvasElement, 'input-panel')
|
|
118
|
+
|
|
119
|
+
const shadow = panel.shadowRoot!
|
|
120
|
+
const tabButtons = shadow.querySelectorAll('.tab-btn')
|
|
121
|
+
expect(tabButtons.length).toBe(5)
|
|
122
|
+
|
|
123
|
+
// Click "Keys" tab
|
|
124
|
+
const keysTab = tabButtons[1] as HTMLButtonElement
|
|
125
|
+
keysTab.click()
|
|
126
|
+
await panel.updateComplete
|
|
127
|
+
|
|
128
|
+
// Verify the active attribute changed
|
|
129
|
+
expect(keysTab.hasAttribute('data-active')).toBe(true)
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verifies that the close button dispatches the `input-panel:close` event.
|
|
135
|
+
*/
|
|
136
|
+
export const CloseEvent: StoryObj = {
|
|
137
|
+
render: () => html`
|
|
138
|
+
<input-panel layout="fixed" style="height: 100%;">
|
|
139
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
140
|
+
</input-panel>
|
|
141
|
+
`,
|
|
142
|
+
play: async ({ canvasElement }) => {
|
|
143
|
+
const panel = await getLitElement(canvasElement, 'input-panel')
|
|
144
|
+
|
|
145
|
+
const closeHandler = fn()
|
|
146
|
+
panel.addEventListener('input-panel:close', closeHandler)
|
|
147
|
+
|
|
148
|
+
const shadow = panel.shadowRoot!
|
|
149
|
+
const closeBtn = shadow.querySelector('.icon-btn:last-child') as HTMLButtonElement
|
|
150
|
+
closeBtn.click()
|
|
151
|
+
|
|
152
|
+
expect(closeHandler).toHaveBeenCalledTimes(1)
|
|
153
|
+
panel.removeEventListener('input-panel:close', closeHandler)
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Verifies that the layout toggle dispatches the `input-panel:layout-change` event.
|
|
159
|
+
* Pin/float button is now icon-only (no text label).
|
|
160
|
+
*/
|
|
161
|
+
export const LayoutToggle: StoryObj = {
|
|
162
|
+
render: () => html`
|
|
163
|
+
<input-panel layout="fixed" style="height: 100%;">
|
|
164
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
165
|
+
</input-panel>
|
|
166
|
+
`,
|
|
167
|
+
play: async ({ canvasElement }) => {
|
|
168
|
+
const panel = await getLitElement(canvasElement, 'input-panel')
|
|
169
|
+
|
|
170
|
+
let receivedLayout = ''
|
|
171
|
+
panel.addEventListener('input-panel:layout-change', ((e: CustomEvent) => {
|
|
172
|
+
receivedLayout = e.detail.layout
|
|
173
|
+
}) as EventListener)
|
|
174
|
+
|
|
175
|
+
const shadow = panel.shadowRoot!
|
|
176
|
+
// Pin/float is first icon-btn in action-group
|
|
177
|
+
const layoutBtn = shadow.querySelector('.action-group .icon-btn') as HTMLButtonElement
|
|
178
|
+
|
|
179
|
+
layoutBtn.click()
|
|
180
|
+
await panel.updateComplete
|
|
181
|
+
|
|
182
|
+
expect(receivedLayout).toBe('floating')
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Floating layout with resize handles visible at the four corners.
|
|
188
|
+
*/
|
|
189
|
+
export const FloatingResize: StoryObj = {
|
|
190
|
+
render: () => html`
|
|
191
|
+
<input-panel layout="floating" style="height: 100%;">
|
|
192
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
193
|
+
<virtual-keyboard-tab slot="keys" floating></virtual-keyboard-tab>
|
|
194
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
195
|
+
<virtual-trackpad-tab slot="trackpad" floating></virtual-trackpad-tab>
|
|
196
|
+
</input-panel>
|
|
197
|
+
`,
|
|
198
|
+
play: async ({ canvasElement }) => {
|
|
199
|
+
const panel = await getLitElement(canvasElement, 'input-panel')
|
|
200
|
+
const shadow = panel.shadowRoot!
|
|
201
|
+
const dialog = shadow.querySelector('.panel-dialog') as HTMLDialogElement
|
|
202
|
+
expect(dialog).toBeTruthy()
|
|
203
|
+
|
|
204
|
+
const handles = shadow.querySelectorAll('.resize-handle')
|
|
205
|
+
expect(handles.length).toBe(4)
|
|
206
|
+
|
|
207
|
+
// Verify each corner class exists
|
|
208
|
+
expect(shadow.querySelector('.resize-tl')).toBeTruthy()
|
|
209
|
+
expect(shadow.querySelector('.resize-tr')).toBeTruthy()
|
|
210
|
+
expect(shadow.querySelector('.resize-bl')).toBeTruthy()
|
|
211
|
+
expect(shadow.querySelector('.resize-br')).toBeTruthy()
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Verifies Fixed mode height slider updates InputPanel internal style variable.
|
|
217
|
+
*/
|
|
218
|
+
export const FixedHeightSync: StoryObj = {
|
|
219
|
+
render: () => html`
|
|
220
|
+
<input-panel layout="fixed" active-tab="settings">
|
|
221
|
+
<input-method-tab slot="input"></input-method-tab>
|
|
222
|
+
<virtual-keyboard-tab slot="keys"></virtual-keyboard-tab>
|
|
223
|
+
<shortcut-tab slot="shortcuts"></shortcut-tab>
|
|
224
|
+
<virtual-trackpad-tab slot="trackpad"></virtual-trackpad-tab>
|
|
225
|
+
</input-panel>
|
|
226
|
+
`,
|
|
227
|
+
play: async ({ canvasElement }) => {
|
|
228
|
+
const panel = await getLitElement(canvasElement, 'input-panel')
|
|
229
|
+
const settings = panel.shadowRoot?.querySelector('input-panel-settings') as LitElement
|
|
230
|
+
await settings.updateComplete
|
|
231
|
+
|
|
232
|
+
const slider = settings.shadowRoot?.querySelector('input[type="range"]') as HTMLInputElement
|
|
233
|
+
slider.value = '320'
|
|
234
|
+
slider.dispatchEvent(new Event('input', { bubbles: true }))
|
|
235
|
+
|
|
236
|
+
await settings.updateComplete
|
|
237
|
+
await panel.updateComplete
|
|
238
|
+
|
|
239
|
+
expect(panel.style.getPropertyValue('--input-panel-fixed-height').trim()).toBe('320px')
|
|
240
|
+
},
|
|
241
|
+
}
|