@sondor-technology/sondor-components 1.0.1-develop.10 → 1.0.1-develop.11
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/form/form-step.d.ts +1 -0
- package/dist/components/form/form-step.d.ts.map +1 -1
- package/dist/components/form/form-step.js +3 -0
- package/dist/components/form/form.d.ts +7 -2
- package/dist/components/form/form.d.ts.map +1 -1
- package/dist/components/form/form.js +134 -42
- package/package.json +1 -1
- package/src/components/form/form-step.ts +5 -1
- package/src/components/form/form.ts +153 -46
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-step.d.ts","sourceRoot":"","sources":["../../../src/components/form/form-step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAY,MAAM,KAAK,CAAC;AAI1C,qBACa,UAAW,SAAQ,UAAU;IACd,KAAK,SAAM;IACK,MAAM,UAAS;
|
|
1
|
+
{"version":3,"file":"form-step.d.ts","sourceRoot":"","sources":["../../../src/components/form/form-step.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAY,MAAM,KAAK,CAAC;AAI1C,qBACa,UAAW,SAAQ,UAAU;IACd,KAAK,SAAM;IACK,MAAM,UAAS;IAIzD,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAE5C,MAAM,CAAC,MAAM,4BAWX;IAEF,MAAM,yCAEL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,UAAU,CAAC;KAC5B;CACF"}
|
|
@@ -35,6 +35,9 @@ __decorate([
|
|
|
35
35
|
__decorate([
|
|
36
36
|
property({ type: Boolean, reflect: true })
|
|
37
37
|
], StFormStep.prototype, "active", void 0);
|
|
38
|
+
__decorate([
|
|
39
|
+
property({ attribute: false })
|
|
40
|
+
], StFormStep.prototype, "validate", void 0);
|
|
38
41
|
StFormStep = __decorate([
|
|
39
42
|
customElement("st-form-step")
|
|
40
43
|
], StFormStep);
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { LitElement } from "lit";
|
|
1
|
+
import { LitElement, PropertyValues } from "lit";
|
|
2
2
|
export type FormLayout = "vertical" | "horizontal" | "inline";
|
|
3
3
|
export declare class StForm extends LitElement {
|
|
4
4
|
layout: FormLayout;
|
|
5
5
|
loading: boolean;
|
|
6
6
|
error: string;
|
|
7
7
|
private _currentStepIndex;
|
|
8
|
-
private _stepElements;
|
|
9
8
|
private _formEl;
|
|
9
|
+
private _modalFooterEl;
|
|
10
|
+
private get _steps();
|
|
10
11
|
static styles: import("lit").CSSResult[];
|
|
12
|
+
connectedCallback(): void;
|
|
13
|
+
disconnectedCallback(): void;
|
|
14
|
+
updated(changedProperties: PropertyValues<this>): void;
|
|
15
|
+
private _updateModalFooter;
|
|
11
16
|
private _handleSlotChange;
|
|
12
17
|
private _updateStepVisibility;
|
|
13
18
|
private _validateCurrentContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../src/components/form/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../src/components/form/form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAa,cAAc,EAAC,MAAM,KAAK,CAAC;AAK1D,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE9D,qBACa,MAAO,SAAQ,UAAU;IACK,MAAM,EAAE,UAAU,CAAc;IAC/B,OAAO,UAAS;IAChC,KAAK,SAAM;IAE5B,OAAO,CAAC,iBAAiB,CAAK;IAExB,OAAO,CAAC,OAAO,CAAmB;IAEjD,OAAO,CAAC,cAAc,CAA4B;IAElD,OAAO,KAAK,MAAM,GAEjB;IAED,MAAM,CAAC,MAAM,4BAqIX;IAEO,iBAAiB,SAMzB;IAEQ,oBAAoB,SAM5B;IAEQ,OAAO,CAAC,iBAAiB,EAAE,cAAc,CAAC,IAAI,CAAC,QAGvD;IAED,OAAO,CAAC,kBAAkB;IAmD1B,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,qBAAqB;YAQf,uBAAuB;IAiFrC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,gBAAgB;YAkCV,aAAa;IA6B3B,MAAM,yCAsFL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF"}
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { LitElement, html, css } from "lit";
|
|
8
|
-
import { customElement, property, state,
|
|
8
|
+
import { customElement, property, state, query } from "lit/decorators.js";
|
|
9
9
|
import { resetStyles } from "../../styles/reset.js";
|
|
10
10
|
let StForm = class StForm extends LitElement {
|
|
11
11
|
constructor() {
|
|
@@ -14,43 +14,132 @@ let StForm = class StForm extends LitElement {
|
|
|
14
14
|
this.loading = false;
|
|
15
15
|
this.error = "";
|
|
16
16
|
this._currentStepIndex = 0;
|
|
17
|
+
this._modalFooterEl = null;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
get _steps() {
|
|
20
|
+
return Array.from(this.querySelectorAll("st-form-step"));
|
|
21
|
+
}
|
|
22
|
+
connectedCallback() {
|
|
23
|
+
super.connectedCallback();
|
|
24
|
+
this.updateComplete.then(() => {
|
|
20
25
|
this._updateStepVisibility();
|
|
26
|
+
this._updateModalFooter();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
disconnectedCallback() {
|
|
30
|
+
super.disconnectedCallback();
|
|
31
|
+
if (this._modalFooterEl) {
|
|
32
|
+
this._modalFooterEl.remove();
|
|
33
|
+
this._modalFooterEl = null;
|
|
21
34
|
}
|
|
22
35
|
}
|
|
36
|
+
updated(changedProperties) {
|
|
37
|
+
super.updated(changedProperties);
|
|
38
|
+
this._updateModalFooter();
|
|
39
|
+
}
|
|
40
|
+
_updateModalFooter() {
|
|
41
|
+
const modal = this.closest("st-modal");
|
|
42
|
+
if (modal) {
|
|
43
|
+
if (!this._modalFooterEl) {
|
|
44
|
+
this._modalFooterEl = document.createElement("div");
|
|
45
|
+
this._modalFooterEl.setAttribute("slot", "footer");
|
|
46
|
+
this._modalFooterEl.style.display = "flex";
|
|
47
|
+
this._modalFooterEl.style.gap = "var(--st-spacing-md)";
|
|
48
|
+
this._modalFooterEl.style.width = "100%";
|
|
49
|
+
this._modalFooterEl.style.justifyContent = "flex-end";
|
|
50
|
+
this._modalFooterEl.style.alignItems = "center";
|
|
51
|
+
modal.appendChild(this._modalFooterEl);
|
|
52
|
+
}
|
|
53
|
+
const steps = this._steps;
|
|
54
|
+
const hasSteps = steps.length > 0;
|
|
55
|
+
const isLastStep = hasSteps ? this._currentStepIndex === steps.length - 1 : true;
|
|
56
|
+
// Imperative DOM management prevents event listener loss and re-rendering conflicts
|
|
57
|
+
this._modalFooterEl.innerHTML = "";
|
|
58
|
+
if (hasSteps && this._currentStepIndex > 0) {
|
|
59
|
+
const backBtn = document.createElement("st-button");
|
|
60
|
+
backBtn.setAttribute("variant", "secondary");
|
|
61
|
+
backBtn.setAttribute("type", "button");
|
|
62
|
+
if (this.loading)
|
|
63
|
+
backBtn.setAttribute("disabled", "");
|
|
64
|
+
backBtn.textContent = "Back";
|
|
65
|
+
backBtn.addEventListener("click", (e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
this._prevStep();
|
|
68
|
+
});
|
|
69
|
+
this._modalFooterEl.appendChild(backBtn);
|
|
70
|
+
}
|
|
71
|
+
const submitBtn = document.createElement("st-button");
|
|
72
|
+
submitBtn.setAttribute("color", "primary");
|
|
73
|
+
submitBtn.setAttribute("type", "button");
|
|
74
|
+
if (this.loading)
|
|
75
|
+
submitBtn.setAttribute("disabled", "");
|
|
76
|
+
submitBtn.textContent = this.loading ? "Submitting..." : (isLastStep ? "Submit" : "Continue");
|
|
77
|
+
submitBtn.addEventListener("click", (e) => {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
this._triggerSubmission();
|
|
80
|
+
});
|
|
81
|
+
this._modalFooterEl.appendChild(submitBtn);
|
|
82
|
+
}
|
|
83
|
+
else if (this._modalFooterEl) {
|
|
84
|
+
this._modalFooterEl.remove();
|
|
85
|
+
this._modalFooterEl = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_handleSlotChange() {
|
|
89
|
+
this._updateStepVisibility();
|
|
90
|
+
this._updateModalFooter();
|
|
91
|
+
}
|
|
23
92
|
_updateStepVisibility() {
|
|
24
|
-
|
|
93
|
+
const steps = this._steps;
|
|
94
|
+
steps.forEach((step, index) => {
|
|
25
95
|
step.active = index === this._currentStepIndex;
|
|
26
96
|
});
|
|
27
97
|
this.requestUpdate();
|
|
28
98
|
}
|
|
29
99
|
async _validateCurrentContext() {
|
|
30
|
-
const
|
|
31
|
-
|
|
100
|
+
const steps = this._steps;
|
|
101
|
+
const scope = steps.length > 0
|
|
102
|
+
? steps[this._currentStepIndex]
|
|
32
103
|
: this;
|
|
33
104
|
let isValid = true;
|
|
34
|
-
|
|
35
|
-
const inputs = Array.from(scope.querySelectorAll("*"));
|
|
105
|
+
const inputs = Array.from(scope.querySelectorAll("st-input, st-select, st-textarea, st-checkbox, st-radio, input, select, textarea"));
|
|
36
106
|
for (const input of inputs) {
|
|
107
|
+
if (input.hasAttribute("disabled") || input.disabled)
|
|
108
|
+
continue;
|
|
109
|
+
let fieldValid = true;
|
|
37
110
|
if (typeof input.checkValidity === "function") {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
111
|
+
fieldValid = input.checkValidity();
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const isRequired = input.hasAttribute("required") || input.required;
|
|
115
|
+
if (isRequired) {
|
|
116
|
+
const isToggle = input.tagName === "ST-CHECKBOX" || input.tagName === "ST-RADIO";
|
|
117
|
+
const val = isToggle ? input.checked : input.value;
|
|
118
|
+
if (val === undefined || val === null || val === "" || val === false) {
|
|
119
|
+
fieldValid = false;
|
|
41
120
|
}
|
|
42
|
-
isValid = false;
|
|
43
121
|
}
|
|
44
122
|
}
|
|
123
|
+
if (!fieldValid) {
|
|
124
|
+
if (typeof input.reportValidity === "function") {
|
|
125
|
+
input.reportValidity();
|
|
126
|
+
}
|
|
127
|
+
else if ("invalid" in input) {
|
|
128
|
+
input.invalid = true;
|
|
129
|
+
}
|
|
130
|
+
if (isValid && typeof input.focus === "function") {
|
|
131
|
+
input.focus();
|
|
132
|
+
}
|
|
133
|
+
isValid = false;
|
|
134
|
+
}
|
|
45
135
|
}
|
|
46
|
-
if (
|
|
136
|
+
if (steps.length === 0 && this._formEl) {
|
|
47
137
|
if (!this._formEl.checkValidity()) {
|
|
48
138
|
isValid = false;
|
|
49
139
|
}
|
|
50
140
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const currentStep = this._stepElements[this._currentStepIndex];
|
|
141
|
+
if (isValid && steps.length > 0) {
|
|
142
|
+
const currentStep = steps[this._currentStepIndex];
|
|
54
143
|
if (typeof currentStep.validate === "function") {
|
|
55
144
|
try {
|
|
56
145
|
this.loading = true;
|
|
@@ -59,7 +148,7 @@ let StForm = class StForm extends LitElement {
|
|
|
59
148
|
isValid = false;
|
|
60
149
|
}
|
|
61
150
|
}
|
|
62
|
-
catch
|
|
151
|
+
catch {
|
|
63
152
|
isValid = false;
|
|
64
153
|
}
|
|
65
154
|
finally {
|
|
@@ -110,8 +199,9 @@ let StForm = class StForm extends LitElement {
|
|
|
110
199
|
const isValid = await this._validateCurrentContext();
|
|
111
200
|
if (!isValid)
|
|
112
201
|
return;
|
|
113
|
-
const
|
|
114
|
-
const
|
|
202
|
+
const steps = this._steps;
|
|
203
|
+
const hasSteps = steps.length > 0;
|
|
204
|
+
const isLastStep = hasSteps ? this._currentStepIndex === steps.length - 1 : true;
|
|
115
205
|
if (hasSteps && !isLastStep) {
|
|
116
206
|
this._currentStepIndex++;
|
|
117
207
|
this._updateStepVisibility();
|
|
@@ -126,8 +216,9 @@ let StForm = class StForm extends LitElement {
|
|
|
126
216
|
}));
|
|
127
217
|
}
|
|
128
218
|
render() {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
219
|
+
const steps = this._steps;
|
|
220
|
+
const hasSteps = steps.length > 0;
|
|
221
|
+
const isInModal = !!this.closest("st-modal");
|
|
131
222
|
return html `
|
|
132
223
|
<div class="${hasSteps ? "has-steps" : ""}" aria-busy=${this.loading}>
|
|
133
224
|
${this.loading
|
|
@@ -140,7 +231,7 @@ let StForm = class StForm extends LitElement {
|
|
|
140
231
|
${hasSteps
|
|
141
232
|
? html `
|
|
142
233
|
<div class="progress-nav" role="tablist">
|
|
143
|
-
${
|
|
234
|
+
${steps.map((step, index) => {
|
|
144
235
|
const isActive = index === this._currentStepIndex;
|
|
145
236
|
const isCompleted = index < this._currentStepIndex;
|
|
146
237
|
return html `
|
|
@@ -158,7 +249,7 @@ let StForm = class StForm extends LitElement {
|
|
|
158
249
|
</div>
|
|
159
250
|
<span>${step.title || `Step ${index + 1}`}</span>
|
|
160
251
|
</div>
|
|
161
|
-
${index <
|
|
252
|
+
${index < steps.length - 1
|
|
162
253
|
? html ` <div class="step-connector ${isCompleted ? "completed" : ""}"></div> `
|
|
163
254
|
: ""}
|
|
164
255
|
`;
|
|
@@ -178,29 +269,33 @@ let StForm = class StForm extends LitElement {
|
|
|
178
269
|
<form @submit=${this._handleSubmit}>
|
|
179
270
|
<slot @slotchange=${this._handleSlotChange}></slot>
|
|
180
271
|
|
|
181
|
-
|
|
182
|
-
${hasSteps && this._currentStepIndex > 0
|
|
272
|
+
${!isInModal
|
|
183
273
|
? html `
|
|
274
|
+
<div class="form-actions">
|
|
275
|
+
${hasSteps && this._currentStepIndex > 0
|
|
276
|
+
? html `
|
|
277
|
+
<st-button
|
|
278
|
+
variant="secondary"
|
|
279
|
+
type="button"
|
|
280
|
+
@click=${this._prevStep}
|
|
281
|
+
?disabled=${this.loading}
|
|
282
|
+
>
|
|
283
|
+
Back
|
|
284
|
+
</st-button>
|
|
285
|
+
`
|
|
286
|
+
: ""}
|
|
287
|
+
|
|
184
288
|
<st-button
|
|
185
|
-
|
|
289
|
+
color="primary"
|
|
186
290
|
type="button"
|
|
187
|
-
@click=${this.
|
|
291
|
+
@click=${this._triggerSubmission}
|
|
188
292
|
?disabled=${this.loading}
|
|
189
293
|
>
|
|
190
|
-
|
|
294
|
+
${this.loading ? "Submitting..." : hasSteps && this._currentStepIndex === steps.length - 1 ? "Submit" : hasSteps ? "Continue" : "Submit"}
|
|
191
295
|
</st-button>
|
|
192
|
-
|
|
296
|
+
</div>
|
|
297
|
+
`
|
|
193
298
|
: ""}
|
|
194
|
-
|
|
195
|
-
<st-button
|
|
196
|
-
color="primary"
|
|
197
|
-
type="button"
|
|
198
|
-
@click=${this._triggerSubmission}
|
|
199
|
-
?disabled=${this.loading}
|
|
200
|
-
>
|
|
201
|
-
${this.loading ? "Submitting..." : isLastStep ? "Submit" : "Continue"}
|
|
202
|
-
</st-button>
|
|
203
|
-
</div>
|
|
204
299
|
</form>
|
|
205
300
|
</div>
|
|
206
301
|
`;
|
|
@@ -352,9 +447,6 @@ __decorate([
|
|
|
352
447
|
__decorate([
|
|
353
448
|
state()
|
|
354
449
|
], StForm.prototype, "_currentStepIndex", void 0);
|
|
355
|
-
__decorate([
|
|
356
|
-
queryAssignedElements({ selector: "st-form-step" })
|
|
357
|
-
], StForm.prototype, "_stepElements", void 0);
|
|
358
450
|
__decorate([
|
|
359
451
|
query("form")
|
|
360
452
|
], StForm.prototype, "_formEl", void 0);
|
package/package.json
CHANGED
|
@@ -6,6 +6,10 @@ import {resetStyles} from "../../styles/reset.js";
|
|
|
6
6
|
export class StFormStep extends LitElement {
|
|
7
7
|
@property({type: String}) title = "";
|
|
8
8
|
@property({type: Boolean, reflect: true}) active = false;
|
|
9
|
+
|
|
10
|
+
// Declare the validate function property (attribute: false prevents reflection of functions to DOM attributes)
|
|
11
|
+
@property({attribute: false})
|
|
12
|
+
validate?: () => Promise<boolean> | boolean;
|
|
9
13
|
|
|
10
14
|
static styles = [
|
|
11
15
|
resetStyles,
|
|
@@ -29,4 +33,4 @@ declare global {
|
|
|
29
33
|
interface HTMLElementTagNameMap {
|
|
30
34
|
"st-form-step": StFormStep;
|
|
31
35
|
}
|
|
32
|
-
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {LitElement, html, css} from "lit";
|
|
2
|
-
import {customElement, property, state,
|
|
1
|
+
import {LitElement, html, css, PropertyValues} from "lit";
|
|
2
|
+
import {customElement, property, state, query} from "lit/decorators.js";
|
|
3
3
|
import {resetStyles} from "../../styles/reset.js";
|
|
4
4
|
import type {StFormStep} from "./form-step.js";
|
|
5
5
|
|
|
@@ -13,11 +13,14 @@ export class StForm extends LitElement {
|
|
|
13
13
|
|
|
14
14
|
@state() private _currentStepIndex = 0;
|
|
15
15
|
|
|
16
|
-
@queryAssignedElements({selector: "st-form-step"})
|
|
17
|
-
private _stepElements!: StFormStep[];
|
|
18
|
-
|
|
19
16
|
@query("form") private _formEl!: HTMLFormElement;
|
|
20
17
|
|
|
18
|
+
private _modalFooterEl: HTMLElement | null = null;
|
|
19
|
+
|
|
20
|
+
private get _steps(): StFormStep[] {
|
|
21
|
+
return Array.from(this.querySelectorAll("st-form-step"));
|
|
22
|
+
}
|
|
23
|
+
|
|
21
24
|
static styles = [
|
|
22
25
|
resetStyles,
|
|
23
26
|
css`
|
|
@@ -153,53 +156,151 @@ export class StForm extends LitElement {
|
|
|
153
156
|
`,
|
|
154
157
|
];
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
override connectedCallback() {
|
|
160
|
+
super.connectedCallback();
|
|
161
|
+
this.updateComplete.then(() => {
|
|
158
162
|
this._updateStepVisibility();
|
|
163
|
+
this._updateModalFooter();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override disconnectedCallback() {
|
|
168
|
+
super.disconnectedCallback();
|
|
169
|
+
if (this._modalFooterEl) {
|
|
170
|
+
this._modalFooterEl.remove();
|
|
171
|
+
this._modalFooterEl = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
override updated(changedProperties: PropertyValues<this>) {
|
|
176
|
+
super.updated(changedProperties);
|
|
177
|
+
this._updateModalFooter();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private _updateModalFooter() {
|
|
181
|
+
const modal = this.closest("st-modal");
|
|
182
|
+
if (modal) {
|
|
183
|
+
if (!this._modalFooterEl) {
|
|
184
|
+
this._modalFooterEl = document.createElement("div");
|
|
185
|
+
this._modalFooterEl.setAttribute("slot", "footer");
|
|
186
|
+
this._modalFooterEl.style.display = "flex";
|
|
187
|
+
this._modalFooterEl.style.gap = "var(--st-spacing-md)";
|
|
188
|
+
this._modalFooterEl.style.width = "100%";
|
|
189
|
+
this._modalFooterEl.style.justifyContent = "flex-end";
|
|
190
|
+
this._modalFooterEl.style.alignItems = "center";
|
|
191
|
+
modal.appendChild(this._modalFooterEl);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const steps = this._steps;
|
|
195
|
+
const hasSteps = steps.length > 0;
|
|
196
|
+
const isLastStep = hasSteps ? this._currentStepIndex === steps.length - 1 : true;
|
|
197
|
+
|
|
198
|
+
// Imperative DOM management prevents event listener loss and re-rendering conflicts
|
|
199
|
+
this._modalFooterEl.innerHTML = "";
|
|
200
|
+
|
|
201
|
+
if (hasSteps && this._currentStepIndex > 0) {
|
|
202
|
+
const backBtn = document.createElement("st-button");
|
|
203
|
+
backBtn.setAttribute("variant", "secondary");
|
|
204
|
+
backBtn.setAttribute("type", "button");
|
|
205
|
+
if (this.loading) backBtn.setAttribute("disabled", "");
|
|
206
|
+
backBtn.textContent = "Back";
|
|
207
|
+
backBtn.addEventListener("click", (e) => {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
this._prevStep();
|
|
210
|
+
});
|
|
211
|
+
this._modalFooterEl.appendChild(backBtn);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const submitBtn = document.createElement("st-button");
|
|
215
|
+
submitBtn.setAttribute("color", "primary");
|
|
216
|
+
submitBtn.setAttribute("type", "button");
|
|
217
|
+
if (this.loading) submitBtn.setAttribute("disabled", "");
|
|
218
|
+
submitBtn.textContent = this.loading ? "Submitting..." : (isLastStep ? "Submit" : "Continue");
|
|
219
|
+
submitBtn.addEventListener("click", (e) => {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
this._triggerSubmission();
|
|
222
|
+
});
|
|
223
|
+
this._modalFooterEl.appendChild(submitBtn);
|
|
224
|
+
|
|
225
|
+
} else if (this._modalFooterEl) {
|
|
226
|
+
this._modalFooterEl.remove();
|
|
227
|
+
this._modalFooterEl = null;
|
|
159
228
|
}
|
|
160
229
|
}
|
|
161
230
|
|
|
231
|
+
private _handleSlotChange() {
|
|
232
|
+
this._updateStepVisibility();
|
|
233
|
+
this._updateModalFooter();
|
|
234
|
+
}
|
|
235
|
+
|
|
162
236
|
private _updateStepVisibility() {
|
|
163
|
-
|
|
237
|
+
const steps = this._steps;
|
|
238
|
+
steps.forEach((step, index) => {
|
|
164
239
|
step.active = index === this._currentStepIndex;
|
|
165
240
|
});
|
|
166
241
|
this.requestUpdate();
|
|
167
242
|
}
|
|
168
243
|
|
|
169
244
|
private async _validateCurrentContext(): Promise<boolean> {
|
|
170
|
-
const
|
|
171
|
-
|
|
245
|
+
const steps = this._steps;
|
|
246
|
+
const scope: ParentNode = steps.length > 0
|
|
247
|
+
? steps[this._currentStepIndex]
|
|
172
248
|
: this;
|
|
173
249
|
|
|
174
250
|
let isValid = true;
|
|
175
251
|
|
|
176
|
-
// 1. Validate standard DOM input components
|
|
177
252
|
const inputs = Array.from(
|
|
178
253
|
scope.querySelectorAll<
|
|
179
|
-
HTMLElement & {
|
|
180
|
-
|
|
254
|
+
HTMLElement & {
|
|
255
|
+
checkValidity?: () => boolean;
|
|
256
|
+
reportValidity?: () => boolean;
|
|
257
|
+
required?: boolean;
|
|
258
|
+
value?: any;
|
|
259
|
+
checked?: boolean;
|
|
260
|
+
invalid?: boolean;
|
|
261
|
+
}
|
|
262
|
+
>("st-input, st-select, st-textarea, st-checkbox, st-radio, input, select, textarea"),
|
|
181
263
|
);
|
|
182
264
|
|
|
183
265
|
for (const input of inputs) {
|
|
266
|
+
if (input.hasAttribute("disabled") || (input as any).disabled) continue;
|
|
267
|
+
|
|
268
|
+
let fieldValid = true;
|
|
269
|
+
|
|
184
270
|
if (typeof input.checkValidity === "function") {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
271
|
+
fieldValid = input.checkValidity();
|
|
272
|
+
} else {
|
|
273
|
+
const isRequired = input.hasAttribute("required") || input.required;
|
|
274
|
+
if (isRequired) {
|
|
275
|
+
const isToggle = input.tagName === "ST-CHECKBOX" || input.tagName === "ST-RADIO";
|
|
276
|
+
const val = isToggle ? input.checked : input.value;
|
|
277
|
+
if (val === undefined || val === null || val === "" || val === false) {
|
|
278
|
+
fieldValid = false;
|
|
188
279
|
}
|
|
189
|
-
isValid = false;
|
|
190
280
|
}
|
|
191
281
|
}
|
|
282
|
+
|
|
283
|
+
if (!fieldValid) {
|
|
284
|
+
if (typeof input.reportValidity === "function") {
|
|
285
|
+
input.reportValidity();
|
|
286
|
+
} else if ("invalid" in input) {
|
|
287
|
+
input.invalid = true;
|
|
288
|
+
}
|
|
289
|
+
if (isValid && typeof (input as any).focus === "function") {
|
|
290
|
+
(input as any).focus();
|
|
291
|
+
}
|
|
292
|
+
isValid = false;
|
|
293
|
+
}
|
|
192
294
|
}
|
|
193
295
|
|
|
194
|
-
if (
|
|
296
|
+
if (steps.length === 0 && this._formEl) {
|
|
195
297
|
if (!this._formEl.checkValidity()) {
|
|
196
298
|
isValid = false;
|
|
197
299
|
}
|
|
198
300
|
}
|
|
199
301
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const currentStep = this._stepElements[this._currentStepIndex] as StFormStep & {
|
|
302
|
+
if (isValid && steps.length > 0) {
|
|
303
|
+
const currentStep = steps[this._currentStepIndex] as StFormStep & {
|
|
203
304
|
validate?: () => Promise<boolean> | boolean;
|
|
204
305
|
};
|
|
205
306
|
|
|
@@ -210,7 +311,7 @@ export class StForm extends LitElement {
|
|
|
210
311
|
if (!stepPassed) {
|
|
211
312
|
isValid = false;
|
|
212
313
|
}
|
|
213
|
-
} catch
|
|
314
|
+
} catch {
|
|
214
315
|
isValid = false;
|
|
215
316
|
} finally {
|
|
216
317
|
this.loading = false;
|
|
@@ -273,8 +374,9 @@ export class StForm extends LitElement {
|
|
|
273
374
|
const isValid = await this._validateCurrentContext();
|
|
274
375
|
if (!isValid) return;
|
|
275
376
|
|
|
276
|
-
const
|
|
277
|
-
const
|
|
377
|
+
const steps = this._steps;
|
|
378
|
+
const hasSteps = steps.length > 0;
|
|
379
|
+
const isLastStep = hasSteps ? this._currentStepIndex === steps.length - 1 : true;
|
|
278
380
|
|
|
279
381
|
if (hasSteps && !isLastStep) {
|
|
280
382
|
this._currentStepIndex++;
|
|
@@ -295,8 +397,9 @@ export class StForm extends LitElement {
|
|
|
295
397
|
}
|
|
296
398
|
|
|
297
399
|
render() {
|
|
298
|
-
const
|
|
299
|
-
const
|
|
400
|
+
const steps = this._steps;
|
|
401
|
+
const hasSteps = steps.length > 0;
|
|
402
|
+
const isInModal = !!this.closest("st-modal");
|
|
300
403
|
|
|
301
404
|
return html`
|
|
302
405
|
<div class="${hasSteps ? "has-steps" : ""}" aria-busy=${this.loading}>
|
|
@@ -310,7 +413,7 @@ export class StForm extends LitElement {
|
|
|
310
413
|
${hasSteps
|
|
311
414
|
? html`
|
|
312
415
|
<div class="progress-nav" role="tablist">
|
|
313
|
-
${
|
|
416
|
+
${steps.map((step, index) => {
|
|
314
417
|
const isActive = index === this._currentStepIndex;
|
|
315
418
|
const isCompleted = index < this._currentStepIndex;
|
|
316
419
|
|
|
@@ -329,7 +432,7 @@ export class StForm extends LitElement {
|
|
|
329
432
|
</div>
|
|
330
433
|
<span>${step.title || `Step ${index + 1}`}</span>
|
|
331
434
|
</div>
|
|
332
|
-
${index <
|
|
435
|
+
${index < steps.length - 1
|
|
333
436
|
? html` <div class="step-connector ${isCompleted ? "completed" : ""}"></div> `
|
|
334
437
|
: ""}
|
|
335
438
|
`;
|
|
@@ -349,29 +452,33 @@ export class StForm extends LitElement {
|
|
|
349
452
|
<form @submit=${this._handleSubmit}>
|
|
350
453
|
<slot @slotchange=${this._handleSlotChange}></slot>
|
|
351
454
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
455
|
+
${!isInModal
|
|
456
|
+
? html`
|
|
457
|
+
<div class="form-actions">
|
|
458
|
+
${hasSteps && this._currentStepIndex > 0
|
|
459
|
+
? html`
|
|
460
|
+
<st-button
|
|
461
|
+
variant="secondary"
|
|
462
|
+
type="button"
|
|
463
|
+
@click=${this._prevStep}
|
|
464
|
+
?disabled=${this.loading}
|
|
465
|
+
>
|
|
466
|
+
Back
|
|
467
|
+
</st-button>
|
|
468
|
+
`
|
|
469
|
+
: ""}
|
|
470
|
+
|
|
355
471
|
<st-button
|
|
356
|
-
|
|
472
|
+
color="primary"
|
|
357
473
|
type="button"
|
|
358
|
-
@click=${this.
|
|
474
|
+
@click=${this._triggerSubmission}
|
|
359
475
|
?disabled=${this.loading}
|
|
360
476
|
>
|
|
361
|
-
|
|
477
|
+
${this.loading ? "Submitting..." : hasSteps && this._currentStepIndex === steps.length - 1 ? "Submit" : hasSteps ? "Continue" : "Submit"}
|
|
362
478
|
</st-button>
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
<st-button
|
|
367
|
-
color="primary"
|
|
368
|
-
type="button"
|
|
369
|
-
@click=${this._triggerSubmission}
|
|
370
|
-
?disabled=${this.loading}
|
|
371
|
-
>
|
|
372
|
-
${this.loading ? "Submitting..." : isLastStep ? "Submit" : "Continue"}
|
|
373
|
-
</st-button>
|
|
374
|
-
</div>
|
|
479
|
+
</div>
|
|
480
|
+
`
|
|
481
|
+
: ""}
|
|
375
482
|
</form>
|
|
376
483
|
</div>
|
|
377
484
|
`;
|