secure-ui-components 0.1.0-beta.1

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +310 -0
  3. package/dist/components/secure-datetime/secure-datetime.css +263 -0
  4. package/dist/components/secure-datetime/secure-datetime.d.ts +124 -0
  5. package/dist/components/secure-datetime/secure-datetime.d.ts.map +1 -0
  6. package/dist/components/secure-datetime/secure-datetime.js +610 -0
  7. package/dist/components/secure-datetime/secure-datetime.js.map +1 -0
  8. package/dist/components/secure-file-upload/secure-file-upload.css +334 -0
  9. package/dist/components/secure-file-upload/secure-file-upload.d.ts +150 -0
  10. package/dist/components/secure-file-upload/secure-file-upload.d.ts.map +1 -0
  11. package/dist/components/secure-file-upload/secure-file-upload.js +911 -0
  12. package/dist/components/secure-file-upload/secure-file-upload.js.map +1 -0
  13. package/dist/components/secure-form/secure-form.css +62 -0
  14. package/dist/components/secure-form/secure-form.d.ts +128 -0
  15. package/dist/components/secure-form/secure-form.d.ts.map +1 -0
  16. package/dist/components/secure-form/secure-form.js +697 -0
  17. package/dist/components/secure-form/secure-form.js.map +1 -0
  18. package/dist/components/secure-input/secure-input.css +168 -0
  19. package/dist/components/secure-input/secure-input.d.ts +114 -0
  20. package/dist/components/secure-input/secure-input.d.ts.map +1 -0
  21. package/dist/components/secure-input/secure-input.js +785 -0
  22. package/dist/components/secure-input/secure-input.js.map +1 -0
  23. package/dist/components/secure-select/secure-select.css +195 -0
  24. package/dist/components/secure-select/secure-select.d.ts +149 -0
  25. package/dist/components/secure-select/secure-select.d.ts.map +1 -0
  26. package/dist/components/secure-select/secure-select.js +634 -0
  27. package/dist/components/secure-select/secure-select.js.map +1 -0
  28. package/dist/components/secure-submit-button/secure-submit-button.css +135 -0
  29. package/dist/components/secure-submit-button/secure-submit-button.d.ts +61 -0
  30. package/dist/components/secure-submit-button/secure-submit-button.d.ts.map +1 -0
  31. package/dist/components/secure-submit-button/secure-submit-button.js +399 -0
  32. package/dist/components/secure-submit-button/secure-submit-button.js.map +1 -0
  33. package/dist/components/secure-table/secure-table.css +341 -0
  34. package/dist/components/secure-table/secure-table.d.ts +64 -0
  35. package/dist/components/secure-table/secure-table.d.ts.map +1 -0
  36. package/dist/components/secure-table/secure-table.js +567 -0
  37. package/dist/components/secure-table/secure-table.js.map +1 -0
  38. package/dist/components/secure-textarea/secure-textarea.css +153 -0
  39. package/dist/components/secure-textarea/secure-textarea.d.ts +111 -0
  40. package/dist/components/secure-textarea/secure-textarea.d.ts.map +1 -0
  41. package/dist/components/secure-textarea/secure-textarea.js +477 -0
  42. package/dist/components/secure-textarea/secure-textarea.js.map +1 -0
  43. package/dist/core/base-component.d.ts +134 -0
  44. package/dist/core/base-component.d.ts.map +1 -0
  45. package/dist/core/base-component.js +303 -0
  46. package/dist/core/base-component.js.map +1 -0
  47. package/dist/core/base.css +37 -0
  48. package/dist/core/security-config.d.ts +89 -0
  49. package/dist/core/security-config.d.ts.map +1 -0
  50. package/dist/core/security-config.js +273 -0
  51. package/dist/core/security-config.js.map +1 -0
  52. package/dist/core/types.d.ts +212 -0
  53. package/dist/core/types.d.ts.map +1 -0
  54. package/dist/core/types.js +7 -0
  55. package/dist/core/types.js.map +1 -0
  56. package/dist/index.d.ts +18 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +19 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/package.json +89 -0
  61. package/dist/styles/tokens.css +257 -0
  62. package/package.json +118 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Secure Submit Button Styles
