@pb33f/cowboy-components 0.7.8 → 0.7.9
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/components/the-doctor/the-doctor.css.js +16 -0
- package/dist/components/the-doctor/the-doctor.d.ts +3 -0
- package/dist/components/the-doctor/the-doctor.js +49 -3
- package/dist/components/time-vortex/tardis-control.css.js +0 -16
- package/dist/components/workspaces/workspace-destroy-dialog.d.ts +17 -0
- package/dist/components/workspaces/workspace-destroy-dialog.js +85 -0
- package/dist/components/workspaces/workspace-form.d.ts +22 -0
- package/dist/components/workspaces/workspace-form.js +194 -0
- package/dist/components/workspaces/workspace-view.css.d.ts +2 -0
- package/dist/components/workspaces/workspace-view.css.js +40 -0
- package/dist/components/workspaces/workspace-view.d.ts +30 -0
- package/dist/components/workspaces/workspace-view.js +196 -0
- package/dist/controllers/diagnostic-controller.d.ts +1 -0
- package/dist/controllers/diagnostic-controller.js +9 -8
- package/dist/controllers/workspace-controller.d.ts +15 -0
- package/dist/controllers/workspace-controller.js +156 -0
- package/dist/cowboy-components.umd.cjs +1558 -1094
- package/dist/css/badges.css.d.ts +2 -0
- package/dist/css/badges.css.js +12 -0
- package/dist/css/button.css.js +1 -1
- package/dist/css/forms.css.js +127 -1
- package/dist/css/lists.css.js +8 -0
- package/dist/css/pb33f-theme.css +1 -0
- package/dist/css/spinner.css.js +29 -0
- package/dist/events/doctor.d.ts +15 -0
- package/dist/events/doctor.js +10 -0
- package/dist/model/form-types.d.ts +32 -0
- package/dist/model/form-types.js +19 -0
- package/dist/model/formable.d.ts +51 -0
- package/dist/model/formable.js +542 -0
- package/dist/model/workspace.d.ts +6 -0
- package/dist/model/workspace.js +1 -0
- package/dist/monacoeditorwork/yaml.worker..bundle.js +14801 -14800
- package/dist/services/workspace-service.d.ts +10 -0
- package/dist/services/workspace-service.js +132 -0
- package/dist/style.css +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, html } from "lit";
|
|
8
|
+
import { property, state } from "lit/decorators.js";
|
|
9
|
+
import buttonCss from "../css/button.css.js";
|
|
10
|
+
import hrCss from "../css/hr.css.js";
|
|
11
|
+
import formsCss from "../css/forms.css.js";
|
|
12
|
+
import { FieldType, RenderType } from "./form-types.js";
|
|
13
|
+
import { FormDirtyEvent, FormValidEvent, FormInvalidEvent } from "../events/doctor.js";
|
|
14
|
+
import { AttentionType } from "../components/attention-box/attention-box.js";
|
|
15
|
+
import spinnerCss from "../css/spinner.css.js";
|
|
16
|
+
// this is AI generated code.
|
|
17
|
+
export class Formable extends LitElement {
|
|
18
|
+
constructor(doc) {
|
|
19
|
+
super();
|
|
20
|
+
this.model = {};
|
|
21
|
+
this.formData = {};
|
|
22
|
+
this.originalData = {};
|
|
23
|
+
this.isDirty = false;
|
|
24
|
+
this.errors = [];
|
|
25
|
+
this.hints = [];
|
|
26
|
+
this.validationErrors = new Map();
|
|
27
|
+
this.topError = null;
|
|
28
|
+
this.loadingMessage = null;
|
|
29
|
+
this.isLoading = false;
|
|
30
|
+
this.onFormSubmit = null;
|
|
31
|
+
this.autoFocus = false;
|
|
32
|
+
if (doc) {
|
|
33
|
+
this.doc = doc;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
setHints(hints) {
|
|
37
|
+
this.hints = hints;
|
|
38
|
+
this.requestUpdate();
|
|
39
|
+
}
|
|
40
|
+
renderErrors(errors) {
|
|
41
|
+
this.errors = errors;
|
|
42
|
+
this.validationErrors.clear();
|
|
43
|
+
errors.forEach(error => {
|
|
44
|
+
this.validationErrors.set(error.key, error.message);
|
|
45
|
+
});
|
|
46
|
+
this.requestUpdate();
|
|
47
|
+
this.dispatchEvent(new CustomEvent(FormInvalidEvent, {
|
|
48
|
+
bubbles: true,
|
|
49
|
+
composed: true,
|
|
50
|
+
detail: { errors }
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
clearErrors() {
|
|
54
|
+
this.errors = [];
|
|
55
|
+
this.validationErrors.clear();
|
|
56
|
+
this.requestUpdate();
|
|
57
|
+
this.dispatchEvent(new CustomEvent(FormValidEvent, {
|
|
58
|
+
bubbles: true,
|
|
59
|
+
composed: true
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
setTopError(error) {
|
|
63
|
+
this.topError = error;
|
|
64
|
+
this.requestUpdate();
|
|
65
|
+
}
|
|
66
|
+
clearTopError() {
|
|
67
|
+
this.topError = null;
|
|
68
|
+
this.requestUpdate();
|
|
69
|
+
}
|
|
70
|
+
setLoading(message) {
|
|
71
|
+
this.loadingMessage = message;
|
|
72
|
+
this.isLoading = true;
|
|
73
|
+
this.requestUpdate();
|
|
74
|
+
}
|
|
75
|
+
notLoading() {
|
|
76
|
+
this.loadingMessage = null;
|
|
77
|
+
this.isLoading = false;
|
|
78
|
+
this.requestUpdate();
|
|
79
|
+
}
|
|
80
|
+
renderForm() {
|
|
81
|
+
return { ...this.formData };
|
|
82
|
+
}
|
|
83
|
+
wipeModel(customDefaults) {
|
|
84
|
+
if (!this.model || typeof this.model !== 'object') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Create a new model with default values based on the original types
|
|
88
|
+
const wipedModel = {};
|
|
89
|
+
Object.keys(this.model).forEach(key => {
|
|
90
|
+
const hint = this.getHintForKey(key);
|
|
91
|
+
// Skip ignored fields completely - don't include them in the wiped model
|
|
92
|
+
if (hint?.renderType === RenderType.Ignored) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Check if custom default is provided for this key
|
|
96
|
+
if (customDefaults && customDefaults.hasOwnProperty(key)) {
|
|
97
|
+
wipedModel[key] = customDefaults[key];
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const value = this.model[key];
|
|
101
|
+
const fieldType = this.getFieldType(value);
|
|
102
|
+
switch (fieldType) {
|
|
103
|
+
case FieldType.Text:
|
|
104
|
+
wipedModel[key] = '';
|
|
105
|
+
break;
|
|
106
|
+
case FieldType.Number:
|
|
107
|
+
wipedModel[key] = 0;
|
|
108
|
+
break;
|
|
109
|
+
case FieldType.Boolean:
|
|
110
|
+
wipedModel[key] = false;
|
|
111
|
+
break;
|
|
112
|
+
case FieldType.Array:
|
|
113
|
+
// Keep the array structure but reset selection to first item
|
|
114
|
+
wipedModel[key] = Array.isArray(value) ? value : [];
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
wipedModel[key] = '';
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// Update both model and form data properly
|
|
122
|
+
this.model = wipedModel;
|
|
123
|
+
this.formData = { ...wipedModel };
|
|
124
|
+
this.originalData = { ...wipedModel };
|
|
125
|
+
this.isDirty = false;
|
|
126
|
+
// Clear any errors
|
|
127
|
+
this.clearErrors();
|
|
128
|
+
this.clearTopError();
|
|
129
|
+
// Request update to re-render the form
|
|
130
|
+
this.requestUpdate();
|
|
131
|
+
}
|
|
132
|
+
renderCancelButton(onClick) {
|
|
133
|
+
return html `
|
|
134
|
+
<sl-button class="close-button" ?disabled="${this.isLoading}" @click="${onClick || (() => { })}">
|
|
135
|
+
Cancel
|
|
136
|
+
</sl-button>
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
renderActionButton(label, onClick) {
|
|
140
|
+
// Store the submit handler for keyboard navigation
|
|
141
|
+
if (onClick) {
|
|
142
|
+
this.onFormSubmit = onClick;
|
|
143
|
+
}
|
|
144
|
+
return html `
|
|
145
|
+
<sl-button
|
|
146
|
+
variant="default"
|
|
147
|
+
class="button-primary ${this.isLoading ? 'loading' : ''}"
|
|
148
|
+
style="float: right"
|
|
149
|
+
?disabled="${this.isLoading}"
|
|
150
|
+
@click="${onClick || (() => { })}"
|
|
151
|
+
>
|
|
152
|
+
${label}
|
|
153
|
+
</sl-button>
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
setFormSubmitHandler(handler) {
|
|
157
|
+
this.onFormSubmit = handler;
|
|
158
|
+
}
|
|
159
|
+
initializeFormData() {
|
|
160
|
+
if (this.model && Object.keys(this.formData).length === 0) {
|
|
161
|
+
this.formData = { ...this.model };
|
|
162
|
+
this.originalData = { ...this.model };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
getFieldType(value) {
|
|
166
|
+
if (value === null || value === undefined)
|
|
167
|
+
return FieldType.Text;
|
|
168
|
+
if (typeof value === 'boolean')
|
|
169
|
+
return FieldType.Boolean;
|
|
170
|
+
if (typeof value === 'number')
|
|
171
|
+
return FieldType.Number;
|
|
172
|
+
if (Array.isArray(value))
|
|
173
|
+
return FieldType.Array;
|
|
174
|
+
if (typeof value === 'string')
|
|
175
|
+
return FieldType.Text;
|
|
176
|
+
return FieldType.Text;
|
|
177
|
+
}
|
|
178
|
+
getHintForKey(key) {
|
|
179
|
+
return this.hints.find(hint => hint.key === key);
|
|
180
|
+
}
|
|
181
|
+
handleFieldChange(key, value) {
|
|
182
|
+
const originalValue = this.originalData[key];
|
|
183
|
+
this.formData[key] = value;
|
|
184
|
+
const wasClean = !this.isDirty;
|
|
185
|
+
this.isDirty = Object.keys(this.formData).some(k => this.formData[k] !== this.originalData[k]);
|
|
186
|
+
if (wasClean && this.isDirty) {
|
|
187
|
+
this.dispatchEvent(new CustomEvent(FormDirtyEvent, {
|
|
188
|
+
bubbles: true,
|
|
189
|
+
composed: true,
|
|
190
|
+
detail: { key, value, originalValue }
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
this.requestUpdate();
|
|
194
|
+
}
|
|
195
|
+
renderHiddenField(key, value) {
|
|
196
|
+
// Hidden fields don't render any visible UI but are included in form data
|
|
197
|
+
// Initialize the hidden field value in form data
|
|
198
|
+
if (this.formData[key] === undefined) {
|
|
199
|
+
this.formData[key] = value;
|
|
200
|
+
}
|
|
201
|
+
return html ``;
|
|
202
|
+
}
|
|
203
|
+
renderTextField(key, value, hint) {
|
|
204
|
+
const hasError = this.validationErrors.has(key);
|
|
205
|
+
const errorMessage = this.validationErrors.get(key);
|
|
206
|
+
const helpText = hasError ? errorMessage : (hint?.helpText || '');
|
|
207
|
+
if (hint?.renderType === RenderType.Textarea) {
|
|
208
|
+
return html `
|
|
209
|
+
<sl-textarea
|
|
210
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
211
|
+
label="${this.formatLabel(key)}"
|
|
212
|
+
value="${this.formData[key] !== undefined ? this.formData[key] : (value || '')}"
|
|
213
|
+
placeholder="${hint.placeholder || ''}"
|
|
214
|
+
help-text="${helpText}"
|
|
215
|
+
?required="${hint.required || false}"
|
|
216
|
+
?disabled="${this.isLoading}"
|
|
217
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
218
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.value)}"
|
|
219
|
+
@keydown="${this.handleKeyDown}"
|
|
220
|
+
></sl-textarea>
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
return html `
|
|
224
|
+
<sl-input
|
|
225
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
226
|
+
label="${this.formatLabel(key)}"
|
|
227
|
+
value="${this.formData[key] !== undefined ? this.formData[key] : (value || '')}"
|
|
228
|
+
placeholder="${hint?.placeholder || ''}"
|
|
229
|
+
help-text="${helpText}"
|
|
230
|
+
?required="${hint?.required || false}"
|
|
231
|
+
?disabled="${this.isLoading}"
|
|
232
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
233
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.value)}"
|
|
234
|
+
@keydown="${this.handleKeyDown}"
|
|
235
|
+
></sl-input>
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
238
|
+
renderNumberField(key, value, hint) {
|
|
239
|
+
const hasError = this.validationErrors.has(key);
|
|
240
|
+
const errorMessage = this.validationErrors.get(key);
|
|
241
|
+
const helpText = hasError ? errorMessage : (hint?.helpText || '');
|
|
242
|
+
if (hint?.renderType === RenderType.Slider) {
|
|
243
|
+
return html `
|
|
244
|
+
<sl-range
|
|
245
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
246
|
+
label="${this.formatLabel(key)}"
|
|
247
|
+
value="${value || 0}"
|
|
248
|
+
min="${hint.min || 0}"
|
|
249
|
+
max="${hint.max || 100}"
|
|
250
|
+
step="${hint.step || 1}"
|
|
251
|
+
help-text="${helpText}"
|
|
252
|
+
?disabled="${this.isLoading}"
|
|
253
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
254
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.value)}"
|
|
255
|
+
@keydown="${this.handleKeyDown}"
|
|
256
|
+
></sl-range>
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
return html `
|
|
260
|
+
<sl-input
|
|
261
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
262
|
+
type="number"
|
|
263
|
+
label="${this.formatLabel(key)}"
|
|
264
|
+
value="${this.formData[key] !== undefined ? this.formData[key] : (value || 0)}"
|
|
265
|
+
style="width: 100px;"
|
|
266
|
+
min="${hint?.min || ''}"
|
|
267
|
+
max="${hint?.max || ''}"
|
|
268
|
+
step="${hint?.step || ''}"
|
|
269
|
+
help-text="${helpText}"
|
|
270
|
+
?required="${hint?.required || false}"
|
|
271
|
+
?disabled="${this.isLoading}"
|
|
272
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
273
|
+
@sl-change="${(e) => this.handleFieldChange(key, Number(e.target.value))}"
|
|
274
|
+
@keydown="${this.handleKeyDown}"
|
|
275
|
+
></sl-input>
|
|
276
|
+
`;
|
|
277
|
+
}
|
|
278
|
+
renderBooleanField(key, value, hint) {
|
|
279
|
+
const hasError = this.validationErrors.has(key);
|
|
280
|
+
const errorMessage = this.validationErrors.get(key);
|
|
281
|
+
const helpText = hasError ? errorMessage : (hint?.helpText || '');
|
|
282
|
+
if (hint?.renderType === RenderType.Switch) {
|
|
283
|
+
return html `
|
|
284
|
+
<sl-switch
|
|
285
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
286
|
+
?checked="${this.formData[key] !== undefined ? this.formData[key] : (value || false)}"
|
|
287
|
+
?disabled="${this.isLoading}"
|
|
288
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
289
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.checked)}"
|
|
290
|
+
@keydown="${this.handleKeyDown}"
|
|
291
|
+
>
|
|
292
|
+
${this.formatLabel(key)}
|
|
293
|
+
</sl-switch>
|
|
294
|
+
${hasError ? html `<div style="color: var(--error-color); font-size: 0.8rem; margin-top: 5px;">${errorMessage}</div>` : ''}
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
return html `
|
|
298
|
+
<section class="checkbox">
|
|
299
|
+
<label class="checkbox-label">${this.formatLabel(key)}</label>
|
|
300
|
+
<sl-checkbox
|
|
301
|
+
class="${hasError ? 'error' : ''}"
|
|
302
|
+
?checked="${this.formData[key] !== undefined ? this.formData[key] : (value || false)}"
|
|
303
|
+
?disabled="${this.isLoading}"
|
|
304
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
305
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.checked)}"
|
|
306
|
+
@keydown="${this.handleKeyDown}"
|
|
307
|
+
>
|
|
308
|
+
${helpText}
|
|
309
|
+
</sl-checkbox>
|
|
310
|
+
</section>
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
renderArrayField(key, value, hint) {
|
|
314
|
+
const hasError = this.validationErrors.has(key);
|
|
315
|
+
const errorMessage = this.validationErrors.get(key);
|
|
316
|
+
const currentValue = this.formData[key] || value[0];
|
|
317
|
+
const helpText = hasError ? errorMessage : (hint?.helpText || '');
|
|
318
|
+
if (hint?.renderType === RenderType.Radio) {
|
|
319
|
+
return html `
|
|
320
|
+
<sl-radio-group
|
|
321
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
322
|
+
label="${this.formatLabel(key)}"
|
|
323
|
+
value="${currentValue}"
|
|
324
|
+
help-text="${helpText}"
|
|
325
|
+
?disabled="${this.isLoading}"
|
|
326
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
327
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.value)}"
|
|
328
|
+
@keydown="${this.handleKeyDown}"
|
|
329
|
+
>
|
|
330
|
+
${value.map(option => html `
|
|
331
|
+
<sl-radio value="${option}" ?disabled="${this.isLoading}">${option}</sl-radio>
|
|
332
|
+
`)}
|
|
333
|
+
</sl-radio-group>
|
|
334
|
+
`;
|
|
335
|
+
}
|
|
336
|
+
return html `
|
|
337
|
+
<sl-select
|
|
338
|
+
class="label-on-left ${hasError ? 'error' : ''}"
|
|
339
|
+
label="${this.formatLabel(key)}"
|
|
340
|
+
value="${currentValue}"
|
|
341
|
+
placeholder="${hint?.placeholder || 'Select an option'}"
|
|
342
|
+
help-text="${helpText}"
|
|
343
|
+
?disabled="${this.isLoading}"
|
|
344
|
+
?autofocus="${this.isFirstVisibleField(key)}"
|
|
345
|
+
@sl-change="${(e) => this.handleFieldChange(key, e.target.value)}"
|
|
346
|
+
@keydown="${this.handleKeyDown}"
|
|
347
|
+
>
|
|
348
|
+
${value.map(option => html `
|
|
349
|
+
<sl-option value="${option}">${option}</sl-option>
|
|
350
|
+
`)}
|
|
351
|
+
</sl-select>
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
formatLabel(key) {
|
|
355
|
+
return key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, ' $1');
|
|
356
|
+
}
|
|
357
|
+
handleKeyDown(event) {
|
|
358
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
359
|
+
const target = event.target;
|
|
360
|
+
// Don't navigate away from textareas (allow normal Enter behavior)
|
|
361
|
+
if (target.tagName.toLowerCase() === 'sl-textarea') {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
event.preventDefault();
|
|
365
|
+
// Capture the current field's value and trigger navigation
|
|
366
|
+
this.handleFieldNavigation(target);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
handleFieldNavigation(currentElement) {
|
|
370
|
+
// First, capture the current field's value immediately
|
|
371
|
+
this.captureCurrentFieldValue(currentElement);
|
|
372
|
+
// Then proceed with navigation based on current state
|
|
373
|
+
const formInputs = this.getFormInputElements();
|
|
374
|
+
const currentIndex = formInputs.indexOf(currentElement);
|
|
375
|
+
if (currentIndex === -1)
|
|
376
|
+
return;
|
|
377
|
+
if (currentIndex < formInputs.length - 1) {
|
|
378
|
+
// Move to next field
|
|
379
|
+
const nextInput = formInputs[currentIndex + 1];
|
|
380
|
+
nextInput.focus();
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Last field - trigger form submission
|
|
384
|
+
this.triggerFormSubmission();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
captureCurrentFieldValue(element) {
|
|
388
|
+
// Find the corresponding model key for this element
|
|
389
|
+
const formInputs = this.getFormInputElements();
|
|
390
|
+
const elementIndex = formInputs.indexOf(element);
|
|
391
|
+
if (elementIndex === -1)
|
|
392
|
+
return;
|
|
393
|
+
// Get the model keys in the same order as rendered
|
|
394
|
+
const modelKeys = Object.keys(this.model).filter(key => {
|
|
395
|
+
const hint = this.getHintForKey(key);
|
|
396
|
+
return hint?.renderType !== RenderType.Hidden && hint?.renderType !== RenderType.Ignored;
|
|
397
|
+
});
|
|
398
|
+
if (elementIndex < modelKeys.length) {
|
|
399
|
+
const key = modelKeys[elementIndex];
|
|
400
|
+
let value;
|
|
401
|
+
// Get value based on element type
|
|
402
|
+
if (element.tagName.toLowerCase() === 'sl-checkbox' ||
|
|
403
|
+
element.tagName.toLowerCase() === 'sl-switch') {
|
|
404
|
+
value = element.checked;
|
|
405
|
+
}
|
|
406
|
+
else if (element.tagName.toLowerCase() === 'sl-input' &&
|
|
407
|
+
element.type === 'number') {
|
|
408
|
+
value = Number(element.value);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
value = element.value;
|
|
412
|
+
}
|
|
413
|
+
// Update form data immediately and synchronously
|
|
414
|
+
this.formData[key] = value;
|
|
415
|
+
// Trigger dirty state event
|
|
416
|
+
this.handleFieldChange(key, value);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
triggerFormSubmission() {
|
|
420
|
+
if (this.onFormSubmit && !this.isLoading) {
|
|
421
|
+
// Use updateComplete promise to ensure all reactive properties are synchronized
|
|
422
|
+
// before submitting, leveraging Lit's update lifecycle
|
|
423
|
+
this.updateComplete.then(() => {
|
|
424
|
+
if (this.onFormSubmit && !this.isLoading) {
|
|
425
|
+
this.onFormSubmit();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
getFormInputElements() {
|
|
431
|
+
if (!this.shadowRoot)
|
|
432
|
+
return [];
|
|
433
|
+
const selector = 'sl-input, sl-textarea, sl-select, sl-checkbox, sl-radio-group, sl-range, sl-switch';
|
|
434
|
+
const elements = Array.from(this.shadowRoot.querySelectorAll(selector));
|
|
435
|
+
// Filter out disabled elements and hidden fields
|
|
436
|
+
return elements.filter(el => {
|
|
437
|
+
return !el.hasAttribute('disabled') &&
|
|
438
|
+
el.offsetParent !== null && // visible
|
|
439
|
+
!el.closest('.form-loading-message, .form-top-error'); // not in message areas
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
isFirstVisibleField(key) {
|
|
443
|
+
if (!this.autoFocus)
|
|
444
|
+
return false;
|
|
445
|
+
// Get visible field keys in order (exclude hidden and ignored fields)
|
|
446
|
+
const visibleKeys = Object.keys(this.model).filter(k => {
|
|
447
|
+
const hint = this.getHintForKey(k);
|
|
448
|
+
return hint?.renderType !== RenderType.Hidden && hint?.renderType !== RenderType.Ignored;
|
|
449
|
+
});
|
|
450
|
+
return visibleKeys.length > 0 && visibleKeys[0] === key;
|
|
451
|
+
}
|
|
452
|
+
renderModel() {
|
|
453
|
+
if (!this.model || Object.keys(this.model).length === 0) {
|
|
454
|
+
return html `<div class="no-model">No model data available</div>`;
|
|
455
|
+
}
|
|
456
|
+
this.initializeFormData();
|
|
457
|
+
const topErrorDisplay = this.topError ? html `
|
|
458
|
+
<section class="form-top-error">
|
|
459
|
+
<pb33f-attention-box type="${AttentionType.Error}">
|
|
460
|
+
${this.topError.message}
|
|
461
|
+
</pb33f-attention-box>
|
|
462
|
+
</section>
|
|
463
|
+
` : '';
|
|
464
|
+
const loadingDisplay = this.isLoading && this.loadingMessage ? html `
|
|
465
|
+
<section class="form-loading-message">
|
|
466
|
+
<pb33f-attention-box type="${AttentionType.Info}">
|
|
467
|
+
${this.loadingMessage}
|
|
468
|
+
<sl-icon name="circle-half" class="spinner-icon-small"></sl-icon>
|
|
469
|
+
</pb33f-attention-box>
|
|
470
|
+
</section>
|
|
471
|
+
` : '';
|
|
472
|
+
return html `
|
|
473
|
+
<div class="dynamic-form ${this.isLoading ? 'loading' : ''}">
|
|
474
|
+
${loadingDisplay}
|
|
475
|
+
${topErrorDisplay}
|
|
476
|
+
<div class="form-fields">
|
|
477
|
+
${Object.keys(this.model).map(key => {
|
|
478
|
+
const value = this.model[key];
|
|
479
|
+
const fieldType = this.getFieldType(value);
|
|
480
|
+
const hint = this.getHintForKey(key);
|
|
481
|
+
// Handle ignored fields - completely skip them
|
|
482
|
+
if (hint?.renderType === RenderType.Ignored) {
|
|
483
|
+
return html ``;
|
|
484
|
+
}
|
|
485
|
+
// Handle hidden fields
|
|
486
|
+
if (hint?.renderType === RenderType.Hidden) {
|
|
487
|
+
return this.renderHiddenField(key, value);
|
|
488
|
+
}
|
|
489
|
+
switch (fieldType) {
|
|
490
|
+
case FieldType.Boolean:
|
|
491
|
+
return this.renderBooleanField(key, value, hint);
|
|
492
|
+
case FieldType.Number:
|
|
493
|
+
return this.renderNumberField(key, value, hint);
|
|
494
|
+
case FieldType.Array:
|
|
495
|
+
return this.renderArrayField(key, value, hint);
|
|
496
|
+
case FieldType.Text:
|
|
497
|
+
default:
|
|
498
|
+
return this.renderTextField(key, value, hint);
|
|
499
|
+
}
|
|
500
|
+
})}
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
Formable.styles = [formsCss, buttonCss, hrCss, spinnerCss];
|
|
507
|
+
__decorate([
|
|
508
|
+
property({ type: Object })
|
|
509
|
+
], Formable.prototype, "model", void 0);
|
|
510
|
+
__decorate([
|
|
511
|
+
state()
|
|
512
|
+
], Formable.prototype, "formData", void 0);
|
|
513
|
+
__decorate([
|
|
514
|
+
state()
|
|
515
|
+
], Formable.prototype, "originalData", void 0);
|
|
516
|
+
__decorate([
|
|
517
|
+
state()
|
|
518
|
+
], Formable.prototype, "isDirty", void 0);
|
|
519
|
+
__decorate([
|
|
520
|
+
state()
|
|
521
|
+
], Formable.prototype, "errors", void 0);
|
|
522
|
+
__decorate([
|
|
523
|
+
state()
|
|
524
|
+
], Formable.prototype, "hints", void 0);
|
|
525
|
+
__decorate([
|
|
526
|
+
state()
|
|
527
|
+
], Formable.prototype, "validationErrors", void 0);
|
|
528
|
+
__decorate([
|
|
529
|
+
state()
|
|
530
|
+
], Formable.prototype, "topError", void 0);
|
|
531
|
+
__decorate([
|
|
532
|
+
state()
|
|
533
|
+
], Formable.prototype, "loadingMessage", void 0);
|
|
534
|
+
__decorate([
|
|
535
|
+
state()
|
|
536
|
+
], Formable.prototype, "isLoading", void 0);
|
|
537
|
+
__decorate([
|
|
538
|
+
state()
|
|
539
|
+
], Formable.prototype, "onFormSubmit", void 0);
|
|
540
|
+
__decorate([
|
|
541
|
+
property({ type: Boolean })
|
|
542
|
+
], Formable.prototype, "autoFocus", void 0);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|