@sc4rfurryx/proteusjs 1.0.0

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 (82) hide show
  1. package/API.md +438 -0
  2. package/FEATURES.md +286 -0
  3. package/LICENSE +21 -0
  4. package/README.md +645 -0
  5. package/dist/.tsbuildinfo +1 -0
  6. package/dist/proteus.cjs.js +16014 -0
  7. package/dist/proteus.cjs.js.map +1 -0
  8. package/dist/proteus.d.ts +3018 -0
  9. package/dist/proteus.esm.js +16005 -0
  10. package/dist/proteus.esm.js.map +1 -0
  11. package/dist/proteus.esm.min.js +8 -0
  12. package/dist/proteus.esm.min.js.map +1 -0
  13. package/dist/proteus.js +16020 -0
  14. package/dist/proteus.js.map +1 -0
  15. package/dist/proteus.min.js +8 -0
  16. package/dist/proteus.min.js.map +1 -0
  17. package/package.json +98 -0
  18. package/src/__tests__/mvp-integration.test.ts +518 -0
  19. package/src/accessibility/AccessibilityEngine.ts +2106 -0
  20. package/src/accessibility/ScreenReaderSupport.ts +444 -0
  21. package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
  22. package/src/animations/FLIPAnimationSystem.ts +491 -0
  23. package/src/compatibility/BrowserCompatibility.ts +1076 -0
  24. package/src/containers/BreakpointSystem.ts +347 -0
  25. package/src/containers/ContainerBreakpoints.ts +726 -0
  26. package/src/containers/ContainerManager.ts +370 -0
  27. package/src/containers/ContainerUnits.ts +336 -0
  28. package/src/containers/ContextIsolation.ts +394 -0
  29. package/src/containers/ElementQueries.ts +411 -0
  30. package/src/containers/SmartContainer.ts +536 -0
  31. package/src/containers/SmartContainers.ts +376 -0
  32. package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
  33. package/src/containers/__tests__/SmartContainers.test.ts +281 -0
  34. package/src/content/ResponsiveImages.ts +570 -0
  35. package/src/core/EventSystem.ts +147 -0
  36. package/src/core/MemoryManager.ts +321 -0
  37. package/src/core/PerformanceMonitor.ts +238 -0
  38. package/src/core/PluginSystem.ts +275 -0
  39. package/src/core/ProteusJS.test.ts +164 -0
  40. package/src/core/ProteusJS.ts +962 -0
  41. package/src/developer/PerformanceProfiler.ts +567 -0
  42. package/src/developer/VisualDebuggingTools.ts +656 -0
  43. package/src/developer/ZeroConfigSystem.ts +593 -0
  44. package/src/index.ts +35 -0
  45. package/src/integration.test.ts +227 -0
  46. package/src/layout/AdaptiveGrid.ts +429 -0
  47. package/src/layout/ContentReordering.ts +532 -0
  48. package/src/layout/FlexboxEnhancer.ts +406 -0
  49. package/src/layout/FlowLayout.ts +545 -0
  50. package/src/layout/SpacingSystem.ts +512 -0
  51. package/src/observers/IntersectionObserverPolyfill.ts +289 -0
  52. package/src/observers/ObserverManager.ts +299 -0
  53. package/src/observers/ResizeObserverPolyfill.ts +179 -0
  54. package/src/performance/BatchDOMOperations.ts +519 -0
  55. package/src/performance/CSSOptimizationEngine.ts +646 -0
  56. package/src/performance/CacheOptimizationSystem.ts +601 -0
  57. package/src/performance/EfficientEventHandler.ts +740 -0
  58. package/src/performance/LazyEvaluationSystem.ts +532 -0
  59. package/src/performance/MemoryManagementSystem.ts +497 -0
  60. package/src/performance/PerformanceMonitor.ts +931 -0
  61. package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
  62. package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
  63. package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
  64. package/src/polyfills/BrowserPolyfills.ts +586 -0
  65. package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
  66. package/src/test/setup.ts +115 -0
  67. package/src/theming/SmartThemeSystem.ts +591 -0
  68. package/src/types/index.ts +134 -0
  69. package/src/typography/ClampScaling.ts +356 -0
  70. package/src/typography/FluidTypography.ts +759 -0
  71. package/src/typography/LineHeightOptimization.ts +430 -0
  72. package/src/typography/LineHeightOptimizer.ts +326 -0
  73. package/src/typography/TextFitting.ts +355 -0
  74. package/src/typography/TypographicScale.ts +428 -0
  75. package/src/typography/VerticalRhythm.ts +369 -0
  76. package/src/typography/__tests__/FluidTypography.test.ts +432 -0
  77. package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
  78. package/src/utils/Logger.ts +173 -0
  79. package/src/utils/debounce.ts +259 -0
  80. package/src/utils/performance.ts +371 -0
  81. package/src/utils/support.ts +106 -0
  82. package/src/utils/version.ts +24 -0
