@forcecalendar/interface 1.0.26 → 1.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcecalendar/interface",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "type": "module",
5
5
  "description": "Official interface layer for forceCalendar Core - Enterprise calendar components",
6
6
  "main": "dist/force-calendar-interface.umd.js",
@@ -50,8 +50,10 @@
50
50
  "@babel/core": "^7.28.5",
51
51
  "@babel/preset-env": "^7.28.5",
52
52
  "babel-jest": "^30.2.0",
53
+ "eslint": "^8.57.1",
53
54
  "jest": "^30.2.0",
54
55
  "jest-environment-jsdom": "^30.2.0",
56
+ "prettier": "^3.8.1",
55
57
  "vite": "^5.0.0"
56
58
  }
57
59
  }
@@ -3,47 +3,47 @@ import { StyleUtils } from '../utils/StyleUtils.js';
3
3
  import { DOMUtils } from '../utils/DOMUtils.js';
4
4
 
5
5
  export class EventForm extends BaseComponent {
6
- constructor() {
7
- super();
8
- this._isVisible = false;
9
- this._cleanupFocusTrap = null;
10
- this.config = {
11
- title: 'New Event',
12
- defaultDuration: 60, // minutes
13
- colors: [
14
- { color: '#2563EB', label: 'Blue' },
15
- { color: '#10B981', label: 'Green' },
16
- { color: '#F59E0B', label: 'Amber' },
17
- { color: '#EF4444', label: 'Red' },
18
- { color: '#8B5CF6', label: 'Purple' },
19
- { color: '#6B7280', label: 'Gray' }
20
- ]
21
- };
22
- this._formData = {
23
- title: '',
24
- start: new Date(),
25
- end: new Date(),
26
- allDay: false,
27
- color: this.config.colors[0].color
28
- };
29
- }
30
-
31
- static get observedAttributes() {
32
- return ['open'];
33
- }
34
-
35
- attributeChangedCallback(name, oldValue, newValue) {
36
- if (name === 'open') {
37
- if (newValue !== null) {
38
- this.open();
39
- } else {
40
- this.close();
41
- }
42
- }
6
+ constructor() {
7
+ super();
8
+ this._isVisible = false;
9
+ this._cleanupFocusTrap = null;
10
+ this.config = {
11
+ title: 'New Event',
12
+ defaultDuration: 60, // minutes
13
+ colors: [
14
+ { color: '#2563EB', label: 'Blue' },
15
+ { color: '#10B981', label: 'Green' },
16
+ { color: '#F59E0B', label: 'Amber' },
17
+ { color: '#EF4444', label: 'Red' },
18
+ { color: '#8B5CF6', label: 'Purple' },
19
+ { color: '#6B7280', label: 'Gray' }
20
+ ]
21
+ };
22
+ this._formData = {
23
+ title: '',
24
+ start: new Date(),
25
+ end: new Date(),
26
+ allDay: false,
27
+ color: this.config.colors[0].color
28
+ };
29
+ }
30
+
31
+ static get observedAttributes() {
32
+ return ['open'];
33
+ }
34
+
35
+ attributeChangedCallback(name, oldValue, newValue) {
36
+ if (name === 'open') {
37
+ if (newValue !== null) {
38
+ this.open();
39
+ } else {
40
+ this.close();
41
+ }
43
42
  }
43
+ }
44
44
 
45
- getStyles() {
46
- return `
45
+ getStyles() {
46
+ return `
47
47
  ${StyleUtils.getBaseStyles()}
48
48
  ${StyleUtils.getButtonStyles()}
49
49
 
@@ -214,10 +214,10 @@ export class EventForm extends BaseComponent {
214
214
  border-color: var(--fc-danger-color);
215
215
  }
216
216
  `;
217
- }
217
+ }
218
218
 
219
- template() {
220
- return `
219
+ template() {
220
+ return `
221
221
  <div class="modal-content" role="dialog" aria-modal="true" aria-labelledby="modal-title">
222
222
  <header class="modal-header">
223
223
  <h3 class="modal-title" id="modal-title">${this.config.title}</h3>
@@ -250,7 +250,9 @@ export class EventForm extends BaseComponent {
250
250
  <div class="form-group">
251
251
  <label id="color-label">Color</label>
252
252
  <div class="color-options" id="color-picker" role="radiogroup" aria-labelledby="color-label">
253
- ${this.config.colors.map(c => `
253
+ ${this.config.colors
254
+ .map(
255
+ c => `
254
256
  <button type="button"
255
257
  class="color-btn ${c.color === this._formData.color ? 'selected' : ''}"
256
258
  style="background-color: ${c.color}"
@@ -259,7 +261,9 @@ export class EventForm extends BaseComponent {
259
261
  aria-label="${c.label}"
260
262
  aria-checked="${c.color === this._formData.color ? 'true' : 'false'}"
261
263
  role="radio"></button>
262
- `).join('')}
264
+ `
265
+ )
266
+ .join('')}
263
267
  </div>
264
268
  </div>
265
269
  </div>
@@ -270,155 +274,155 @@ export class EventForm extends BaseComponent {
270
274
  </footer>
271
275
  </div>
272
276
  `;
273
- }
274
-
275
- afterRender() {
276
- // Bind elements
277
- this.modalContent = this.$('.modal-content');
278
- this.titleInput = this.$('#event-title');
279
- this.startInput = this.$('#event-start');
280
- this.endInput = this.$('#event-end');
281
- this.colorContainer = this.$('#color-picker');
282
-
283
- this.titleGroup = this.$('#title-group');
284
- this.endGroup = this.$('#end-group');
285
-
286
- // Event Listeners using addListener for automatic cleanup
287
- this.addListener(this.$('#close-x'), 'click', () => this.close());
288
- this.addListener(this.$('#cancel-btn'), 'click', () => this.close());
289
- this.addListener(this.$('#save-btn'), 'click', () => this.save());
290
-
291
- this.colorContainer.querySelectorAll('.color-btn').forEach(btn => {
292
- this.addListener(btn, 'click', (e) => {
293
- this._formData.color = e.currentTarget.dataset.color;
294
- this.updateColorSelection();
295
- });
296
- });
297
-
298
- // Close on backdrop click
299
- this.addListener(this, 'click', (e) => {
300
- if (e.target === this) this.close();
301
- });
302
-
303
- // Close on Escape key - only add once to prevent memory leaks
304
- if (!this._keydownListenerAdded) {
305
- this._handleKeyDown = (e) => {
306
- if (e.key === 'Escape' && this.hasAttribute('open')) {
307
- this.close();
308
- }
309
- };
310
- window.addEventListener('keydown', this._handleKeyDown);
311
- this._keydownListenerAdded = true;
277
+ }
278
+
279
+ afterRender() {
280
+ // Bind elements
281
+ this.modalContent = this.$('.modal-content');
282
+ this.titleInput = this.$('#event-title');
283
+ this.startInput = this.$('#event-start');
284
+ this.endInput = this.$('#event-end');
285
+ this.colorContainer = this.$('#color-picker');
286
+
287
+ this.titleGroup = this.$('#title-group');
288
+ this.endGroup = this.$('#end-group');
289
+
290
+ // Event Listeners using addListener for automatic cleanup
291
+ this.addListener(this.$('#close-x'), 'click', () => this.close());
292
+ this.addListener(this.$('#cancel-btn'), 'click', () => this.close());
293
+ this.addListener(this.$('#save-btn'), 'click', () => this.save());
294
+
295
+ this.colorContainer.querySelectorAll('.color-btn').forEach(btn => {
296
+ this.addListener(btn, 'click', e => {
297
+ this._formData.color = e.currentTarget.dataset.color;
298
+ this.updateColorSelection();
299
+ });
300
+ });
301
+
302
+ // Close on backdrop click
303
+ this.addListener(this, 'click', e => {
304
+ if (e.target === this) this.close();
305
+ });
306
+
307
+ // Close on Escape key - only add once to prevent memory leaks
308
+ if (!this._keydownListenerAdded) {
309
+ this._handleKeyDown = e => {
310
+ if (e.key === 'Escape' && this.hasAttribute('open')) {
311
+ this.close();
312
312
  }
313
+ };
314
+ window.addEventListener('keydown', this._handleKeyDown);
315
+ this._keydownListenerAdded = true;
313
316
  }
314
-
315
- updateColorSelection() {
316
- const buttons = this.colorContainer.querySelectorAll('.color-btn');
317
- buttons.forEach(btn => {
318
- const isSelected = btn.dataset.color === this._formData.color;
319
- btn.classList.toggle('selected', isSelected);
320
- btn.setAttribute('aria-checked', isSelected ? 'true' : 'false');
321
- });
317
+ }
318
+
319
+ updateColorSelection() {
320
+ const buttons = this.colorContainer.querySelectorAll('.color-btn');
321
+ buttons.forEach(btn => {
322
+ const isSelected = btn.dataset.color === this._formData.color;
323
+ btn.classList.toggle('selected', isSelected);
324
+ btn.setAttribute('aria-checked', isSelected ? 'true' : 'false');
325
+ });
326
+ }
327
+
328
+ open(initialDate = new Date()) {
329
+ if (!this.hasAttribute('open')) {
330
+ this.setAttribute('open', '');
322
331
  }
323
332
 
324
- open(initialDate = new Date()) {
325
- if (!this.hasAttribute('open')) {
326
- this.setAttribute('open', '');
327
- }
328
-
329
- // Reset errors
330
- this.titleGroup.classList.remove('has-error');
331
- this.endGroup.classList.remove('has-error');
332
-
333
- // Initialize form data
334
- this._formData.start = initialDate;
335
- this._formData.end = new Date(initialDate.getTime() + this.config.defaultDuration * 60 * 1000);
336
- this._formData.title = '';
337
- this._formData.color = this.config.colors[0].color;
338
-
339
- // Update inputs
340
- if (this.startInput) {
341
- this.titleInput.value = '';
342
- this.startInput.value = this.formatDateForInput(this._formData.start);
343
- this.endInput.value = this.formatDateForInput(this._formData.end);
344
- this.updateColorSelection();
345
-
346
- // Focus trapping
347
- this._cleanupFocusTrap = DOMUtils.trapFocus(this.modalContent);
348
- }
333
+ // Reset errors
334
+ this.titleGroup.classList.remove('has-error');
335
+ this.endGroup.classList.remove('has-error');
336
+
337
+ // Initialize form data
338
+ this._formData.start = initialDate;
339
+ this._formData.end = new Date(initialDate.getTime() + this.config.defaultDuration * 60 * 1000);
340
+ this._formData.title = '';
341
+ this._formData.color = this.config.colors[0].color;
342
+
343
+ // Update inputs
344
+ if (this.startInput) {
345
+ this.titleInput.value = '';
346
+ this.startInput.value = this.formatDateForInput(this._formData.start);
347
+ this.endInput.value = this.formatDateForInput(this._formData.end);
348
+ this.updateColorSelection();
349
+
350
+ // Focus trapping
351
+ this._cleanupFocusTrap = DOMUtils.trapFocus(this.modalContent);
349
352
  }
353
+ }
350
354
 
351
- close() {
352
- this.removeAttribute('open');
353
- if (this._cleanupFocusTrap) {
354
- this._cleanupFocusTrap();
355
- this._cleanupFocusTrap = null;
356
- }
355
+ close() {
356
+ this.removeAttribute('open');
357
+ if (this._cleanupFocusTrap) {
358
+ this._cleanupFocusTrap();
359
+ this._cleanupFocusTrap = null;
357
360
  }
361
+ }
358
362
 
359
- validate() {
360
- let isValid = true;
361
-
362
- // Reset errors
363
- this.titleGroup.classList.remove('has-error');
364
- this.endGroup.classList.remove('has-error');
365
-
366
- // Check title
367
- if (!this.titleInput.value.trim()) {
368
- this.titleGroup.classList.add('has-error');
369
- isValid = false;
370
- }
363
+ validate() {
364
+ let isValid = true;
371
365
 
372
- // Check date range
373
- const start = new Date(this.startInput.value);
374
- const end = new Date(this.endInput.value);
375
- if (end <= start) {
376
- this.endGroup.classList.add('has-error');
377
- isValid = false;
378
- }
366
+ // Reset errors
367
+ this.titleGroup.classList.remove('has-error');
368
+ this.endGroup.classList.remove('has-error');
379
369
 
380
- return isValid;
370
+ // Check title
371
+ if (!this.titleInput.value.trim()) {
372
+ this.titleGroup.classList.add('has-error');
373
+ isValid = false;
381
374
  }
382
375
 
383
- save() {
384
- if (!this.validate()) return;
385
-
386
- const event = {
387
- title: this.titleInput.value.trim(),
388
- start: new Date(this.startInput.value),
389
- end: new Date(this.endInput.value),
390
- backgroundColor: this._formData.color
391
- };
392
-
393
- this.emit('save', event);
394
- this.close();
376
+ // Check date range
377
+ const start = new Date(this.startInput.value);
378
+ const end = new Date(this.endInput.value);
379
+ if (end <= start) {
380
+ this.endGroup.classList.add('has-error');
381
+ isValid = false;
395
382
  }
396
383
 
397
- formatDateForInput(date) {
398
- // Handle local date string for datetime-local input
399
- const pad = (num) => String(num).padStart(2, '0');
400
- const year = date.getFullYear();
401
- const month = pad(date.getMonth() + 1);
402
- const day = pad(date.getDate());
403
- const hours = pad(date.getHours());
404
- const minutes = pad(date.getMinutes());
405
-
406
- return `${year}-${month}-${day}T${hours}:${minutes}`;
384
+ return isValid;
385
+ }
386
+
387
+ save() {
388
+ if (!this.validate()) return;
389
+
390
+ const event = {
391
+ title: this.titleInput.value.trim(),
392
+ start: new Date(this.startInput.value),
393
+ end: new Date(this.endInput.value),
394
+ backgroundColor: this._formData.color
395
+ };
396
+
397
+ this.emit('save', event);
398
+ this.close();
399
+ }
400
+
401
+ formatDateForInput(date) {
402
+ // Handle local date string for datetime-local input
403
+ const pad = num => String(num).padStart(2, '0');
404
+ const year = date.getFullYear();
405
+ const month = pad(date.getMonth() + 1);
406
+ const day = pad(date.getDate());
407
+ const hours = pad(date.getHours());
408
+ const minutes = pad(date.getMinutes());
409
+
410
+ return `${year}-${month}-${day}T${hours}:${minutes}`;
411
+ }
412
+
413
+ unmount() {
414
+ if (this._cleanupFocusTrap) {
415
+ this._cleanupFocusTrap();
407
416
  }
408
-
409
- unmount() {
410
- if (this._cleanupFocusTrap) {
411
- this._cleanupFocusTrap();
412
- }
413
- // Clean up window listener
414
- if (this._handleKeyDown) {
415
- window.removeEventListener('keydown', this._handleKeyDown);
416
- this._handleKeyDown = null;
417
- this._keydownListenerAdded = false;
418
- }
417
+ // Clean up window listener
418
+ if (this._handleKeyDown) {
419
+ window.removeEventListener('keydown', this._handleKeyDown);
420
+ this._handleKeyDown = null;
421
+ this._keydownListenerAdded = false;
419
422
  }
423
+ }
420
424
  }
421
425
 
422
426
  if (!customElements.get('forcecal-event-form')) {
423
- customElements.define('forcecal-event-form', EventForm);
427
+ customElements.define('forcecal-event-form', EventForm);
424
428
  }