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.
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/components/secure-datetime/secure-datetime.css +263 -0
- package/dist/components/secure-datetime/secure-datetime.d.ts +124 -0
- package/dist/components/secure-datetime/secure-datetime.d.ts.map +1 -0
- package/dist/components/secure-datetime/secure-datetime.js +610 -0
- package/dist/components/secure-datetime/secure-datetime.js.map +1 -0
- package/dist/components/secure-file-upload/secure-file-upload.css +334 -0
- package/dist/components/secure-file-upload/secure-file-upload.d.ts +150 -0
- package/dist/components/secure-file-upload/secure-file-upload.d.ts.map +1 -0
- package/dist/components/secure-file-upload/secure-file-upload.js +911 -0
- package/dist/components/secure-file-upload/secure-file-upload.js.map +1 -0
- package/dist/components/secure-form/secure-form.css +62 -0
- package/dist/components/secure-form/secure-form.d.ts +128 -0
- package/dist/components/secure-form/secure-form.d.ts.map +1 -0
- package/dist/components/secure-form/secure-form.js +697 -0
- package/dist/components/secure-form/secure-form.js.map +1 -0
- package/dist/components/secure-input/secure-input.css +168 -0
- package/dist/components/secure-input/secure-input.d.ts +114 -0
- package/dist/components/secure-input/secure-input.d.ts.map +1 -0
- package/dist/components/secure-input/secure-input.js +785 -0
- package/dist/components/secure-input/secure-input.js.map +1 -0
- package/dist/components/secure-select/secure-select.css +195 -0
- package/dist/components/secure-select/secure-select.d.ts +149 -0
- package/dist/components/secure-select/secure-select.d.ts.map +1 -0
- package/dist/components/secure-select/secure-select.js +634 -0
- package/dist/components/secure-select/secure-select.js.map +1 -0
- package/dist/components/secure-submit-button/secure-submit-button.css +135 -0
- package/dist/components/secure-submit-button/secure-submit-button.d.ts +61 -0
- package/dist/components/secure-submit-button/secure-submit-button.d.ts.map +1 -0
- package/dist/components/secure-submit-button/secure-submit-button.js +399 -0
- package/dist/components/secure-submit-button/secure-submit-button.js.map +1 -0
- package/dist/components/secure-table/secure-table.css +341 -0
- package/dist/components/secure-table/secure-table.d.ts +64 -0
- package/dist/components/secure-table/secure-table.d.ts.map +1 -0
- package/dist/components/secure-table/secure-table.js +567 -0
- package/dist/components/secure-table/secure-table.js.map +1 -0
- package/dist/components/secure-textarea/secure-textarea.css +153 -0
- package/dist/components/secure-textarea/secure-textarea.d.ts +111 -0
- package/dist/components/secure-textarea/secure-textarea.d.ts.map +1 -0
- package/dist/components/secure-textarea/secure-textarea.js +477 -0
- package/dist/components/secure-textarea/secure-textarea.js.map +1 -0
- package/dist/core/base-component.d.ts +134 -0
- package/dist/core/base-component.d.ts.map +1 -0
- package/dist/core/base-component.js +303 -0
- package/dist/core/base-component.js.map +1 -0
- package/dist/core/base.css +37 -0
- package/dist/core/security-config.d.ts +89 -0
- package/dist/core/security-config.d.ts.map +1 -0
- package/dist/core/security-config.js +273 -0
- package/dist/core/security-config.js.map +1 -0
- package/dist/core/types.d.ts +212 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +89 -0
- package/dist/styles/tokens.css +257 -0
- 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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"}
|