@@ -0,0 +1,491 @@
1
+ /**
2
+ * FLIP Animation System for ProteusJS
3
+ * First, Last, Invert, Play animations for smooth layout transitions
4
+ */
5
+
6
+ export interface FLIPConfig {
7
+ duration: number;
8
+ easing: string;
9
+ respectMotionPreference: boolean;
10
+ batchAnimations: boolean;
11
+ performanceMode: 'smooth' | 'performance' | 'auto';
12
+ maxConcurrentAnimations: number;
13
+ }
14
+
15
+ export interface FLIPState {
16
+ element: Element;
17
+ first: DOMRect;
18
+ last: DOMRect;
19
+ invert: { x: number; y: number; scaleX: number; scaleY: number };
20
+ player: Animation | null;
21
+ }
22
+
23
+ export class FLIPAnimationSystem {
24
+ private config: Required<FLIPConfig>;
25
+ private activeAnimations: Map<Element, FLIPState> = new Map();
26
+ private animationQueue: (() => Promise<void>)[] = [];
27
+ private isProcessing: boolean = false;
28
+
29
+ constructor(config: Partial<FLIPConfig> = {}) {
30
+ this.config = {
31
+ duration: 300,
32
+ easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
33
+ respectMotionPreference: true,
34
+ batchAnimations: true,
35
+ performanceMode: 'auto',
36
+ maxConcurrentAnimations: 10,
37
+ ...config
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Animate element from current position to new position
43
+ */
44
+ public async animate(
45
+ element: Element,
46
+ newPosition: () => void,
47
+ options: Partial<FLIPConfig> = {}
48
+ ): Promise<void> {
49
+ const config = { ...this.config, ...options };
50
+
51
+ if (this.shouldSkipAnimation()) {
52
+ newPosition();
53
+ return;
54
+ }
55
+
56
+ // FLIP: First - record initial position
57
+ const first = element.getBoundingClientRect();
58
+
59
+ // FLIP: Last - apply changes and record final position
60
+ newPosition();
61
+ const last = element.getBoundingClientRect();
62
+
63
+ // FLIP: Invert - calculate the difference
64
+ const invert = this.calculateInvert(first, last);
65
+
66
+ // Skip if no change
67
+ if (invert.x === 0 && invert.y === 0 && invert.scaleX === 1 && invert.scaleY === 1) {
68
+ return;
69
+ }
70
+
71
+ // FLIP: Play - animate to final position
72
+ return this.playAnimation(element, invert, config);
73
+ }
74
+
75
+ /**
76
+ * Animate multiple elements in batch
77
+ */
78
+ public async animateBatch(
79
+ animations: Array<{
80
+ element: Element;
81
+ newPosition: () => void;
82
+ options?: Partial<FLIPConfig>;
83
+ }>
84
+ ): Promise<void> {
85
+ if (!this.config.batchAnimations) {
86
+ // Run animations sequentially
87
+ for (const anim of animations) {
88
+ await this.animate(anim.element, anim.newPosition, anim.options);
89
+ }
90
+ return;
91
+ }
92
+
93
+ // Batch FLIP: First - record all initial positions
94
+ const flipStates = animations.map(anim => ({
95
+ element: anim.element,
96
+ first: anim.element.getBoundingClientRect(),
97
+ newPosition: anim.newPosition,
98
+ options: anim.options || {}
99
+ }));
100
+
101
+ // Batch FLIP: Last - apply all changes
102
+ flipStates.forEach(state => state.newPosition());
103
+
104
+ // Batch FLIP: Invert and Play
105
+ const playPromises = flipStates.map(state => {
106
+ const last = state.element.getBoundingClientRect();
107
+ const invert = this.calculateInvert(state.first, last);
108
+
109
+ if (invert.x === 0 && invert.y === 0 && invert.scaleX === 1 && invert.scaleY === 1) {
110
+ return Promise.resolve();
111
+ }
112
+
113
+ const config = { ...this.config, ...state.options };
114
+ return this.playAnimation(state.element, invert, config);
115
+ });
116
+
117
+ await Promise.all(playPromises);
118
+ }
119
+
120
+ /**
121
+ * Cancel animation for element
122
+ */
123
+ public cancel(element: Element): void {
124
+ const flipState = this.activeAnimations.get(element);
125
+ if (flipState?.player) {
126
+ flipState.player.cancel();
127
+ this.activeAnimations.delete(element);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Cancel all active animations
133
+ */
134
+ public cancelAll(): void {
135
+ this.activeAnimations.forEach((flipState, element) => {
136
+ this.cancel(element);
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Get active animation count
142
+ */
143
+ public getActiveCount(): number {
144
+ return this.activeAnimations.size;
145
+ }
146
+
147
+ /**
148
+ * Check if element is animating
149
+ */
150
+ public isAnimating(element: Element): boolean {
151
+ return this.activeAnimations.has(element);
152
+ }
153
+
154
+ /**
155
+ * Destroy animation system
156
+ */
157
+ public destroy(): void {
158
+ this.cancelAll();
159
+ this.animationQueue = [];
160
+ }
161
+
162
+ /**
163
+ * Calculate invert values
164
+ */
165
+ private calculateInvert(first: DOMRect, last: DOMRect): {
166
+ x: number;
167
+ y: number;
168
+ scaleX: number;
169
+ scaleY: number;
170
+ } {
171
+ return {
172
+ x: first.left - last.left,
173
+ y: first.top - last.top,
174
+ scaleX: first.width / last.width,
175
+ scaleY: first.height / last.height
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Play FLIP animation
181
+ */
182
+ private async playAnimation(
183
+ element: Element,
184
+ invert: { x: number; y: number; scaleX: number; scaleY: number },
185
+ config: Required<FLIPConfig>
186
+ ): Promise<void> {
187
+ return new Promise((resolve, reject) => {
188
+ const htmlElement = element as HTMLElement;
189
+
190
+ // Apply initial transform (inverted position)
191
+ const initialTransform = `translate(${invert.x}px, ${invert.y}px) scale(${invert.scaleX}, ${invert.scaleY})`;
192
+ htmlElement.style.transform = initialTransform;
193
+
194
+ // Create animation
195
+ const animation = htmlElement.animate([
196
+ { transform: initialTransform },
197
+ { transform: 'translate(0, 0) scale(1, 1)' }
198
+ ], {
199
+ duration: config.duration,
200
+ easing: config.easing,
201
+ fill: 'forwards'
202
+ });
203
+
204
+ // Store animation state
205
+ const flipState: FLIPState = {
206
+ element,
207
+ first: { x: 0, y: 0, width: 0, height: 0 } as DOMRect,
208
+ last: { x: 0, y: 0, width: 0, height: 0 } as DOMRect,
209
+ invert,
210
+ player: animation
211
+ };
212
+
213
+ this.activeAnimations.set(element, flipState);
214
+
215
+ // Handle animation completion
216
+ animation.addEventListener('finish', () => {
217
+ htmlElement.style.transform = '';
218
+ this.activeAnimations.delete(element);
219
+ resolve();
220
+ });
221
+
222
+ animation.addEventListener('cancel', () => {
223
+ htmlElement.style.transform = '';
224
+ this.activeAnimations.delete(element);
225
+ reject(new Error('Animation cancelled'));
226
+ });
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Check if animations should be skipped
232
+ */
233
+ private shouldSkipAnimation(): boolean {
234
+ if (this.config.respectMotionPreference) {
235
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
236
+ if (prefersReducedMotion) {
237
+ return true;
238
+ }
239
+ }
240
+
241
+ if (this.config.performanceMode === 'performance') {
242
+ return this.activeAnimations.size >= this.config.maxConcurrentAnimations;
243
+ }
244
+
245
+ return false;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Micro-interactions system for subtle UI feedback
251
+ */
252
+ export class MicroInteractions {
253
+ private static readonly INTERACTIONS = {
254
+ hover: {
255
+ scale: 1.05,
256
+ duration: 200,
257
+ easing: 'ease-out'
258
+ },
259
+ press: {
260
+ scale: 0.95,
261
+ duration: 100,
262
+ easing: 'ease-in'
263
+ },
264
+ focus: {
265
+ scale: 1.02,
266
+ duration: 150,
267
+ easing: 'ease-out'
268
+ }
269
+ };
270
+
271
+ /**
272
+ * Add hover micro-interaction
273
+ */
274
+ public static addHover(element: Element, options: Partial<typeof MicroInteractions.INTERACTIONS.hover> = {}): void {
275
+ const config = { ...MicroInteractions.INTERACTIONS.hover, ...options };
276
+ const htmlElement = element as HTMLElement;
277
+
278
+ htmlElement.addEventListener('mouseenter', () => {
279
+ htmlElement.style.transition = `transform ${config.duration}ms ${config.easing}`;
280
+ htmlElement.style.transform = `scale(${config.scale})`;
281
+ });
282
+
283
+ htmlElement.addEventListener('mouseleave', () => {
284
+ htmlElement.style.transform = 'scale(1)';
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Add press micro-interaction
290
+ */
291
+ public static addPress(element: Element, options: Partial<typeof MicroInteractions.INTERACTIONS.press> = {}): void {
292
+ const config = { ...MicroInteractions.INTERACTIONS.press, ...options };
293
+ const htmlElement = element as HTMLElement;
294
+
295
+ htmlElement.addEventListener('mousedown', () => {
296
+ htmlElement.style.transition = `transform ${config.duration}ms ${config.easing}`;
297
+ htmlElement.style.transform = `scale(${config.scale})`;
298
+ });
299
+
300
+ htmlElement.addEventListener('mouseup', () => {
301
+ htmlElement.style.transform = 'scale(1)';
302
+ });
303
+
304
+ htmlElement.addEventListener('mouseleave', () => {
305
+ htmlElement.style.transform = 'scale(1)';
306
+ });
307
+ }
308
+
309
+ /**
310
+ * Add focus micro-interaction
311
+ */
312
+ public static addFocus(element: Element, options: Partial<typeof MicroInteractions.INTERACTIONS.focus> = {}): void {
313
+ const config = { ...MicroInteractions.INTERACTIONS.focus, ...options };
314
+ const htmlElement = element as HTMLElement;
315
+
316
+ htmlElement.addEventListener('focus', () => {
317
+ htmlElement.style.transition = `transform ${config.duration}ms ${config.easing}`;
318
+ htmlElement.style.transform = `scale(${config.scale})`;
319
+ });
320
+
321
+ htmlElement.addEventListener('blur', () => {
322
+ htmlElement.style.transform = 'scale(1)';
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Add ripple effect
328
+ */
329
+ public static addRipple(element: Element, color: string = 'rgba(255, 255, 255, 0.3)'): void {
330
+ const htmlElement = element as HTMLElement;
331
+ htmlElement.style.position = 'relative';
332
+ htmlElement.style.overflow = 'hidden';
333
+
334
+ htmlElement.addEventListener('click', (event) => {
335
+ const rect = htmlElement.getBoundingClientRect();
336
+ const size = Math.max(rect.width, rect.height);
337
+ const x = event.clientX - rect.left - size / 2;
338
+ const y = event.clientY - rect.top - size / 2;
339
+
340
+ const ripple = document.createElement('div');
341
+ ripple.style.cssText = `
342
+ position: absolute;
343
+ width: ${size}px;
344
+ height: ${size}px;
345
+ left: ${x}px;
346
+ top: ${y}px;
347
+ background: ${color};
348
+ border-radius: 50%;
349
+ transform: scale(0);
350
+ animation: ripple 600ms ease-out;
351
+ pointer-events: none;
352
+ `;
353
+
354
+ // Add ripple animation CSS if not exists
355
+ if (!document.getElementById('ripple-animation')) {
356
+ const style = document.createElement('style');
357
+ style.id = 'ripple-animation';
358
+ style.textContent = `
359
+ @keyframes ripple {
360
+ to {
361
+ transform: scale(4);
362
+ opacity: 0;
363
+ }
364
+ }
365
+ `;
366
+ document.head.appendChild(style);
367
+ }
368
+
369
+ htmlElement.appendChild(ripple);
370
+
371
+ setTimeout(() => {
372
+ ripple.remove();
373
+ }, 600);
374
+ });
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Scroll-based animations system
380
+ */
381
+ export class ScrollAnimations {
382
+ private observer: IntersectionObserver | null = null;
383
+ private animations: Map<Element, () => void> = new Map();
384
+
385
+ constructor() {
386
+ this.setupIntersectionObserver();
387
+ }
388
+
389
+ /**
390
+ * Add scroll-triggered animation
391
+ */
392
+ public addAnimation(
393
+ element: Element,
394
+ animation: () => void,
395
+ options: IntersectionObserverInit = {}
396
+ ): void {
397
+ this.animations.set(element, animation);
398
+
399
+ if (this.observer) {
400
+ this.observer.observe(element);
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Remove scroll animation
406
+ */
407
+ public removeAnimation(element: Element): void {
408
+ this.animations.delete(element);
409
+
410
+ if (this.observer) {
411
+ this.observer.unobserve(element);
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Destroy scroll animations
417
+ */
418
+ public destroy(): void {
419
+ this.observer?.disconnect();
420
+ this.animations.clear();
421
+ }
422
+
423
+ /**
424
+ * Setup intersection observer
425
+ */
426
+ private setupIntersectionObserver(): void {
427
+ if (!window.IntersectionObserver) {
428
+ console.warn('IntersectionObserver not supported');
429
+ return;
430
+ }
431
+
432
+ this.observer = new IntersectionObserver((entries) => {
433
+ entries.forEach(entry => {
434
+ if (entry.isIntersecting) {
435
+ const animation = this.animations.get(entry.target);
436
+ if (animation) {
437
+ animation();
438
+ // Remove one-time animations
439
+ this.removeAnimation(entry.target);
440
+ }
441
+ }
442
+ });
443
+ }, {
444
+ threshold: 0.1,
445
+ rootMargin: '50px'
446
+ });
447
+ }
448
+
449
+ /**
450
+ * Fade in animation
451
+ */
452
+ public static fadeIn(element: Element, duration: number = 600): void {
453
+ const htmlElement = element as HTMLElement;
454
+ htmlElement.style.opacity = '0';
455
+ htmlElement.style.transition = `opacity ${duration}ms ease-in-out`;
456
+
457
+ requestAnimationFrame(() => {
458
+ htmlElement.style.opacity = '1';
459
+ });
460
+ }
461
+
462
+ /**
463
+ * Slide up animation
464
+ */
465
+ public static slideUp(element: Element, duration: number = 600): void {
466
+ const htmlElement = element as HTMLElement;
467
+ htmlElement.style.transform = 'translateY(50px)';
468
+ htmlElement.style.opacity = '0';
469
+ htmlElement.style.transition = `transform ${duration}ms ease-out, opacity ${duration}ms ease-out`;
470
+
471
+ requestAnimationFrame(() => {
472
+ htmlElement.style.transform = 'translateY(0)';
473
+ htmlElement.style.opacity = '1';
474
+ });
475
+ }
476
+
477
+ /**
478
+ * Scale in animation
479
+ */
480
+ public static scaleIn(element: Element, duration: number = 600): void {
481
+ const htmlElement = element as HTMLElement;
482
+ htmlElement.style.transform = 'scale(0.8)';
483
+ htmlElement.style.opacity = '0';
484
+ htmlElement.style.transition = `transform ${duration}ms ease-out, opacity ${duration}ms ease-out`;
485
+
486
+ requestAnimationFrame(() => {
487
+ htmlElement.style.transform = 'scale(1)';
488
+ htmlElement.style.opacity = '1';
489
+ });
490
+ }
491
+ }