3
+ *
4
+ * Security-aware submit button with smooth state transitions:
5
+ * disabled ↔ enabled, loading, hover, focus, active.
6
+ * All animations respect prefers-reduced-motion.
7
+ */
8
+
9
+ :host {
10
+ display: inline-block;
11
+ }
12
+
13
+ .submit-container {
14
+ margin-top: 8px;
15
+ position: relative;
16
+ }
17
+
18
+ .submit-btn {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ gap: var(--secure-ui-space-2);
23
+ padding: var(--secure-ui-button-padding-y) var(--secure-ui-button-padding-x);
24
+ min-height: var(--secure-ui-button-height);
25
+ font-family: inherit;
26
+ font-size: var(--secure-ui-font-size-sm);
27
+ font-weight: var(--secure-ui-button-font-weight);
28
+ line-height: 1;
29
+ border-radius: var(--secure-ui-button-border-radius);
30
+ border: var(--secure-ui-border-width-base) solid transparent;
31
+ cursor: pointer;
32
+ transition: background-color var(--secure-ui-transition-base) var(--secure-ui-transition-ease-out),
33
+ border-color var(--secure-ui-transition-base) var(--secure-ui-transition-ease-out),
34
+ opacity var(--secure-ui-transition-base) var(--secure-ui-transition-ease-out),
35
+ box-shadow var(--secure-ui-transition-base) var(--secure-ui-transition-ease-out),
36
+ color var(--secure-ui-transition-base) var(--secure-ui-transition-ease-out),
37
+ transform var(--secure-ui-transition-fast) var(--secure-ui-transition-ease-out);
38
+ background-color: var(--secure-ui-color-info);
39
+ color: var(--secure-ui-color-text-inverse);
40
+ user-select: none;
41
+ }
42
+
43
+ .submit-btn:hover:not(:disabled) {
44
+ background-color: color-mix(in srgb, var(--secure-ui-color-info) 82%, black);
45
+ transform: translateY(-1px);
46
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.25);
47
+ }
48
+
49
+ /* Focus indicator: outline for forced-color/high-contrast + box-shadow for standard mode */
50
+ .submit-btn:focus-visible {
51
+ outline: 2px solid var(--secure-ui-color-info);
52
+ outline-offset: 2px;
53
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
54
+ }
55
+
56
+ @media (forced-colors: active) {
57
+ .submit-btn:focus-visible {
58
+ outline: 2px solid ButtonText;
59
+ box-shadow: none;
60
+ }
61
+ }
62
+
63
+ .submit-btn:active:not(:disabled) {
64
+ background-color: color-mix(in srgb, var(--secure-ui-color-info) 72%, black);
65
+ transform: scale(0.98);
66
+ box-shadow: none;
67
+ }
68
+
69
+ /* Disabled state — smooth transition from enabled */
70
+ .submit-btn:disabled {
71
+ background-color: var(--secure-ui-color-bg-disabled);
72
+ color: var(--secure-ui-color-text-disabled);
73
+ border-color: var(--secure-ui-color-border);
74
+ cursor: not-allowed;
75
+ opacity: 0.7;
76
+ transform: none;
77
+ box-shadow: none;
78
+ }
79
+
80
+ /* Visually hidden utility — used for the disabled-state hint */
81
+ .sr-only {
82
+ position: absolute;
83
+ width: 1px;
84
+ height: 1px;
85
+ padding: 0;
86
+ margin: -1px;
87
+ overflow: hidden;
88
+ clip: rect(0, 0, 0, 0);
89
+ white-space: nowrap;
90
+ border: 0;
91
+ }
92
+
93
+ /* Label / loading crossfade */
94
+ .btn-label,
95
+ .btn-loading {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ gap: 8px;
99
+ transition: opacity 0.2s ease-out;
100
+ }
101
+
102
+ .btn-label.hidden,
103
+ .btn-loading.hidden {
104
+ display: none;
105
+ }
106
+
107
+ /* Spinner */
108
+ .spinner {
109
+ display: inline-block;
110
+ width: 14px;
111
+ height: 14px;
112
+ border: 2px solid currentColor;
113
+ border-right-color: transparent;
114
+ border-radius: 50%;
115
+ animation: spin 0.6s linear infinite;
116
+ }
117
+
118
+ @keyframes spin {
119
+ to { transform: rotate(360deg); }
120
+ }
121
+
122
+ @media (prefers-reduced-motion: reduce) {
123
+ .submit-btn {
124
+ transition: none !important;
125
+ }
126
+ .btn-label,
127
+ .btn-loading {
128
+ transition: none !important;
129
+ }
130
+ .spinner {
131
+ animation: none;
132
+ border-right-color: currentColor;
133
+ opacity: 0.5;
134
+ }
135
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @fileoverview Secure Submit Button Component
3
+ *
4
+ * A security-aware form submit button that integrates with <secure-form>.
5
+ * The button's enabled/disabled state is driven by the parent form's security
6
+ * tier and field validation state.
7
+ *
8
+ * Tier behaviour:
9
+ * - public: Button enabled by default (validation.required = false)
10
+ * - authenticated: Button disabled until all required fields are valid
11
+ * - sensitive: Button disabled until all required fields are valid
12
+ * - critical: Button disabled until all required fields are valid
13
+ *
14
+ * Usage:
15
+ * <secure-form action="/api/data" method="POST" security-tier="sensitive">
16
+ * <secure-input label="Name" name="name" required></secure-input>
17
+ * <secure-submit-button label="Submit"></secure-submit-button>
18
+ * </secure-form>
19
+ *
20
+ * @module secure-submit-button
21
+ * @license MIT
22
+ */
23
+ import { SecureBaseComponent } from '../../core/base-component.js';
24
+ /**
25
+ * Secure Submit Button Web Component
26
+ *
27
+ * Provides a security-aware submit button that monitors parent form validity
28
+ * and enables/disables based on the form's security tier requirements.
29
+ *
30
+ * @extends SecureBaseComponent
31
+ */
32
+ export declare class SecureSubmitButton extends SecureBaseComponent {
33
+ #private;
34
+ /**
35
+ * Observed attributes
36
+ */
37
+ static get observedAttributes(): string[];
38
+ constructor();
39
+ /**
40
+ * Connected to DOM — discover form, attach listeners, evaluate state
41
+ */
42
+ connectedCallback(): void;
43
+ /**
44
+ * Render the button inside shadow DOM
45
+ */
46
+ protected render(): DocumentFragment | HTMLElement | null;
47
+ protected handleAttributeChange(name: string, _oldValue: string | null, newValue: string | null): void;
48
+ /**
49
+ * Whether the button is disabled
50
+ */
51
+ get disabled(): boolean;
52
+ set disabled(value: boolean);
53
+ /**
54
+ * The button label text
55
+ */
56
+ get label(): string;
57
+ set label(value: string);
58
+ disconnectedCallback(): void;
59
+ }
60
+ export default SecureSubmitButton;
61
+ //# sourceMappingURL=secure-submit-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-submit-button.d.ts","sourceRoot":"","sources":["../../../src/components/secure-submit-button/secure-submit-button.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAInE;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,mBAAmB;;IAmEzD;;OAEG;IACH,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAOxC;;IASD;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAiBzB;;OAEG;IACH,SAAS,CAAC,MAAM,IAAI,gBAAgB,GAAG,WAAW,GAAG,IAAI;IAqPzD,SAAS,CAAC,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAyBtG;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAO1B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAcD,oBAAoB,IAAI,IAAI;CAW7B;AAKD,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,399 @@
1
+ /**
2
+ * @fileoverview Secure Submit Button Component
3
+ *
4
+ * A security-aware form submit button that integrates with <secure-form>.
5
+ * The button's enabled/disabled state is driven by the parent form's security
6
+ * tier and field validation state.
7
+ *
8
+ * Tier behaviour:
9
+ * - public: Button enabled by default (validation.required = false)
10
+ * - authenticated: Button disabled until all required fields are valid
11
+ * - sensitive: Button disabled until all required fields are valid
12
+ * - critical: Button disabled until all required fields are valid
13
+ *
14
+ * Usage:
15
+ * <secure-form action="/api/data" method="POST" security-tier="sensitive">
16
+ * <secure-input label="Name" name="name" required></secure-input>
17
+ * <secure-submit-button label="Submit"></secure-submit-button>
18
+ * </secure-form>
19
+ *
20
+ * @module secure-submit-button
21
+ * @license MIT
22
+ */
23
+ import { SecureBaseComponent } from '../../core/base-component.js';
24
+ import { getTierConfig } from '../../core/security-config.js';
25
+ /**
26
+ * Secure Submit Button Web Component
27
+ *
28
+ * Provides a security-aware submit button that monitors parent form validity
29
+ * and enables/disables based on the form's security tier requirements.
30
+ *
31
+ * @extends SecureBaseComponent
32
+ */
33
+ export class SecureSubmitButton extends SecureBaseComponent {
34
+ /**
35
+ * Button element reference inside shadow DOM
36
+ * @private
37
+ */
38
+ #buttonElement = null;
39
+ /**
40
+ * Label span element
41
+ * @private
42
+ */
43
+ #labelElement = null;
44
+ /**
45
+ * Loading indicator element
46
+ * @private
47
+ */
48
+ #loadingElement = null;
49
+ /**
50
+ * Reference to the parent <secure-form> element
51
+ * @private
52
+ */
53
+ #parentForm = null;
54
+ /**
55
+ * Whether the parent form is currently valid
56
+ * @private
57
+ */
58
+ #isFormValid = false;
59
+ /**
60
+ * Whether a submission is in progress
61
+ * @private
62
+ */
63
+ #isSubmitting = false;
64
+ /**
65
+ * Effective security tier (inherited from form or explicit)
66
+ * @private
67
+ */
68
+ #effectiveTier = 'critical';
69
+ /**
70
+ * Effective tier config
71
+ * @private
72
+ */
73
+ #effectiveConfig;
74
+ /**
75
+ * Unique instance ID for aria attribute association
76
+ * @private
77
+ */
78
+ #instanceId = `secure-submit-button-${Math.random().toString(36).substr(2, 9)}`;
79
+ /**
80
+ * Bound event handler for field change events
81
+ * @private
82
+ */
83
+ #boundHandleFieldChange;
84
+ /**
85
+ * Bound click handler
86
+ * @private
87
+ */
88
+ #boundHandleClick;
89
+ /**
90
+ * Observed attributes
91
+ */
92
+ static get observedAttributes() {
93
+ return [
94
+ ...super.observedAttributes,
95
+ 'label',
96
+ 'loading-label',
97
+ 'disabled'
98
+ ];
99
+ }
100
+ constructor() {
101
+ super();
102
+ this.#effectiveConfig = getTierConfig(this.#effectiveTier);
103
+ this.#boundHandleFieldChange = this.#handleFieldChange.bind(this);
104
+ this.#boundHandleClick = this.#handleClick.bind(this);
105
+ }
106
+ /**
107
+ * Connected to DOM — discover form, attach listeners, evaluate state
108
+ */
109
+ connectedCallback() {
110
+ super.connectedCallback();
111
+ // Defer form discovery to ensure parent secure-form has initialized
112
+ // (secure-form creates its <form> element in its own connectedCallback)
113
+ queueMicrotask(() => {
114
+ this.#discoverParentForm();
115
+ this.#resolveEffectiveTier();
116
+ this.#attachFormListeners();
117
+ this.#evaluateValidity();
118
+ this.audit('submit_button_initialized', {
119
+ tier: this.#effectiveTier,
120
+ hasParentForm: !!this.#parentForm
121
+ });
122
+ });
123
+ }
124
+ /**
125
+ * Render the button inside shadow DOM
126
+ */
127
+ render() {
128
+ const fragment = document.createDocumentFragment();
129
+ const container = document.createElement('div');
130
+ container.className = 'submit-container';
131
+ // Visually hidden hint explains why the button may be disabled (WCAG 1.3.5, 4.1.2)
132
+ const hintId = `${this.#instanceId}-hint`;
133
+ const hint = document.createElement('p');
134
+ hint.id = hintId;
135
+ hint.className = 'sr-only';
136
+ hint.textContent = 'Complete all required fields to enable this button';
137
+ container.appendChild(hint);
138
+ // Create button (type="button" — cannot use type="submit" in shadow DOM)
139
+ this.#buttonElement = document.createElement('button');
140
+ this.#buttonElement.type = 'button';
141
+ this.#buttonElement.className = 'submit-btn';
142
+ this.#buttonElement.disabled = true; // Disabled by default until validity is evaluated
143
+ this.#buttonElement.setAttribute('aria-disabled', 'true');
144
+ this.#buttonElement.setAttribute('aria-describedby', hintId);
145
+ // Label span
146
+ this.#labelElement = document.createElement('span');
147
+ this.#labelElement.className = 'btn-label';
148
+ this.#labelElement.textContent = this.sanitizeValue(this.getAttribute('label') || 'Submit');
149
+ // Loading indicator span (hidden by default)
150
+ this.#loadingElement = document.createElement('span');
151
+ this.#loadingElement.className = 'btn-loading hidden';
152
+ this.#loadingElement.setAttribute('aria-hidden', 'true');
153
+ const spinner = document.createElement('span');
154
+ spinner.className = 'spinner';
155
+ const loadingText = document.createElement('span');
156
+ loadingText.textContent = this.sanitizeValue(this.getAttribute('loading-label') || 'Submitting...');
157
+ this.#loadingElement.appendChild(spinner);
158
+ this.#loadingElement.appendChild(loadingText);
159
+ this.#buttonElement.appendChild(this.#labelElement);
160
+ this.#buttonElement.appendChild(this.#loadingElement);
161
+ // Click handler
162
+ this.#buttonElement.addEventListener('click', this.#boundHandleClick);
163
+ container.appendChild(this.#buttonElement);
164
+ fragment.appendChild(container);
165
+ this.addComponentStyles(this.#getComponentStyles());
166
+ return fragment;
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // Form discovery & tier resolution
170
+ // ---------------------------------------------------------------------------
171
+ /**
172
+ * Find the parent <secure-form> element
173
+ * @private
174
+ */
175
+ #discoverParentForm() {
176
+ this.#parentForm = this.closest('secure-form');
177
+ }
178
+ /**
179
+ * Determine the effective security tier.
180
+ * If the button has an explicit security-tier attribute, use it.
181
+ * Otherwise, inherit from the parent form.
182
+ * @private
183
+ */
184
+ #resolveEffectiveTier() {
185
+ const ownTier = this.getAttribute('security-tier');
186
+ if (!ownTier && this.#parentForm) {
187
+ // Inherit from parent form
188
+ this.#effectiveTier = this.#parentForm.securityTier;
189
+ }
190
+ else {
191
+ this.#effectiveTier = this.securityTier;
192
+ }
193
+ this.#effectiveConfig = getTierConfig(this.#effectiveTier);
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Validation monitoring
197
+ // ---------------------------------------------------------------------------
198
+ /**
199
+ * Attach event listeners on the parent form to monitor field changes
200
+ * @private
201
+ */
202
+ #attachFormListeners() {
203
+ const target = this.#parentForm || this.parentElement;
204
+ if (!target)
205
+ return;
206
+ target.addEventListener('secure-input', this.#boundHandleFieldChange);
207
+ target.addEventListener('secure-textarea', this.#boundHandleFieldChange);
208
+ target.addEventListener('secure-select', this.#boundHandleFieldChange);
209
+ target.addEventListener('secure-datetime', this.#boundHandleFieldChange);
210
+ }
211
+ /**
212
+ * Handle a field change event — re-evaluate form validity
213
+ * @private
214
+ */
215
+ #handleFieldChange(_event) {
216
+ this.#evaluateValidity();
217
+ }
218
+ /**
219
+ * Evaluate form validity and enable/disable the button accordingly
220
+ * @private
221
+ */
222
+ #evaluateValidity() {
223
+ // If manually disabled via attribute, stay disabled
224
+ if (this.hasAttribute('disabled')) {
225
+ this.#setButtonDisabled(true);
226
+ return;
227
+ }
228
+ // If currently submitting, stay disabled
229
+ if (this.#isSubmitting) {
230
+ return;
231
+ }
232
+ // Public tier: validation not required, button always enabled
233
+ if (!this.#effectiveConfig.validation.required) {
234
+ this.#isFormValid = true;
235
+ this.#setButtonDisabled(false);
236
+ return;
237
+ }
238
+ // Authenticated / Sensitive / Critical: check form validity
239
+ if (this.#parentForm && typeof this.#parentForm.valid === 'boolean') {
240
+ this.#isFormValid = this.#parentForm.valid;
241
+ }
242
+ else {
243
+ // Fallback: manually query fields
244
+ this.#isFormValid = this.#checkFieldsValid();
245
+ }
246
+ this.#setButtonDisabled(!this.#isFormValid);
247
+ }
248
+ /**
249
+ * Fallback field validation when no parent form is found
250
+ * @private
251
+ */
252
+ #checkFieldsValid() {
253
+ const container = this.#parentForm || this.parentElement;
254
+ if (!container)
255
+ return false;
256
+ const fields = container.querySelectorAll('secure-input, secure-textarea, secure-select, secure-datetime, secure-file-upload');
257
+ for (const field of fields) {
258
+ const typedField = field;
259
+ if (typeof typedField.valid === 'boolean' && !typedField.valid) {
260
+ return false;
261
+ }
262
+ }
263
+ // If there are no fields and validation is required, stay disabled
264
+ return fields.length > 0;
265
+ }
266
+ /**
267
+ * Update the button's disabled state
268
+ * @private
269
+ */
270
+ #setButtonDisabled(disabled) {
271
+ if (!this.#buttonElement)
272
+ return;
273
+ this.#buttonElement.disabled = disabled;
274
+ this.#buttonElement.setAttribute('aria-disabled', String(disabled));
275
+ }
276
+ // ---------------------------------------------------------------------------
277
+ // Click & submission
278
+ // ---------------------------------------------------------------------------
279
+ /**
280
+ * Handle button click — rate limit, audit, trigger form submission
281
+ * @private
282
+ */
283
+ #handleClick() {
284
+ if (this.#isSubmitting || this.#buttonElement?.disabled)
285
+ return;
286
+ // Rate limit check
287
+ const rateLimitCheck = this.checkRateLimit();
288
+ if (!rateLimitCheck.allowed) {
289
+ this.audit('submit_button_rate_limited', {
290
+ retryAfter: rateLimitCheck.retryAfter
291
+ });
292
+ return;
293
+ }
294
+ this.audit('submit_button_clicked', {
295
+ tier: this.#effectiveTier,
296
+ formValid: this.#isFormValid
297
+ });
298
+ // Show loading state
299
+ this.#setLoading(true);
300
+ // Trigger form submission via parent secure-form
301
+ if (this.#parentForm) {
302
+ this.#parentForm.submit();
303
+ }
304
+ this.#setLoading(false);
305
+ // Loading state remains until explicitly reset via setLoading(false)
306
+ // or the form's success/error response handler resets it.
307
+ }
308
+ /**
309
+ * Toggle loading/submitting state
310
+ * @private
311
+ */
312
+ #setLoading(loading) {
313
+ this.#isSubmitting = loading;
314
+ if (this.#buttonElement) {
315
+ this.#buttonElement.disabled = loading;
316
+ this.#buttonElement.setAttribute('aria-disabled', String(loading));
317
+ }
318
+ if (this.#labelElement) {
319
+ this.#labelElement.classList.toggle('hidden', loading);
320
+ }
321
+ if (this.#loadingElement) {
322
+ this.#loadingElement.classList.toggle('hidden', !loading);
323
+ this.#loadingElement.setAttribute('aria-hidden', String(!loading));
324
+ }
325
+ }
326
+ // ---------------------------------------------------------------------------
327
+ // Attribute changes
328
+ // ---------------------------------------------------------------------------
329
+ handleAttributeChange(name, _oldValue, newValue) {
330
+ switch (name) {
331
+ case 'label':
332
+ if (this.#labelElement) {
333
+ this.#labelElement.textContent = this.sanitizeValue(newValue || 'Submit');
334
+ }
335
+ break;
336
+ case 'loading-label':
337
+ if (this.#loadingElement) {
338
+ const textSpan = this.#loadingElement.querySelector('span:last-child');
339
+ if (textSpan) {
340
+ textSpan.textContent = this.sanitizeValue(newValue || 'Submitting...');
341
+ }
342
+ }
343
+ break;
344
+ case 'disabled':
345
+ this.#evaluateValidity();
346
+ break;
347
+ }
348
+ }
349
+ // ---------------------------------------------------------------------------
350
+ // Public API
351
+ // ---------------------------------------------------------------------------
352
+ /**
353
+ * Whether the button is disabled
354
+ */
355
+ get disabled() {
356
+ return this.#buttonElement ? this.#buttonElement.disabled : true;
357
+ }
358
+ set disabled(value) {
359
+ if (value) {
360
+ this.setAttribute('disabled', '');
361
+ }
362
+ else {
363
+ this.removeAttribute('disabled');
364
+ }
365
+ this.#evaluateValidity();
366
+ }
367
+ /**
368
+ * The button label text
369
+ */
370
+ get label() {
371
+ return this.getAttribute('label') || 'Submit';
372
+ }
373
+ set label(value) {
374
+ this.setAttribute('label', value);
375
+ }
376
+ // ---------------------------------------------------------------------------
377
+ // Styles
378
+ // ---------------------------------------------------------------------------
379
+ #getComponentStyles() {
380
+ return new URL('./secure-submit-button.css', import.meta.url).href;
381
+ }
382
+ // ---------------------------------------------------------------------------
383
+ // Cleanup
384
+ // ---------------------------------------------------------------------------
385
+ disconnectedCallback() {
386
+ super.disconnectedCallback();
387
+ const target = this.#parentForm || this.parentElement;
388
+ if (target) {
389
+ target.removeEventListener('secure-input', this.#boundHandleFieldChange);
390
+ target.removeEventListener('secure-textarea', this.#boundHandleFieldChange);
391
+ target.removeEventListener('secure-select', this.#boundHandleFieldChange);
392
+ target.removeEventListener('secure-datetime', this.#boundHandleFieldChange);
393
+ }
394
+ }
395
+ }
396
+ // Register the custom element
397
+ customElements.define('secure-submit-button', SecureSubmitButton);
398
+ export default SecureSubmitButton;
399
+ //# sourceMappingURL=secure-submit-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-submit-button.js","sourceRoot":"","sources":["../../../src/components/secure-submit-button/secure-submit-button.ts"],"names":[],"mappings":"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"}