@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,512 @@
|
|
1
|
+
/**
|
2
|
+
* Responsive Spacing System for ProteusJS
|
3
|
+
* Fluid spacing with proportional scaling and accessibility compliance
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface SpacingConfig {
|
7
|
+
baseSize: number;
|
8
|
+
scale: 'minor-second' | 'major-second' | 'minor-third' | 'major-third' | 'perfect-fourth' | 'golden-ratio' | number;
|
9
|
+
density: 'compact' | 'comfortable' | 'spacious';
|
10
|
+
containerAware: boolean;
|
11
|
+
accessibility: boolean;
|
12
|
+
touchTargets: boolean;
|
13
|
+
minTouchSize: number;
|
14
|
+
maxSpacing: number;
|
15
|
+
responsive: boolean;
|
16
|
+
breakpoints?: Record<string, Partial<SpacingConfig>>;
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface SpacingScale {
|
20
|
+
xs: number;
|
21
|
+
sm: number;
|
22
|
+
md: number;
|
23
|
+
lg: number;
|
24
|
+
xl: number;
|
25
|
+
xxl: number;
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface TouchTargetConfig {
|
29
|
+
minSize: number;
|
30
|
+
preferredSize: number;
|
31
|
+
spacing: number;
|
32
|
+
interactive: boolean;
|
33
|
+
}
|
34
|
+
|
35
|
+
export interface SpacingState {
|
36
|
+
currentScale: SpacingScale;
|
37
|
+
containerSize: number;
|
38
|
+
scaleFactor: number;
|
39
|
+
touchCompliant: boolean;
|
40
|
+
appliedSpacing: Map<Element, string>;
|
41
|
+
}
|
42
|
+
|
43
|
+
export class SpacingSystem {
|
44
|
+
private element: Element;
|
45
|
+
private config: Required<SpacingConfig>;
|
46
|
+
private state: SpacingState;
|
47
|
+
private resizeObserver: ResizeObserver | null = null;
|
48
|
+
|
49
|
+
private static readonly SCALE_RATIOS = {
|
50
|
+
'minor-second': 1.067,
|
51
|
+
'major-second': 1.125,
|
52
|
+
'minor-third': 1.2,
|
53
|
+
'major-third': 1.25,
|
54
|
+
'perfect-fourth': 1.333,
|
55
|
+
'golden-ratio': 1.618
|
56
|
+
};
|
57
|
+
|
58
|
+
private static readonly DENSITY_MULTIPLIERS = {
|
59
|
+
'compact': 0.8,
|
60
|
+
'comfortable': 1.0,
|
61
|
+
'spacious': 1.25
|
62
|
+
};
|
63
|
+
|
64
|
+
private static readonly WCAG_MIN_TOUCH_SIZE = 44; // pixels
|
65
|
+
|
66
|
+
constructor(element: Element, config: Partial<SpacingConfig> = {}) {
|
67
|
+
this.element = element;
|
68
|
+
this.config = {
|
69
|
+
baseSize: 16,
|
70
|
+
scale: 'minor-third',
|
71
|
+
density: 'comfortable',
|
72
|
+
containerAware: true,
|
73
|
+
accessibility: true,
|
74
|
+
touchTargets: true,
|
75
|
+
minTouchSize: SpacingSystem.WCAG_MIN_TOUCH_SIZE,
|
76
|
+
maxSpacing: 128,
|
77
|
+
responsive: true,
|
78
|
+
breakpoints: {},
|
79
|
+
...config
|
80
|
+
};
|
81
|
+
|
82
|
+
this.state = this.createInitialState();
|
83
|
+
this.setupSpacing();
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Activate the spacing system
|
88
|
+
*/
|
89
|
+
public activate(): void {
|
90
|
+
this.calculateSpacing();
|
91
|
+
this.applySpacing();
|
92
|
+
this.setupObservers();
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Deactivate and clean up
|
97
|
+
*/
|
98
|
+
public deactivate(): void {
|
99
|
+
this.cleanupObservers();
|
100
|
+
this.removeSpacing();
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Update spacing configuration
|
105
|
+
*/
|
106
|
+
public updateConfig(newConfig: Partial<SpacingConfig>): void {
|
107
|
+
this.config = { ...this.config, ...newConfig };
|
108
|
+
this.activate();
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Get current spacing state
|
113
|
+
*/
|
114
|
+
public getState(): SpacingState {
|
115
|
+
return { ...this.state };
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Apply spacing to specific element
|
120
|
+
*/
|
121
|
+
public applyToElement(element: Element, spacingType: keyof SpacingScale): void {
|
122
|
+
const spacing = this.state.currentScale[spacingType];
|
123
|
+
const htmlElement = element as HTMLElement;
|
124
|
+
|
125
|
+
htmlElement.style.setProperty('--spacing', `${spacing}px`);
|
126
|
+
this.state.appliedSpacing.set(element, spacingType);
|
127
|
+
}
|
128
|
+
|
129
|
+
/**
|
130
|
+
* Apply margin spacing
|
131
|
+
*/
|
132
|
+
public applyMargin(element: Element, spacing: keyof SpacingScale | number): void {
|
133
|
+
const value = typeof spacing === 'number' ? spacing : this.state.currentScale[spacing];
|
134
|
+
const htmlElement = element as HTMLElement;
|
135
|
+
|
136
|
+
htmlElement.style.margin = `${value}px`;
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* Apply padding spacing
|
141
|
+
*/
|
142
|
+
public applyPadding(element: Element, spacing: keyof SpacingScale | number): void {
|
143
|
+
const value = typeof spacing === 'number' ? spacing : this.state.currentScale[spacing];
|
144
|
+
const htmlElement = element as HTMLElement;
|
145
|
+
|
146
|
+
htmlElement.style.padding = `${value}px`;
|
147
|
+
}
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Apply gap spacing (for flex/grid)
|
151
|
+
*/
|
152
|
+
public applyGap(element: Element, spacing: keyof SpacingScale | number): void {
|
153
|
+
const value = typeof spacing === 'number' ? spacing : this.state.currentScale[spacing];
|
154
|
+
const htmlElement = element as HTMLElement;
|
155
|
+
|
156
|
+
htmlElement.style.gap = `${value}px`;
|
157
|
+
}
|
158
|
+
|
159
|
+
/**
|
160
|
+
* Ensure touch target compliance
|
161
|
+
*/
|
162
|
+
public ensureTouchTargets(): void {
|
163
|
+
if (!this.config.touchTargets) return;
|
164
|
+
|
165
|
+
const interactiveElements = this.findInteractiveElements();
|
166
|
+
|
167
|
+
interactiveElements.forEach(element => {
|
168
|
+
this.makeTouchCompliant(element);
|
169
|
+
});
|
170
|
+
}
|
171
|
+
|
172
|
+
/**
|
173
|
+
* Generate spacing scale
|
174
|
+
*/
|
175
|
+
public generateScale(): SpacingScale {
|
176
|
+
const ratio = typeof this.config.scale === 'number'
|
177
|
+
? this.config.scale
|
178
|
+
: SpacingSystem.SCALE_RATIOS[this.config.scale];
|
179
|
+
|
180
|
+
const densityMultiplier = SpacingSystem.DENSITY_MULTIPLIERS[this.config.density];
|
181
|
+
const containerMultiplier = this.calculateContainerMultiplier();
|
182
|
+
|
183
|
+
const baseSize = this.config.baseSize * densityMultiplier * containerMultiplier;
|
184
|
+
|
185
|
+
return {
|
186
|
+
xs: Math.round(baseSize / (ratio * ratio)), // base / ratio²
|
187
|
+
sm: Math.round(baseSize / ratio), // base / ratio
|
188
|
+
md: Math.round(baseSize), // base
|
189
|
+
lg: Math.round(baseSize * ratio), // base * ratio
|
190
|
+
xl: Math.round(baseSize * ratio * ratio), // base * ratio²
|
191
|
+
xxl: Math.min(Math.round(baseSize * ratio * ratio * ratio), this.config.maxSpacing) // base * ratio³
|
192
|
+
};
|
193
|
+
}
|
194
|
+
|
195
|
+
/**
|
196
|
+
* Calculate optimal spacing for content
|
197
|
+
*/
|
198
|
+
public calculateOptimalSpacing(contentType: 'text' | 'interactive' | 'layout' | 'component'): keyof SpacingScale {
|
199
|
+
switch (contentType) {
|
200
|
+
case 'text':
|
201
|
+
return this.config.density === 'compact' ? 'sm' : 'md';
|
202
|
+
case 'interactive':
|
203
|
+
return this.config.accessibility ? 'lg' : 'md';
|
204
|
+
case 'layout':
|
205
|
+
return this.config.density === 'spacious' ? 'xl' : 'lg';
|
206
|
+
case 'component':
|
207
|
+
return 'md';
|
208
|
+
default:
|
209
|
+
return 'md';
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
/**
|
214
|
+
* Validate accessibility compliance
|
215
|
+
*/
|
216
|
+
public validateAccessibility(): { compliant: boolean; issues: string[] } {
|
217
|
+
const issues: string[] = [];
|
218
|
+
|
219
|
+
if (this.config.touchTargets) {
|
220
|
+
const interactiveElements = this.findInteractiveElements();
|
221
|
+
|
222
|
+
interactiveElements.forEach(element => {
|
223
|
+
const rect = element.getBoundingClientRect();
|
224
|
+
const minSize = this.config.minTouchSize;
|
225
|
+
|
226
|
+
if (rect.width < minSize || rect.height < minSize) {
|
227
|
+
issues.push(`Touch target too small: ${rect.width}x${rect.height} (minimum: ${minSize}x${minSize})`);
|
228
|
+
}
|
229
|
+
});
|
230
|
+
}
|
231
|
+
|
232
|
+
// Check spacing ratios
|
233
|
+
const scale = this.state.currentScale;
|
234
|
+
const ratios = [
|
235
|
+
scale.sm / scale.xs,
|
236
|
+
scale.md / scale.sm,
|
237
|
+
scale.lg / scale.md,
|
238
|
+
scale.xl / scale.lg
|
239
|
+
];
|
240
|
+
|
241
|
+
const inconsistentRatios = ratios.length > 0 && ratios.some(ratio => Math.abs(ratio - ratios[0]!) > 0.1);
|
242
|
+
if (inconsistentRatios) {
|
243
|
+
issues.push('Inconsistent spacing ratios detected');
|
244
|
+
}
|
245
|
+
|
246
|
+
return {
|
247
|
+
compliant: issues.length === 0,
|
248
|
+
issues
|
249
|
+
};
|
250
|
+
}
|
251
|
+
|
252
|
+
/**
|
253
|
+
* Setup initial spacing
|
254
|
+
*/
|
255
|
+
private setupSpacing(): void {
|
256
|
+
this.calculateSpacing();
|
257
|
+
}
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Calculate spacing values
|
261
|
+
*/
|
262
|
+
private calculateSpacing(): void {
|
263
|
+
const containerRect = this.element.getBoundingClientRect();
|
264
|
+
const activeConfig = this.getActiveConfig(containerRect.width);
|
265
|
+
|
266
|
+
// Update state
|
267
|
+
this.state = {
|
268
|
+
currentScale: this.generateScale(),
|
269
|
+
containerSize: containerRect.width,
|
270
|
+
scaleFactor: this.calculateContainerMultiplier(),
|
271
|
+
touchCompliant: this.validateTouchCompliance(),
|
272
|
+
appliedSpacing: new Map()
|
273
|
+
};
|
274
|
+
}
|
275
|
+
|
276
|
+
/**
|
277
|
+
* Apply spacing to elements
|
278
|
+
*/
|
279
|
+
private applySpacing(): void {
|
280
|
+
// Apply CSS custom properties
|
281
|
+
const htmlElement = this.element as HTMLElement;
|
282
|
+
const scale = this.state.currentScale;
|
283
|
+
|
284
|
+
Object.entries(scale).forEach(([key, value]) => {
|
285
|
+
htmlElement.style.setProperty(`--spacing-${key}`, `${value}px`);
|
286
|
+
});
|
287
|
+
|
288
|
+
// Apply spacing classes
|
289
|
+
this.addSpacingCSS();
|
290
|
+
|
291
|
+
// Ensure touch targets if enabled
|
292
|
+
if (this.config.touchTargets) {
|
293
|
+
this.ensureTouchTargets();
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
/**
|
298
|
+
* Calculate container-based multiplier
|
299
|
+
*/
|
300
|
+
private calculateContainerMultiplier(): number {
|
301
|
+
if (!this.config.containerAware) return 1;
|
302
|
+
|
303
|
+
const containerWidth = this.element.getBoundingClientRect().width;
|
304
|
+
|
305
|
+
// Scale spacing based on container size
|
306
|
+
// Smaller containers get smaller spacing, larger containers get larger spacing
|
307
|
+
const baseWidth = 800; // Reference width
|
308
|
+
const minMultiplier = 0.75;
|
309
|
+
const maxMultiplier = 1.5;
|
310
|
+
|
311
|
+
const ratio = containerWidth / baseWidth;
|
312
|
+
return Math.max(minMultiplier, Math.min(maxMultiplier, ratio));
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* Get active configuration based on container width
|
317
|
+
*/
|
318
|
+
private getActiveConfig(containerWidth: number): Required<SpacingConfig> {
|
319
|
+
let activeConfig = { ...this.config };
|
320
|
+
|
321
|
+
if (this.config.breakpoints) {
|
322
|
+
const sortedBreakpoints = Object.entries(this.config.breakpoints)
|
323
|
+
.map(([name, config]) => ({ name, width: parseInt(name), config }))
|
324
|
+
.sort((a, b) => a.width - b.width);
|
325
|
+
|
326
|
+
for (const breakpoint of sortedBreakpoints) {
|
327
|
+
if (containerWidth >= breakpoint.width) {
|
328
|
+
activeConfig = { ...activeConfig, ...breakpoint.config };
|
329
|
+
}
|
330
|
+
}
|
331
|
+
}
|
332
|
+
|
333
|
+
return activeConfig;
|
334
|
+
}
|
335
|
+
|
336
|
+
/**
|
337
|
+
* Find interactive elements
|
338
|
+
*/
|
339
|
+
private findInteractiveElements(): Element[] {
|
340
|
+
const interactiveSelectors = [
|
341
|
+
'button',
|
342
|
+
'a[href]',
|
343
|
+
'input',
|
344
|
+
'select',
|
345
|
+
'textarea',
|
346
|
+
'[tabindex]:not([tabindex="-1"])',
|
347
|
+
'[role="button"]',
|
348
|
+
'[role="link"]',
|
349
|
+
'[onclick]'
|
350
|
+
].join(', ');
|
351
|
+
|
352
|
+
return Array.from(this.element.querySelectorAll(interactiveSelectors));
|
353
|
+
}
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Make element touch compliant
|
357
|
+
*/
|
358
|
+
private makeTouchCompliant(element: Element): void {
|
359
|
+
const htmlElement = element as HTMLElement;
|
360
|
+
const rect = element.getBoundingClientRect();
|
361
|
+
const minSize = this.config.minTouchSize;
|
362
|
+
|
363
|
+
if (rect.width < minSize || rect.height < minSize) {
|
364
|
+
htmlElement.style.minWidth = `${minSize}px`;
|
365
|
+
htmlElement.style.minHeight = `${minSize}px`;
|
366
|
+
htmlElement.style.display = htmlElement.style.display || 'inline-block';
|
367
|
+
}
|
368
|
+
|
369
|
+
// Ensure adequate spacing between touch targets
|
370
|
+
const spacing = this.state.currentScale.sm;
|
371
|
+
htmlElement.style.margin = `${spacing / 2}px`;
|
372
|
+
}
|
373
|
+
|
374
|
+
/**
|
375
|
+
* Validate touch compliance
|
376
|
+
*/
|
377
|
+
private validateTouchCompliance(): boolean {
|
378
|
+
if (!this.config.touchTargets) return true;
|
379
|
+
|
380
|
+
const interactiveElements = this.findInteractiveElements();
|
381
|
+
|
382
|
+
return interactiveElements.every(element => {
|
383
|
+
const rect = element.getBoundingClientRect();
|
384
|
+
return rect.width >= this.config.minTouchSize && rect.height >= this.config.minTouchSize;
|
385
|
+
});
|
386
|
+
}
|
387
|
+
|
388
|
+
/**
|
389
|
+
* Add spacing CSS utilities
|
390
|
+
*/
|
391
|
+
private addSpacingCSS(): void {
|
392
|
+
const styleId = 'proteus-spacing-styles';
|
393
|
+
if (document.getElementById(styleId)) return;
|
394
|
+
|
395
|
+
const style = document.createElement('style');
|
396
|
+
style.id = styleId;
|
397
|
+
|
398
|
+
const scale = this.state.currentScale;
|
399
|
+
let css = ':root {\n';
|
400
|
+
|
401
|
+
Object.entries(scale).forEach(([key, value]) => {
|
402
|
+
css += ` --spacing-${key}: ${value}px;\n`;
|
403
|
+
});
|
404
|
+
|
405
|
+
css += '}\n\n';
|
406
|
+
|
407
|
+
// Utility classes
|
408
|
+
Object.entries(scale).forEach(([key, value]) => {
|
409
|
+
css += `.m-${key} { margin: ${value}px; }\n`;
|
410
|
+
css += `.mt-${key} { margin-top: ${value}px; }\n`;
|
411
|
+
css += `.mr-${key} { margin-right: ${value}px; }\n`;
|
412
|
+
css += `.mb-${key} { margin-bottom: ${value}px; }\n`;
|
413
|
+
css += `.ml-${key} { margin-left: ${value}px; }\n`;
|
414
|
+
css += `.mx-${key} { margin-left: ${value}px; margin-right: ${value}px; }\n`;
|
415
|
+
css += `.my-${key} { margin-top: ${value}px; margin-bottom: ${value}px; }\n`;
|
416
|
+
|
417
|
+
css += `.p-${key} { padding: ${value}px; }\n`;
|
418
|
+
css += `.pt-${key} { padding-top: ${value}px; }\n`;
|
419
|
+
css += `.pr-${key} { padding-right: ${value}px; }\n`;
|
420
|
+
css += `.pb-${key} { padding-bottom: ${value}px; }\n`;
|
421
|
+
css += `.pl-${key} { padding-left: ${value}px; }\n`;
|
422
|
+
css += `.px-${key} { padding-left: ${value}px; padding-right: ${value}px; }\n`;
|
423
|
+
css += `.py-${key} { padding-top: ${value}px; padding-bottom: ${value}px; }\n`;
|
424
|
+
|
425
|
+
css += `.gap-${key} { gap: ${value}px; }\n`;
|
426
|
+
});
|
427
|
+
|
428
|
+
// Touch target utilities
|
429
|
+
css += `
|
430
|
+
.touch-target {
|
431
|
+
min-width: ${this.config.minTouchSize}px;
|
432
|
+
min-height: ${this.config.minTouchSize}px;
|
433
|
+
display: inline-block;
|
434
|
+
}
|
435
|
+
|
436
|
+
.touch-spacing {
|
437
|
+
margin: ${this.state.currentScale.sm / 2}px;
|
438
|
+
}
|
439
|
+
`;
|
440
|
+
|
441
|
+
style.textContent = css;
|
442
|
+
document.head.appendChild(style);
|
443
|
+
}
|
444
|
+
|
445
|
+
/**
|
446
|
+
* Setup observers
|
447
|
+
*/
|
448
|
+
private setupObservers(): void {
|
449
|
+
if (!this.config.responsive) return;
|
450
|
+
|
451
|
+
this.resizeObserver = new ResizeObserver(() => {
|
452
|
+
this.calculateSpacing();
|
453
|
+
this.applySpacing();
|
454
|
+
});
|
455
|
+
|
456
|
+
this.resizeObserver.observe(this.element);
|
457
|
+
}
|
458
|
+
|
459
|
+
/**
|
460
|
+
* Clean up observers
|
461
|
+
*/
|
462
|
+
private cleanupObservers(): void {
|
463
|
+
if (this.resizeObserver) {
|
464
|
+
this.resizeObserver.disconnect();
|
465
|
+
this.resizeObserver = null;
|
466
|
+
}
|
467
|
+
}
|
468
|
+
|
469
|
+
/**
|
470
|
+
* Remove spacing
|
471
|
+
*/
|
472
|
+
private removeSpacing(): void {
|
473
|
+
const htmlElement = this.element as HTMLElement;
|
474
|
+
const scale = this.state.currentScale;
|
475
|
+
|
476
|
+
// Remove CSS custom properties
|
477
|
+
Object.keys(scale).forEach(key => {
|
478
|
+
htmlElement.style.removeProperty(`--spacing-${key}`);
|
479
|
+
});
|
480
|
+
|
481
|
+
// Remove applied spacing
|
482
|
+
this.state.appliedSpacing.forEach((spacingType, element) => {
|
483
|
+
(element as HTMLElement).style.removeProperty('--spacing');
|
484
|
+
});
|
485
|
+
|
486
|
+
// Remove style element
|
487
|
+
const styleElement = document.getElementById('proteus-spacing-styles');
|
488
|
+
if (styleElement) {
|
489
|
+
styleElement.remove();
|
490
|
+
}
|
491
|
+
}
|
492
|
+
|
493
|
+
/**
|
494
|
+
* Create initial state
|
495
|
+
*/
|
496
|
+
private createInitialState(): SpacingState {
|
497
|
+
return {
|
498
|
+
currentScale: {
|
499
|
+
xs: 4,
|
500
|
+
sm: 8,
|
501
|
+
md: 16,
|
502
|
+
lg: 24,
|
503
|
+
xl: 32,
|
504
|
+
xxl: 48
|
505
|
+
},
|
506
|
+
containerSize: 0,
|
507
|
+
scaleFactor: 1,
|
508
|
+
touchCompliant: true,
|
509
|
+
appliedSpacing: new Map()
|
510
|
+
};
|
511
|
+
}
|
512
|
+
}
|