secure-ui-components 0.2.2 → 0.2.4

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 (28) hide show
  1. package/dist/components/secure-card/secure-card.js +1 -766
  2. package/dist/components/secure-datetime/secure-datetime.js +1 -570
  3. package/dist/components/secure-file-upload/secure-file-upload.js +1 -868
  4. package/dist/components/secure-form/secure-form.js +1 -797
  5. package/dist/components/secure-input/secure-input.css +67 -1
  6. package/dist/components/secure-input/secure-input.d.ts +14 -0
  7. package/dist/components/secure-input/secure-input.d.ts.map +1 -1
  8. package/dist/components/secure-input/secure-input.js +1 -805
  9. package/dist/components/secure-input/secure-input.js.map +1 -1
  10. package/dist/components/secure-password-confirm/secure-password-confirm.js +1 -329
  11. package/dist/components/secure-select/secure-select.js +1 -589
  12. package/dist/components/secure-submit-button/secure-submit-button.js +1 -378
  13. package/dist/components/secure-table/secure-table.js +33 -528
  14. package/dist/components/secure-telemetry-provider/secure-telemetry-provider.js +1 -201
  15. package/dist/components/secure-textarea/secure-textarea.css +66 -1
  16. package/dist/components/secure-textarea/secure-textarea.d.ts +11 -0
  17. package/dist/components/secure-textarea/secure-textarea.d.ts.map +1 -1
  18. package/dist/components/secure-textarea/secure-textarea.js +1 -436
  19. package/dist/components/secure-textarea/secure-textarea.js.map +1 -1
  20. package/dist/core/base-component.d.ts +18 -0
  21. package/dist/core/base-component.d.ts.map +1 -1
  22. package/dist/core/base-component.js +1 -455
  23. package/dist/core/base-component.js.map +1 -1
  24. package/dist/core/security-config.js +1 -242
  25. package/dist/core/types.js +0 -2
  26. package/dist/index.js +1 -17
  27. package/dist/package.json +4 -2
  28. package/package.json +4 -2
@@ -29,573 +29,4 @@
29
29
  *
30
30
  * @module secure-datetime
31
31
  * @license MIT
