@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,532 @@
1
+ /**
2
+ * Smart Content Reordering for ProteusJS
3
+ * Priority-based reordering with accessibility and FLIP animations
4
+ */
5
+
6
+ export interface ReorderConfig {
7
+ priorities: Map<Element, number>;
8
+ breakpoints: Record<string, ReorderRule[]>;
9
+ accessibility: boolean;
10
+ animations: boolean;
11
+ focusManagement: boolean;
12
+ preserveTabOrder: boolean;
13
+ animationDuration: number;
14
+ easing: string;
15
+ }
16
+
17
+ export interface ReorderRule {
18
+ selector: string;
19
+ priority: number;
20
+ condition?: 'min-width' | 'max-width' | 'aspect-ratio';
21
+ value?: number;
22
+ action: 'move-first' | 'move-last' | 'move-before' | 'move-after' | 'hide' | 'show';
23
+ target?: string;
24
+ }
25
+
26
+ export interface FLIPState {
27
+ element: Element;
28
+ first: DOMRect;
29
+ last: DOMRect;
30
+ invert: { x: number; y: number };
31
+ play: boolean;
32
+ }
33
+
34
+ export interface ReorderState {
35
+ originalOrder: Element[];
36
+ currentOrder: Element[];
37
+ activeRules: ReorderRule[];
38
+ focusedElement: Element | null;
39
+ animating: boolean;
40
+ flipStates: Map<Element, FLIPState>;
41
+ }
42
+
43
+ export class ContentReordering {
44
+ private element: Element;
45
+ private config: Required<ReorderConfig>;
46
+ private state: ReorderState;
47
+ private resizeObserver: ResizeObserver | null = null;
48
+ private mutationObserver: MutationObserver | null = null;
49
+
50
+ constructor(element: Element, config: Partial<ReorderConfig> = {}) {
51
+ this.element = element;
52
+ this.config = {
53
+ priorities: new Map(),
54
+ breakpoints: {},
55
+ accessibility: true,
56
+ animations: true,
57
+ focusManagement: true,
58
+ preserveTabOrder: true,
59
+ animationDuration: 300,
60
+ easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
61
+ ...config
62
+ };
63
+
64
+ this.state = this.createInitialState();
65
+ this.setupReordering();
66
+ }
67
+
68
+ /**
69
+ * Activate content reordering
70
+ */
71
+ public activate(): void {
72
+ this.captureOriginalOrder();
73
+ this.applyReordering();
74
+ this.setupObservers();
75
+ }
76
+
77
+ /**
78
+ * Deactivate and restore original order
79
+ */
80
+ public deactivate(): void {
81
+ this.cleanupObservers();
82
+ this.restoreOriginalOrder();
83
+ }
84
+
85
+ /**
86
+ * Update reordering configuration
87
+ */
88
+ public updateConfig(newConfig: Partial<ReorderConfig>): void {
89
+ this.config = { ...this.config, ...newConfig };
90
+ this.applyReordering();
91
+ }
92
+
93
+ /**
94
+ * Set element priority
95
+ */
96
+ public setPriority(element: Element, priority: number): void {
97
+ this.config.priorities.set(element, priority);
98
+ this.applyReordering();
99
+ }
100
+
101
+ /**
102
+ * Add reorder rule
103
+ */
104
+ public addRule(breakpoint: string, rule: ReorderRule): void {
105
+ if (!this.config.breakpoints[breakpoint]) {
106
+ this.config.breakpoints[breakpoint] = [];
107
+ }
108
+ this.config.breakpoints[breakpoint].push(rule);
109
+ this.applyReordering();
110
+ }
111
+
112
+ /**
113
+ * Get current reorder state
114
+ */
115
+ public getState(): ReorderState {
116
+ return { ...this.state };
117
+ }
118
+
119
+ /**
120
+ * Manually reorder elements
121
+ */
122
+ public reorder(newOrder: Element[]): void {
123
+ if (this.config.animations) {
124
+ this.animateReorder(newOrder);
125
+ } else {
126
+ this.applyOrder(newOrder);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Restore original order
132
+ */
133
+ public restoreOriginalOrder(): void {
134
+ this.reorder(this.state.originalOrder);
135
+ }
136
+
137
+ /**
138
+ * Setup initial reordering
139
+ */
140
+ private setupReordering(): void {
141
+ this.captureOriginalOrder();
142
+ }
143
+
144
+ /**
145
+ * Capture the original DOM order
146
+ */
147
+ private captureOriginalOrder(): void {
148
+ this.state.originalOrder = Array.from(this.element.children);
149
+ this.state.currentOrder = [...this.state.originalOrder];
150
+ }
151
+
152
+ /**
153
+ * Apply reordering based on current configuration
154
+ */
155
+ private applyReordering(): void {
156
+ const containerWidth = this.element.getBoundingClientRect().width;
157
+ const activeRules = this.getActiveRules(containerWidth);
158
+
159
+ this.state.activeRules = activeRules;
160
+
161
+ // Calculate new order
162
+ const newOrder = this.calculateNewOrder(activeRules);
163
+
164
+ // Apply reordering
165
+ if (this.config.animations && !this.arraysEqual(newOrder, this.state.currentOrder)) {
166
+ this.animateReorder(newOrder);
167
+ } else {
168
+ this.applyOrder(newOrder);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Get active rules for current container width
174
+ */
175
+ private getActiveRules(containerWidth: number): ReorderRule[] {
176
+ const activeRules: ReorderRule[] = [];
177
+
178
+ Object.entries(this.config.breakpoints).forEach(([breakpoint, rules]) => {
179
+ const breakpointWidth = parseInt(breakpoint);
180
+
181
+ if (containerWidth >= breakpointWidth) {
182
+ activeRules.push(...rules);
183
+ }
184
+ });
185
+
186
+ return activeRules;
187
+ }
188
+
189
+ /**
190
+ * Calculate new element order
191
+ */
192
+ private calculateNewOrder(rules: ReorderRule[]): Element[] {
193
+ let newOrder = [...this.state.originalOrder];
194
+
195
+ // Apply priority-based sorting first
196
+ if (this.config.priorities.size > 0) {
197
+ newOrder.sort((a, b) => {
198
+ const priorityA = this.config.priorities.get(a) || 0;
199
+ const priorityB = this.config.priorities.get(b) || 0;
200
+ return priorityA - priorityB;
201
+ });
202
+ }
203
+
204
+ // Apply rules
205
+ rules.forEach(rule => {
206
+ const elements = Array.from(this.element.querySelectorAll(rule.selector));
207
+
208
+ elements.forEach(element => {
209
+ if (this.shouldApplyRule(rule)) {
210
+ newOrder = this.applyRule(newOrder, element, rule);
211
+ }
212
+ });
213
+ });
214
+
215
+ return newOrder;
216
+ }
217
+
218
+ /**
219
+ * Check if rule should be applied
220
+ */
221
+ private shouldApplyRule(rule: ReorderRule): boolean {
222
+ if (!rule.condition || !rule.value) return true;
223
+
224
+ const containerRect = this.element.getBoundingClientRect();
225
+
226
+ switch (rule.condition) {
227
+ case 'min-width':
228
+ return containerRect.width >= rule.value;
229
+ case 'max-width':
230
+ return containerRect.width <= rule.value;
231
+ case 'aspect-ratio':
232
+ return (containerRect.width / containerRect.height) >= rule.value;
233
+ default:
234
+ return true;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Apply a single reorder rule
240
+ */
241
+ private applyRule(order: Element[], element: Element, rule: ReorderRule): Element[] {
242
+ const currentIndex = order.indexOf(element);
243
+ if (currentIndex === -1) return order;
244
+
245
+ const newOrder = [...order];
246
+ newOrder.splice(currentIndex, 1); // Remove element
247
+
248
+ switch (rule.action) {
249
+ case 'move-first':
250
+ newOrder.unshift(element);
251
+ break;
252
+ case 'move-last':
253
+ newOrder.push(element);
254
+ break;
255
+ case 'move-before':
256
+ if (rule.target) {
257
+ const targetElement = this.element.querySelector(rule.target);
258
+ const targetIndex = newOrder.indexOf(targetElement as Element);
259
+ if (targetIndex !== -1) {
260
+ newOrder.splice(targetIndex, 0, element);
261
+ } else {
262
+ newOrder.push(element);
263
+ }
264
+ }
265
+ break;
266
+ case 'move-after':
267
+ if (rule.target) {
268
+ const targetElement = this.element.querySelector(rule.target);
269
+ const targetIndex = newOrder.indexOf(targetElement as Element);
270
+ if (targetIndex !== -1) {
271
+ newOrder.splice(targetIndex + 1, 0, element);
272
+ } else {
273
+ newOrder.push(element);
274
+ }
275
+ }
276
+ break;
277
+ case 'hide':
278
+ (element as HTMLElement).style.display = 'none';
279
+ newOrder.push(element); // Keep in DOM but hidden
280
+ break;
281
+ case 'show':
282
+ (element as HTMLElement).style.display = '';
283
+ newOrder.push(element);
284
+ break;
285
+ default:
286
+ newOrder.push(element);
287
+ }
288
+
289
+ return newOrder;
290
+ }
291
+
292
+ /**
293
+ * Animate reordering using FLIP technique
294
+ */
295
+ private animateReorder(newOrder: Element[]): void {
296
+ if (this.state.animating) return;
297
+
298
+ this.state.animating = true;
299
+
300
+ // FLIP: First - record initial positions
301
+ const flipStates = new Map<Element, FLIPState>();
302
+ this.state.currentOrder.forEach(element => {
303
+ flipStates.set(element, {
304
+ element,
305
+ first: element.getBoundingClientRect(),
306
+ last: { x: 0, y: 0, width: 0, height: 0 } as DOMRect,
307
+ invert: { x: 0, y: 0 },
308
+ play: false
309
+ });
310
+ });
311
+
312
+ // Preserve focus
313
+ const focusedElement = this.preserveFocus();
314
+
315
+ // FLIP: Last - apply new order and record final positions
316
+ this.applyOrder(newOrder);
317
+
318
+ flipStates.forEach((flipState, element) => {
319
+ flipState.last = element.getBoundingClientRect();
320
+
321
+ // FLIP: Invert - calculate the difference
322
+ flipState.invert = {
323
+ x: flipState.first.left - flipState.last.left,
324
+ y: flipState.first.top - flipState.last.top
325
+ };
326
+
327
+ // Apply initial transform
328
+ if (flipState.invert.x !== 0 || flipState.invert.y !== 0) {
329
+ (element as HTMLElement).style.transform =
330
+ `translate(${flipState.invert.x}px, ${flipState.invert.y}px)`;
331
+ flipState.play = true;
332
+ }
333
+ });
334
+
335
+ // FLIP: Play - animate to final positions
336
+ requestAnimationFrame(() => {
337
+ flipStates.forEach((flipState, element) => {
338
+ if (flipState.play) {
339
+ const htmlElement = element as HTMLElement;
340
+ htmlElement.style.transition =
341
+ `transform ${this.config.animationDuration}ms ${this.config.easing}`;
342
+ htmlElement.style.transform = 'translate(0, 0)';
343
+ }
344
+ });
345
+
346
+ // Clean up after animation
347
+ setTimeout(() => {
348
+ flipStates.forEach((flipState, element) => {
349
+ const htmlElement = element as HTMLElement;
350
+ htmlElement.style.transition = '';
351
+ htmlElement.style.transform = '';
352
+ });
353
+
354
+ this.state.animating = false;
355
+
356
+ // Restore focus
357
+ this.restoreFocus(focusedElement);
358
+
359
+ // Update accessibility
360
+ if (this.config.accessibility) {
361
+ this.updateAccessibility();
362
+ }
363
+ }, this.config.animationDuration);
364
+ });
365
+
366
+ this.state.flipStates = flipStates;
367
+ }
368
+
369
+ /**
370
+ * Apply new order without animation
371
+ */
372
+ private applyOrder(newOrder: Element[]): void {
373
+ const fragment = document.createDocumentFragment();
374
+
375
+ newOrder.forEach(element => {
376
+ fragment.appendChild(element);
377
+ });
378
+
379
+ this.element.appendChild(fragment);
380
+ this.state.currentOrder = newOrder;
381
+ }
382
+
383
+ /**
384
+ * Preserve focus during reordering
385
+ */
386
+ private preserveFocus(): Element | null {
387
+ if (!this.config.focusManagement) return null;
388
+
389
+ const focusedElement = document.activeElement;
390
+ if (focusedElement && this.element.contains(focusedElement)) {
391
+ this.state.focusedElement = focusedElement;
392
+ return focusedElement;
393
+ }
394
+
395
+ return null;
396
+ }
397
+
398
+ /**
399
+ * Restore focus after reordering
400
+ */
401
+ private restoreFocus(focusedElement: Element | null): void {
402
+ if (!this.config.focusManagement || !focusedElement) return;
403
+
404
+ // Check if element is still focusable
405
+ if (document.contains(focusedElement)) {
406
+ (focusedElement as HTMLElement).focus();
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Update accessibility attributes
412
+ */
413
+ private updateAccessibility(): void {
414
+ if (!this.config.accessibility) return;
415
+
416
+ // Update tab order if preserveTabOrder is enabled
417
+ if (this.config.preserveTabOrder) {
418
+ this.updateTabOrder();
419
+ }
420
+
421
+ // Announce changes to screen readers
422
+ this.announceChanges();
423
+ }
424
+
425
+ /**
426
+ * Update tab order to match visual order
427
+ */
428
+ private updateTabOrder(): void {
429
+ const focusableElements = this.state.currentOrder.filter(element => {
430
+ const htmlElement = element as HTMLElement;
431
+ return htmlElement.tabIndex >= 0 || this.isFocusable(element);
432
+ });
433
+
434
+ focusableElements.forEach((element, index) => {
435
+ (element as HTMLElement).tabIndex = index + 1;
436
+ });
437
+ }
438
+
439
+ /**
440
+ * Check if element is focusable
441
+ */
442
+ private isFocusable(element: Element): boolean {
443
+ const focusableSelectors = [
444
+ 'a[href]',
445
+ 'button:not([disabled])',
446
+ 'input:not([disabled])',
447
+ 'select:not([disabled])',
448
+ 'textarea:not([disabled])',
449
+ '[contenteditable="true"]'
450
+ ];
451
+
452
+ return focusableSelectors.some(selector => element.matches(selector));
453
+ }
454
+
455
+ /**
456
+ * Announce changes to screen readers
457
+ */
458
+ private announceChanges(): void {
459
+ const announcement = document.createElement('div');
460
+ announcement.setAttribute('aria-live', 'polite');
461
+ announcement.setAttribute('aria-atomic', 'true');
462
+ announcement.style.cssText = `
463
+ position: absolute;
464
+ left: -10000px;
465
+ width: 1px;
466
+ height: 1px;
467
+ overflow: hidden;
468
+ `;
469
+ announcement.textContent = 'Content order has been updated';
470
+
471
+ document.body.appendChild(announcement);
472
+
473
+ setTimeout(() => {
474
+ document.body.removeChild(announcement);
475
+ }, 1000);
476
+ }
477
+
478
+ /**
479
+ * Check if two arrays are equal
480
+ */
481
+ private arraysEqual(a: Element[], b: Element[]): boolean {
482
+ return a.length === b.length && a.every((element, index) => element === b[index]);
483
+ }
484
+
485
+ /**
486
+ * Setup observers
487
+ */
488
+ private setupObservers(): void {
489
+ this.resizeObserver = new ResizeObserver(() => {
490
+ this.applyReordering();
491
+ });
492
+ this.resizeObserver.observe(this.element);
493
+
494
+ this.mutationObserver = new MutationObserver(() => {
495
+ this.captureOriginalOrder();
496
+ this.applyReordering();
497
+ });
498
+ this.mutationObserver.observe(this.element, {
499
+ childList: true,
500
+ subtree: false
501
+ });
502
+ }
503
+
504
+ /**
505
+ * Clean up observers
506
+ */
507
+ private cleanupObservers(): void {
508
+ if (this.resizeObserver) {
509
+ this.resizeObserver.disconnect();
510
+ this.resizeObserver = null;
511
+ }
512
+
513
+ if (this.mutationObserver) {
514
+ this.mutationObserver.disconnect();
515
+ this.mutationObserver = null;
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Create initial state
521
+ */
522
+ private createInitialState(): ReorderState {
523
+ return {
524
+ originalOrder: [],
525
+ currentOrder: [],
526
+ activeRules: [],
527
+ focusedElement: null,
528
+ animating: false,
529
+ flipStates: new Map()
530
+ };
531
+ }
532
+ }