@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.
- package/dist/components/checkbox/checkbox.d.ts +3 -0
- package/dist/components/checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox/checkbox.js +38 -20
- package/dist/components/form/form.d.ts.map +1 -1
- package/dist/components/form/form.js +11 -6
- package/dist/components/icon/icon.d.ts +2 -1
- package/dist/components/icon/icon.d.ts.map +1 -1
- package/dist/components/modal/modal.d.ts +11 -0
- package/dist/components/modal/modal.d.ts.map +1 -1
- package/dist/components/modal/modal.js +103 -15
- package/package.json +1 -1
- package/src/components/checkbox/checkbox.ts +54 -26
- package/src/components/form/form.ts +15 -9
- package/src/components/icon/icon.ts +3 -1
- package/src/components/modal/modal.ts +103 -15
|
@@ -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;
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
36
|
-
const inputs = Array.from(
|
|
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:
|
|
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,
|
|
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,
|
|
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.
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
@@ -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.
|
|
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 =
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
this._inputEl.validationMessage
|
|
181
|
-
this.
|
|
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
|
-
|
|
180
|
-
|
|
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
|
|
185
|
+
// Find all elements that expose validation functions (e.g. checkValidity / reportValidity)
|
|
187
186
|
const inputs = Array.from(
|
|
188
|
-
|
|
189
|
-
HTMLElement & {checkValidity?: () => boolean; reportValidity?: () =>
|
|
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:
|
|
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
|
-
|
|
33
|
-
|
|
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.
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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>
|