@forcecalendar/interface 1.0.27 → 1.0.29

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.
@@ -3,133 +3,133 @@
3
3
  */
4
4
 
5
5
  export class StyleUtils {
6
- /**
7
- * Default theme colors
8
- */
9
- static colors = {
10
- primary: '#3B82F6', // Modern Blue
11
- secondary: '#64748B', // Slate
12
- accent: '#F59E0B', // Amber
13
- danger: '#EF4444', // Red
14
- warning: '#F97316', // Orange
15
- info: '#06B6D4', // Cyan
16
- success: '#22C55E', // Green
17
- light: '#F8FAFC',
18
- dark: '#0F172A',
19
- white: '#FFFFFF',
20
- gray: {
21
- 50: '#F8FAFC',
22
- 100: '#F1F5F9',
23
- 200: '#E2E8F0',
24
- 300: '#CBD5E1',
25
- 400: '#94A3B8',
26
- 500: '#64748B',
27
- 600: '#475569',
28
- 700: '#334155',
29
- 800: '#1E293B',
30
- 900: '#0F172A'
31
- }
32
- };
33
-
34
- /**
35
- * Common CSS variables
36
- */
37
- static cssVariables = {
38
- // "Pro" Palette - Functional & Sharp
39
- '--fc-primary-color': '#2563EB', // International Blue (Focus)
40
- '--fc-primary-hover': '#1D4ED8',
41
- '--fc-primary-light': '#EFF6FF',
42
-
43
- // Neutral Scale (Slate/Gray for structure)
44
- '--fc-text-color': '#111827', // Almost Black
45
- '--fc-text-secondary': '#6B7280', // Cool Gray
46
- '--fc-text-light': '#9CA3AF',
47
-
48
- '--fc-border-color': '#E5E7EB', // Crisp Light Gray
49
- '--fc-border-color-hover': '#D1D5DB',
50
-
51
- '--fc-background': '#FFFFFF',
52
- '--fc-background-alt': '#FAFAFA', // Very subtle off-white
53
- '--fc-background-hover': '#F3F4F6',
54
- '--fc-background-active': '#E5E7EB',
55
-
56
- // Semantic Colors
57
- '--fc-accent-color': '#F59E0B',
58
- '--fc-danger-color': '#EF4444',
59
- '--fc-success-color': '#10B981',
60
-
61
- // Typography - optimized for UI density
62
- '--fc-font-family': 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
63
- '--fc-font-size-xs': '11px',
64
- '--fc-font-size-sm': '12px',
65
- '--fc-font-size-base': '13px', // Slightly smaller for density
66
- '--fc-font-size-lg': '15px',
67
- '--fc-font-size-xl': '18px',
68
- '--fc-font-size-2xl': '24px',
69
- '--fc-line-height': '1.4',
70
- '--fc-font-weight-normal': '400',
71
- '--fc-font-weight-medium': '500',
72
- '--fc-font-weight-semibold': '600',
73
- '--fc-font-weight-bold': '700',
74
-
75
- // Spacing - Tighter
76
- '--fc-spacing-xs': '2px',
77
- '--fc-spacing-sm': '6px',
78
- '--fc-spacing-md': '10px',
79
- '--fc-spacing-lg': '14px',
80
- '--fc-spacing-xl': '20px',
81
- '--fc-spacing-2xl': '28px',
82
-
83
- // Border
84
- '--fc-border-width': '1px',
85
- '--fc-border-radius-sm': '3px', // Micro rounding
86
- '--fc-border-radius': '5px',
87
- '--fc-border-radius-lg': '8px',
88
- '--fc-border-radius-full': '9999px',
89
-
90
- // Shadows - Minimal/Functional
91
- '--fc-shadow-sm': '0 1px 1px rgba(0,0,0,0.05)',
92
- '--fc-shadow': '0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06)',
93
- '--fc-shadow-md': '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
94
- '--fc-shadow-lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
95
-
96
- // Transitions - Snappy
97
- '--fc-transition-fast': '100ms ease-out',
98
- '--fc-transition': '150ms ease-out',
99
- '--fc-transition-slow': '250ms ease-out',
100
-
101
- // Z-index
102
- '--fc-z-dropdown': '1000',
103
- '--fc-z-modal': '2000',
104
- '--fc-z-tooltip': '3000'
105
- };
106
-
107
- /**
108
- * Get CSS variable value
109
- */
110
- static getCSSVariable(name, element = document.documentElement) {
111
- return getComputedStyle(element).getPropertyValue(name).trim();
6
+ /**
7
+ * Default theme colors
8
+ */
9
+ static colors = {
10
+ primary: '#3B82F6', // Modern Blue
11
+ secondary: '#64748B', // Slate
12
+ accent: '#F59E0B', // Amber
13
+ danger: '#EF4444', // Red
14
+ warning: '#F97316', // Orange
15
+ info: '#06B6D4', // Cyan
16
+ success: '#22C55E', // Green
17
+ light: '#F8FAFC',
18
+ dark: '#0F172A',
19
+ white: '#FFFFFF',
20
+ gray: {
21
+ 50: '#F8FAFC',
22
+ 100: '#F1F5F9',
23
+ 200: '#E2E8F0',
24
+ 300: '#CBD5E1',
25
+ 400: '#94A3B8',
26
+ 500: '#64748B',
27
+ 600: '#475569',
28
+ 700: '#334155',
29
+ 800: '#1E293B',
30
+ 900: '#0F172A'
112
31
  }
113
-
114
- /**
115
- * Set CSS variables
116
- */
117
- static setCSSVariables(variables, element = document.documentElement) {
118
- Object.entries(variables).forEach(([key, value]) => {
119
- element.style.setProperty(key, value);
120
- });
121
- }
122
-
123
- /**
124
- * Generate base styles
125
- */
126
- static getBaseStyles() {
127
- return `
32
+ };
33
+
34
+ /**
35
+ * Common CSS variables
36
+ */
37
+ static cssVariables = {
38
+ // "Pro" Palette - Functional & Sharp
39
+ '--fc-primary-color': '#2563EB', // International Blue (Focus)
40
+ '--fc-primary-hover': '#1D4ED8',
41
+ '--fc-primary-light': '#EFF6FF',
42
+
43
+ // Neutral Scale (Slate/Gray for structure)
44
+ '--fc-text-color': '#111827', // Almost Black
45
+ '--fc-text-secondary': '#6B7280', // Cool Gray
46
+ '--fc-text-light': '#9CA3AF',
47
+
48
+ '--fc-border-color': '#E5E7EB', // Crisp Light Gray
49
+ '--fc-border-color-hover': '#D1D5DB',
50
+
51
+ '--fc-background': '#FFFFFF',
52
+ '--fc-background-alt': '#FAFAFA', // Very subtle off-white
53
+ '--fc-background-hover': '#F3F4F6',
54
+ '--fc-background-active': '#E5E7EB',
55
+
56
+ // Semantic Colors
57
+ '--fc-accent-color': '#F59E0B',
58
+ '--fc-danger-color': '#EF4444',
59
+ '--fc-success-color': '#10B981',
60
+
61
+ // Typography - optimized for UI density
62
+ '--fc-font-family': 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
63
+ '--fc-font-size-xs': '11px',
64
+ '--fc-font-size-sm': '12px',
65
+ '--fc-font-size-base': '13px', // Slightly smaller for density
66
+ '--fc-font-size-lg': '15px',
67
+ '--fc-font-size-xl': '18px',
68
+ '--fc-font-size-2xl': '24px',
69
+ '--fc-line-height': '1.4',
70
+ '--fc-font-weight-normal': '400',
71
+ '--fc-font-weight-medium': '500',
72
+ '--fc-font-weight-semibold': '600',
73
+ '--fc-font-weight-bold': '700',
74
+
75
+ // Spacing - Tighter
76
+ '--fc-spacing-xs': '2px',
77
+ '--fc-spacing-sm': '6px',
78
+ '--fc-spacing-md': '10px',
79
+ '--fc-spacing-lg': '14px',
80
+ '--fc-spacing-xl': '20px',
81
+ '--fc-spacing-2xl': '28px',
82
+
83
+ // Border
84
+ '--fc-border-width': '1px',
85
+ '--fc-border-radius-sm': '3px', // Micro rounding
86
+ '--fc-border-radius': '5px',
87
+ '--fc-border-radius-lg': '8px',
88
+ '--fc-border-radius-full': '9999px',
89
+
90
+ // Shadows - Minimal/Functional
91
+ '--fc-shadow-sm': '0 1px 1px rgba(0,0,0,0.05)',
92
+ '--fc-shadow': '0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06)',
93
+ '--fc-shadow-md': '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
94
+ '--fc-shadow-lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
95
+
96
+ // Transitions - Snappy
97
+ '--fc-transition-fast': '100ms ease-out',
98
+ '--fc-transition': '150ms ease-out',
99
+ '--fc-transition-slow': '250ms ease-out',
100
+
101
+ // Z-index
102
+ '--fc-z-dropdown': '1000',
103
+ '--fc-z-modal': '2000',
104
+ '--fc-z-tooltip': '3000'
105
+ };
106
+
107
+ /**
108
+ * Get CSS variable value
109
+ */
110
+ static getCSSVariable(name, element = document.documentElement) {
111
+ return getComputedStyle(element).getPropertyValue(name).trim();
112
+ }
113
+
114
+ /**
115
+ * Set CSS variables
116
+ */
117
+ static setCSSVariables(variables, element = document.documentElement) {
118
+ Object.entries(variables).forEach(([key, value]) => {
119
+ element.style.setProperty(key, value);
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Generate base styles
125
+ */
126
+ static getBaseStyles() {
127
+ return `
128
128
  :host {
129
129
  /* Apply CSS variables */
130
130
  ${Object.entries(this.cssVariables)
131
- .map(([key, value]) => `${key}: ${value};`)
132
- .join('\n ')}
131
+ .map(([key, value]) => `${key}: ${value};`)
132
+ .join('\n ')}
133
133
 
134
134
  /* Base styles */
135
135
  display: block;
@@ -178,13 +178,13 @@ export class StyleUtils {
178
178
  outline-offset: 2px;
179
179
  }
180
180
  `;
181
- }
181
+ }
182
182
 
183
- /**
184
- * Generate button styles
185
- */
186
- static getButtonStyles() {
187
- return `
183
+ /**
184
+ * Generate button styles
185
+ */
186
+ static getButtonStyles() {
187
+ return `
188
188
  .fc-btn {
189
189
  display: inline-flex;
190
190
  align-items: center;
@@ -270,108 +270,145 @@ export class StyleUtils {
270
270
  border-radius: var(--fc-border-radius-full);
271
271
  }
272
272
  `;
273
+ }
274
+
275
+ /**
276
+ * Darken color by percentage
277
+ */
278
+ static darken(color, percent) {
279
+ const num = parseInt(color.replace('#', ''), 16);
280
+ const amt = Math.round(2.55 * percent);
281
+ const R = (num >> 16) - amt;
282
+ const G = ((num >> 8) & 0x00ff) - amt;
283
+ const B = (num & 0x0000ff) - amt;
284
+ return (
285
+ '#' +
286
+ (
287
+ 0x1000000 +
288
+ (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
289
+ (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
290
+ (B < 255 ? (B < 1 ? 0 : B) : 255)
291
+ )
292
+ .toString(16)
293
+ .slice(1)
294
+ );
295
+ }
296
+
297
+ /**
298
+ * Lighten color by percentage
299
+ */
300
+ static lighten(color, percent) {
301
+ const num = parseInt(color.replace('#', ''), 16);
302
+ const amt = Math.round(2.55 * percent);
303
+ const R = (num >> 16) + amt;
304
+ const G = ((num >> 8) & 0x00ff) + amt;
305
+ const B = (num & 0x0000ff) + amt;
306
+ return (
307
+ '#' +
308
+ (
309
+ 0x1000000 +
310
+ (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
311
+ (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
312
+ (B < 255 ? (B < 1 ? 0 : B) : 255)
313
+ )
314
+ .toString(16)
315
+ .slice(1)
316
+ );
317
+ }
318
+
319
+ /**
320
+ * Get contrast color (black or white) for background
321
+ */
322
+ static getContrastColor(bgColor) {
323
+ const color = bgColor.replace('#', '');
324
+ const r = parseInt(color.substr(0, 2), 16);
325
+ const g = parseInt(color.substr(2, 2), 16);
326
+ const b = parseInt(color.substr(4, 2), 16);
327
+ const yiq = (r * 299 + g * 587 + b * 114) / 1000;
328
+ return yiq >= 128 ? '#000000' : '#FFFFFF';
329
+ }
330
+
331
+ /**
332
+ * Sanitize color value to prevent CSS injection
333
+ * Returns the color if valid, or a fallback color if invalid
334
+ */
335
+ static sanitizeColor(color, fallback = 'var(--fc-primary-color)') {
336
+ if (!color || typeof color !== 'string') {
337
+ return fallback;
273
338
  }
274
339
 
275
- /**
276
- * Darken color by percentage
277
- */
278
- static darken(color, percent) {
279
- const num = parseInt(color.replace('#', ''), 16);
280
- const amt = Math.round(2.55 * percent);
281
- const R = (num >> 16) - amt;
282
- const G = (num >> 8 & 0x00FF) - amt;
283
- const B = (num & 0x0000FF) - amt;
284
- return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
285
- (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
286
- (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
340
+ // Trim and check for dangerous characters that could break out of CSS
341
+ const trimmed = color.trim();
342
+ if (/[;{}()<>\"\'\\]/.test(trimmed)) {
343
+ return fallback;
287
344
  }
288
345
 
289
- /**
290
- * Lighten color by percentage
291
- */
292
- static lighten(color, percent) {
293
- const num = parseInt(color.replace('#', ''), 16);
294
- const amt = Math.round(2.55 * percent);
295
- const R = (num >> 16) + amt;
296
- const G = (num >> 8 & 0x00FF) + amt;
297
- const B = (num & 0x0000FF) + amt;
298
- return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
299
- (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
300
- (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
346
+ // Allow hex colors (#RGB, #RRGGBB, #RRGGBBAA)
347
+ if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(trimmed)) {
348
+ return trimmed;
301
349
  }
302
350
 
303
- /**
304
- * Get contrast color (black or white) for background
305
- */
306
- static getContrastColor(bgColor) {
307
- const color = bgColor.replace('#', '');
308
- const r = parseInt(color.substr(0, 2), 16);
309
- const g = parseInt(color.substr(2, 2), 16);
310
- const b = parseInt(color.substr(4, 2), 16);
311
- const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
312
- return yiq >= 128 ? '#000000' : '#FFFFFF';
351
+ // Allow CSS variables
352
+ if (/^var\(--[a-zA-Z0-9-]+\)$/.test(trimmed)) {
353
+ return trimmed;
313
354
  }
314
355
 
315
- /**
316
- * Sanitize color value to prevent CSS injection
317
- * Returns the color if valid, or a fallback color if invalid
318
- */
319
- static sanitizeColor(color, fallback = 'var(--fc-primary-color)') {
320
- if (!color || typeof color !== 'string') {
321
- return fallback;
322
- }
323
-
324
- // Trim and check for dangerous characters that could break out of CSS
325
- const trimmed = color.trim();
326
- if (/[;{}()<>\"\'\\]/.test(trimmed)) {
327
- return fallback;
328
- }
329
-
330
- // Allow hex colors (#RGB, #RRGGBB, #RRGGBBAA)
331
- if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(trimmed)) {
332
- return trimmed;
333
- }
334
-
335
- // Allow CSS variables
336
- if (/^var\(--[a-zA-Z0-9-]+\)$/.test(trimmed)) {
337
- return trimmed;
338
- }
339
-
340
- // Allow rgb/rgba with numbers only
341
- if (/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*(0|1|0?\.\d+))?\s*\)$/.test(trimmed)) {
342
- return trimmed;
343
- }
344
-
345
- // Allow safe CSS color keywords
346
- const safeKeywords = [
347
- 'transparent', 'currentColor', 'inherit',
348
- 'black', 'white', 'red', 'green', 'blue', 'yellow', 'orange', 'purple',
349
- 'pink', 'brown', 'gray', 'grey', 'cyan', 'magenta', 'lime', 'navy',
350
- 'teal', 'aqua', 'maroon', 'olive', 'silver', 'fuchsia'
351
- ];
352
- if (safeKeywords.includes(trimmed.toLowerCase())) {
353
- return trimmed;
354
- }
355
-
356
- return fallback;
356
+ // Allow rgb/rgba with numbers only
357
+ if (/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*(0|1|0?\.\d+))?\s*\)$/.test(trimmed)) {
358
+ return trimmed;
357
359
  }
358
360
 
359
- /**
360
- * Convert hex to rgba
361
- */
362
- static hexToRgba(hex, alpha = 1) {
363
- const color = hex.replace('#', '');
364
- const r = parseInt(color.substr(0, 2), 16);
365
- const g = parseInt(color.substr(2, 2), 16);
366
- const b = parseInt(color.substr(4, 2), 16);
367
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
361
+ // Allow safe CSS color keywords
362
+ const safeKeywords = [
363
+ 'transparent',
364
+ 'currentColor',
365
+ 'inherit',
366
+ 'black',
367
+ 'white',
368
+ 'red',
369
+ 'green',
370
+ 'blue',
371
+ 'yellow',
372
+ 'orange',
373
+ 'purple',
374
+ 'pink',
375
+ 'brown',
376
+ 'gray',
377
+ 'grey',
378
+ 'cyan',
379
+ 'magenta',
380
+ 'lime',
381
+ 'navy',
382
+ 'teal',
383
+ 'aqua',
384
+ 'maroon',
385
+ 'olive',
386
+ 'silver',
387
+ 'fuchsia'
388
+ ];
389
+ if (safeKeywords.includes(trimmed.toLowerCase())) {
390
+ return trimmed;
368
391
  }
369
392
 
370
- /**
371
- * Generate grid styles
372
- */
373
- static getGridStyles() {
374
- return `
393
+ return fallback;
394
+ }
395
+
396
+ /**
397
+ * Convert hex to rgba
398
+ */
399
+ static hexToRgba(hex, alpha = 1) {
400
+ const color = hex.replace('#', '');
401
+ const r = parseInt(color.substr(0, 2), 16);
402
+ const g = parseInt(color.substr(2, 2), 16);
403
+ const b = parseInt(color.substr(4, 2), 16);
404
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
405
+ }
406
+
407
+ /**
408
+ * Generate grid styles
409
+ */
410
+ static getGridStyles() {
411
+ return `
375
412
  .fc-grid {
376
413
  display: grid;
377
414
  gap: 1px;
@@ -403,34 +440,34 @@ export class StyleUtils {
403
440
  letter-spacing: 0.5px;
404
441
  }
405
442
  `;
406
- }
407
-
408
- /**
409
- * Get responsive breakpoints
410
- */
411
- static breakpoints = {
412
- xs: '320px',
413
- sm: '576px',
414
- md: '768px',
415
- lg: '992px',
416
- xl: '1200px',
417
- '2xl': '1400px'
418
- };
419
-
420
- /**
421
- * Generate media query
422
- */
423
- static mediaQuery(breakpoint, styles) {
424
- const size = this.breakpoints[breakpoint];
425
- if (!size) return '';
426
- return `@media (min-width: ${size}) { ${styles} }`;
427
- }
428
-
429
- /**
430
- * Animation keyframes
431
- */
432
- static getAnimations() {
433
- return `
443
+ }
444
+
445
+ /**
446
+ * Get responsive breakpoints
447
+ */
448
+ static breakpoints = {
449
+ xs: '320px',
450
+ sm: '576px',
451
+ md: '768px',
452
+ lg: '992px',
453
+ xl: '1200px',
454
+ '2xl': '1400px'
455
+ };
456
+
457
+ /**
458
+ * Generate media query
459
+ */
460
+ static mediaQuery(breakpoint, styles) {
461
+ const size = this.breakpoints[breakpoint];
462
+ if (!size) return '';
463
+ return `@media (min-width: ${size}) { ${styles} }`;
464
+ }
465
+
466
+ /**
467
+ * Animation keyframes
468
+ */
469
+ static getAnimations() {
470
+ return `
434
471
  @keyframes fc-fade-in {
435
472
  from { opacity: 0; }
436
473
  to { opacity: 1; }
@@ -486,7 +523,7 @@ export class StyleUtils {
486
523
  animation: fc-scale-in var(--fc-transition);
487
524
  }
488
525
  `;
489
- }
526
+ }
490
527
  }
491
528
 
492
- export default StyleUtils;
529
+ export default StyleUtils;