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,334 @@
1
+ /**
2
+ * Secure File Upload Component Styles
3
+ * Bulma-inspired design using design tokens for full customizability
4
+ */
5
+
6
+ /* Container */
7
+ .file-upload-container {
8
+ position: relative;
9
+ margin-bottom: var(--secure-ui-form-gap);
10
+ font-family: var(--secure-ui-font-family-base);
11
+ }
12
+
13
+ /* Label */
14
+ label {
15
+ display: block;
16
+ margin-bottom: var(--secure-ui-form-label-margin-bottom);
17
+ font-size: var(--secure-ui-label-font-size);
18
+ font-weight: var(--secure-ui-label-font-weight);
19
+ color: var(--secure-ui-label-color);
20
+ }
21
+
22
+ .label-suffix {
23
+ font-weight: var(--secure-ui-font-weight-normal);
24
+ color: var(--secure-ui-color-text-secondary);
25
+ font-size: var(--secure-ui-font-size-xs);
26
+ margin-left: var(--secure-ui-space-1);
27
+ }
28
+
29
+ .security-badge {
30
+ display: inline-block;
31
+ padding: var(--secure-ui-badge-padding);
32
+ margin-left: var(--secure-ui-space-2);
33
+ font-size: var(--secure-ui-badge-font-size);
34
+ font-weight: var(--secure-ui-font-weight-semibold);
35
+ border-radius: var(--secure-ui-badge-border-radius);
36
+ text-transform: uppercase;
37
+ background-color: var(--secure-ui-color-bg-tertiary);
38
+ color: var(--secure-ui-color-text-secondary);
39
+ border: var(--secure-ui-border-width-thin) solid var(--secure-ui-color-border);
40
+ }
41
+
42
+ /* Bulma-style file wrapper */
43
+ .drop-zone {
44
+ position: relative;
45
+ display: inline-flex;
46
+ align-items: stretch;
47
+ justify-content: flex-start;
48
+ border-radius: var(--secure-ui-input-border-radius);
49
+ cursor: pointer;
50
+ width: 100%;
51
+ }
52
+
53
+ .drop-zone.drag-over {
54
+ transform: scale(1.01);
55
+ }
56
+
57
+ .drop-zone.error .file-cta {
58
+ border-color: var(--secure-ui-color-error);
59
+ color: var(--secure-ui-color-error);
60
+ }
61
+
62
+ /* Hidden native file input — positioned over the entire drop zone */
63
+ .file-input {
64
+ position: absolute;
65
+ top: 0;
66
+ left: 0;
67
+ width: 100%;
68
+ height: 100%;
69
+ opacity: 0;
70
+ cursor: pointer;
71
+ z-index: 1;
72
+ }
73
+
74
+ /* Suppress the native (now invisible) focus ring on the input itself */
75
+ .file-input:focus {
76
+ outline: none;
77
+ }
78
+
79
+ /* Show a visible focus ring on the CTA when the hidden input has keyboard focus
80
+ (WCAG 2.4.7, 2.4.11) — also triggers on programmatic focus */
81
+ .drop-zone:focus-within .file-cta {
82
+ outline: 2px solid var(--secure-ui-color-border-focus);
83
+ outline-offset: 2px;
84
+ box-shadow: var(--secure-ui-shadow-focus);
85
+ }
86
+
87
+ @media (forced-colors: active) {
88
+ .drop-zone:focus-within .file-cta {
89
+ outline: 2px solid ButtonText;
90
+ box-shadow: none;
91
+ }
92
+ }
93
+
94
+ .drop-zone-content {
95
+ display: flex;
96
+ align-items: stretch;
97
+ width: 100%;
98
+ pointer-events: none;
99
+ }
100
+
101
+ /* Call-to-action button (Choose file) */
102
+ .file-cta {
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ padding: var(--secure-ui-input-padding-y) var(--secure-ui-input-padding-x);
107
+ background-color: var(--secure-ui-color-bg-secondary);
108
+ border: var(--secure-ui-input-border-width) solid var(--secure-ui-input-border-color);
109
+ border-radius: var(--secure-ui-input-border-radius);
110
+ color: var(--secure-ui-color-text-primary);
111
+ font-size: var(--secure-ui-input-font-size);
112
+ font-weight: var(--secure-ui-font-weight-medium);
113
+ white-space: nowrap;
114
+ transition: all var(--secure-ui-transition-base) var(--secure-ui-transition-ease-in-out);
115
+ }
116
+
117
+ .drop-zone:hover .file-cta {
118
+ background-color: var(--secure-ui-color-bg-tertiary);
119
+ border-color: var(--secure-ui-input-border-color-hover);
120
+ }
121
+
122
+ .drop-zone:active .file-cta {
123
+ background-color: var(--secure-ui-color-bg-tertiary);
124
+ }
125
+
126
+ .drop-zone.drag-over .file-cta {
127
+ background-color: var(--secure-ui-color-primary-light, rgba(102, 126, 234, 0.1));
128
+ border-color: var(--secure-ui-color-primary);
129
+ color: var(--secure-ui-color-primary);
130
+ }
131
+
132
+ /* Upload icon */
133
+ .drop-icon {
134
+ font-size: var(--secure-ui-font-size-base);
135
+ margin-right: var(--secure-ui-space-2);
136
+ display: flex;
137
+ align-items: center;
138
+ }
139
+
140
+ /* Button text */
141
+ .drop-text {
142
+ font-size: var(--secure-ui-font-size-base);
143
+ }
144
+
145
+ /* File name display area */
146
+ .file-name-display {
147
+ display: flex;
148
+ align-items: center;
149
+ flex: 1;
150
+ padding: var(--secure-ui-input-padding-y) var(--secure-ui-input-padding-x);
151
+ border: var(--secure-ui-input-border-width) solid var(--secure-ui-input-border-color);
152
+ border-left: none;
153
+ border-radius: 0 var(--secure-ui-input-border-radius) var(--secure-ui-input-border-radius) 0;
154
+ background-color: var(--secure-ui-input-bg);
155
+ color: var(--secure-ui-input-placeholder-color);
156
+ font-size: var(--secure-ui-input-font-size);
157
+ max-width: 100%;
158
+ overflow: hidden;
159
+ text-overflow: ellipsis;
160
+ white-space: nowrap;
161
+ }
162
+
163
+ .file-name-display.has-file {
164
+ color: var(--secure-ui-input-text-color);
165
+ }
166
+
167
+ /* When has file name, adjust CTA border radius */
168
+ .drop-zone-content.has-name .file-cta {
169
+ border-radius: var(--secure-ui-input-border-radius) 0 0 var(--secure-ui-input-border-radius);
170
+ border-right: none;
171
+ }
172
+
173
+ /* Accepted types hint */
174
+ .drop-hint {
175
+ display: block;
176
+ text-align: right;
177
+ margin-top: var(--secure-ui-space-1);
178
+ font-size: var(--secure-ui-font-size-xs);
179
+ color: var(--secure-ui-color-text-secondary);
180
+ }
181
+
182
+ /* Preview container for selected files */
183
+ .preview-container {
184
+ margin-top: var(--secure-ui-space-3);
185
+ }
186
+
187
+ .file-preview {
188
+ display: flex;
189
+ align-items: center;
190
+ padding: var(--secure-ui-space-3);
191
+ background-color: var(--secure-ui-color-bg-secondary);
192
+ border: var(--secure-ui-border-width-thin) solid var(--secure-ui-color-border);
193
+ border-radius: var(--secure-ui-input-border-radius);
194
+ margin-bottom: var(--secure-ui-space-2);
195
+ }
196
+
197
+ .file-name {
198
+ flex: 1;
199
+ font-size: var(--secure-ui-font-size-sm);
200
+ color: var(--secure-ui-color-text-primary);
201
+ overflow: hidden;
202
+ text-overflow: ellipsis;
203
+ white-space: nowrap;
204
+ }
205
+
206
+ .file-size {
207
+ font-size: var(--secure-ui-font-size-xs);
208
+ color: var(--secure-ui-color-text-secondary);
209
+ margin-left: var(--secure-ui-space-3);
210
+ white-space: nowrap;
211
+ }
212
+
213
+ .remove-file {
214
+ background: none;
215
+ border: none;
216
+ color: var(--secure-ui-color-error);
217
+ font-size: var(--secure-ui-font-size-base);
218
+ cursor: pointer;
219
+ padding: var(--secure-ui-space-1) var(--secure-ui-space-2);
220
+ margin-left: var(--secure-ui-space-2);
221
+ border-radius: var(--secure-ui-border-radius-sm);
222
+ transition: background-color var(--secure-ui-transition-base) var(--secure-ui-transition-ease-in-out);
223
+ pointer-events: auto;
224
+ }
225
+
226
+ .remove-file:hover {
227
+ background-color: var(--secure-ui-color-error-light, rgba(239, 68, 68, 0.1));
228
+ }
229
+
230
+ /* Error container */
231
+ .error-container {
232
+ position: absolute;
233
+ bottom: -1rem;
234
+ left: 0;
235
+ display: var(--error-display);
236
+ margin-top: var(--secure-ui-form-error-margin-top);
237
+ padding: var(--secure-ui-space-2) var(--secure-ui-space-3);
238
+ background-color: var(--secure-ui-color-error-light, rgba(239, 68, 68, 0.1));
239
+ border-radius: var(--secure-ui-border-radius-sm);
240
+ color: var(--secure-ui-color-error);
241
+ font-size: var(--secure-ui-error-font-size);
242
+ }
243
+
244
+ /* .error-container:not(.hidden) {
245
+ display: block;
246
+ } */
247
+
248
+ .error-container.hidden {
249
+ max-height: 0;
250
+ opacity: 0;
251
+ transform: translateY(-4px);
252
+ margin-top: 0;
253
+ }
254
+
255
+ /* Security Tier Styles */
256
+ /* :host([security-tier="public"]) .file-cta {
257
+ border-color: var(--secure-ui-tier-public);
258
+ }
259
+
260
+ :host([security-tier="public"]) .file-name-display {
261
+ border-color: var(--secure-ui-tier-public);
262
+ }
263
+
264
+ :host([security-tier="authenticated"]) .file-cta {
265
+ border-color: var(--secure-ui-tier-authenticated);
266
+ }
267
+
268
+ :host([security-tier="authenticated"]) .file-name-display {
269
+ border-color: var(--secure-ui-tier-authenticated);
270
+ }
271
+
272
+ :host([security-tier="authenticated"]) .security-badge {
273
+ background-color: rgba(33, 150, 243, 0.1);
274
+ color: var(--secure-ui-tier-authenticated);
275
+ border-color: var(--secure-ui-tier-authenticated);
276
+ }
277
+
278
+ :host([security-tier="sensitive"]) .file-cta {
279
+ border-color: var(--secure-ui-tier-sensitive);
280
+ }
281
+
282
+ :host([security-tier="sensitive"]) .file-name-display {
283
+ border-color: var(--secure-ui-tier-sensitive);
284
+ }
285
+
286
+ :host([security-tier="sensitive"]) .security-badge {
287
+ background-color: rgba(255, 152, 0, 0.1);
288
+ color: var(--secure-ui-tier-sensitive);
289
+ border-color: var(--secure-ui-tier-sensitive);
290
+ }
291
+
292
+ :host([security-tier="critical"]) .file-cta {
293
+ border-color: var(--secure-ui-tier-critical);
294
+ }
295
+
296
+ :host([security-tier="critical"]) .file-name-display {
297
+ border-color: var(--secure-ui-tier-critical);
298
+ }
299
+
300
+ :host([security-tier="critical"]) .security-badge {
301
+ background-color: rgba(244, 67, 54, 0.1);
302
+ color: var(--secure-ui-tier-critical);
303
+ border-color: var(--secure-ui-tier-critical);
304
+ } */
305
+
306
+ /* Disabled state */
307
+ .drop-zone.disabled {
308
+ opacity: var(--secure-ui-input-disabled-opacity);
309
+ cursor: not-allowed;
310
+ }
311
+
312
+ .drop-zone.disabled .file-cta {
313
+ background-color: var(--secure-ui-input-disabled-bg);
314
+ }
315
+
316
+ /* Scanning state */
317
+ .drop-zone.scanning {
318
+ pointer-events: none;
319
+ opacity: 0.7;
320
+ }
321
+
322
+ .drop-zone.scanning .file-cta {
323
+ border-color: var(--secure-ui-color-primary);
324
+ }
325
+
326
+ .drop-zone.scanning .file-name-display {
327
+ color: var(--secure-ui-color-primary);
328
+ animation: scanning-pulse 1.2s ease-in-out infinite;
329
+ }
330
+
331
+ @keyframes scanning-pulse {
332
+ 0%, 100% { opacity: 0.5; }
333
+ 50% { opacity: 1; }
334
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @fileoverview Secure File Upload Component
3
+ *
4
+ * A security-first file upload component that implements progressive enhancement,
5
+ * file type validation, size limits, scan hook integration, and audit logging.
6
+ *
7
+ * Progressive Enhancement Strategy:
8
+ * 1. Without JavaScript: Falls back to native HTML5 file input
9
+ * 2. With JavaScript: Enhances with validation, preview, drag-drop, security checks
10
+ *
11
+ * Usage:
12
+ * <secure-file-upload
13
+ * security-tier="sensitive"
14
+ * name="document"
15
+ * label="Upload Document"
16
+ * accept=".pdf,.doc,.docx"
17
+ * max-size="5242880"
18
+ * required
19
+ * ></secure-file-upload>
20
+ *
21
+ * Security Features:
22
+ * - File type validation (MIME type and extension)
23
+ * - File size limits based on security tier
24
+ * - Scan hook integration (e.g. server-side malware scanning)
25
+ * - Content validation before upload
26
+ * - Rate limiting on uploads
27
+ * - Comprehensive audit logging
28
+ * - Drag-and-drop with validation
29
+ * - Preview for safe file types
30
+ *
31
+ * @module secure-file-upload
32
+ * @license MIT
33
+ */
34
+ import { SecureBaseComponent } from '../../core/base-component.js';
35
+ /**
36
+ * Result returned by a scan hook function.
37
+ */
38
+ export interface ScanHookResult {
39
+ /** Whether the file passed the scan */
40
+ valid: boolean;
41
+ /** Reason for rejection (when valid is false) */
42
+ reason?: string;
43
+ }
44
+ /**
45
+ * A function that scans a file and returns a pass/fail result.
46
+ * Typically sends the file to a server-side scanning endpoint.
47
+ */
48
+ export type ScanHookFn = (file: File) => Promise<ScanHookResult>;
49
+ /**
50
+ * Secure File Upload Web Component
51
+ *
52
+ * Provides a security-hardened file upload field with progressive enhancement.
53
+ * The component works as a standard file input without JavaScript and
54
+ * enhances with security features when JavaScript is available.
55
+ *
56
+ * @extends SecureBaseComponent
57
+ */
58
+ export declare class SecureFileUpload extends SecureBaseComponent {
59
+ #private;
60
+ /**
61
+ * Observed attributes for this component
62
+ *
63
+ * @static
64
+ */
65
+ static get observedAttributes(): string[];
66
+ /**
67
+ * Constructor
68
+ */
69
+ constructor();
70
+ /**
71
+ * Render the file upload component
72
+ *
73
+ * Security Note: We use a native <input type="file"> element wrapped in our
74
+ * web component to ensure progressive enhancement. The native input works
75
+ * without JavaScript, and we enhance it with security features when JS is available.
76
+ *
77
+ * @protected
78
+ */
79
+ protected render(): DocumentFragment | HTMLElement | null;
80
+ /**
81
+ * Handle attribute changes
82
+ *
83
+ * @protected
84
+ */
85
+ protected handleAttributeChange(name: string, _oldValue: string | null, newValue: string | null): void;
86
+ /**
87
+ * Get selected files
88
+ *
89
+ * @public
90
+ */
91
+ get files(): FileList | null;
92
+ /**
93
+ * Get the input name
94
+ *
95
+ * @public
96
+ */
97
+ get name(): string;
98
+ /**
99
+ * Check if the upload is valid
100
+ *
101
+ * @public
102
+ */
103
+ get valid(): boolean;
104
+ /**
105
+ * Clear selected files
106
+ *
107
+ * @public
108
+ */
109
+ clear(): void;
110
+ /**
111
+ * Register a scan hook function for server-side file scanning.
112
+ *
113
+ * The hook receives each selected file and must return a Promise that
114
+ * resolves to `{ valid: boolean; reason?: string }`. When `valid` is
115
+ * false the file is rejected and the reason is shown to the user.
116
+ *
117
+ * @example
118
+ * ```js
119
+ * const upload = document.querySelector('secure-file-upload');
120
+ * upload.setScanHook(async (file) => {
121
+ * const form = new FormData();
122
+ * form.append('file', file);
123
+ * const res = await fetch('/api/scan', { method: 'POST', body: form });
124
+ * const { clean, threat } = await res.json();
125
+ * return { valid: clean, reason: threat };
126
+ * });
127
+ * ```
128
+ *
129
+ * @public
130
+ */
131
+ setScanHook(hook: ScanHookFn): void;
132
+ /**
133
+ * Check whether a scan hook is registered
134
+ *
135
+ * @public
136
+ */
137
+ get hasScanHook(): boolean;
138
+ /**
139
+ * Check whether a scan is currently in progress
140
+ *
141
+ * @public
142
+ */
143
+ get scanning(): boolean;
144
+ /**
145
+ * Cleanup on disconnect
146
+ */
147
+ disconnectedCallback(): void;
148
+ }
149
+ export default SecureFileUpload;
150
+ //# sourceMappingURL=secure-file-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-file-upload.d.ts","sourceRoot":"","sources":["../../../src/components/secure-file-upload/secure-file-upload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGnE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEjE;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,SAAQ,mBAAmB;;IAyEvD;;;;OAIG;IACH,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAWxC;IAED;;OAEG;;IAKH;;;;;;;;OAQG;IACH,SAAS,CAAC,MAAM,IAAI,gBAAgB,GAAG,WAAW,GAAG,IAAI;IAkvBzD;;;;OAIG;IACH,SAAS,CAAC,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IActG;;;;OAIG;IACH,IAAI,KAAK,IAAI,QAAQ,GAAG,IAAI,CAE3B;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,IAAI,KAAK,IAAI,OAAO,CAQnB;IAED;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAUnC;;;;OAIG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAS7B;AAKD,eAAe,gBAAgB,CAAC"}