panelset 0.5.0 → 0.5.2

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 (38) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/panelset.js +25 -12
  3. package/dist/panelset.js.map +1 -1
  4. package/package.json +5 -6
  5. package/src/docs/assets/scripts/copybutton.js +0 -44
  6. package/src/docs/assets/scripts/example-async.js +0 -161
  7. package/src/docs/assets/scripts/example-closable.js +0 -27
  8. package/src/docs/assets/scripts/example-megamenu.js +0 -84
  9. package/src/docs/assets/scripts/example.js +0 -29
  10. package/src/docs/assets/scripts/main.js +0 -7
  11. package/src/docs/assets/styles/_base.scss +0 -13
  12. package/src/docs/assets/styles/_code.scss +0 -121
  13. package/src/docs/assets/styles/_demos.scss +0 -180
  14. package/src/docs/assets/styles/_landingpage.scss +0 -41
  15. package/src/docs/assets/styles/_layout.scss +0 -80
  16. package/src/docs/assets/styles/_sidebar.scss +0 -67
  17. package/src/docs/assets/styles/_typography.scss +0 -116
  18. package/src/docs/assets/styles/_variables.scss +0 -32
  19. package/src/docs/assets/styles/docs.scss +0 -64
  20. package/src/docs/views/api-reference.pug +0 -474
  21. package/src/docs/views/configuration.pug +0 -173
  22. package/src/docs/views/events.pug +0 -222
  23. package/src/docs/views/examples/async.pug +0 -268
  24. package/src/docs/views/examples/basic.pug +0 -155
  25. package/src/docs/views/examples/closable.pug +0 -97
  26. package/src/docs/views/getting-started.pug +0 -99
  27. package/src/docs/views/index.pug +0 -38
  28. package/src/docs/views/templates/includes/_head.pug +0 -11
  29. package/src/docs/views/templates/includes/_mixins.pug +0 -100
  30. package/src/docs/views/templates/includes/_scripts.pug +0 -14
  31. package/src/docs/views/templates/includes/_sidebar.pug +0 -18
  32. package/src/docs/views/templates/layouts/_base.pug +0 -36
  33. package/src/docs/views/transitions.pug +0 -141
  34. package/src/lib/index.ts +0 -685
  35. package/src/lib/styles/_base.scss +0 -99
  36. package/src/lib/styles/_loading.scss +0 -47
  37. package/src/lib/styles/_variables.scss +0 -19
  38. package/src/lib/styles/panelset.scss +0 -3
