@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.
- package/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,406 @@
|
|
1
|
+
/**
|
2
|
+
* Enhanced Flexbox System for ProteusJS
|
3
|
+
* Container query integration with intelligent flex calculations
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface FlexboxConfig {
|
7
|
+
direction: 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
8
|
+
wrap: 'nowrap' | 'wrap' | 'wrap-reverse' | 'auto';
|
9
|
+
justify: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly';
|
10
|
+
align: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
|
11
|
+
gap: number | 'fluid';
|
12
|
+
responsive: boolean;
|
13
|
+
autoWrap: boolean;
|
14
|
+
minItemWidth?: number;
|
15
|
+
maxItemWidth?: number;
|
16
|
+
itemGrowRatio?: number;
|
17
|
+
itemShrinkRatio?: number;
|
18
|
+
breakpoints?: Record<string, Partial<FlexboxConfig>>;
|
19
|
+
}
|
20
|
+
|
21
|
+
export interface FlexItemConfig {
|
22
|
+
grow: number;
|
23
|
+
shrink: number;
|
24
|
+
basis: string | number;
|
25
|
+
order?: number;
|
26
|
+
alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
|
27
|
+
responsive?: boolean;
|
28
|
+
}
|
29
|
+
|
30
|
+
export interface FlexboxState {
|
31
|
+
containerWidth: number;
|
32
|
+
containerHeight: number;
|
33
|
+
itemCount: number;
|
34
|
+
wrappedLines: number;
|
35
|
+
optimalItemWidth: number;
|
36
|
+
actualGap: number;
|
37
|
+
overflow: boolean;
|
38
|
+
}
|
39
|
+
|
40
|
+
export class FlexboxEnhancer {
|
41
|
+
private element: Element;
|
42
|
+
private config: Required<FlexboxConfig>;
|
43
|
+
private state: FlexboxState;
|
44
|
+
private resizeObserver: ResizeObserver | null = null;
|
45
|
+
private mutationObserver: MutationObserver | null = null;
|
46
|
+
|
47
|
+
constructor(element: Element, config: Partial<FlexboxConfig> = {}) {
|
48
|
+
this.element = element;
|
49
|
+
this.config = {
|
50
|
+
direction: 'row',
|
51
|
+
wrap: 'auto',
|
52
|
+
justify: 'flex-start',
|
53
|
+
align: 'stretch',
|
54
|
+
gap: 16,
|
55
|
+
responsive: true,
|
56
|
+
autoWrap: true,
|
57
|
+
minItemWidth: 200,
|
58
|
+
maxItemWidth: 800,
|
59
|
+
itemGrowRatio: 1,
|
60
|
+
itemShrinkRatio: 1,
|
61
|
+
breakpoints: {},
|
62
|
+
...config
|
63
|
+
};
|
64
|
+
|
65
|
+
this.state = this.createInitialState();
|
66
|
+
this.setupFlexbox();
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Activate the enhanced flexbox
|
71
|
+
*/
|
72
|
+
public activate(): void {
|
73
|
+
this.updateFlexbox();
|
74
|
+
this.setupObservers();
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Deactivate and clean up
|
79
|
+
*/
|
80
|
+
public deactivate(): void {
|
81
|
+
this.cleanupObservers();
|
82
|
+
this.removeFlexboxStyles();
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Update flexbox configuration
|
87
|
+
*/
|
88
|
+
public updateConfig(newConfig: Partial<FlexboxConfig>): void {
|
89
|
+
this.config = { ...this.config, ...newConfig };
|
90
|
+
this.updateFlexbox();
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Get current flexbox state
|
95
|
+
*/
|
96
|
+
public getState(): FlexboxState {
|
97
|
+
return { ...this.state };
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Configure individual flex item
|
102
|
+
*/
|
103
|
+
public configureItem(item: Element, config: FlexItemConfig): void {
|
104
|
+
const htmlItem = item as HTMLElement;
|
105
|
+
|
106
|
+
htmlItem.style.flexGrow = config.grow.toString();
|
107
|
+
htmlItem.style.flexShrink = config.shrink.toString();
|
108
|
+
htmlItem.style.flexBasis = typeof config.basis === 'number'
|
109
|
+
? `${config.basis}px`
|
110
|
+
: config.basis;
|
111
|
+
|
112
|
+
if (config.order !== undefined) {
|
113
|
+
htmlItem.style.order = config.order.toString();
|
114
|
+
}
|
115
|
+
|
116
|
+
if (config.alignSelf) {
|
117
|
+
htmlItem.style.alignSelf = config.alignSelf;
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Auto-configure all flex items
|
123
|
+
*/
|
124
|
+
public autoConfigureItems(): void {
|
125
|
+
const items = Array.from(this.element.children) as HTMLElement[];
|
126
|
+
const optimalConfig = this.calculateOptimalItemConfig();
|
127
|
+
|
128
|
+
items.forEach(item => {
|
129
|
+
this.configureItem(item, optimalConfig);
|
130
|
+
});
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Detect if wrapping is needed
|
135
|
+
*/
|
136
|
+
public shouldWrap(): boolean {
|
137
|
+
if (!this.config.autoWrap) return this.config.wrap === 'wrap';
|
138
|
+
|
139
|
+
const containerWidth = this.element.getBoundingClientRect().width;
|
140
|
+
const itemCount = this.element.children.length;
|
141
|
+
const gap = this.getGapValue();
|
142
|
+
const minItemWidth = this.config.minItemWidth || 200;
|
143
|
+
|
144
|
+
const totalMinWidth = (itemCount * minItemWidth) + (gap * (itemCount - 1));
|
145
|
+
return totalMinWidth > containerWidth;
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Calculate optimal space distribution
|
150
|
+
*/
|
151
|
+
public calculateSpaceDistribution(): {
|
152
|
+
itemWidth: number;
|
153
|
+
gap: number;
|
154
|
+
remainingSpace: number;
|
155
|
+
} {
|
156
|
+
const containerWidth = this.element.getBoundingClientRect().width;
|
157
|
+
const itemCount = this.element.children.length;
|
158
|
+
const gap = this.getGapValue();
|
159
|
+
|
160
|
+
if (this.shouldWrap()) {
|
161
|
+
// Calculate for wrapped layout
|
162
|
+
const itemsPerLine = this.calculateItemsPerLine();
|
163
|
+
const availableWidth = containerWidth - (gap * (itemsPerLine - 1));
|
164
|
+
const itemWidth = availableWidth / itemsPerLine;
|
165
|
+
|
166
|
+
return {
|
167
|
+
itemWidth,
|
168
|
+
gap,
|
169
|
+
remainingSpace: 0
|
170
|
+
};
|
171
|
+
} else {
|
172
|
+
// Calculate for single line
|
173
|
+
const totalGapSpace = gap * (itemCount - 1);
|
174
|
+
const availableWidth = containerWidth - totalGapSpace;
|
175
|
+
const itemWidth = availableWidth / itemCount;
|
176
|
+
|
177
|
+
return {
|
178
|
+
itemWidth,
|
179
|
+
gap,
|
180
|
+
remainingSpace: Math.max(0, containerWidth - (itemCount * itemWidth + totalGapSpace))
|
181
|
+
};
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
/**
|
186
|
+
* Calculate items per line for wrapped layout
|
187
|
+
*/
|
188
|
+
public calculateItemsPerLine(): number {
|
189
|
+
const containerWidth = this.element.getBoundingClientRect().width;
|
190
|
+
const minItemWidth = this.config.minItemWidth || 200;
|
191
|
+
const gap = this.getGapValue();
|
192
|
+
|
193
|
+
// Binary search for optimal items per line
|
194
|
+
let min = 1;
|
195
|
+
let max = this.element.children.length;
|
196
|
+
let optimal = 1;
|
197
|
+
|
198
|
+
while (min <= max) {
|
199
|
+
const mid = Math.floor((min + max) / 2);
|
200
|
+
const totalWidth = (mid * minItemWidth) + (gap * (mid - 1));
|
201
|
+
|
202
|
+
if (totalWidth <= containerWidth) {
|
203
|
+
optimal = mid;
|
204
|
+
min = mid + 1;
|
205
|
+
} else {
|
206
|
+
max = mid - 1;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
return optimal;
|
211
|
+
}
|
212
|
+
|
213
|
+
/**
|
214
|
+
* Setup initial flexbox styles
|
215
|
+
*/
|
216
|
+
private setupFlexbox(): void {
|
217
|
+
const htmlElement = this.element as HTMLElement;
|
218
|
+
htmlElement.style.display = 'flex';
|
219
|
+
this.applyFlexboxStyles();
|
220
|
+
}
|
221
|
+
|
222
|
+
/**
|
223
|
+
* Update flexbox layout
|
224
|
+
*/
|
225
|
+
private updateFlexbox(): void {
|
226
|
+
const containerRect = this.element.getBoundingClientRect();
|
227
|
+
const activeConfig = this.getActiveConfig(containerRect.width);
|
228
|
+
|
229
|
+
// Update state
|
230
|
+
this.state = {
|
231
|
+
containerWidth: containerRect.width,
|
232
|
+
containerHeight: containerRect.height,
|
233
|
+
itemCount: this.element.children.length,
|
234
|
+
wrappedLines: this.calculateWrappedLines(),
|
235
|
+
optimalItemWidth: this.calculateSpaceDistribution().itemWidth,
|
236
|
+
actualGap: this.getGapValue(),
|
237
|
+
overflow: this.checkOverflow()
|
238
|
+
};
|
239
|
+
|
240
|
+
// Apply styles with active config
|
241
|
+
this.applyFlexboxStyles(activeConfig);
|
242
|
+
|
243
|
+
// Auto-configure items if enabled
|
244
|
+
if (this.config.autoWrap) {
|
245
|
+
this.autoConfigureItems();
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* Apply flexbox styles
|
251
|
+
*/
|
252
|
+
private applyFlexboxStyles(config: Required<FlexboxConfig> = this.config): void {
|
253
|
+
const htmlElement = this.element as HTMLElement;
|
254
|
+
|
255
|
+
htmlElement.style.flexDirection = config.direction;
|
256
|
+
htmlElement.style.flexWrap = config.wrap === 'auto'
|
257
|
+
? (this.shouldWrap() ? 'wrap' : 'nowrap')
|
258
|
+
: config.wrap;
|
259
|
+
htmlElement.style.justifyContent = config.justify;
|
260
|
+
htmlElement.style.alignItems = config.align;
|
261
|
+
htmlElement.style.gap = `${this.getGapValue()}px`;
|
262
|
+
}
|
263
|
+
|
264
|
+
/**
|
265
|
+
* Calculate optimal item configuration
|
266
|
+
*/
|
267
|
+
private calculateOptimalItemConfig(): FlexItemConfig {
|
268
|
+
const spaceDistribution = this.calculateSpaceDistribution();
|
269
|
+
const isWrapped = this.shouldWrap();
|
270
|
+
|
271
|
+
return {
|
272
|
+
grow: isWrapped ? 0 : this.config.itemGrowRatio,
|
273
|
+
shrink: this.config.itemShrinkRatio,
|
274
|
+
basis: isWrapped ? `${spaceDistribution.itemWidth}px` : 'auto',
|
275
|
+
alignSelf: 'auto'
|
276
|
+
};
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Calculate number of wrapped lines
|
281
|
+
*/
|
282
|
+
private calculateWrappedLines(): number {
|
283
|
+
if (!this.shouldWrap()) return 1;
|
284
|
+
|
285
|
+
const itemsPerLine = this.calculateItemsPerLine();
|
286
|
+
return Math.ceil(this.element.children.length / itemsPerLine);
|
287
|
+
}
|
288
|
+
|
289
|
+
/**
|
290
|
+
* Check if content overflows
|
291
|
+
*/
|
292
|
+
private checkOverflow(): boolean {
|
293
|
+
const containerWidth = this.element.getBoundingClientRect().width;
|
294
|
+
const spaceDistribution = this.calculateSpaceDistribution();
|
295
|
+
const totalRequiredWidth = this.element.children.length * spaceDistribution.itemWidth +
|
296
|
+
(this.element.children.length - 1) * spaceDistribution.gap;
|
297
|
+
|
298
|
+
return totalRequiredWidth > containerWidth && !this.shouldWrap();
|
299
|
+
}
|
300
|
+
|
301
|
+
/**
|
302
|
+
* Get active configuration based on container width
|
303
|
+
*/
|
304
|
+
private getActiveConfig(containerWidth: number): Required<FlexboxConfig> {
|
305
|
+
let activeConfig = { ...this.config };
|
306
|
+
|
307
|
+
if (this.config.breakpoints) {
|
308
|
+
const sortedBreakpoints = Object.entries(this.config.breakpoints)
|
309
|
+
.map(([name, config]) => ({ name, width: parseInt(name), config }))
|
310
|
+
.sort((a, b) => a.width - b.width);
|
311
|
+
|
312
|
+
for (const breakpoint of sortedBreakpoints) {
|
313
|
+
if (containerWidth >= breakpoint.width) {
|
314
|
+
activeConfig = { ...activeConfig, ...breakpoint.config };
|
315
|
+
}
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
return activeConfig;
|
320
|
+
}
|
321
|
+
|
322
|
+
/**
|
323
|
+
* Get gap value in pixels
|
324
|
+
*/
|
325
|
+
private getGapValue(): number {
|
326
|
+
if (this.config.gap === 'fluid') {
|
327
|
+
const containerWidth = this.element.getBoundingClientRect().width;
|
328
|
+
return Math.max(8, Math.min(32, containerWidth * 0.02));
|
329
|
+
}
|
330
|
+
return this.config.gap as number;
|
331
|
+
}
|
332
|
+
|
333
|
+
/**
|
334
|
+
* Setup observers for responsive behavior
|
335
|
+
*/
|
336
|
+
private setupObservers(): void {
|
337
|
+
if (!this.config.responsive) return;
|
338
|
+
|
339
|
+
this.resizeObserver = new ResizeObserver(() => {
|
340
|
+
this.updateFlexbox();
|
341
|
+
});
|
342
|
+
this.resizeObserver.observe(this.element);
|
343
|
+
|
344
|
+
this.mutationObserver = new MutationObserver(() => {
|
345
|
+
this.updateFlexbox();
|
346
|
+
});
|
347
|
+
this.mutationObserver.observe(this.element, {
|
348
|
+
childList: true,
|
349
|
+
subtree: false
|
350
|
+
});
|
351
|
+
}
|
352
|
+
|
353
|
+
/**
|
354
|
+
* Clean up observers
|
355
|
+
*/
|
356
|
+
private cleanupObservers(): void {
|
357
|
+
if (this.resizeObserver) {
|
358
|
+
this.resizeObserver.disconnect();
|
359
|
+
this.resizeObserver = null;
|
360
|
+
}
|
361
|
+
|
362
|
+
if (this.mutationObserver) {
|
363
|
+
this.mutationObserver.disconnect();
|
364
|
+
this.mutationObserver = null;
|
365
|
+
}
|
366
|
+
}
|
367
|
+
|
368
|
+
/**
|
369
|
+
* Remove flexbox styles
|
370
|
+
*/
|
371
|
+
private removeFlexboxStyles(): void {
|
372
|
+
const htmlElement = this.element as HTMLElement;
|
373
|
+
|
374
|
+
htmlElement.style.removeProperty('display');
|
375
|
+
htmlElement.style.removeProperty('flex-direction');
|
376
|
+
htmlElement.style.removeProperty('flex-wrap');
|
377
|
+
htmlElement.style.removeProperty('justify-content');
|
378
|
+
htmlElement.style.removeProperty('align-items');
|
379
|
+
htmlElement.style.removeProperty('gap');
|
380
|
+
|
381
|
+
// Remove item styles
|
382
|
+
Array.from(this.element.children).forEach(child => {
|
383
|
+
const htmlChild = child as HTMLElement;
|
384
|
+
htmlChild.style.removeProperty('flex-grow');
|
385
|
+
htmlChild.style.removeProperty('flex-shrink');
|
386
|
+
htmlChild.style.removeProperty('flex-basis');
|
387
|
+
htmlChild.style.removeProperty('order');
|
388
|
+
htmlChild.style.removeProperty('align-self');
|
389
|
+
});
|
390
|
+
}
|
391
|
+
|
392
|
+
/**
|
393
|
+
* Create initial state
|
394
|
+
*/
|
395
|
+
private createInitialState(): FlexboxState {
|
396
|
+
return {
|
397
|
+
containerWidth: 0,
|
398
|
+
containerHeight: 0,
|
399
|
+
itemCount: 0,
|
400
|
+
wrappedLines: 1,
|
401
|
+
optimalItemWidth: 0,
|
402
|
+
actualGap: 16,
|
403
|
+
overflow: false
|
404
|
+
};
|
405
|
+
}
|
406
|
+
}
|