@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,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
|
+
}
|