@sondor-technology/sondor-components 1.0.1-develop.6 → 1.0.1-develop.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.
@@ -11,11 +11,14 @@ export declare class StCheckbox extends LitElement {
11
11
  disabled: boolean;
12
12
  invalid: boolean;
13
13
  required: boolean;
14
+ errorMessage: string;
14
15
  ariaLabel: string;
15
16
  constructor();
16
17
  static styles: import("lit").CSSResult[];
17
18
  updated(changedProperties: PropertyValues<this>): void;
18
19
  private _syncValidity;
20
+ checkValidity(): boolean;
21
+ reportValidity(): boolean;
19
22
  formResetCallback(): void;
20
23
  private _handleChange;
21
24
  render(): import("lit-html").TemplateResult<1>;
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/checkbox/checkbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAa,cAAc,EAAC,MAAM,KAAK,CAAC;AAI1D,qBACa,UAAW,SAAQ,UAAU;IACxC,MAAM,CAAC,cAAc,UAAQ;IAC7B,OAAO,CAAC,UAAU,CAAmB;IAErB,OAAO,CAAC,QAAQ,CAAoB;IAEX,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAQ;IAChC,IAAI,SAAM;IAEzB,KAAK,SAAQ;IAEG,OAAO,UAAS;IAChB,aAAa,UAAS;IAEtB,QAAQ,UAAS;IACjB,OAAO,UAAS;IAChB,QAAQ,UAAS;IAEtB,SAAS,SAAM;IAEtD,cAaG;IAED,MAAM,CAAC,MAAM,4BAuHX;IAEO,OAAO,CAAC,iBAAiB,EAAE,cAAc,CAAC,IAAI,CAAC,QAYvD;IAED,OAAO,CAAC,aAAa;IASrB,iBAAiB,SAKhB;IAED,OAAO,CAAC,aAAa;IAcrB,MAAM,yCAyBL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
1
+ {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/checkbox/checkbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAa,cAAc,EAAC,MAAM,KAAK,CAAC;AAI1D,qBACa,UAAW,SAAQ,UAAU;IACxC,MAAM,CAAC,cAAc,UAAQ;IAC7B,OAAO,CAAC,UAAU,CAAmB;IAErB,OAAO,CAAC,QAAQ,CAAoB;IAEX,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAQ;IAChC,IAAI,SAAM;IACzB,KAAK,SAAQ;IAEG,OAAO,UAAS;IAChB,aAAa,UAAS;IAEtB,QAAQ,UAAS;IACjB,OAAO,UAAS;IAChB,QAAQ,UAAS;IACjC,YAAY,SAAM;IAEP,SAAS,SAAM;IAEpD,cAWC;IAED,MAAM,CAAC,MAAM,4BA6GX;IAEO,OAAO,CAAC,iBAAiB,EAAE,cAAc,CAAC,IAAI,CAAC,QAWvD;IAED,OAAO,CAAC,aAAa;IAmBd,aAAa,IAAI,OAAO,CAkB9B;IAEM,cAAc,IAAI,OAAO,CAK/B;IAED,iBAAiB,SAMhB;IAED,OAAO,CAAC,aAAa;IAkBrB,MAAM,yCAwBL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -12,28 +12,25 @@ let StCheckbox = class StCheckbox extends LitElement {
12
12
  super();
13
13
  this.size = "md";
14
14
  this.name = "";
15
- // Checkboxes default to 'on' natively if no value is provided
16
15
  this.value = "on";
17
16
  this.checked = false;
18
17
  this.indeterminate = false;
19
18
  this.disabled = false;
20
19
  this.invalid = false;
21
20
  this.required = false;
21
+ this.errorMessage = "";
22
22
  this.ariaLabel = "";
23
23
  this._internals = this.attachInternals();
24
- // 1. Trigger error state if the user clicks away and the field is invalid
25
24
  this.addEventListener('focusout', () => {
26
- this.invalid = !this._inputEl.validity.valid;
25
+ this.invalid = !this.checkValidity();
27
26
  });
28
- // 2. Trigger error state if the st-form tries to submit and fails
29
27
  this.addEventListener('invalid', () => {
30
- this.invalid = true;
28
+ this.invalid = !this.checkValidity();
31
29
  });
32
30
  }
33
31
  updated(changedProperties) {
34
32
  super.updated(changedProperties);
35
- if (changedProperties.has("checked") || changedProperties.has("value")) {
36
- // Form API: If checked, submit the value. If unchecked, submit null to remove it from FormData.
33
+ if (changedProperties.has("checked") || changedProperties.has("value") || changedProperties.has("errorMessage")) {
37
34
  this._internals.setFormValue(this.checked ? this.value : null);
38
35
  this._syncValidity();
39
36
  }
@@ -44,11 +41,38 @@ let StCheckbox = class StCheckbox extends LitElement {
44
41
  _syncValidity() {
45
42
  if (!this._inputEl)
46
43
  return;
47
- this._internals.setValidity(this._inputEl.validity, this._inputEl.validationMessage, this._inputEl);
44
+ if (this.required && !this.checked) {
45
+ const message = this.errorMessage || this._inputEl.validationMessage || "Please check this box if you want to proceed.";
46
+ this._internals.setValidity({ valueMissing: true }, message, this._inputEl);
47
+ }
48
+ else {
49
+ this._internals.setValidity(this._inputEl.validity, this._inputEl.validationMessage, this._inputEl);
50
+ }
51
+ }
52
+ checkValidity() {
53
+ if (!this._inputEl)
54
+ return true;
55
+ if (this.required && !this.checked) {
56
+ const message = this.errorMessage || this._inputEl.validationMessage || "Please check this box if you want to proceed.";
57
+ this._internals.setValidity({ valueMissing: true }, message, this._inputEl);
58
+ this.invalid = true;
59
+ return false;
60
+ }
61
+ const isValid = this._inputEl.checkValidity();
62
+ this._syncValidity();
63
+ this.invalid = !isValid;
64
+ return isValid;
65
+ }
66
+ reportValidity() {
67
+ this._syncValidity();
68
+ const isValid = this._internals.reportValidity();
69
+ this.invalid = !isValid;
70
+ return isValid;
48
71
  }
49
72
  formResetCallback() {
50
73
  this.checked = false;
51
74
  this.indeterminate = false;
75
+ this.invalid = false;
52
76
  this._internals.setFormValue(null);
53
77
  this.updateComplete.then(() => this._syncValidity());
54
78
  }
@@ -56,11 +80,13 @@ let StCheckbox = class StCheckbox extends LitElement {
56
80
  e.stopPropagation();
57
81
  if (this.disabled)
58
82
  return;
59
- // Standard native behavior: clicking an indeterminate checkbox unsets the indeterminate state
60
83
  if (this.indeterminate) {
61
84
  this.indeterminate = false;
62
85
  }
63
86
  this.checked = this._inputEl.checked;
87
+ if (this.checkValidity()) {
88
+ this.invalid = false;
89
+ }
64
90
  this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
65
91
  }
66
92
  render() {
@@ -78,7 +104,6 @@ let StCheckbox = class StCheckbox extends LitElement {
78
104
  @change=${this._handleChange}
79
105
  />
80
106
  <div class="control">
81
- <!-- Render both SVGs and use CSS to conditionally reveal them based on host attributes -->
82
107
  ${this.indeterminate
83
108
  ? html `<svg class="icon-minus" viewBox="0 0 24 24"><path d="M5 12h14"></path></svg>`
84
109
  : html `<svg class="icon-check" viewBox="0 0 24 24">
@@ -120,7 +145,6 @@ StCheckbox.styles = [
120
145
  font-size: var(--st-font-size-base);
121
146
  }
122
147
 
123
- /* Visually hide the native input but keep it in the DOM for focus/screen readers */
124
148
  input {
125
149
  position: absolute;
126
150
  width: var(--checkbox-size);
@@ -133,7 +157,6 @@ StCheckbox.styles = [
133
157
  border-width: 0;
134
158
  }
135
159
 
136
- /* Custom Checkbox Box */
137
160
  .control {
138
161
  display: flex;
139
162
  align-items: center;
@@ -145,30 +168,25 @@ StCheckbox.styles = [
145
168
  background-color: var(--st-color-bg);
146
169
  transition: all 0.2s ease;
147
170
  flex-shrink: 0;
148
- /* Aligns the box nicely with standard text line-height */
149
171
  margin-top: 0.125rem;
150
172
  }
151
173
 
152
- /* Hover State */
153
174
  label:hover .control {
154
175
  border-color: var(--st-color-primary-hover);
155
176
  }
156
177
 
157
- /* Focus State */
158
178
  input:focus-visible + .control {
159
179
  box-shadow:
160
180
  0 0 0 2px var(--st-color-bg),
161
181
  0 0 0 4px var(--st-color-primary);
162
182
  }
163
183
 
164
- /* Checked & Indeterminate States */
165
184
  :host([checked]) .control,
166
185
  :host([indeterminate]) .control {
167
186
  border-color: var(--st-color-primary);
168
187
  background-color: var(--st-color-primary);
169
188
  }
170
189
 
171
- /* SVG Icons */
172
190
  svg {
173
191
  width: var(--checkbox-icon-size);
174
192
  height: var(--checkbox-icon-size);
@@ -184,14 +202,12 @@ StCheckbox.styles = [
184
202
  opacity 0.2s ease;
185
203
  }
186
204
 
187
- /* Reveal Icons */
188
205
  :host([checked]) svg.icon-check,
189
206
  :host([indeterminate]) svg.icon-minus {
190
207
  opacity: 1;
191
208
  transform: scale(1);
192
209
  }
193
210
 
194
- /* Disabled State */
195
211
  :host([disabled]) {
196
212
  pointer-events: none;
197
213
  }
@@ -200,7 +216,6 @@ StCheckbox.styles = [
200
216
  cursor: not-allowed;
201
217
  }
202
218
 
203
- /* Invalid State */
204
219
  :host([invalid]) .control {
205
220
  border-color: var(--st-color-error);
206
221
  }
@@ -238,6 +253,9 @@ __decorate([
238
253
  __decorate([
239
254
  property({ type: Boolean, reflect: true })
240
255
  ], StCheckbox.prototype, "required", void 0);
256
+ __decorate([
257
+ property({ type: String })
258
+ ], StCheckbox.prototype, "errorMessage", void 0);
241
259
  __decorate([
242
260
  property({ attribute: "aria-label" })
243
261
  ], StCheckbox.prototype, "ariaLabel", void 0);
@@ -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,4BA8IX;IAEF,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,uBAAuB;IA8B/B,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,gBAAgB;IAuCxB,OAAO,CAAC,aAAa;IA4BrB,MAAM,yCAkFL;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,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,4BA8IX;IAEF,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,uBAAuB;IAoC/B,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,gBAAgB;IAuCxB,OAAO,CAAC,aAAa;IA4BrB,MAAM,yCAkFL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF"}
@@ -27,13 +27,12 @@ let StForm = class StForm extends LitElement {
27
27
  this.requestUpdate();
28
28
  }
29
29
  _validateCurrentContext() {
30
- if (this._stepElements.length === 0) {
31
- return this._formEl.checkValidity();
32
- }
33
- const currentStep = this._stepElements[this._currentStepIndex];
30
+ const scope = this._stepElements.length > 0
31
+ ? this._stepElements[this._currentStepIndex]
32
+ : this;
34
33
  let isValid = true;
35
- // Find all custom and native form inputs assigned to the current step
36
- const inputs = Array.from(currentStep.querySelectorAll("*"));
34
+ // Find all elements that expose validation functions (e.g. checkValidity / reportValidity)
35
+ const inputs = Array.from(scope.querySelectorAll("*"));
37
36
  for (const input of inputs) {
38
37
  if (typeof input.checkValidity === "function") {
39
38
  if (!input.checkValidity()) {
@@ -45,6 +44,12 @@ let StForm = class StForm extends LitElement {
45
44
  }
46
45
  }
47
46
  }
47
+ // Also check standard form elements if not using steps
48
+ if (this._stepElements.length === 0 && this._formEl) {
49
+ if (!this._formEl.checkValidity()) {
50
+ isValid = false;
51
+ }
52
+ }
48
53
  return isValid;
49
54
  }
50
55
  _triggerSubmission() {
@@ -1,8 +1,9 @@
1
1
  import { LitElement } from 'lit';
2
2
  import { IconName } from './icon-paths.js';
3
+ export type IconVariant = 'outline' | 'solid';
3
4
  export declare class StIcon extends LitElement {
4
5
  name: IconName | string;
5
- variant: 'outline' | 'solid';
6
+ variant: IconVariant;
6
7
  color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
7
8
  size: 'sm' | 'md' | 'lg' | 'xl';
8
9
  static styles: import("lit").CSSResult[];
@@ -1 +1 @@
1
- {"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../../../src/components/icon/icon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,EAAa,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGtD,qBACa,MAAO,SAAQ,UAAU;IACR,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAM;IAC7B,OAAO,EAAE,SAAS,GAAG,OAAO,CAAa;IAG1B,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAa;IAGlG,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAQ;IAElF,MAAM,CAAC,MAAM,4BAsCX;IAEF,MAAM,yCAmBL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF"}
1
+ {"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../../../src/components/icon/icon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,EAAa,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGtD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9C,qBACa,MAAO,SAAQ,UAAU;IACR,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAM;IAC7B,OAAO,EAAE,WAAW,CAAa;IAGlB,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAa;IAGlG,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAQ;IAElF,MAAM,CAAC,MAAM,4BAsCX;IAEF,MAAM,yCAmBL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF"}
@@ -8,10 +8,21 @@ export declare class StModal extends LitElement {
8
8
  * Title text displayed in the modal header.
9
9
  */
10
10
  heading: string;
11
+ /**
12
+ * Size of the modal (sm, md, lg, xl, xxl).
13
+ */
14
+ size: 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
15
+ /**
16
+ * If true, the modal cannot be closed by clicking the backdrop or pressing Esc.
17
+ */
18
+ static: boolean;
11
19
  private dialogEl;
20
+ private isClosing;
12
21
  static styles: import("lit").CSSResult[];
13
22
  updated(changedProperties: PropertyValues): void;
23
+ private closeWithAnimation;
14
24
  private handleClose;
25
+ private handleCancel;
15
26
  private onDialogClose;
16
27
  private handleBackdropClick;
17
28
  render(): import("lit-html").TemplateResult<1>;
@@ -1 +1 @@
1
- {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../../src/components/modal/modal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,cAAc,EAAE,MAAM,KAAK,CAAC;AAI5D,qBACa,OAAQ,SAAQ,UAAU;IACrC;;OAEG;IACyC,IAAI,UAAS;IAEzD;;OAEG;IACyB,OAAO,SAAM;IAExB,OAAO,CAAC,QAAQ,CAAqB;IAEtD,MAAM,CAAC,MAAM,4BAwEX;IAEF,OAAO,CAAC,iBAAiB,EAAE,cAAc,QAUxC;IAED,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,mBAAmB;IAY3B,MAAM,yCAuBL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB;CACF"}
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../../src/components/modal/modal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,cAAc,EAAW,MAAM,KAAK,CAAC;AAIrE,qBACa,OAAQ,SAAQ,UAAU;IACrC;;OAEG;IACyC,IAAI,UAAS;IAEzD;;OAEG;IACyB,OAAO,SAAM;IAEzC;;OAEG;IACyB,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAQ;IAE3E;;OAEG;IAC0B,MAAM,UAAS;IAE3B,OAAO,CAAC,QAAQ,CAAqB;IAEtD,OAAO,CAAC,SAAS,CAAS;IAE1B,MAAM,CAAC,MAAM,4BAkHX;IAEF,OAAO,CAAC,iBAAiB,EAAE,cAAc,QAYxC;IAED,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,mBAAmB;IAe3B,MAAM,yCA4BL;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB;CACF"}
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
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
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { LitElement, html, css } from 'lit';
7
+ import { LitElement, html, css, nothing } from 'lit';
8
8
  import { customElement, property, query } from 'lit/decorators.js';
9
9
  import { resetStyles } from '../../styles/reset.js';
10
10
  let StModal = class StModal extends LitElement {
@@ -18,23 +18,55 @@ let StModal = class StModal extends LitElement {
18
18
  * Title text displayed in the modal header.
19
19
  */
20
20
  this.heading = '';
21
+ /**
22
+ * Size of the modal (sm, md, lg, xl, xxl).
23
+ */
24
+ this.size = 'md';
25
+ /**
26
+ * If true, the modal cannot be closed by clicking the backdrop or pressing Esc.
27
+ */
28
+ this.static = false;
29
+ this.isClosing = false;
21
30
  }
22
31
  updated(changedProperties) {
23
32
  if (changedProperties.has('open')) {
24
- if (this.dialogEl) {
25
- if (this.open && !this.dialogEl.open) {
33
+ if (this.open) {
34
+ this.dialogEl.classList.remove('closing');
35
+ this.isClosing = false;
36
+ if (!this.dialogEl.open) {
26
37
  this.dialogEl.showModal();
27
38
  }
28
- else if (!this.open && this.dialogEl.open) {
29
- this.dialogEl.close();
30
- }
39
+ }
40
+ else if (this.dialogEl.open && !this.isClosing) {
41
+ this.closeWithAnimation();
31
42
  }
32
43
  }
33
44
  }
45
+ closeWithAnimation() {
46
+ if (this.isClosing)
47
+ return;
48
+ this.isClosing = true;
49
+ this.dialogEl.classList.add('closing');
50
+ const onAnimationEnd = () => {
51
+ this.dialogEl.removeEventListener('animationend', onAnimationEnd);
52
+ this.dialogEl.classList.remove('closing');
53
+ this.isClosing = false;
54
+ if (!this.open) {
55
+ this.dialogEl.close();
56
+ }
57
+ };
58
+ this.dialogEl.addEventListener('animationend', onAnimationEnd);
59
+ }
34
60
  handleClose() {
35
61
  this.open = false;
36
62
  this.dispatchEvent(new CustomEvent('st-close', { bubbles: true, composed: true }));
37
63
  }
64
+ handleCancel(e) {
65
+ e.preventDefault();
66
+ if (!this.static) {
67
+ this.handleClose();
68
+ }
69
+ }
38
70
  onDialogClose() {
39
71
  if (this.open) {
40
72
  this.open = false;
@@ -42,28 +74,36 @@ let StModal = class StModal extends LitElement {
42
74
  }
43
75
  }
44
76
  handleBackdropClick(e) {
77
+ if (this.static)
78
+ return;
45
79
  const rect = this.dialogEl.getBoundingClientRect();
46
- if (e.clientX < rect.left ||
80
+ const clickedOutside = e.clientX < rect.left ||
47
81
  e.clientX > rect.right ||
48
82
  e.clientY < rect.top ||
49
- e.clientY > rect.bottom) {
83
+ e.clientY > rect.bottom;
84
+ if (clickedOutside) {
50
85
  this.handleClose();
51
86
  }
52
87
  }
53
88
  render() {
54
89
  return html `
55
90
  <dialog
56
- @cancel=${this.handleClose}
91
+ class="size-${this.size}"
92
+ aria-labelledby=${this.heading ? 'modal-heading' : nothing}
93
+ @cancel=${this.handleCancel}
57
94
  @click=${this.handleBackdropClick}
58
95
  @close=${this.onDialogClose}
59
96
  >
60
97
  <div class="modal-header" @click=${(e) => e.stopPropagation()}>
61
98
  <slot name="header">
62
- <h3 class="modal-title">${this.heading}</h3>
99
+ <h3 class="modal-title" id="modal-heading">${this.heading}</h3>
63
100
  </slot>
64
- <button class="close-btn" @click=${this.handleClose} aria-label="Close modal">
65
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
66
- </button>
101
+
102
+ ${!this.static ? html `
103
+ <button class="close-btn" @click=${this.handleClose} aria-label="Close modal">
104
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
105
+ </button>
106
+ ` : nothing}
67
107
  </div>
68
108
  <div class="modal-body" @click=${(e) => e.stopPropagation()}>
69
109
  <slot></slot>
@@ -88,17 +128,59 @@ StModal.styles = [
88
128
  border-radius: var(--st-radius-lg, 0.5rem);
89
129
  background-color: var(--st-color-surface);
90
130
  color: var(--st-color-text);
91
- width: 100%;
92
- max-width: 500px;
131
+
132
+ /* Responsive width calculation */
133
+ width: calc(100% - 2rem);
134
+ margin: auto;
135
+
93
136
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
94
137
  font-family: var(--st-font-family, inherit);
95
138
  }
96
139
 
140
+ /* Sizes */
141
+ dialog.size-sm { max-width: 400px; }
142
+ dialog.size-md { max-width: 500px; }
143
+ dialog.size-lg { max-width: 800px; }
144
+ dialog.size-xl { max-width: 1024px; }
145
+ dialog.size-xxl { max-width: 1280px; }
146
+
97
147
  dialog::backdrop {
98
148
  background-color: rgba(0, 0, 0, 0.5);
99
149
  backdrop-filter: blur(2px);
100
150
  }
101
151
 
152
+ /* Animations */
153
+ @keyframes fade-in {
154
+ from { opacity: 0; }
155
+ to { opacity: 1; }
156
+ }
157
+ @keyframes fade-out {
158
+ from { opacity: 1; }
159
+ to { opacity: 0; }
160
+ }
161
+ @keyframes slide-up {
162
+ from { opacity: 0; transform: translateY(20px) scale(0.95); }
163
+ to { opacity: 1; transform: translateY(0) scale(1); }
164
+ }
165
+ @keyframes slide-down {
166
+ from { opacity: 1; transform: translateY(0) scale(1); }
167
+ to { opacity: 0; transform: translateY(20px) scale(0.95); }
168
+ }
169
+
170
+ dialog[open] {
171
+ animation: slide-up 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
172
+ }
173
+ dialog[open]::backdrop {
174
+ animation: fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
175
+ }
176
+
177
+ dialog.closing {
178
+ animation: slide-down 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
179
+ }
180
+ dialog.closing::backdrop {
181
+ animation: fade-out 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
182
+ }
183
+
102
184
  .modal-header {
103
185
  display: flex;
104
186
  align-items: center;
@@ -154,6 +236,12 @@ __decorate([
154
236
  __decorate([
155
237
  property({ type: String })
156
238
  ], StModal.prototype, "heading", void 0);
239
+ __decorate([
240
+ property({ type: String })
241
+ ], StModal.prototype, "size", void 0);
242
+ __decorate([
243
+ property({ type: Boolean })
244
+ ], StModal.prototype, "static", void 0);
157
245
  __decorate([
158
246
  query('dialog')
159
247
  ], StModal.prototype, "dialogEl", 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.6",
3
+ "version": "1.0.1-develop.9",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,6 @@ export class StCheckbox extends LitElement {
11
11
 
12
12
  @property({type: String, reflect: true}) size: "sm" | "md" | "lg" = "md";
13
13
  @property({type: String, reflect: true}) name = "";
14
- // Checkboxes default to 'on' natively if no value is provided
15
14
  @property({type: String}) value = "on";
16
15
 
17
16
  @property({type: Boolean, reflect: true}) checked = false;
@@ -20,21 +19,20 @@ export class StCheckbox extends LitElement {
20
19
  @property({type: Boolean, reflect: true}) disabled = false;
21
20
  @property({type: Boolean, reflect: true}) invalid = false;
22
21
  @property({type: Boolean, reflect: true}) required = false;
22
+ @property({type: String}) errorMessage = "";
23
23
 
24
24
  @property({attribute: "aria-label"}) ariaLabel = "";
25
25
 
26
- constructor() {
26
+ constructor() {
27
27
  super();
28
28
  this._internals = this.attachInternals();
29
29
 
30
- // 1. Trigger error state if the user clicks away and the field is invalid
31
30
  this.addEventListener('focusout', () => {
32
- this.invalid = !this._inputEl.validity.valid;
31
+ this.invalid = !this.checkValidity();
33
32
  });
34
33
 
35
- // 2. Trigger error state if the st-form tries to submit and fails
36
34
  this.addEventListener('invalid', () => {
37
- this.invalid = true;
35
+ this.invalid = !this.checkValidity();
38
36
  });
39
37
  }
40
38
 
@@ -67,7 +65,6 @@ constructor() {
67
65
  font-size: var(--st-font-size-base);
68
66
  }
69
67
 
70
- /* Visually hide the native input but keep it in the DOM for focus/screen readers */
71
68
  input {
72
69
  position: absolute;
73
70
  width: var(--checkbox-size);
@@ -80,7 +77,6 @@ constructor() {
80
77
  border-width: 0;
81
78
  }
82
79
 
83
- /* Custom Checkbox Box */
84
80
  .control {
85
81
  display: flex;
86
82
  align-items: center;
@@ -92,30 +88,25 @@ constructor() {
92
88
  background-color: var(--st-color-bg);
93
89
  transition: all 0.2s ease;
94
90
  flex-shrink: 0;
95
- /* Aligns the box nicely with standard text line-height */
96
91
  margin-top: 0.125rem;
97
92
  }
98
93
 
99
- /* Hover State */
100
94
  label:hover .control {
101
95
  border-color: var(--st-color-primary-hover);
102
96
  }
103
97
 
104
- /* Focus State */
105
98
  input:focus-visible + .control {
106
99
  box-shadow:
107
100
  0 0 0 2px var(--st-color-bg),
108
101
  0 0 0 4px var(--st-color-primary);
109
102
  }
110
103
 
111
- /* Checked & Indeterminate States */
112
104
  :host([checked]) .control,
113
105
  :host([indeterminate]) .control {
114
106
  border-color: var(--st-color-primary);
115
107
  background-color: var(--st-color-primary);
116
108
  }
117
109
 
118
- /* SVG Icons */
119
110
  svg {
120
111
  width: var(--checkbox-icon-size);
121
112
  height: var(--checkbox-icon-size);
@@ -131,14 +122,12 @@ constructor() {
131
122
  opacity 0.2s ease;
132
123
  }
133
124
 
134
- /* Reveal Icons */
135
125
  :host([checked]) svg.icon-check,
136
126
  :host([indeterminate]) svg.icon-minus {
137
127
  opacity: 1;
138
128
  transform: scale(1);
139
129
  }
140
130
 
141
- /* Disabled State */
142
131
  :host([disabled]) {
143
132
  pointer-events: none;
144
133
  }
@@ -147,7 +136,6 @@ constructor() {
147
136
  cursor: not-allowed;
148
137
  }
149
138
 
150
- /* Invalid State */
151
139
  :host([invalid]) .control {
152
140
  border-color: var(--st-color-error);
153
141
  }
@@ -162,8 +150,7 @@ constructor() {
162
150
  override updated(changedProperties: PropertyValues<this>) {
163
151
  super.updated(changedProperties);
164
152
 
165
- if (changedProperties.has("checked") || changedProperties.has("value")) {
166
- // Form API: If checked, submit the value. If unchecked, submit null to remove it from FormData.
153
+ if (changedProperties.has("checked") || changedProperties.has("value") || changedProperties.has("errorMessage")) {
167
154
  this._internals.setFormValue(this.checked ? this.value : null);
168
155
  this._syncValidity();
169
156
  }
@@ -175,16 +162,54 @@ constructor() {
175
162
 
176
163
  private _syncValidity() {
177
164
  if (!this._inputEl) return;
178
- this._internals.setValidity(
179
- this._inputEl.validity,
180
- this._inputEl.validationMessage,
181
- this._inputEl,
182
- );
165
+
166
+ if (this.required && !this.checked) {
167
+ const message = this.errorMessage || this._inputEl.validationMessage || "Please check this box if you want to proceed.";
168
+ this._internals.setValidity(
169
+ {valueMissing: true},
170
+ message,
171
+ this._inputEl,
172
+ );
173
+ } else {
174
+ this._internals.setValidity(
175
+ this._inputEl.validity,
176
+ this._inputEl.validationMessage,
177
+ this._inputEl,
178
+ );
179
+ }
180
+ }
181
+
182
+ public checkValidity(): boolean {
183
+ if (!this._inputEl) return true;
184
+
185
+ if (this.required && !this.checked) {
186
+ const message = this.errorMessage || this._inputEl.validationMessage || "Please check this box if you want to proceed.";
187
+ this._internals.setValidity(
188
+ {valueMissing: true},
189
+ message,
190
+ this._inputEl,
191
+ );
192
+ this.invalid = true;
193
+ return false;
194
+ }
195
+
196
+ const isValid = this._inputEl.checkValidity();
197
+ this._syncValidity();
198
+ this.invalid = !isValid;
199
+ return isValid;
200
+ }
201
+
202
+ public reportValidity(): boolean {
203
+ this._syncValidity();
204
+ const isValid = this._internals.reportValidity();
205
+ this.invalid = !isValid;
206
+ return isValid;
183
207
  }
184
208
 
185
209
  formResetCallback() {
186
210
  this.checked = false;
187
211
  this.indeterminate = false;
212
+ this.invalid = false;
188
213
  this._internals.setFormValue(null);
189
214
  this.updateComplete.then(() => this._syncValidity());
190
215
  }
@@ -194,12 +219,16 @@ constructor() {
194
219
 
195
220
  if (this.disabled) return;
196
221
 
197
- // Standard native behavior: clicking an indeterminate checkbox unsets the indeterminate state
198
222
  if (this.indeterminate) {
199
223
  this.indeterminate = false;
200
224
  }
201
225
 
202
226
  this.checked = this._inputEl.checked;
227
+
228
+ if (this.checkValidity()) {
229
+ this.invalid = false;
230
+ }
231
+
203
232
  this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
204
233
  }
205
234
 
@@ -218,7 +247,6 @@ constructor() {
218
247
  @change=${this._handleChange}
219
248
  />
220
249
  <div class="control">
221
- <!-- Render both SVGs and use CSS to conditionally reveal them based on host attributes -->
222
250
  ${this.indeterminate
223
251
  ? html`<svg class="icon-minus" viewBox="0 0 24 24"><path d="M5 12h14"></path></svg>`
224
252
  : html`<svg class="icon-check" viewBox="0 0 24 24">
@@ -235,4 +263,4 @@ declare global {
235
263
  interface HTMLElementTagNameMap {
236
264
  "st-checkbox": StCheckbox;
237
265
  }
238
- }
266
+ }
@@ -176,20 +176,19 @@ export class StForm extends LitElement {
176
176
  }
177
177
 
178
178
  private _validateCurrentContext(): boolean {
179
- if (this._stepElements.length === 0) {
180
- return this._formEl.checkValidity();
181
- }
179
+ const scope: ParentNode = this._stepElements.length > 0
180
+ ? this._stepElements[this._currentStepIndex]
181
+ : this;
182
182
 
183
- const currentStep = this._stepElements[this._currentStepIndex];
184
183
  let isValid = true;
185
184
 
186
- // Find all custom and native form inputs assigned to the current step
185
+ // Find all elements that expose validation functions (e.g. checkValidity / reportValidity)
187
186
  const inputs = Array.from(
188
- currentStep.querySelectorAll<
189
- HTMLElement & {checkValidity?: () => boolean; reportValidity?: () => void}
187
+ scope.querySelectorAll<
188
+ HTMLElement & {checkValidity?: () => boolean; reportValidity?: () => boolean}
190
189
  >("*"),
191
190
  );
192
-
191
+
193
192
  for (const input of inputs) {
194
193
  if (typeof input.checkValidity === "function") {
195
194
  if (!input.checkValidity()) {
@@ -202,6 +201,13 @@ export class StForm extends LitElement {
202
201
  }
203
202
  }
204
203
 
204
+ // Also check standard form elements if not using steps
205
+ if (this._stepElements.length === 0 && this._formEl) {
206
+ if (!this._formEl.checkValidity()) {
207
+ isValid = false;
208
+ }
209
+ }
210
+
205
211
  return isValid;
206
212
  }
207
213
 
@@ -373,4 +379,4 @@ declare global {
373
379
  interface HTMLElementTagNameMap {
374
380
  "st-form": StForm;
375
381
  }
376
- }
382
+ }
@@ -3,10 +3,12 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import { iconPaths, IconName } from './icon-paths.js';
4
4
  import { resetStyles } from '../../styles/reset.js';
5
5
 
6
+ export type IconVariant = 'outline' | 'solid';
7
+
6
8
  @customElement('st-icon')
7
9
  export class StIcon extends LitElement {
8
10
  @property({ type: String }) name: IconName | string = '';
9
- @property({ type: String }) variant: 'outline' | 'solid' = 'outline';
11
+ @property({ type: String }) variant: IconVariant = 'outline';
10
12
 
11
13
  // Semantic Colors
12
14
  @property({ type: String, reflect: true }) color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' = 'default';
@@ -1,4 +1,4 @@
1
- import { LitElement, html, css, PropertyValues } from 'lit';
1
+ import { LitElement, html, css, PropertyValues, nothing } from 'lit';
2
2
  import { customElement, property, query } from 'lit/decorators.js';
3
3
  import { resetStyles } from '../../styles/reset.js';
4
4
 
@@ -14,8 +14,20 @@ export class StModal extends LitElement {
14
14
  */
15
15
  @property({ type: String }) heading = '';
16
16
 
17
+ /**
18
+ * Size of the modal (sm, md, lg, xl, xxl).
19
+ */
20
+ @property({ type: String }) size: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' = 'md';
21
+
22
+ /**
23
+ * If true, the modal cannot be closed by clicking the backdrop or pressing Esc.
24
+ */
25
+ @property({ type: Boolean }) static = false;
26
+
17
27
  @query('dialog') private dialogEl!: HTMLDialogElement;
18
28
 
29
+ private isClosing = false;
30
+
19
31
  static styles = [
20
32
  resetStyles,
21
33
  css`
@@ -29,17 +41,59 @@ export class StModal extends LitElement {
29
41
  border-radius: var(--st-radius-lg, 0.5rem);
30
42
  background-color: var(--st-color-surface);
31
43
  color: var(--st-color-text);
32
- width: 100%;
33
- max-width: 500px;
44
+
45
+ /* Responsive width calculation */
46
+ width: calc(100% - 2rem);
47
+ margin: auto;
48
+
34
49
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
35
50
  font-family: var(--st-font-family, inherit);
36
51
  }
37
52
 
53
+ /* Sizes */
54
+ dialog.size-sm { max-width: 400px; }
55
+ dialog.size-md { max-width: 500px; }
56
+ dialog.size-lg { max-width: 800px; }
57
+ dialog.size-xl { max-width: 1024px; }
58
+ dialog.size-xxl { max-width: 1280px; }
59
+
38
60
  dialog::backdrop {
39
61
  background-color: rgba(0, 0, 0, 0.5);
40
62
  backdrop-filter: blur(2px);
41
63
  }
42
64
 
65
+ /* Animations */
66
+ @keyframes fade-in {
67
+ from { opacity: 0; }
68
+ to { opacity: 1; }
69
+ }
70
+ @keyframes fade-out {
71
+ from { opacity: 1; }
72
+ to { opacity: 0; }
73
+ }
74
+ @keyframes slide-up {
75
+ from { opacity: 0; transform: translateY(20px) scale(0.95); }
76
+ to { opacity: 1; transform: translateY(0) scale(1); }
77
+ }
78
+ @keyframes slide-down {
79
+ from { opacity: 1; transform: translateY(0) scale(1); }
80
+ to { opacity: 0; transform: translateY(20px) scale(0.95); }
81
+ }
82
+
83
+ dialog[open] {
84
+ animation: slide-up 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
85
+ }
86
+ dialog[open]::backdrop {
87
+ animation: fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
88
+ }
89
+
90
+ dialog.closing {
91
+ animation: slide-down 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
92
+ }
93
+ dialog.closing::backdrop {
94
+ animation: fade-out 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
95
+ }
96
+
43
97
  .modal-header {
44
98
  display: flex;
45
99
  align-items: center;
@@ -92,21 +146,47 @@ export class StModal extends LitElement {
92
146
 
93
147
  updated(changedProperties: PropertyValues) {
94
148
  if (changedProperties.has('open')) {
95
- if (this.dialogEl) {
96
- if (this.open && !this.dialogEl.open) {
149
+ if (this.open) {
150
+ this.dialogEl.classList.remove('closing');
151
+ this.isClosing = false;
152
+ if (!this.dialogEl.open) {
97
153
  this.dialogEl.showModal();
98
- } else if (!this.open && this.dialogEl.open) {
99
- this.dialogEl.close();
100
154
  }
155
+ } else if (this.dialogEl.open && !this.isClosing) {
156
+ this.closeWithAnimation();
101
157
  }
102
158
  }
103
159
  }
104
160
 
161
+ private closeWithAnimation() {
162
+ if (this.isClosing) return;
163
+ this.isClosing = true;
164
+ this.dialogEl.classList.add('closing');
165
+
166
+ const onAnimationEnd = () => {
167
+ this.dialogEl.removeEventListener('animationend', onAnimationEnd);
168
+ this.dialogEl.classList.remove('closing');
169
+ this.isClosing = false;
170
+ if (!this.open) {
171
+ this.dialogEl.close();
172
+ }
173
+ };
174
+
175
+ this.dialogEl.addEventListener('animationend', onAnimationEnd);
176
+ }
177
+
105
178
  private handleClose() {
106
179
  this.open = false;
107
180
  this.dispatchEvent(new CustomEvent('st-close', { bubbles: true, composed: true }));
108
181
  }
109
182
 
183
+ private handleCancel(e: Event) {
184
+ e.preventDefault();
185
+ if (!this.static) {
186
+ this.handleClose();
187
+ }
188
+ }
189
+
110
190
  private onDialogClose() {
111
191
  if (this.open) {
112
192
  this.open = false;
@@ -115,13 +195,16 @@ export class StModal extends LitElement {
115
195
  }
116
196
 
117
197
  private handleBackdropClick(e: MouseEvent) {
198
+ if (this.static) return;
199
+
118
200
  const rect = this.dialogEl.getBoundingClientRect();
119
- if (
201
+ const clickedOutside =
120
202
  e.clientX < rect.left ||
121
203
  e.clientX > rect.right ||
122
204
  e.clientY < rect.top ||
123
- e.clientY > rect.bottom
124
- ) {
205
+ e.clientY > rect.bottom;
206
+
207
+ if (clickedOutside) {
125
208
  this.handleClose();
126
209
  }
127
210
  }
@@ -129,17 +212,22 @@ export class StModal extends LitElement {
129
212
  render() {
130
213
  return html`
131
214
  <dialog
132
- @cancel=${this.handleClose}
215
+ class="size-${this.size}"
216
+ aria-labelledby=${this.heading ? 'modal-heading' : nothing}
217
+ @cancel=${this.handleCancel}
133
218
  @click=${this.handleBackdropClick}
134
219
  @close=${this.onDialogClose}
135
220
  >
136
221
  <div class="modal-header" @click=${(e: MouseEvent) => e.stopPropagation()}>
137
222
  <slot name="header">
138
- <h3 class="modal-title">${this.heading}</h3>
223
+ <h3 class="modal-title" id="modal-heading">${this.heading}</h3>
139
224
  </slot>
140
- <button class="close-btn" @click=${this.handleClose} aria-label="Close modal">
141
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
142
- </button>
225
+
226
+ ${!this.static ? html`
227
+ <button class="close-btn" @click=${this.handleClose} aria-label="Close modal">
228
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
229
+ </button>
230
+ ` : nothing}
143
231
  </div>
144
232
  <div class="modal-body" @click=${(e: MouseEvent) => e.stopPropagation()}>
145
233
  <slot></slot>