@eclipse-docks/core 0.7.108 → 0.7.110
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/dist/api/index.js +3 -3
- package/dist/api/services.d.ts +1 -1
- package/dist/api/services.d.ts.map +1 -1
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/{api-BilEZ2UQ.js → api-GQXzRSc-.js} +2 -2
- package/dist/{api-BilEZ2UQ.js.map → api-GQXzRSc-.js.map} +1 -1
- package/dist/components/command.d.ts +2 -0
- package/dist/components/command.d.ts.map +1 -1
- package/dist/core/dialogservice.d.ts.map +1 -1
- package/dist/dialogs/form-dialog.d.ts +46 -0
- package/dist/dialogs/form-dialog.d.ts.map +1 -0
- package/dist/dialogs/index.d.ts +1 -0
- package/dist/dialogs/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/parts/index.js +1 -1
- package/dist/parts/tabs.d.ts.map +1 -1
- package/dist/{parts-DECzUKtn.js → parts-DYgvdov3.js} +188 -11
- package/dist/parts-DYgvdov3.js.map +1 -0
- package/package.json +1 -1
- package/src/api/services.ts +7 -1
- package/src/api/types.ts +4 -1
- package/src/components/command.ts +13 -10
- package/src/core/dialogservice.ts +6 -1
- package/src/dialogs/form-dialog.ts +239 -0
- package/src/dialogs/index.ts +8 -0
- package/src/parts/tabs.ts +15 -1
- package/dist/parts-DECzUKtn.js.map +0 -1
package/package.json
CHANGED
package/src/api/services.ts
CHANGED
|
@@ -35,8 +35,14 @@ export {
|
|
|
35
35
|
navigableInfoDialog,
|
|
36
36
|
confirmDialog,
|
|
37
37
|
promptDialog,
|
|
38
|
+
formDialog,
|
|
39
|
+
formDialogRequired,
|
|
40
|
+
validateFormDialogFields,
|
|
38
41
|
filebrowserDialog,
|
|
39
|
-
type NavigableDialogAction
|
|
42
|
+
type NavigableDialogAction,
|
|
43
|
+
type FormDialogField,
|
|
44
|
+
type FormDialogFieldType,
|
|
45
|
+
type FormDialogOptions,
|
|
40
46
|
} from '../dialogs';
|
|
41
47
|
export {
|
|
42
48
|
toastInfo,
|
package/src/api/types.ts
CHANGED
|
@@ -36,7 +36,10 @@ export type {
|
|
|
36
36
|
export type {
|
|
37
37
|
NavigableDialogAction,
|
|
38
38
|
FilebrowserDialogMode,
|
|
39
|
-
FilebrowserDialogState
|
|
39
|
+
FilebrowserDialogState,
|
|
40
|
+
FormDialogField,
|
|
41
|
+
FormDialogFieldType,
|
|
42
|
+
FormDialogOptions,
|
|
40
43
|
} from '../dialogs';
|
|
41
44
|
export type {
|
|
42
45
|
TreeNode,
|
|
@@ -60,6 +60,16 @@ export class DocksCommand extends DocksWidget {
|
|
|
60
60
|
@state()
|
|
61
61
|
private dropdownContributions: Contribution[] = []
|
|
62
62
|
|
|
63
|
+
private closeDropdown(dropdown: { open?: boolean } | null | undefined) {
|
|
64
|
+
if (dropdown && dropdown.open !== undefined) {
|
|
65
|
+
dropdown.open = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private closeParentDropdown() {
|
|
70
|
+
this.closeDropdown(this.closest('wa-dropdown') as { open?: boolean } | null)
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
private handleClick(event?: Event) {
|
|
64
74
|
if (this.disabled) return
|
|
65
75
|
|
|
@@ -67,27 +77,20 @@ export class DocksCommand extends DocksWidget {
|
|
|
67
77
|
event.stopPropagation()
|
|
68
78
|
}
|
|
69
79
|
|
|
80
|
+
this.closeParentDropdown()
|
|
81
|
+
|
|
70
82
|
if (this.action) {
|
|
71
83
|
this.action(event)
|
|
72
84
|
return
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
if (this.cmd) {
|
|
76
|
-
const dropdown = this.closest('wa-dropdown') as any;
|
|
77
|
-
if (dropdown && dropdown.open !== undefined) {
|
|
78
|
-
dropdown.open = false;
|
|
79
|
-
}
|
|
80
88
|
void this.executeCommand(this.cmd, this.params);
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
private handleSelect(event: CustomEvent) {
|
|
85
|
-
|
|
86
|
-
// This ensures the dropdown is hidden before the command executes
|
|
87
|
-
const dropdown = event.target as any;
|
|
88
|
-
if (dropdown && dropdown.open !== undefined) {
|
|
89
|
-
dropdown.open = false;
|
|
90
|
-
}
|
|
93
|
+
this.closeDropdown(event.target as { open?: boolean })
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
private isInDropdown(): boolean {
|
|
@@ -182,8 +182,13 @@ class DialogService {
|
|
|
182
182
|
}
|
|
183
183
|
const stateWithClose = { ...state, close: cleanup };
|
|
184
184
|
|
|
185
|
+
const dialogLabel =
|
|
186
|
+
state && typeof state === 'object' && typeof (state as { label?: unknown }).label === 'string'
|
|
187
|
+
? (state as { label: string }).label
|
|
188
|
+
: (contribution.label || dialogId);
|
|
189
|
+
|
|
185
190
|
const template = html`
|
|
186
|
-
<wa-dialog label="${
|
|
191
|
+
<wa-dialog label="${dialogLabel}" open @wa-request-close=${cleanup}>
|
|
187
192
|
<style>
|
|
188
193
|
.dialog-service-content {
|
|
189
194
|
display: flex;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { html, css, PropertyValues } from "lit";
|
|
2
|
+
import { customElement, state, property } from "lit/decorators.js";
|
|
3
|
+
import { DocksDialogContent } from "../parts/dialog-content";
|
|
4
|
+
import { contributionRegistry } from "../core/contributionregistry";
|
|
5
|
+
import {
|
|
6
|
+
DIALOG_CONTRIBUTION_TARGET,
|
|
7
|
+
OK_BUTTON,
|
|
8
|
+
CANCEL_BUTTON,
|
|
9
|
+
dialogService,
|
|
10
|
+
} from "../core/dialogservice";
|
|
11
|
+
|
|
12
|
+
/** HTML input types supported by {@link formDialog}. */
|
|
13
|
+
export type FormDialogFieldType = "text" | "password" | "url" | "email" | "number";
|
|
14
|
+
|
|
15
|
+
export interface FormDialogField {
|
|
16
|
+
/** Key in the returned values record. */
|
|
17
|
+
name: string;
|
|
18
|
+
label: string;
|
|
19
|
+
type?: FormDialogFieldType;
|
|
20
|
+
value?: string;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** When false, empty values are allowed. Defaults to true. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FormDialogOptions {
|
|
27
|
+
/** Dialog window title (shown on `wa-dialog`). */
|
|
28
|
+
label: string;
|
|
29
|
+
fields: FormDialogField[];
|
|
30
|
+
/** Optional message above the fields (plain text or markdown). */
|
|
31
|
+
message?: string;
|
|
32
|
+
markdown?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface FormDialogState extends FormDialogOptions {
|
|
36
|
+
resolve: (values: Record<string, string> | null) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@customElement("docks-form-dialog-content")
|
|
40
|
+
export class DocksFormDialogContent extends DocksDialogContent {
|
|
41
|
+
@property({ attribute: false })
|
|
42
|
+
fields: FormDialogField[] = [];
|
|
43
|
+
|
|
44
|
+
@property({ type: String })
|
|
45
|
+
message = "";
|
|
46
|
+
|
|
47
|
+
@property({ type: Boolean })
|
|
48
|
+
markdown = false;
|
|
49
|
+
|
|
50
|
+
@state()
|
|
51
|
+
private values: Record<string, string> = {};
|
|
52
|
+
|
|
53
|
+
@state()
|
|
54
|
+
private validationError = "";
|
|
55
|
+
|
|
56
|
+
static styles = [
|
|
57
|
+
...DocksDialogContent.styles,
|
|
58
|
+
css`
|
|
59
|
+
.form-fields {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 0.75rem;
|
|
63
|
+
min-width: 22rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.field label {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
gap: 0.25rem;
|
|
70
|
+
font-size: var(--wa-font-size-s);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.field-label {
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
color: var(--wa-color-text-normal);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
wa-input {
|
|
79
|
+
width: 100%;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.validation-error {
|
|
83
|
+
color: var(--wa-color-danger-600, #dc2626);
|
|
84
|
+
font-size: var(--wa-font-size-s);
|
|
85
|
+
}
|
|
86
|
+
`,
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
async firstUpdated(changedProperties: PropertyValues) {
|
|
90
|
+
super.firstUpdated(changedProperties);
|
|
91
|
+
const initial: Record<string, string> = {};
|
|
92
|
+
for (const field of this.fields) {
|
|
93
|
+
initial[field.name] = field.value ?? "";
|
|
94
|
+
}
|
|
95
|
+
this.values = initial;
|
|
96
|
+
|
|
97
|
+
await this.updateComplete;
|
|
98
|
+
const input = this.shadowRoot?.querySelector("wa-input");
|
|
99
|
+
const inputEl = (input as HTMLElement & { shadowRoot?: ShadowRoot })?.shadowRoot?.querySelector(
|
|
100
|
+
"input",
|
|
101
|
+
);
|
|
102
|
+
inputEl?.focus();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getResult(): Record<string, string> {
|
|
106
|
+
return { ...this.values };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setValidationError(message: string): void {
|
|
110
|
+
this.validationError = message;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private updateField(name: string, value: string): void {
|
|
114
|
+
this.values = { ...this.values, [name]: value };
|
|
115
|
+
if (this.validationError) this.validationError = "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private handleKeyDown(event: KeyboardEvent): void {
|
|
119
|
+
if (event.key === "Enter") {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
this.dispatchEvent(new CustomEvent("dialog-ok", { bubbles: true, composed: true }));
|
|
122
|
+
} else if (event.key === "Escape") {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
this.dispatchEvent(new CustomEvent("dialog-cancel", { bubbles: true, composed: true }));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
render() {
|
|
129
|
+
return html`
|
|
130
|
+
${this.message ? this.renderMessage(this.message, this.markdown) : null}
|
|
131
|
+
<div class="form-fields" @keydown=${this.handleKeyDown}>
|
|
132
|
+
${this.fields.map(
|
|
133
|
+
(field) => html`
|
|
134
|
+
<div class="field">
|
|
135
|
+
<label>
|
|
136
|
+
<span class="field-label">${field.label}</span>
|
|
137
|
+
<wa-input
|
|
138
|
+
type=${field.type ?? "text"}
|
|
139
|
+
.value=${this.values[field.name] ?? ""}
|
|
140
|
+
placeholder=${field.placeholder ?? ""}
|
|
141
|
+
@input=${(event: Event) =>
|
|
142
|
+
this.updateField(
|
|
143
|
+
field.name,
|
|
144
|
+
(event.target as HTMLInputElement).value,
|
|
145
|
+
)}
|
|
146
|
+
></wa-input>
|
|
147
|
+
</label>
|
|
148
|
+
</div>
|
|
149
|
+
`,
|
|
150
|
+
)}
|
|
151
|
+
${this.validationError
|
|
152
|
+
? html`<div class="validation-error">${this.validationError}</div>`
|
|
153
|
+
: null}
|
|
154
|
+
</div>
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
declare global {
|
|
160
|
+
interface HTMLElementTagNameMap {
|
|
161
|
+
"docks-form-dialog-content": DocksFormDialogContent;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function trimValues(values: Record<string, string>): Record<string, string> {
|
|
166
|
+
return Object.fromEntries(
|
|
167
|
+
Object.entries(values).map(([key, value]) => [key, value.trim()]),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function findFormDialogContent(): DocksFormDialogContent | null {
|
|
172
|
+
return document.querySelector("docks-form-dialog-content");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Validates required fields; returns an error message or null if valid. */
|
|
176
|
+
export function validateFormDialogFields(
|
|
177
|
+
fields: FormDialogField[],
|
|
178
|
+
values: Record<string, string>,
|
|
179
|
+
): string | null {
|
|
180
|
+
for (const field of fields) {
|
|
181
|
+
if (field.required === false) continue;
|
|
182
|
+
if (!values[field.name]?.trim()) {
|
|
183
|
+
return `${field.label} is required.`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
|
|
190
|
+
id: "form",
|
|
191
|
+
label: "Form",
|
|
192
|
+
buttons: [OK_BUTTON, CANCEL_BUTTON],
|
|
193
|
+
component: (state?: FormDialogState) => {
|
|
194
|
+
if (!state) {
|
|
195
|
+
return html`<div>Error: No form dialog state</div>`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return html`
|
|
199
|
+
<docks-form-dialog-content
|
|
200
|
+
.message=${state.message ?? ""}
|
|
201
|
+
.markdown=${state.markdown ?? false}
|
|
202
|
+
.fields=${state.fields}
|
|
203
|
+
></docks-form-dialog-content>
|
|
204
|
+
`;
|
|
205
|
+
},
|
|
206
|
+
onButton: async (id: string, result: unknown, state?: FormDialogState) => {
|
|
207
|
+
if (!state) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (id === "ok") {
|
|
212
|
+
const values = trimValues((result ?? {}) as Record<string, string>);
|
|
213
|
+
const error = validateFormDialogFields(state.fields, values);
|
|
214
|
+
if (error) {
|
|
215
|
+
findFormDialogContent()?.setValidationError(error);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
state.resolve(values);
|
|
219
|
+
} else {
|
|
220
|
+
state.resolve(null);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return true;
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export function formDialog(options: FormDialogOptions): Promise<Record<string, string> | null> {
|
|
228
|
+
return new Promise((resolve) => {
|
|
229
|
+
dialogService.open("form", { ...options, resolve });
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function formDialogRequired(
|
|
234
|
+
options: FormDialogOptions,
|
|
235
|
+
): Promise<Record<string, string>> {
|
|
236
|
+
const values = await formDialog(options);
|
|
237
|
+
if (!values) throw new Error("Cancelled");
|
|
238
|
+
return values;
|
|
239
|
+
}
|
package/src/dialogs/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export { promptDialog } from './prompt-dialog';
|
|
2
|
+
export {
|
|
3
|
+
formDialog,
|
|
4
|
+
formDialogRequired,
|
|
5
|
+
validateFormDialogFields,
|
|
6
|
+
type FormDialogField,
|
|
7
|
+
type FormDialogFieldType,
|
|
8
|
+
type FormDialogOptions,
|
|
9
|
+
} from './form-dialog';
|
|
2
10
|
export { infoDialog } from './info-dialog';
|
|
3
11
|
export { confirmDialog } from './confirm-dialog';
|
|
4
12
|
export { navigableInfoDialog, type NavigableDialogAction } from './navigable-info-dialog';
|
package/src/parts/tabs.ts
CHANGED
|
@@ -469,13 +469,27 @@ export class DocksTabs extends DocksContainer {
|
|
|
469
469
|
overflow: hidden;
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
-
:
|
|
472
|
+
/* Top: nav (order 0) → row 1 auto, body (order 2) → row 2 1fr. */
|
|
473
|
+
:host([placement="top"]) wa-tab-group::part(base) {
|
|
473
474
|
display: grid;
|
|
474
475
|
grid-template-rows: auto minmax(0, 1fr);
|
|
475
476
|
height: 100%;
|
|
476
477
|
width: 100%;
|
|
477
478
|
}
|
|
478
479
|
|
|
480
|
+
/* Bottom: body (order 1) → row 1 1fr, nav (order 2) → row 2 auto. */
|
|
481
|
+
:host([placement="bottom"]) wa-tab-group::part(base) {
|
|
482
|
+
display: grid;
|
|
483
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
484
|
+
height: 100%;
|
|
485
|
+
width: 100%;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
:host([placement="bottom"]) wa-tab-group::part(body) {
|
|
489
|
+
min-height: 0;
|
|
490
|
+
overflow: hidden;
|
|
491
|
+
}
|
|
492
|
+
|
|
479
493
|
:host(:is([placement="start"], [placement="end"])) wa-tab-group::part(base) {
|
|
480
494
|
height: 100%;
|
|
481
495
|
width: 100%;
|