@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.
@@ -2,6 +2,7 @@ import { LitElement } from "lit";
2
2
  export declare class StFormStep extends LitElement {
3
3
  title: string;
4
4
  active: boolean;
5
+ validate?: () => Promise<boolean> | boolean;
5
6
  static styles: import("lit").CSSResult[];
6
7
  render(): import("lit-html").TemplateResult<1>;
7
8
  }
@@ -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;IAEzD,MAAM,CAAC,MAAM,4BAWX;IAEF,MAAM,yCAEL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,UAAU,CAAC;KAC5B;CACF"}
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,EAAY,MAAM,KAAK,CAAC;AAK1C,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;IAGvC,OAAO,CAAC,aAAa,CAAgB;IAEtB,OAAO,CAAC,OAAO,CAAmB;IAEjD,MAAM,CAAC,MAAM,4BAqIX;IAEF,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,qBAAqB;YAOf,uBAAuB;IAuDrC,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,gBAAgB;YAkCV,aAAa;IA4B3B,MAAM,yCAiFL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF"}
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, queryAssignedElements, query } from "lit/decorators.js";
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
- _handleSlotChange() {
19
- if (this._stepElements.length > 0) {
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
- this._stepElements.forEach((step, index) => {
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 scope = this._stepElements.length > 0
31
- ? this._stepElements[this._currentStepIndex]
100
+ const steps = this._steps;
101
+ const scope = steps.length > 0
102
+ ? steps[this._currentStepIndex]
32
103
  : this;
33
104
  let isValid = true;
34
- // 1. Validate standard DOM input components
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
- if (!input.checkValidity()) {
39
- if (isValid && typeof input.reportValidity === "function") {
40
- input.reportValidity();
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 (this._stepElements.length === 0 && this._formEl) {
136
+ if (steps.length === 0 && this._formEl) {
47
137
  if (!this._formEl.checkValidity()) {
48
138
  isValid = false;
49
139
  }
50
140
  }
51
- // 2. Execute step-level async/sync validation hook if defined
52
- if (isValid && this._stepElements.length > 0) {
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 (err) {
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 hasSteps = this._stepElements.length > 0;
114
- const isLastStep = hasSteps ? this._currentStepIndex === this._stepElements.length - 1 : true;
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 hasSteps = this._stepElements?.length > 0;
130
- const isLastStep = hasSteps ? this._currentStepIndex === this._stepElements.length - 1 : true;
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
- ${this._stepElements.map((step, index) => {
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 < this._stepElements.length - 1
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
- <div class="form-actions">
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
- variant="secondary"
289
+ color="primary"
186
290
  type="button"
187
- @click=${this._prevStep}
291
+ @click=${this._triggerSubmission}
188
292
  ?disabled=${this.loading}
189
293
  >
190
- Back
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sondor-technology/sondor-components",
3
- "version": "1.0.1-develop.10",
3
+ "version": "1.0.1-develop.11",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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, queryAssignedElements, query} from "lit/decorators.js";
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
- private _handleSlotChange() {
157
- if (this._stepElements.length > 0) {
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
- this._stepElements.forEach((step, index) => {
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 scope: ParentNode = this._stepElements.length > 0
171
- ? this._stepElements[this._currentStepIndex]
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 & {checkValidity?: () => boolean; reportValidity?: () => boolean}
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
- if (!input.checkValidity()) {
186
- if (isValid && typeof input.reportValidity === "function") {
187
- input.reportValidity();
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 (this._stepElements.length === 0 && this._formEl) {
296
+ if (steps.length === 0 && this._formEl) {
195
297
  if (!this._formEl.checkValidity()) {
196
298
  isValid = false;
197
299
  }
198
300
  }
199
301
 
200
- // 2. Execute step-level async/sync validation hook if defined
201
- if (isValid && this._stepElements.length > 0) {
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 (err) {
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 hasSteps = this._stepElements.length > 0;
277
- const isLastStep = hasSteps ? this._currentStepIndex === this._stepElements.length - 1 : true;
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 hasSteps = this._stepElements?.length > 0;
299
- const isLastStep = hasSteps ? this._currentStepIndex === this._stepElements.length - 1 : true;
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
- ${this._stepElements.map((step, index) => {
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 < this._stepElements.length - 1
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
- <div class="form-actions">
353
- ${hasSteps && this._currentStepIndex > 0
354
- ? html`
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
- variant="secondary"
472
+ color="primary"
357
473
  type="button"
358
- @click=${this._prevStep}
474
+ @click=${this._triggerSubmission}
359
475
  ?disabled=${this.loading}
360
476
  >
361
- Back
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
  `;