@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,411 @@
|
|
1
|
+
/**
|
2
|
+
* Element Queries for ProteusJS
|
3
|
+
* Advanced container-based queries with complex logic support
|
4
|
+
*/
|
5
|
+
|
6
|
+
import type { ElementQuery } from '../types';
|
7
|
+
import { BreakpointSystem } from './BreakpointSystem';
|
8
|
+
|
9
|
+
export interface QueryCondition {
|
10
|
+
property: 'width' | 'height' | 'aspect-ratio' | 'content-size' | 'orientation';
|
11
|
+
operator: 'min' | 'max' | 'exact' | 'range';
|
12
|
+
value: number | string | [number, number];
|
13
|
+
unit?: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
export interface QueryRule {
|
17
|
+
conditions: QueryCondition[];
|
18
|
+
logic: 'and' | 'or';
|
19
|
+
callback?: (matches: boolean, element: Element) => void;
|
20
|
+
cssClass?: string;
|
21
|
+
cssProperties?: Record<string, string>;
|
22
|
+
}
|
23
|
+
|
24
|
+
export interface QueryResult {
|
25
|
+
matches: boolean;
|
26
|
+
matchedConditions: QueryCondition[];
|
27
|
+
failedConditions: QueryCondition[];
|
28
|
+
element: Element;
|
29
|
+
timestamp: number;
|
30
|
+
}
|
31
|
+
|
32
|
+
export class ElementQueries {
|
33
|
+
private queries: Map<Element, QueryRule[]> = new Map();
|
34
|
+
private lastResults: Map<Element, QueryResult[]> = new Map();
|
35
|
+
private breakpointSystem: BreakpointSystem;
|
36
|
+
|
37
|
+
constructor(breakpointSystem: BreakpointSystem) {
|
38
|
+
this.breakpointSystem = breakpointSystem;
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Add element query rule
|
43
|
+
*/
|
44
|
+
public addQuery(
|
45
|
+
element: Element,
|
46
|
+
rule: QueryRule
|
47
|
+
): () => void {
|
48
|
+
if (!this.queries.has(element)) {
|
49
|
+
this.queries.set(element, []);
|
50
|
+
}
|
51
|
+
|
52
|
+
const rules = this.queries.get(element)!;
|
53
|
+
rules.push(rule);
|
54
|
+
|
55
|
+
// Initial evaluation
|
56
|
+
this.evaluateElement(element);
|
57
|
+
|
58
|
+
// Return removal function
|
59
|
+
return () => this.removeQuery(element, rule);
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Remove specific query rule
|
64
|
+
*/
|
65
|
+
public removeQuery(element: Element, rule: QueryRule): boolean {
|
66
|
+
const rules = this.queries.get(element);
|
67
|
+
if (!rules) return false;
|
68
|
+
|
69
|
+
const index = rules.indexOf(rule);
|
70
|
+
if (index === -1) return false;
|
71
|
+
|
72
|
+
rules.splice(index, 1);
|
73
|
+
|
74
|
+
if (rules.length === 0) {
|
75
|
+
this.queries.delete(element);
|
76
|
+
this.lastResults.delete(element);
|
77
|
+
} else {
|
78
|
+
this.evaluateElement(element);
|
79
|
+
}
|
80
|
+
|
81
|
+
return true;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Remove all queries for element
|
86
|
+
*/
|
87
|
+
public removeAllQueries(element: Element): boolean {
|
88
|
+
const hadQueries = this.queries.has(element);
|
89
|
+
this.queries.delete(element);
|
90
|
+
this.lastResults.delete(element);
|
91
|
+
return hadQueries;
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Evaluate all queries for an element
|
96
|
+
*/
|
97
|
+
public evaluateElement(element: Element): QueryResult[] {
|
98
|
+
const rules = this.queries.get(element);
|
99
|
+
if (!rules || rules.length === 0) return [];
|
100
|
+
|
101
|
+
const dimensions = this.getElementDimensions(element);
|
102
|
+
const results: QueryResult[] = [];
|
103
|
+
|
104
|
+
rules.forEach(rule => {
|
105
|
+
const result = this.evaluateRule(element, rule, dimensions);
|
106
|
+
results.push(result);
|
107
|
+
|
108
|
+
// Apply effects if query matches
|
109
|
+
if (result.matches) {
|
110
|
+
this.applyQueryEffects(element, rule);
|
111
|
+
} else {
|
112
|
+
this.removeQueryEffects(element, rule);
|
113
|
+
}
|
114
|
+
|
115
|
+
// Call callback if provided
|
116
|
+
if (rule.callback) {
|
117
|
+
rule.callback(result.matches, element);
|
118
|
+
}
|
119
|
+
});
|
120
|
+
|
121
|
+
this.lastResults.set(element, results);
|
122
|
+
return results;
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Evaluate all elements
|
127
|
+
*/
|
128
|
+
public evaluateAll(): Map<Element, QueryResult[]> {
|
129
|
+
const allResults = new Map<Element, QueryResult[]>();
|
130
|
+
|
131
|
+
this.queries.forEach((rules, element) => {
|
132
|
+
const results = this.evaluateElement(element);
|
133
|
+
allResults.set(element, results);
|
134
|
+
});
|
135
|
+
|
136
|
+
return allResults;
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* Get last evaluation results for element
|
141
|
+
*/
|
142
|
+
public getLastResults(element: Element): QueryResult[] {
|
143
|
+
return this.lastResults.get(element) || [];
|
144
|
+
}
|
145
|
+
|
146
|
+
/**
|
147
|
+
* Check if any query matches for element
|
148
|
+
*/
|
149
|
+
public hasMatches(element: Element): boolean {
|
150
|
+
const results = this.getLastResults(element);
|
151
|
+
return results.some(result => result.matches);
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Get matching queries for element
|
156
|
+
*/
|
157
|
+
public getMatches(element: Element): QueryResult[] {
|
158
|
+
const results = this.getLastResults(element);
|
159
|
+
return results.filter(result => result.matches);
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Create query from CSS-like syntax
|
164
|
+
*/
|
165
|
+
public parseQuery(queryString: string): QueryRule {
|
166
|
+
// Parse CSS-like query syntax: "(min-width: 300px) and (max-height: 500px)"
|
167
|
+
const conditions: QueryCondition[] = [];
|
168
|
+
let logic: 'and' | 'or' = 'and';
|
169
|
+
|
170
|
+
// Simple parser for basic queries
|
171
|
+
const conditionRegex = /\(([^)]+)\)/g;
|
172
|
+
const matches = Array.from(queryString.matchAll(conditionRegex));
|
173
|
+
|
174
|
+
matches.forEach(match => {
|
175
|
+
const conditionStr = match[1]!.trim();
|
176
|
+
const condition = this.parseCondition(conditionStr);
|
177
|
+
if (condition) {
|
178
|
+
conditions.push(condition);
|
179
|
+
}
|
180
|
+
});
|
181
|
+
|
182
|
+
// Detect logic operator
|
183
|
+
if (queryString.includes(' or ')) {
|
184
|
+
logic = 'or';
|
185
|
+
}
|
186
|
+
|
187
|
+
return { conditions, logic };
|
188
|
+
}
|
189
|
+
|
190
|
+
/**
|
191
|
+
* Get query statistics
|
192
|
+
*/
|
193
|
+
public getStats(): object {
|
194
|
+
const totalElements = this.queries.size;
|
195
|
+
const totalQueries = Array.from(this.queries.values())
|
196
|
+
.reduce((sum, rules) => sum + rules.length, 0);
|
197
|
+
|
198
|
+
const matchingElements = Array.from(this.lastResults.entries())
|
199
|
+
.filter(([element, results]) => results.some(r => r.matches))
|
200
|
+
.length;
|
201
|
+
|
202
|
+
return {
|
203
|
+
totalElements,
|
204
|
+
totalQueries,
|
205
|
+
matchingElements,
|
206
|
+
averageQueriesPerElement: totalElements > 0 ? totalQueries / totalElements : 0
|
207
|
+
};
|
208
|
+
}
|
209
|
+
|
210
|
+
/**
|
211
|
+
* Clear all queries
|
212
|
+
*/
|
213
|
+
public clear(): void {
|
214
|
+
// Remove effects from all elements
|
215
|
+
this.queries.forEach((rules, element) => {
|
216
|
+
rules.forEach(rule => {
|
217
|
+
this.removeQueryEffects(element, rule);
|
218
|
+
});
|
219
|
+
});
|
220
|
+
|
221
|
+
this.queries.clear();
|
222
|
+
this.lastResults.clear();
|
223
|
+
}
|
224
|
+
|
225
|
+
/**
|
226
|
+
* Evaluate a single rule against element dimensions
|
227
|
+
*/
|
228
|
+
private evaluateRule(
|
229
|
+
element: Element,
|
230
|
+
rule: QueryRule,
|
231
|
+
dimensions: ElementDimensions
|
232
|
+
): QueryResult {
|
233
|
+
const matchedConditions: QueryCondition[] = [];
|
234
|
+
const failedConditions: QueryCondition[] = [];
|
235
|
+
|
236
|
+
rule.conditions.forEach(condition => {
|
237
|
+
const matches = this.evaluateCondition(condition, dimensions);
|
238
|
+
if (matches) {
|
239
|
+
matchedConditions.push(condition);
|
240
|
+
} else {
|
241
|
+
failedConditions.push(condition);
|
242
|
+
}
|
243
|
+
});
|
244
|
+
|
245
|
+
let overallMatch: boolean;
|
246
|
+
if (rule.logic === 'and') {
|
247
|
+
overallMatch = failedConditions.length === 0;
|
248
|
+
} else {
|
249
|
+
overallMatch = matchedConditions.length > 0;
|
250
|
+
}
|
251
|
+
|
252
|
+
return {
|
253
|
+
matches: overallMatch,
|
254
|
+
matchedConditions,
|
255
|
+
failedConditions,
|
256
|
+
element,
|
257
|
+
timestamp: Date.now()
|
258
|
+
};
|
259
|
+
}
|
260
|
+
|
261
|
+
/**
|
262
|
+
* Evaluate a single condition
|
263
|
+
*/
|
264
|
+
private evaluateCondition(
|
265
|
+
condition: QueryCondition,
|
266
|
+
dimensions: ElementDimensions
|
267
|
+
): boolean {
|
268
|
+
const { property, operator, value } = condition;
|
269
|
+
let targetValue: number;
|
270
|
+
|
271
|
+
switch (property) {
|
272
|
+
case 'width':
|
273
|
+
targetValue = dimensions.width;
|
274
|
+
break;
|
275
|
+
case 'height':
|
276
|
+
targetValue = dimensions.height;
|
277
|
+
break;
|
278
|
+
case 'aspect-ratio':
|
279
|
+
targetValue = dimensions.aspectRatio;
|
280
|
+
break;
|
281
|
+
case 'content-size':
|
282
|
+
targetValue = dimensions.contentSize;
|
283
|
+
break;
|
284
|
+
case 'orientation':
|
285
|
+
targetValue = dimensions.width > dimensions.height ? 1 : 0; // 1 for landscape, 0 for portrait
|
286
|
+
break;
|
287
|
+
default:
|
288
|
+
return false;
|
289
|
+
}
|
290
|
+
|
291
|
+
switch (operator) {
|
292
|
+
case 'min':
|
293
|
+
return targetValue >= (value as number);
|
294
|
+
case 'max':
|
295
|
+
return targetValue <= (value as number);
|
296
|
+
case 'exact':
|
297
|
+
return Math.abs(targetValue - (value as number)) < 1;
|
298
|
+
case 'range':
|
299
|
+
const [min, max] = value as [number, number];
|
300
|
+
return targetValue >= min && targetValue <= max;
|
301
|
+
default:
|
302
|
+
return false;
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
/**
|
307
|
+
* Parse condition string
|
308
|
+
*/
|
309
|
+
private parseCondition(conditionStr: string): QueryCondition | null {
|
310
|
+
// Parse conditions like "min-width: 300px", "aspect-ratio: 16/9"
|
311
|
+
const parts = conditionStr.split(':').map(s => s.trim());
|
312
|
+
if (parts.length !== 2) return null;
|
313
|
+
|
314
|
+
const [propertyOperator, valueStr] = parts;
|
315
|
+
|
316
|
+
// Parse property and operator
|
317
|
+
let property: QueryCondition['property'];
|
318
|
+
let operator: QueryCondition['operator'];
|
319
|
+
|
320
|
+
if (propertyOperator!.startsWith('min-')) {
|
321
|
+
operator = 'min';
|
322
|
+
property = propertyOperator!.substring(4) as any;
|
323
|
+
} else if (propertyOperator!.startsWith('max-')) {
|
324
|
+
operator = 'max';
|
325
|
+
property = propertyOperator!.substring(4) as any;
|
326
|
+
} else {
|
327
|
+
operator = 'exact';
|
328
|
+
property = propertyOperator! as any;
|
329
|
+
}
|
330
|
+
|
331
|
+
// Parse value
|
332
|
+
let value: number | string | [number, number];
|
333
|
+
|
334
|
+
if (property === 'aspect-ratio' && valueStr!.includes('/')) {
|
335
|
+
const [w, h] = valueStr!.split('/').map(Number);
|
336
|
+
value = w! / h!;
|
337
|
+
} else if (valueStr!.includes('-')) {
|
338
|
+
// Range value like "300-500"
|
339
|
+
const [min, max] = valueStr!.split('-').map(s => parseFloat(s));
|
340
|
+
value = [min!, max!];
|
341
|
+
operator = 'range';
|
342
|
+
} else {
|
343
|
+
value = parseFloat(valueStr!);
|
344
|
+
}
|
345
|
+
|
346
|
+
return { property, operator, value };
|
347
|
+
}
|
348
|
+
|
349
|
+
/**
|
350
|
+
* Get element dimensions
|
351
|
+
*/
|
352
|
+
private getElementDimensions(element: Element): ElementDimensions {
|
353
|
+
const rect = element.getBoundingClientRect();
|
354
|
+
const width = rect.width;
|
355
|
+
const height = rect.height;
|
356
|
+
|
357
|
+
return {
|
358
|
+
width,
|
359
|
+
height,
|
360
|
+
aspectRatio: height > 0 ? width / height : 0,
|
361
|
+
contentSize: Math.sqrt(width * height), // Geometric mean
|
362
|
+
orientation: width > height ? 'landscape' : 'portrait'
|
363
|
+
};
|
364
|
+
}
|
365
|
+
|
366
|
+
/**
|
367
|
+
* Apply query effects (CSS classes, properties)
|
368
|
+
*/
|
369
|
+
private applyQueryEffects(element: Element, rule: QueryRule): void {
|
370
|
+
const htmlElement = element as HTMLElement;
|
371
|
+
|
372
|
+
// Apply CSS class
|
373
|
+
if (rule.cssClass) {
|
374
|
+
htmlElement.classList.add(rule.cssClass);
|
375
|
+
}
|
376
|
+
|
377
|
+
// Apply CSS properties
|
378
|
+
if (rule.cssProperties) {
|
379
|
+
Object.entries(rule.cssProperties).forEach(([property, value]) => {
|
380
|
+
htmlElement.style.setProperty(property, value);
|
381
|
+
});
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
/**
|
386
|
+
* Remove query effects
|
387
|
+
*/
|
388
|
+
private removeQueryEffects(element: Element, rule: QueryRule): void {
|
389
|
+
const htmlElement = element as HTMLElement;
|
390
|
+
|
391
|
+
// Remove CSS class
|
392
|
+
if (rule.cssClass) {
|
393
|
+
htmlElement.classList.remove(rule.cssClass);
|
394
|
+
}
|
395
|
+
|
396
|
+
// Remove CSS properties
|
397
|
+
if (rule.cssProperties) {
|
398
|
+
Object.keys(rule.cssProperties).forEach(property => {
|
399
|
+
htmlElement.style.removeProperty(property);
|
400
|
+
});
|
401
|
+
}
|
402
|
+
}
|
403
|
+
}
|
404
|
+
|
405
|
+
interface ElementDimensions {
|
406
|
+
width: number;
|
407
|
+
height: number;
|
408
|
+
aspectRatio: number;
|
409
|
+
contentSize: number;
|
410
|
+
orientation: 'landscape' | 'portrait';
|
411
|
+
}
|