package/src/lib/index.ts DELETED
@@ -1,685 +0,0 @@
1
- import './styles/panelset.scss';
2
-
3
- // Configuration types
4
- export interface PanelSetConfig {
5
- transitions?: boolean | {
6
- panels?: boolean;
7
- height?: boolean;
8
- };
9
- closable?: boolean;
10
- emptyPanelHeight?: number;
11
- loadingDelay?: number;
12
- debug?: boolean;
13
- selector?: string;
14
- }
15
-
16
- // Event detail types
17
- export interface ReadyEventDetail {
18
- container: HTMLElement;
19
- instance: PanelSet;
20
- }
21
-
22
- export interface BeforeActivateEventDetail {
23
- panelId: string;
24
- targetPanel: HTMLElement;
25
- outgoingPanel: HTMLElement | null;
26
- signal: AbortSignal;
27
- promise: Promise<void> | null;
28
- }
29
-
30
- export interface ActivationEventDetail {
31
- panelId: string;
32
- trigger: string | null;
33
- }
34
-
35
- export interface ActivationAbortedEventDetail {
36
- panelId: string;
37
- trigger: string | null;
38
- }
39
-
40
- // Handler options
41
- export interface HandlerOptions {
42
- once?: boolean;
43
- }
44
-
45
- // Show options
46
- export interface ShowOptions {
47
- trigger?: string;
48
- }
49
-
50
- // Async content handler type
51
- export type AsyncContentHandler = (
52
- targetPanel: HTMLElement,
53
- signal: AbortSignal
54
- ) => Promise<void> | void;
55
-
56
- // Extend HTMLElement to include panelSet property
57
- declare global {
58
- interface HTMLElement {
59
- panelSet?: PanelSet;
60
- }
61
- }
62
-
63
- export class PanelSet {
64
- // Default configuration
65
- static defaults: Required<Omit<PanelSetConfig, 'selector'>> = {
66
- transitions: true,
67
- closable: false,
68
- emptyPanelHeight: 200,
69
- loadingDelay: 300,
70
- debug: false
71
- };
72
-
73
- // Instance properties
74
- element!: HTMLElement;
75
- config!: Required<Omit<PanelSetConfig, 'selector'>>;
76
- panels!: HTMLElement[];
77
- activePanel!: HTMLElement;
78
- panelWrapper!: HTMLElement;
79
- pendingPanel!: HTMLElement;
80
-
81
- private _openCloseGeneration: number = 0;
82
- private _isLoadingAsync: boolean = false;
83
- // private _isTransitioning: boolean = false;
84
- private _currentAbortController?: AbortController;
85
-
86
- // Parse data attributes from element
87
- static _getDataConfig(element: HTMLElement): Partial<PanelSetConfig> {
88
- const data = element.dataset;
89
- const config: Partial<PanelSetConfig> = {};
90
-
91
- // Define attribute types
92
- const attrs: Record<string, 'json' | 'boolean' | 'number'> = {
93
- transitions: 'json',
94
- closable: 'boolean',
95
- emptyPanelHeight: 'number',
96
- loadingDelay: 'number',
97
- debug: 'boolean'
98
- };
99
-
100
- // Parse each attribute
101
- for (const [key, type] of Object.entries(attrs)) {
102
- if (!(key in data)) continue;
103
-
104
- const value = data[key];
105
- if (value === undefined) continue;
106
-
107
- switch (type) {
108
- case 'boolean':
109
- (config as any)[key] = value !== 'false';
110
- break;
111
- case 'number':
112
- (config as any)[key] = parseInt(value, 10);
113
- break;
114
- case 'json':
115
- try {
116
- (config as any)[key] = JSON.parse(value);
117
- } catch {
118
- // Fall back to boolean parsing
119
- (config as any)[key] = value !== 'false';
120
- }
121
- break;
122
- }
123
- }
124
-
125
- return config;
126
- }
127
-
128
- // Merge configurations
129
- static _mergeConfig(
130
- defaults: Required<Omit<PanelSetConfig, 'selector'>>,
131
- dataConfig: Partial<PanelSetConfig>,
132
- options: Partial<PanelSetConfig>
133
- ): Required<Omit<PanelSetConfig, 'selector'>> {
134
- return {
135
- ...defaults,
136
- ...dataConfig,
137
- ...options
138
- } as Required<Omit<PanelSetConfig, 'selector'>>;
139
- }
140
-
141
- /**
142
- * Initialize PanelSet instances
143
- * @param selectorOrOptions - CSS selector string or config object
144
- * @param options - Additional config options (when first param is selector)
145
- * @returns Array of PanelSet instances
146
- */
147
- static init(selectorOrOptions: string | PanelSetConfig = {}, options: PanelSetConfig = {}): PanelSet[] {
148
- // Handle different call signatures
149
- let selector: string;
150
- let config: PanelSetConfig;
151
-
152
- if (typeof selectorOrOptions === 'string') {
153
- // init('#demo') or init('#demo', {debug: true})
154
- selector = selectorOrOptions;
155
- config = options;
156
- } else {
157
- // init() or init({selector: '#demo', debug: true})
158
- config = selectorOrOptions;
159
- selector = config.selector || '[data-panelset]';
160
- }
161
-
162
- const elements = document.querySelectorAll<HTMLElement>(selector);
163
- const instances: PanelSet[] = [];
164
-
165
- elements.forEach(el => {
166
- // Skip if already initialized
167
- if (el.panelSet || el.dataset.panelset === 'true') {
168
- if (el.panelSet) instances.push(el.panelSet);
169
- return;
170
- }
171
-
172
- const instance = new PanelSet(el, config);
173
- instances.push(instance);
174
- });
175
-
176
- return instances;
177
- }
178
-
179
- constructor(elementOrSelector: HTMLElement | string, options: PanelSetConfig = {}) {
180
- // Handle both element and selector
181
- let element: HTMLElement | null;
182
- if (typeof elementOrSelector === 'string') {
183
- element = document.querySelector<HTMLElement>(elementOrSelector);
184
- if (!element) {
185
- throw new Error(`PanelSet: No element found for selector "${elementOrSelector}"`);
186
- }
187
- } else {
188
- element = elementOrSelector;
189
- }
190
-
191
- this.element = element;
192
-
193
- // Check if already initialized
194
- if (element.panelSet || element.dataset.panelset === 'true') {
195
- console.warn('PanelSet: Element already initialized, returning existing instance');
196
- return element.panelSet!;
197
- }
198
-
199
- // Store instance on element
200
- element.panelSet = this;
201
-
202
- const dataConfig = PanelSet._getDataConfig(element);
203
- this.config = PanelSet._mergeConfig(
204
- PanelSet.defaults,
205
- dataConfig,
206
- options
207
- );
208
-
209
- this.panels = Array.from(element.querySelectorAll<HTMLElement>('[role="tabpanel"]'));
210
- this.activePanel =
211
- this.panels.find(p => p.classList.contains('active')) || this.panels[0];
212
- this.panelWrapper =
213
- this.element.querySelector<HTMLElement>('.panel-wrapper') || this._autoWrapPanels();
214
-
215
- this.pendingPanel = this.activePanel;
216
-
217
- this._internalInit();
218
-
219
- // Mark as initialized, can be used in CSS selectors for visual confirmation
220
- this.element.dataset.panelset = 'true';
221
-
222
- this._log(`Initialized (${this.panels.length} panels)`);
223
- this._dispatch<ReadyEventDetail>('ps:ready', { container: this.element, instance: this });
224
- }
225
-
226
- // Debug logging helper
227
- private _log(message: string): void {
228
- if (!this.config.debug) return;
229
- const id = this.element.id || 'no id';
230
- console.log(`[PanelSet] - "${id}" -`, message);
231
- }
232
-
233
- private _autoWrapPanels(): HTMLElement {
234
- const wrapper = document.createElement('div');
235
- wrapper.className = 'panel-wrapper';
236
- this.panels.forEach(panel => wrapper.appendChild(panel));
237
- this.element.appendChild(wrapper);
238
- return wrapper;
239
- }
240
-
241
- private _internalInit(): void {
242
- this.panels.forEach(panel => {
243
- panel.classList.remove('fade', 'incoming');
244
- if (panel !== this.activePanel) {
245
- panel.hidden = true;
246
- panel.classList.remove('active');
247
- } else {
248
- panel.hidden = false;
249
- panel.classList.add('active');
250
- }
251
- });
252
- this.element.style.height = '';
253
- }
254
-
255
- // Dispatch custom event helper
256
- private _dispatch<T = unknown>(eventName: string, detail: T): void {
257
- this.element.dispatchEvent(
258
- new CustomEvent(eventName, {
259
- detail,
260
- bubbles: true,
261
- cancelable: false
262
- })
263
- );
264
- }
265
-
266
- /* --- Modular helpers --- */
267
-
268
- private _getVerticalMetrics(el: HTMLElement | null): number {
269
- if (!el) return 0;
270
- const s = getComputedStyle(el);
271
- return ['paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth']
272
- .reduce((sum, prop) => sum + (parseFloat(s[prop as keyof CSSStyleDeclaration] as string) || 0), 0);
273
- }
274
-
275
- private _measureHeight(panel: HTMLElement): number {
276
- let total = panel.offsetHeight;
277
- total += this._getVerticalMetrics(this.panelWrapper);
278
- total += this._getVerticalMetrics(this.element);
279
- return total;
280
- }
281
-
282
- private _cleanupPanels(newPanel: HTMLElement): void {
283
- this.panels.forEach(panel => {
284
- panel.classList.remove('fade', 'incoming');
285
- if (panel !== newPanel) {
286
- panel.classList.remove('active');
287
- panel.hidden = true;
288
- } else {
289
- panel.classList.add('active');
290
- panel.hidden = false;
291
- }
292
- });
293
- this.element.style.height = '';
294
- this.element.classList.remove('is-transitioning');
295
- this.activePanel = newPanel;
296
- }
297
-
298
- private _waitForTransition(element: HTMLElement): Promise<void> {
299
- return new Promise(resolve => {
300
- const styles = getComputedStyle(element);
301
- const duration = parseFloat(styles.transitionDuration) || 0;
302
- const delay = parseFloat(styles.transitionDelay) || 0;
303
- if (duration + delay === 0) {
304
- resolve();
305
- return;
306
- }
307
- const handler = (e: TransitionEvent) => {
308
- if (e.target !== element) return;
309
- element.removeEventListener('transitionend', handler);
310
- resolve();
311
- };
312
- element.addEventListener('transitionend', handler);
313
- });
314
- }
315
-
316
- // Shared helper for open/close
317
- private _animateOpenClose(isOpening: boolean, withTransition: boolean): void {
318
- const action = isOpening ? 'opening' : 'closing';
319
- const oppositeAction = isOpening ? 'closing' : 'opening';
320
- const oppositeClass = `is-${oppositeAction}`;
321
- const actionClass = `is-${action}`;
322
-
323
- this._log(isOpening ? 'Opening' : 'Closing');
324
-
325
- this._openCloseGeneration++;
326
- const myGeneration = this._openCloseGeneration;
327
-
328
- // Remove opposite state if interrupting
329
- if (this.element.classList.contains(oppositeClass)) {
330
- this.element.classList.remove(oppositeClass);
331
- }
332
-
333
- const targetHeight = isOpening ? this._measureHeight(this.pendingPanel) : 0;
334
-
335
- if (withTransition && this.config.transitions) {
336
- this.element.classList.add(actionClass);
337
-
338
- const currentHeight = this.element.offsetHeight;
339
- this.element.style.height = `${currentHeight}px`;
340
-
341
- if (isOpening) this.element.classList.remove('is-closed');
342
-
343
- requestAnimationFrame(() => {
344
- this.element.style.height = `${targetHeight}px`;
345
-
346
- this._waitForTransition(this.element).then(() => {
347
- if (this._openCloseGeneration === myGeneration) {
348
- this.element.style.height = '';
349
- this.element.classList.remove(actionClass);
350
- if (!isOpening) this.element.classList.add('is-closed');
351
- }
352
- });
353
- });
354
- } else {
355
- if (isOpening) {
356
- this.element.classList.remove('is-closed');
357
- } else {
358
- this.element.classList.add('is-closed');
359
- }
360
- this.element.style.height = '';
361
- }
362
- }
363
-
364
- /**
365
- * Get the ID of the currently active panel
366
- * @returns Panel ID or null if no panel is active
367
- */
368
- getActive(): string | null {
369
- return this.pendingPanel?.id || null;
370
- }
371
-
372
- /**
373
- * Open a closable panelset
374
- * @param withTransition - Whether to animate
375
- */
376
- open(withTransition: boolean = true): void {
377
- if (!this.config.closable) {
378
- this._log('Cannot open: closable is false');
379
- return;
380
- }
381
-
382
- const isClosed = this.element.classList.contains('is-closed');
383
- const isClosing = this.element.classList.contains('is-closing');
384
-
385
- if (!isClosed && !isClosing) return;
386
-
387
- this._animateOpenClose(true, withTransition);
388
- }
389
-
390
- /**
391
- * Close a closable panelset
392
- * @param withTransition - Whether to animate
393
- */
394
- close(withTransition: boolean = true): void {
395
- if (!this.config.closable) {
396
- this._log('Cannot close: closable is false');
397
- return;
398
- }
399
-
400
- const isClosed = this.element.classList.contains('is-closed');
401
- const isOpening = this.element.classList.contains('is-opening');
402
-
403
- if (isClosed && !isOpening) return;
404
-
405
- this._animateOpenClose(false, withTransition);
406
- }
407
-
408
- /**
409
- * Toggle a closable panelset between open and closed
410
- * @param withTransition - Whether to animate
411
- */
412
- toggle(withTransition: boolean = true): void {
413
- const isClosed = this.element.classList.contains('is-closed');
414
- const isClosing = this.element.classList.contains('is-closing');
415
-
416
- // If closed or closing, open it
417
- if (isClosed || isClosing) {
418
- this.open(withTransition);
419
- } else {
420
- this.close(withTransition);
421
- }
422
- }
423
-
424
- /**
425
- * Register a handler for async content loading
426
- * @param handler - Async content handler function
427
- * @param options - Handler options (once: whether to load only once)
428
- */
429
- onBeforeActivate(handler: AsyncContentHandler, options: HandlerOptions = {}): void {
430
- const once = options.once === true; // Default: false (always reload)
431
-
432
- this.element.addEventListener('ps:beforeactivate', (e) => {
433
- const event = e as CustomEvent<BeforeActivateEventDetail>;
434
- const { targetPanel, signal } = event.detail;
435
-
436
- // Skip if already loaded and once=true
437
- if (once && targetPanel.dataset.loaded === 'true') {
438
- if (this.config.debug) {
439
- this._log(`Skipping ${targetPanel.id} (already loaded)`);
440
- }
441
- return;
442
- }
443
-
444
- // Call user handler
445
- const result = handler(targetPanel, signal);
446
-
447
- // If handler returns a promise, attach it to the event
448
- if (result && typeof result.then === 'function') {
449
- event.detail.promise = result
450
- .then(() => {
451
- // Auto-mark as loaded on success if once=true
452
- if (once) {
453
- targetPanel.dataset.loaded = 'true';
454
- }
455
- })
456
- .catch(error => {
457
- if (error.name === 'AbortError') {
458
- if (this.config.debug) {
459
- this._log(`Load aborted: ${targetPanel.id}`);
460
- }
461
- throw error;
462
- } else {
463
- this._log(`Load failed: ${error.message}`);
464
- throw error;
465
- }
466
- });
467
- }
468
- });
469
- }
470
-
471
- /* --- Main logic --- */
472
-
473
- /**
474
- * Show a panel by ID
475
- * @param panelId - ID of the panel to show
476
- * @param withTransition - Whether to animate the transition
477
- * @param options - Additional options (trigger name)
478
- */
479
- async show(panelId: string, withTransition: boolean = true, options: ShowOptions = {}): Promise<void> {
480
- const newPanel = this.panels.find(p => p.id === panelId);
481
-
482
- if (!newPanel) {
483
- this._log(`Panel not found: ${panelId}`);
484
- return;
485
- }
486
-
487
- if (newPanel === this.pendingPanel) return;
488
-
489
- const prevPanel = this.pendingPanel;
490
- const prevPanelId = prevPanel?.id;
491
- this.pendingPanel = newPanel;
492
-
493
- this.element.classList.remove('is-loading');
494
-
495
- if (prevPanel && prevPanel !== this.activePanel && prevPanel !== newPanel) {
496
- prevPanel.classList.remove('incoming');
497
- if (prevPanel.hidden) {
498
- // Was never visible, keep hidden
499
- } else {
500
- prevPanel.classList.remove('active');
501
- }
502
- }
503
-
504
- const wasLoadingAsync = this._isLoadingAsync;
505
-
506
- if (this._currentAbortController) {
507
- this._currentAbortController.abort();
508
-
509
- // Only fire abort if we're cancelling an async operation
510
- if (wasLoadingAsync && prevPanelId && prevPanelId !== panelId) {
511
- this._dispatch<ActivationAbortedEventDetail>('ps:activationaborted', {
512
- panelId: prevPanelId,
513
- trigger: null
514
- });
515
- }
516
- }
517
-
518
- const abortController = new AbortController();
519
- this._currentAbortController = abortController;
520
- this._isLoadingAsync = false;
521
-
522
- this._log(`${prevPanel?.id || 'none'} → ${panelId}`);
523
-
524
- const beforeActivateDetail: BeforeActivateEventDetail = {
525
- panelId,
526
- targetPanel: newPanel,
527
- outgoingPanel: prevPanel,
528
- signal: abortController.signal,
529
- promise: null
530
- };
531
-
532
- const beforeActivateEvent = new CustomEvent('ps:beforeactivate', {
533
- detail: beforeActivateDetail,
534
- bubbles: true,
535
- cancelable: false
536
- });
537
-
538
- this.element.dispatchEvent(beforeActivateEvent);
539
-
540
- const userPromise = beforeActivateDetail.promise;
541
-
542
- if (userPromise) {
543
- this._isLoadingAsync = true;
544
- this._log('Waiting for content...');
545
-
546
- let spinnerTimeout: ReturnType<typeof setTimeout> | undefined;
547
- let loadingShown = false;
548
-
549
- if (this.config.loadingDelay > 0) {
550
- spinnerTimeout = setTimeout(() => {
551
- this.element.classList.add('is-loading');
552
- loadingShown = true;
553
- }, this.config.loadingDelay);
554
- } else {
555
- this.element.classList.add('is-loading');
556
- loadingShown = true;
557
- }
558
-
559
- const hasPreviousPanel = this.activePanel && this.activePanel !== newPanel;
560
-
561
- if (!hasPreviousPanel) {
562
- const shouldTransition = withTransition !== false && this.config.transitions !== false;
563
- let heightTransition = shouldTransition;
564
- if (typeof this.config.transitions === 'object') {
565
- heightTransition = shouldTransition && this.config.transitions.height !== false;
566
- }
567
-
568
- if (heightTransition) {
569
- const currentHeight = this.element.offsetHeight;
570
- this.element.style.height = `${currentHeight}px`;
571
-
572
- requestAnimationFrame(() => {
573
- this.element.style.height = `${this.config.emptyPanelHeight}px`;
574
- });
575
- }
576
- }
577
-
578
- try {
579
- await userPromise;
580
-
581
- if (spinnerTimeout) clearTimeout(spinnerTimeout);
582
-
583
- if (abortController.signal.aborted) {
584
- this._log(`Aborted during load: ${panelId}`);
585
- if (loadingShown) this.element.classList.remove('is-loading');
586
- return;
587
- }
588
-
589
- this._log('Content loaded');
590
- } catch (error) {
591
- if (spinnerTimeout) clearTimeout(spinnerTimeout);
592
-
593
- const err = error as Error;
594
- this._log(`Load failed: ${err.message}`);
595
- if (loadingShown) this.element.classList.remove('is-loading');
596
-
597
- if (err.name !== 'AbortError') {
598
- console.error('Panel load error:', error);
599
- }
600
-
601
- return;
602
- }
603
-
604
- if (loadingShown) this.element.classList.remove('is-loading');
605
- }
606
-
607
- if (abortController.signal.aborted) {
608
- this._log(`Aborted: ${panelId}`);
609
- return;
610
- }
611
-
612
- this._dispatch<ActivationEventDetail>('ps:activationstart', {
613
- panelId,
614
- trigger: options.trigger || null
615
- });
616
-
617
- const shouldTransition = withTransition !== false && this.config.transitions !== false;
618
-
619
- let panelTransition = shouldTransition;
620
- let heightTransition = shouldTransition;
621
-
622
- if (typeof this.config.transitions === 'object') {
623
- panelTransition = shouldTransition && this.config.transitions.panels !== false;
624
- heightTransition = shouldTransition && this.config.transitions.height !== false;
625
- }
626
-
627
- this.panels.forEach(panel => panel.classList.toggle('fade', panelTransition));
628
-
629
- const startHeight = this.element.offsetHeight;
630
- if (heightTransition) {
631
- this.element.style.height = `${startHeight}px`;
632
- }
633
-
634
- const outgoingPanel = this.activePanel;
635
-
636
- newPanel.hidden = false;
637
- newPanel.classList.add('incoming');
638
- if (panelTransition) {
639
- // Apply general transitioning class for overflow: hidden
640
- this.element.classList.add('is-transitioning');
641
- }
642
- if (outgoingPanel && outgoingPanel !== newPanel) {
643
- outgoingPanel.classList.remove('active', 'incoming');
644
- outgoingPanel.hidden = false;
645
- }
646
-
647
- requestAnimationFrame(() => {
648
- newPanel.classList.add('active');
649
- if (outgoingPanel && outgoingPanel !== newPanel) {
650
- outgoingPanel.classList.remove('incoming');
651
- }
652
-
653
- const targetHeight = this._measureHeight(newPanel);
654
- const heightChanged = startHeight !== targetHeight;
655
-
656
- if (heightTransition) {
657
- this.element.style.height = `${targetHeight}px`;
658
- }
659
-
660
- const promises: Promise<void>[] = [];
661
- if (panelTransition) {
662
- promises.push(this._waitForTransition(newPanel));
663
- }
664
- if (heightTransition && heightChanged) {
665
- promises.push(this._waitForTransition(this.element));
666
- }
667
- if (!promises.length) promises.push(Promise.resolve());
668
-
669
- Promise.all(promises).then(() => {
670
- // Check if interrupted by another activation
671
- if (this.pendingPanel !== newPanel) {
672
- this._log(`Interrupted: ${panelId}`);
673
- return;
674
- }
675
-
676
- this._cleanupPanels(newPanel);
677
- this._log(`✓ ${panelId}`);
678
- this._dispatch<ActivationEventDetail>('ps:activationcomplete', {
679
- panelId,
680
- trigger: options.trigger || null
681
- });
682
- });
683
- });
684
- }
685
- }