32
- */
33
- import { SecureBaseComponent } from '../../core/base-component.js';
34
- import { SecurityTier } from '../../core/security-config.js';
35
- /**
36
- * Secure DateTime Web Component
37
- *
38
- * Provides a security-hardened date/time picker with progressive enhancement.
39
- * The component works as a standard HTML5 date/time input without JavaScript and
40
- * enhances with security features when JavaScript is available.
41
- *
42
- * @extends SecureBaseComponent
43
- */
44
- export class SecureDateTime extends SecureBaseComponent {
45
- /**
46
- * Input element reference
47
- * @private
48
- */
49
- #inputElement = null;
50
- /**
51
- * Label element reference
52
- * @private
53
- */
54
- #labelElement = null;
55
- /**
56
- * Error container element reference
57
- * @private
58
- */
59
- #errorContainer = null;
60
- /**
61
- * Timezone display element
62
- * @private
63
- */
64
- #timezoneElement = null;
65
- /**
66
- * Unique ID for this datetime instance
67
- * @private
68
- */
69
- #instanceId = `secure-datetime-${Math.random().toString(36).substring(2, 11)}`;
70
- /**
71
- * Observed attributes for this component
72
- *
73
- * @static
74
- */
75
- static get observedAttributes() {
76
- return [
77
- ...super.observedAttributes,
78
- 'name',
79
- 'type',
80
- 'label',
81
- 'required',
82
- 'min',
83
- 'max',
84
- 'step',
85
- 'value',
86
- 'show-timezone'
87
- ];
88
- }
89
- /**
90
- * Constructor
91
- */
92
- constructor() {
93
- super();
94
- }
95
- /**
96
- * Render the datetime component
97
- *
98
- * Security Note: We use native HTML5 date/time inputs wrapped in our web component
99
- * to ensure progressive enhancement and browser-native date validation.
100
- *
101
- * @protected
102
- */
103
- render() {
104
- const fragment = document.createDocumentFragment();
105
- const container = document.createElement('div');
106
- container.className = 'datetime-container';
107
- container.setAttribute('part', 'container');
108
- // Create label
109
- const label = this.getAttribute('label');
110
- if (label) {
111
- this.#labelElement = document.createElement('label');
112
- this.#labelElement.htmlFor = this.#instanceId;
113
- this.#labelElement.textContent = this.sanitizeValue(label);
114
- this.#labelElement.setAttribute('part', 'label');
115
- container.appendChild(this.#labelElement);
116
- }
117
- // Create input wrapper
118
- const inputWrapper = document.createElement('div');
119
- inputWrapper.className = 'input-wrapper';
120
- inputWrapper.setAttribute('part', 'wrapper');
121
- // Create the datetime input element
122
- this.#inputElement = document.createElement('input');
123
- this.#inputElement.id = this.#instanceId;
124
- this.#inputElement.className = 'datetime-field';
125
- this.#inputElement.setAttribute('part', 'input');
126
- // Apply attributes
127
- this.#applyDateTimeAttributes();
128
- // Set up event listeners
129
- this.#attachEventListeners();
130
- inputWrapper.appendChild(this.#inputElement);
131
- // Add timezone display if requested
132
- if (this.hasAttribute('show-timezone')) {
133
- this.#timezoneElement = document.createElement('span');
134
- this.#timezoneElement.className = 'timezone-display';
135
- this.#timezoneElement.textContent = this.#getTimezoneString();
136
- inputWrapper.appendChild(this.#timezoneElement);
137
- }
138
- container.appendChild(inputWrapper);
139
- // Create error container
140
- // role="alert" already implies aria-live="assertive" — do not override with polite
141
- this.#errorContainer = document.createElement('div');
142
- this.#errorContainer.className = 'error-container hidden';
143
- this.#errorContainer.setAttribute('role', 'alert');
144
- this.#errorContainer.setAttribute('part', 'error');
145
- this.#errorContainer.id = `${this.#instanceId}-error`;
146
- container.appendChild(this.#errorContainer);
147
- // Add component styles (CSP-compliant via adoptedStyleSheets)
148
- this.addComponentStyles(this.#getComponentStyles());
149
- fragment.appendChild(container);
150
- return fragment;
151
- }
152
- /**
153
- * Apply attributes to the datetime input
154
- *
155
- * @private
156
- */
157
- #applyDateTimeAttributes() {
158
- const config = this.config;
159
- // Name attribute
160
- const name = this.getAttribute('name');
161
- if (name) {
162
- this.#inputElement.name = this.sanitizeValue(name);
163
- }
164
- // Accessible name fallback when no visible label is provided
165
- if (!this.getAttribute('label') && name) {
166
- this.#inputElement.setAttribute('aria-label', this.sanitizeValue(name));
167
- }
168
- // Link input to its error container for screen readers
169
- this.#inputElement.setAttribute('aria-describedby', `${this.#instanceId}-error`);
170
- // Timezone element label
171
- if (this.#timezoneElement) {
172
- this.#timezoneElement.setAttribute('aria-label', `Timezone: ${this.#getTimezoneString()}`);
173
- }
174
- // Type attribute (date, time, datetime-local, month, week)
175
- const type = this.getAttribute('type') || 'date';
176
- const validTypes = ['date', 'time', 'datetime-local', 'month', 'week'];
177
- if (validTypes.includes(type)) {
178
- this.#inputElement.type = type;
179
- }
180
- else {
181
- console.warn(`Invalid datetime type "${type}", defaulting to "date"`);
182
- this.#inputElement.type = 'date';
183
- }
184
- // Required attribute
185
- if (this.hasAttribute('required') || config.validation.required) {
186
- this.#inputElement.required = true;
187
- this.#inputElement.setAttribute('aria-required', 'true');
188
- }
189
- // Min/max constraints
190
- const min = this.getAttribute('min');
191
- if (min) {
192
- this.#inputElement.min = this.#validateDateTimeValue(min);
193
- }
194
- const max = this.getAttribute('max');
195
- if (max) {
196
- this.#inputElement.max = this.#validateDateTimeValue(max);
197
- }
198
- // Step attribute
199
- const step = this.getAttribute('step');
200
- if (step) {
201
- this.#inputElement.step = step;
202
- }
203
- // Disabled state
204
- if (this.hasAttribute('disabled')) {
205
- this.#inputElement.disabled = true;
206
- }
207
- // Readonly state
208
- if (this.hasAttribute('readonly')) {
209
- this.#inputElement.readOnly = true;
210
- }
211
- // Autocomplete control
212
- if (!config.storage.allowAutocomplete) {
213
- this.#inputElement.autocomplete = 'off';
214
- }
215
- // Initial value
216
- const value = this.getAttribute('value');
217
- if (value) {
218
- this.#inputElement.value = this.#validateDateTimeValue(value);
219
- }
220
- }
221
- /**
222
- * Validate and sanitize datetime value
223
- *
224
- * Security Note: Prevent injection of invalid date formats
225
- *
226
- * @private
227
- */
228
- #validateDateTimeValue(value) {
229
- if (!value)
230
- return '';
231
- // Basic format validation based on input type
232
- const type = this.#inputElement?.type || this.getAttribute('type') || 'date';
233
- const patterns = {
234
- 'date': /^\d{4}-\d{2}-\d{2}$/,
235
- // eslint-disable-next-line security/detect-unsafe-regex
236
- 'time': /^\d{2}:\d{2}(:\d{2})?$/,
237
- // eslint-disable-next-line security/detect-unsafe-regex
238
- 'datetime-local': /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$/,
239
- 'month': /^\d{4}-\d{2}$/,
240
- 'week': /^\d{4}-W\d{2}$/
241
- };
242
- const pattern = Object.hasOwn(patterns, type) ? patterns[type] : undefined;
243
- if (pattern && !pattern.test(value)) {
244
- console.warn(`Invalid ${type} format: ${value}`);
245
- return '';
246
- }
247
- return value;
248
- }
249
- /**
250
- * Get timezone string for display
251
- *
252
- * @private
253
- */
254
- #getTimezoneString() {
255
- const offset = new Date().getTimezoneOffset();
256
- const hours = Math.abs(Math.floor(offset / 60));
257
- const minutes = Math.abs(offset % 60);
258
- const sign = offset <= 0 ? '+' : '-';
259
- return `UTC${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
260
- }
261
- /**
262
- * Attach event listeners
263
- *
264
- * @private
265
- */
266
- #attachEventListeners() {
267
- // Focus event - audit logging + telemetry
268
- this.#inputElement.addEventListener('focus', () => {
269
- this.recordTelemetryFocus();
270
- this.audit('datetime_focused', {
271
- name: this.#inputElement.name,
272
- type: this.#inputElement.type
273
- });
274
- });
275
- // Input event - real-time validation + telemetry
276
- this.#inputElement.addEventListener('input', (e) => {
277
- this.recordTelemetryInput(e);
278
- this.#handleInput(e);
279
- });
280
- // Change event - validation and audit
281
- this.#inputElement.addEventListener('change', (e) => {
282
- this.#handleChange(e);
283
- });
284
- // Blur event - final validation + telemetry
285
- this.#inputElement.addEventListener('blur', () => {
286
- this.recordTelemetryBlur();
287
- this.#validateAndShowErrors();
288
- this.audit('datetime_blurred', {
289
- name: this.#inputElement.name,
290
- hasValue: this.#inputElement.value.length > 0
291
- });
292
- });
293
- }
294
- /**
295
- * Handle input events
296
- *
297
- * @private
298
- */
299
- #handleInput(_event) {
300
- // Clear previous errors on input
301
- this.#clearErrors();
302
- // Dispatch custom event for parent forms
303
- this.dispatchEvent(new CustomEvent('secure-datetime', {
304
- detail: {
305
- name: this.#inputElement.name,
306
- value: this.#inputElement.value,
307
- type: this.#inputElement.type,
308
- tier: this.securityTier
309
- },
310
- bubbles: true,
311
- composed: true
312
- }));
313
- }
314
- /**
315
- * Handle change events
316
- *
317
- * @private
318
- */
319
- #handleChange(_event) {
320
- const value = this.#inputElement.value;
321
- // Validate the value
322
- const isValid = this.#validateDateTimeValue(value);
323
- if (!isValid && value) {
324
- this.#showError('Invalid date/time format');
325
- return;
326
- }
327
- // Clear errors
328
- this.#clearErrors();
329
- // Audit log
330
- this.audit('datetime_changed', {
331
- name: this.#inputElement.name,
332
- type: this.#inputElement.type,
333
- value: value
334
- });
335
- }
336
- /**
337
- * Validate the datetime and show error messages
338
- *
339
- * @private
340
- */
341
- #validateAndShowErrors() {
342
- // Check rate limit first
343
- const rateLimitCheck = this.checkRateLimit();
344
- if (!rateLimitCheck.allowed) {
345
- this.#showError(`Too many attempts. Please wait ${Math.ceil(rateLimitCheck.retryAfter / 1000)} seconds.`);
346
- return;
347
- }
348
- const value = this.#inputElement.value;
349
- // Check required
350
- if (this.#inputElement.required && !value) {
351
- this.#showError('This field is required');
352
- return;
353
- }
354
- // Check min/max constraints
355
- if (value) {
356
- if (this.#inputElement.min && value < this.#inputElement.min) {
357
- this.#showError(`Value must be after ${this.#formatDateForDisplay(this.#inputElement.min)}`);
358
- return;
359
- }
360
- if (this.#inputElement.max && value > this.#inputElement.max) {
361
- this.#showError(`Value must be before ${this.#formatDateForDisplay(this.#inputElement.max)}`);
362
- return;
363
- }
364
- }
365
- // Additional validation for CRITICAL tier
366
- if (this.securityTier === SecurityTier.CRITICAL && value) {
367
- const date = new Date(value);
368
- // Ensure date is valid
369
- if (isNaN(date.getTime())) {
370
- this.#showError('Invalid date/time');
371
- return;
372
- }
373
- // Prevent dates too far in the past or future (potential attack)
374
- const year = date.getFullYear();
375
- if (year < 1900 || year > 2100) {
376
- this.#showError('Date must be between 1900 and 2100');
377
- return;
378
- }
379
- }
380
- }
381
- /**
382
- * Format date for display in error messages
383
- *
384
- * @private
385
- */
386
- #formatDateForDisplay(dateString) {
387
- try {
388
- const date = new Date(dateString);
389
- return date.toLocaleString();
390
- }
391
- catch (_e) {
392
- return dateString;
393
- }
394
- }
395
- /**
396
- * Show error message
397
- *
398
- * @private
399
- */
400
- #showError(message) {
401
- this.#errorContainer.textContent = message;
402
- // Force reflow so browser registers the hidden state with content,
403
- // then remove hidden to trigger the CSS transition
404
- void this.#errorContainer.offsetHeight;
405
- this.#errorContainer.classList.remove('hidden');
406
- this.#inputElement.classList.add('error');
407
- this.#inputElement.setAttribute('aria-invalid', 'true');
408
- }
409
- /**
410
- * Clear error messages
411
- *
412
- * @private
413
- */
414
- #clearErrors() {
415
- // Start the hide animation first, clear text only after transition ends
416
- this.#errorContainer.classList.add('hidden');
417
- this.#errorContainer.addEventListener('transitionend', () => {
418
- if (this.#errorContainer.classList.contains('hidden')) {
419
- this.#errorContainer.textContent = '';
420
- }
421
- }, { once: true });
422
- this.#inputElement.classList.remove('error');
423
- this.#inputElement.removeAttribute('aria-invalid');
424
- }
425
- /**
426
- * Get component-specific styles
427
- *
428
- * @private
429
- */
430
- #getComponentStyles() {
431
- return new URL('./secure-datetime.css', import.meta.url).href;
432
- }
433
- /**
434
- * Handle attribute changes
435
- *
436
- * @protected
437
- */
438
- handleAttributeChange(name, _oldValue, newValue) {
439
- if (!this.#inputElement)
440
- return;
441
- switch (name) {
442
- case 'disabled':
443
- this.#inputElement.disabled = this.hasAttribute('disabled');
444
- break;
445
- case 'readonly':
446
- this.#inputElement.readOnly = this.hasAttribute('readonly');
447
- break;
448
- case 'value':
449
- if (newValue !== this.#inputElement.value) {
450
- this.#inputElement.value = this.#validateDateTimeValue(newValue || '');
451
- }
452
- break;
453
- case 'min':
454
- this.#inputElement.min = this.#validateDateTimeValue(newValue || '');
455
- break;
456
- case 'max':
457
- this.#inputElement.max = this.#validateDateTimeValue(newValue || '');
458
- break;
459
- }
460
- }
461
- /**
462
- * Get the current value
463
- *
464
- * @public
465
- */
466
- get value() {
467
- return this.#inputElement ? this.#inputElement.value : '';
468
- }
469
- /**
470
- * Set the value
471
- *
472
- * @public
473
- */
474
- set value(value) {
475
- if (this.#inputElement) {
476
- this.#inputElement.value = this.#validateDateTimeValue(value || '');
477
- }
478
- }
479
- /**
480
- * Get the input name
481
- *
482
- * @public
483
- */
484
- get name() {
485
- return this.#inputElement ? this.#inputElement.name : '';
486
- }
487
- /**
488
- * Get value as Date object
489
- *
490
- * @public
491
- */
492
- getValueAsDate() {
493
- if (!this.#inputElement || !this.#inputElement.value) {
494
- return null;
495
- }
496
- try {
497
- const date = new Date(this.#inputElement.value);
498
- return isNaN(date.getTime()) ? null : date;
499
- }
500
- catch (_e) {
501
- return null;
502
- }
503
- }
504
- /**
505
- * Set value from Date object
506
- *
507
- * @public
508
- */
509
- setValueFromDate(date) {
510
- if (!this.#inputElement || !(date instanceof Date)) {
511
- return;
512
- }
513
- const type = this.#inputElement.type;
514
- let value = '';
515
- switch (type) {
516
- case 'date':
517
- value = date.toISOString().split('T')[0];
518
- break;
519
- case 'time':
520
- value = date.toTimeString().slice(0, 5);
521
- break;
522
- case 'datetime-local':
523
- value = date.toISOString().slice(0, 16);
524
- break;
525
- case 'month':
526
- value = date.toISOString().slice(0, 7);
527
- break;
528
- case 'week':
529
- // ISO week calculation
530
- const weekDate = new Date(date);
531
- weekDate.setHours(0, 0, 0, 0);
532
- weekDate.setDate(weekDate.getDate() + 4 - (weekDate.getDay() || 7));
533
- const yearStart = new Date(weekDate.getFullYear(), 0, 1);
534
- const weekNo = Math.ceil((((weekDate.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
535
- value = `${weekDate.getFullYear()}-W${weekNo.toString().padStart(2, '0')}`;
536
- break;
537
- }
538
- this.#inputElement.value = value;
539
- }
540
- /**
541
- * Check if the datetime is valid
542
- *
543
- * @public
544
- */
545
- get valid() {
546
- const required = this.hasAttribute('required') || this.config.validation.required;
547
- const value = this.#inputElement ? this.#inputElement.value : '';
548
- // Check required
549
- if (required && !value) {
550
- return false;
551
- }
552
- // Check format
553
- if (value && !this.#validateDateTimeValue(value)) {
554
- return false;
555
- }
556
- // Check min/max
557
- if (this.#inputElement) {
558
- if (this.#inputElement.min && value < this.#inputElement.min) {
559
- return false;
560
- }
561
- if (this.#inputElement.max && value > this.#inputElement.max) {
562
- return false;
563
- }
564
- }
565
- return true;
566
- }
567
- /**
568
- * Focus the input
569
- *
570
- * @public
571
- */
572
- focus() {
573
- if (this.#inputElement) {
574
- this.#inputElement.focus();
575
- }
576
- }
577
- /**
578
- * Blur the input
579
- *
580
- * @public
581
- */
582
- blur() {
583
- if (this.#inputElement) {
584
- this.#inputElement.blur();
585
- }
586
- }
587
- /**
588
- * Cleanup on disconnect
589
- */
590
- disconnectedCallback() {
591
- super.disconnectedCallback();
592
- // Clear value
593
- if (this.#inputElement) {
594
- this.#inputElement.value = '';
595
- }
596
- }
597
- }
598
- // Define the custom element
599
- customElements.define('secure-datetime', SecureDateTime);
600
- export default SecureDateTime;
601
- //# sourceMappingURL=secure-datetime.js.map
32
+ */import{SecureBaseComponent as u}from"../../core/base-component.js";import{SecurityTier as d}from"../../core/security-config.js";class l extends u{#t=null;#r=null;#e=null;#a=null;#n=`secure-datetime-${Math.random().toString(36).substring(2,11)}`;static get observedAttributes(){return[...super.observedAttributes,"name","type","label","required","min","max","step","value","show-timezone"]}constructor(){super()}render(){const t=document.createDocumentFragment(),e=document.createElement("div");e.className="datetime-container",e.setAttribute("part","container");const i=this.getAttribute("label");i&&(this.#r=document.createElement("label"),this.#r.htmlFor=this.#n,this.#r.textContent=this.sanitizeValue(i),this.#r.setAttribute("part","label"),e.appendChild(this.#r));const s=document.createElement("div");return s.className="input-wrapper",s.setAttribute("part","wrapper"),this.#t=document.createElement("input"),this.#t.id=this.#n,this.#t.className="datetime-field",this.#t.setAttribute("part","input"),this.#d(),this.#o(),s.appendChild(this.#t),this.hasAttribute("show-timezone")&&(this.#a=document.createElement("span"),this.#a.className="timezone-display",this.#a.textContent=this.#h(),s.appendChild(this.#a)),e.appendChild(s),this.#e=document.createElement("div"),this.#e.className="error-container hidden",this.#e.setAttribute("role","alert"),this.#e.setAttribute("part","error"),this.#e.id=`${this.#n}-error`,e.appendChild(this.#e),this.addComponentStyles(this.#b()),t.appendChild(e),t}#d(){const t=this.config,e=this.getAttribute("name");e&&(this.#t.name=this.sanitizeValue(e)),!this.getAttribute("label")&&e&&this.#t.setAttribute("aria-label",this.sanitizeValue(e)),this.#t.setAttribute("aria-describedby",`${this.#n}-error`),this.#a&&this.#a.setAttribute("aria-label",`Timezone: ${this.#h()}`);const i=this.getAttribute("type")||"date";["date","time","datetime-local","month","week"].includes(i)?this.#t.type=i:(console.warn(`Invalid datetime type "${i}", defaulting to "date"`),this.#t.type="date"),(this.hasAttribute("required")||t.validation.required)&&(this.#t.required=!0,this.#t.setAttribute("aria-required","true"));const a=this.getAttribute("min");a&&(this.#t.min=this.#i(a));const r=this.getAttribute("max");r&&(this.#t.max=this.#i(r));const n=this.getAttribute("step");n&&(this.#t.step=n),this.hasAttribute("disabled")&&(this.#t.disabled=!0),this.hasAttribute("readonly")&&(this.#t.readOnly=!0),t.storage.allowAutocomplete||(this.#t.autocomplete="off");const h=this.getAttribute("value");h&&(this.#t.value=this.#i(h))}#i(t){if(!t)return"";const e=this.#t?.type||this.getAttribute("type")||"date",i={date:/^\d{4}-\d{2}-\d{2}$/,time:/^\d{2}:\d{2}(:\d{2})?$/,"datetime-local":/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$/,month:/^\d{4}-\d{2}$/,week:/^\d{4}-W\d{2}$/},s=Object.hasOwn(i,e)?i[e]:void 0;return s&&!s.test(t)?(console.warn(`Invalid ${e} format: ${t}`),""):t}#h(){const t=new Date().getTimezoneOffset(),e=Math.abs(Math.floor(t/60)),i=Math.abs(t%60);return`UTC${t<=0?"+":"-"}${e.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`}#o(){this.#t.addEventListener("focus",()=>{this.recordTelemetryFocus(),this.audit("datetime_focused",{name:this.#t.name,type:this.#t.type})}),this.#t.addEventListener("input",t=>{this.recordTelemetryInput(t),this.#c(t)}),this.#t.addEventListener("change",t=>{this.#m(t)}),this.#t.addEventListener("blur",()=>{this.recordTelemetryBlur(),this.#p(),this.audit("datetime_blurred",{name:this.#t.name,hasValue:this.#t.value.length>0})})}#c(t){this.#u(),this.dispatchEvent(new CustomEvent("secure-datetime",{detail:{name:this.#t.name,value:this.#t.value,type:this.#t.type,tier:this.securityTier},bubbles:!0,composed:!0}))}#m(t){const e=this.#t.value;if(!this.#i(e)&&e){this.#s("Invalid date/time format");return}this.#u(),this.audit("datetime_changed",{name:this.#t.name,type:this.#t.type,value:e})}#p(){const t=this.checkRateLimit();if(!t.allowed){this.#s(`Too many attempts. Please wait ${Math.ceil(t.retryAfter/1e3)} seconds.`);return}const e=this.#t.value;if(this.#t.required&&!e){this.#s("This field is required");return}if(e){if(this.#t.min&&e<this.#t.min){this.#s(`Value must be after ${this.#l(this.#t.min)}`);return}if(this.#t.max&&e>this.#t.max){this.#s(`Value must be before ${this.#l(this.#t.max)}`);return}}if(this.securityTier===d.CRITICAL&&e){const i=new Date(e);if(isNaN(i.getTime())){this.#s("Invalid date/time");return}const s=i.getFullYear();if(s<1900||s>2100){this.#s("Date must be between 1900 and 2100");return}}}#l(t){try{return new Date(t).toLocaleString()}catch{return t}}#s(t){this.#e.textContent=t,this.#e.offsetHeight,this.#e.classList.remove("hidden"),this.#t.classList.add("error"),this.#t.setAttribute("aria-invalid","true")}#u(){this.#e.classList.add("hidden"),this.#e.addEventListener("transitionend",()=>{this.#e.classList.contains("hidden")&&(this.#e.textContent="")},{once:!0}),this.#t.classList.remove("error"),this.#t.removeAttribute("aria-invalid")}#b(){return new URL("./secure-datetime.css",import.meta.url).href}handleAttributeChange(t,e,i){if(this.#t)switch(t){case"disabled":this.#t.disabled=this.hasAttribute("disabled");break;case"readonly":this.#t.readOnly=this.hasAttribute("readonly");break;case"value":i!==this.#t.value&&(this.#t.value=this.#i(i||""));break;case"min":this.#t.min=this.#i(i||"");break;case"max":this.#t.max=this.#i(i||"");break}}get value(){return this.#t?this.#t.value:""}set value(t){this.#t&&(this.#t.value=this.#i(t||""))}get name(){return this.#t?this.#t.name:""}getValueAsDate(){if(!this.#t||!this.#t.value)return null;try{const t=new Date(this.#t.value);return isNaN(t.getTime())?null:t}catch{return null}}setValueFromDate(t){if(!this.#t||!(t instanceof Date))return;const e=this.#t.type;let i="";switch(e){case"date":i=t.toISOString().split("T")[0];break;case"time":i=t.toTimeString().slice(0,5);break;case"datetime-local":i=t.toISOString().slice(0,16);break;case"month":i=t.toISOString().slice(0,7);break;case"week":const s=new Date(t);s.setHours(0,0,0,0),s.setDate(s.getDate()+4-(s.getDay()||7));const a=new Date(s.getFullYear(),0,1),r=Math.ceil(((s.getTime()-a.getTime())/864e5+1)/7);i=`${s.getFullYear()}-W${r.toString().padStart(2,"0")}`;break}this.#t.value=i}get valid(){const t=this.hasAttribute("required")||this.config.validation.required,e=this.#t?this.#t.value:"";return!(t&&!e||e&&!this.#i(e)||this.#t&&(this.#t.min&&e<this.#t.min||this.#t.max&&e>this.#t.max))}focus(){this.#t&&this.#t.focus()}blur(){this.#t&&this.#t.blur()}disconnectedCallback(){super.disconnectedCallback(),this.#t&&(this.#t.value="")}}customElements.define("secure-datetime",l);var p=l;export{l as SecureDateTime,p as default};