@moni-labs/moni-ui 0.4.13 → 0.4.16
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/custom-elements.json +101 -81
- package/dist/browser/moni-ui.iife.min.js +1538 -1384
- package/dist/browser/moni-ui.iife.min.js.br +0 -0
- package/dist/browser/moni-ui.iife.min.js.gz +0 -0
- package/dist/browser/moni-ui.iife.min.js.map +1 -1
- package/dist/browser/moni-ui.min.js +2751 -2501
- package/dist/browser/moni-ui.min.js.br +0 -0
- package/dist/browser/moni-ui.min.js.gz +0 -0
- package/dist/browser/moni-ui.min.js.map +1 -1
- package/dist/cdn/chunks/{class-map-DsL1Jr2T.js → class-map-QyT9W7rZ.js} +1 -1
- package/dist/cdn/chunks/{decorate-DxJYS4gw.js → decorate-B7wZvwrn.js} +629 -543
- package/dist/cdn/chunks/if-defined-5hr_ccFw.js +1 -0
- package/dist/cdn/chunks/{live-D52JqeKf.js → live-Hm1Di_ND.js} +1 -1
- package/dist/cdn/components/moni-app-bar.js +1 -1
- package/dist/cdn/components/moni-badge.js +1 -1
- package/dist/cdn/components/moni-bottom-sheet.js +2 -1
- package/dist/cdn/components/moni-button-group.js +1 -1
- package/dist/cdn/components/moni-button-segment.js +1 -1
- package/dist/cdn/components/moni-button.js +1 -1
- package/dist/cdn/components/moni-card.js +1 -1
- package/dist/cdn/components/moni-carousel.js +1 -1
- package/dist/cdn/components/moni-checkbox.js +1 -1
- package/dist/cdn/components/moni-chip.js +1 -1
- package/dist/cdn/components/moni-color-field.js +1 -1
- package/dist/cdn/components/moni-context-menu.js +1 -1
- package/dist/cdn/components/moni-dialog.js +1 -1
- package/dist/cdn/components/moni-divider.js +1 -1
- package/dist/cdn/components/moni-expansion.js +1 -1
- package/dist/cdn/components/moni-fab-menu.js +1 -1
- package/dist/cdn/components/moni-fab.js +1 -1
- package/dist/cdn/components/moni-file-field.js +1 -1
- package/dist/cdn/components/moni-icon.js +1 -1
- package/dist/cdn/components/moni-list-item.js +1 -1
- package/dist/cdn/components/moni-list.js +1 -1
- package/dist/cdn/components/moni-loading-indicator.js +1 -1
- package/dist/cdn/components/moni-menu-item.js +1 -1
- package/dist/cdn/components/moni-menu.js +1 -1
- package/dist/cdn/components/moni-morph-modal.js +3 -2
- package/dist/cdn/components/moni-nav-item.js +1 -1
- package/dist/cdn/components/moni-nav.js +1 -1
- package/dist/cdn/components/moni-progress.js +1 -1
- package/dist/cdn/components/moni-radio.js +1 -1
- package/dist/cdn/components/moni-ripple.js +1 -1
- package/dist/cdn/components/moni-segmented-button.js +1 -1
- package/dist/cdn/components/moni-select-option.js +1 -1
- package/dist/cdn/components/moni-select.js +512 -473
- package/dist/cdn/components/moni-shape.js +1 -1
- package/dist/cdn/components/moni-side-sheet.js +1 -1
- package/dist/cdn/components/moni-slider.js +1 -1
- package/dist/cdn/components/moni-snackbar.js +1 -1
- package/dist/cdn/components/moni-split-button.js +1 -1
- package/dist/cdn/components/moni-step.js +1 -1
- package/dist/cdn/components/moni-stepper.js +1 -1
- package/dist/cdn/components/moni-switch.js +1 -1
- package/dist/cdn/components/moni-tab.js +1 -1
- package/dist/cdn/components/moni-tabs.js +1 -1
- package/dist/cdn/components/moni-text-field.js +122 -95
- package/dist/cdn/components/moni-textarea.js +52 -52
- package/dist/cdn/components/moni-time-picker.js +1 -1
- package/dist/cdn/components/moni-toolbar.js +1 -1
- package/dist/cdn/components/moni-tooltip.js +1 -1
- package/dist/cdn/components/moni-typography.js +1 -1
- package/dist/cdn/importmap.json +98 -98
- package/dist/components/_base/field-styles.d.ts.map +1 -1
- package/dist/components/_base/field-styles.js +662 -576
- package/dist/components/moni-bottom-sheet.d.ts +19 -5
- package/dist/components/moni-bottom-sheet.d.ts.map +1 -1
- package/dist/components/moni-bottom-sheet.js +111 -30
- package/dist/components/moni-morph-modal.d.ts +10 -0
- package/dist/components/moni-morph-modal.d.ts.map +1 -1
- package/dist/components/moni-morph-modal.js +323 -217
- package/dist/components/moni-select.d.ts +16 -16
- package/dist/components/moni-select.d.ts.map +1 -1
- package/dist/components/moni-select.js +797 -727
- package/dist/components/moni-text-field.d.ts +15 -15
- package/dist/components/moni-text-field.d.ts.map +1 -1
- package/dist/components/moni-text-field.js +224 -180
- package/dist/components/moni-textarea.d.ts +13 -13
- package/dist/components/moni-textarea.d.ts.map +1 -1
- package/dist/components/moni-textarea.js +127 -122
- package/docs/api.json +109 -89
- package/docs/assets/moni-color-field/shape--square.png +0 -0
- package/docs/assets/moni-file-field/shape--square.png +0 -0
- package/docs/assets/moni-morph-modal/autoHeight--false.png +0 -0
- package/docs/assets/moni-morph-modal/autoHeight--true.png +0 -0
- package/docs/components/bottom-sheet.md +2 -1
- package/docs/components/morph-modal.md +16 -7
- package/docs/components/select.md +74 -74
- package/docs/components/text-field.md +75 -66
- package/docs/components/textarea.md +51 -51
- package/docs/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/_base/field-styles.ts +723 -637
- package/src/components/moni-bottom-sheet.test.ts +331 -0
- package/src/components/moni-bottom-sheet.ts +117 -29
- package/src/components/moni-morph-modal.test.ts +490 -413
- package/src/components/moni-morph-modal.ts +2274 -2162
- package/src/components/moni-select.test.ts +489 -466
- package/src/components/moni-select.ts +1963 -1815
- package/src/components/moni-text-field.test.ts +366 -342
- package/src/components/moni-text-field.ts +717 -645
- package/src/components/moni-textarea.test.ts +201 -197
- package/src/components/moni-textarea.ts +476 -460
- package/dist/cdn/chunks/if-defined-DdzvcoI7.js +0 -1
|
@@ -432,4 +432,335 @@ describe('moni-bottom-sheet', () => {
|
|
|
432
432
|
expect(closeSpy).not.toHaveBeenCalled();
|
|
433
433
|
expect(el.open).toBe(true);
|
|
434
434
|
});
|
|
435
|
+
|
|
436
|
+
it('detecta arrastre fuera de la pantalla y marca como cerrado', async () => {
|
|
437
|
+
el.handle = true;
|
|
438
|
+
el.open = true;
|
|
439
|
+
await el.updateComplete;
|
|
440
|
+
|
|
441
|
+
const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
|
|
442
|
+
const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
|
|
443
|
+
|
|
444
|
+
handle.setPointerCapture = vi.fn();
|
|
445
|
+
handle.releasePointerCapture = vi.fn();
|
|
446
|
+
|
|
447
|
+
vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
|
|
448
|
+
height: 300,
|
|
449
|
+
width: 375,
|
|
450
|
+
x: 0,
|
|
451
|
+
y: 300,
|
|
452
|
+
top: 300,
|
|
453
|
+
bottom: 600,
|
|
454
|
+
left: 0,
|
|
455
|
+
right: 375,
|
|
456
|
+
toJSON: () => {}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const closeSpy = vi.fn();
|
|
460
|
+
el.addEventListener('close', closeSpy);
|
|
461
|
+
|
|
462
|
+
handle.dispatchEvent(
|
|
463
|
+
new PointerEvent('pointerdown', {
|
|
464
|
+
bubbles: true,
|
|
465
|
+
clientX: 100,
|
|
466
|
+
clientY: 400,
|
|
467
|
+
pointerId: 1
|
|
468
|
+
})
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// Arrastrar hacia el borde inferior/fuera de pantalla
|
|
472
|
+
dialog.dispatchEvent(
|
|
473
|
+
new PointerEvent('pointermove', {
|
|
474
|
+
bubbles: true,
|
|
475
|
+
clientX: 100,
|
|
476
|
+
clientY: window.innerHeight || 800,
|
|
477
|
+
pointerId: 1
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// Soltar fuera de la pantalla
|
|
482
|
+
handle.dispatchEvent(
|
|
483
|
+
new PointerEvent('pointerup', {
|
|
484
|
+
bubbles: true,
|
|
485
|
+
clientX: 100,
|
|
486
|
+
clientY: (window.innerHeight || 800) + 50,
|
|
487
|
+
pointerId: 1
|
|
488
|
+
})
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
dialog.dispatchEvent(new Event('transitionend'));
|
|
492
|
+
await el.updateComplete;
|
|
493
|
+
|
|
494
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
495
|
+
expect(el.open).toBe(false);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('cierra y marca como open=false cuando ocurre pointercancel tras arrastrar hacia abajo', async () => {
|
|
499
|
+
el.handle = true;
|
|
500
|
+
el.open = true;
|
|
501
|
+
await el.updateComplete;
|
|
502
|
+
|
|
503
|
+
const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
|
|
504
|
+
const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
|
|
505
|
+
|
|
506
|
+
handle.setPointerCapture = vi.fn();
|
|
507
|
+
handle.releasePointerCapture = vi.fn();
|
|
508
|
+
|
|
509
|
+
vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
|
|
510
|
+
height: 300,
|
|
511
|
+
width: 375,
|
|
512
|
+
x: 0,
|
|
513
|
+
y: 300,
|
|
514
|
+
top: 300,
|
|
515
|
+
bottom: 600,
|
|
516
|
+
left: 0,
|
|
517
|
+
right: 375,
|
|
518
|
+
toJSON: () => {}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
const closeSpy = vi.fn();
|
|
522
|
+
el.addEventListener('close', closeSpy);
|
|
523
|
+
|
|
524
|
+
handle.dispatchEvent(
|
|
525
|
+
new PointerEvent('pointerdown', {
|
|
526
|
+
bubbles: true,
|
|
527
|
+
clientX: 100,
|
|
528
|
+
clientY: 400,
|
|
529
|
+
pointerId: 1
|
|
530
|
+
})
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
// Arrastre hacia abajo significativo
|
|
534
|
+
dialog.dispatchEvent(
|
|
535
|
+
new PointerEvent('pointermove', {
|
|
536
|
+
bubbles: true,
|
|
537
|
+
clientX: 100,
|
|
538
|
+
clientY: 550, // 150px
|
|
539
|
+
pointerId: 1
|
|
540
|
+
})
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// El navegador cancela el puntero (por ejemplo, salida de viewport en móvil/pantalla táctil)
|
|
544
|
+
dialog.dispatchEvent(
|
|
545
|
+
new PointerEvent('pointercancel', {
|
|
546
|
+
bubbles: true,
|
|
547
|
+
clientX: 0,
|
|
548
|
+
clientY: 0,
|
|
549
|
+
pointerId: 1
|
|
550
|
+
})
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
dialog.dispatchEvent(new Event('transitionend'));
|
|
554
|
+
await el.updateComplete;
|
|
555
|
+
|
|
556
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
557
|
+
expect(el.open).toBe(false);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('cierra y marca como open=false cuando ocurre lostpointercapture tras arrastrar hacia abajo', async () => {
|
|
561
|
+
el.handle = true;
|
|
562
|
+
el.open = true;
|
|
563
|
+
await el.updateComplete;
|
|
564
|
+
|
|
565
|
+
const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
|
|
566
|
+
const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
|
|
567
|
+
|
|
568
|
+
handle.setPointerCapture = vi.fn();
|
|
569
|
+
handle.releasePointerCapture = vi.fn();
|
|
570
|
+
|
|
571
|
+
vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
|
|
572
|
+
height: 300,
|
|
573
|
+
width: 375,
|
|
574
|
+
x: 0,
|
|
575
|
+
y: 300,
|
|
576
|
+
top: 300,
|
|
577
|
+
bottom: 600,
|
|
578
|
+
left: 0,
|
|
579
|
+
right: 375,
|
|
580
|
+
toJSON: () => {}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const closeSpy = vi.fn();
|
|
584
|
+
el.addEventListener('close', closeSpy);
|
|
585
|
+
|
|
586
|
+
handle.dispatchEvent(
|
|
587
|
+
new PointerEvent('pointerdown', {
|
|
588
|
+
bubbles: true,
|
|
589
|
+
clientX: 100,
|
|
590
|
+
clientY: 400,
|
|
591
|
+
pointerId: 1
|
|
592
|
+
})
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
dialog.dispatchEvent(
|
|
596
|
+
new PointerEvent('pointermove', {
|
|
597
|
+
bubbles: true,
|
|
598
|
+
clientX: 100,
|
|
599
|
+
clientY: 520, // 120px
|
|
600
|
+
pointerId: 1
|
|
601
|
+
})
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
dialog.dispatchEvent(
|
|
605
|
+
new PointerEvent('lostpointercapture', {
|
|
606
|
+
bubbles: true,
|
|
607
|
+
clientX: 0,
|
|
608
|
+
clientY: 0,
|
|
609
|
+
pointerId: 1
|
|
610
|
+
})
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
dialog.dispatchEvent(new Event('transitionend'));
|
|
614
|
+
await el.updateComplete;
|
|
615
|
+
|
|
616
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
617
|
+
expect(el.open).toBe(false);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('cierra y marca como open=false cuando se arrastra un bottom sheet expandido hacia abajo', async () => {
|
|
621
|
+
el.handle = true;
|
|
622
|
+
el.open = true;
|
|
623
|
+
await el.updateComplete;
|
|
624
|
+
|
|
625
|
+
const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
|
|
626
|
+
const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
|
|
627
|
+
dialog.classList.add('expanded');
|
|
628
|
+
|
|
629
|
+
handle.setPointerCapture = vi.fn();
|
|
630
|
+
handle.releasePointerCapture = vi.fn();
|
|
631
|
+
|
|
632
|
+
vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
|
|
633
|
+
height: 600,
|
|
634
|
+
width: 375,
|
|
635
|
+
x: 0,
|
|
636
|
+
y: 100,
|
|
637
|
+
top: 100,
|
|
638
|
+
bottom: 700,
|
|
639
|
+
left: 0,
|
|
640
|
+
right: 375,
|
|
641
|
+
toJSON: () => {}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
const closeSpy = vi.fn();
|
|
645
|
+
el.addEventListener('close', closeSpy);
|
|
646
|
+
|
|
647
|
+
handle.dispatchEvent(
|
|
648
|
+
new PointerEvent('pointerdown', {
|
|
649
|
+
bubbles: true,
|
|
650
|
+
clientX: 100,
|
|
651
|
+
clientY: 100,
|
|
652
|
+
pointerId: 1
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
dialog.dispatchEvent(
|
|
657
|
+
new PointerEvent('pointermove', {
|
|
658
|
+
bubbles: true,
|
|
659
|
+
clientX: 100,
|
|
660
|
+
clientY: 550, // Delta de 450px hacia abajo (arrastrado fuera / muy abajo)
|
|
661
|
+
pointerId: 1
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
handle.dispatchEvent(
|
|
666
|
+
new PointerEvent('pointerup', {
|
|
667
|
+
bubbles: true,
|
|
668
|
+
clientX: 100,
|
|
669
|
+
clientY: 550,
|
|
670
|
+
pointerId: 1
|
|
671
|
+
})
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
dialog.dispatchEvent(new Event('transitionend'));
|
|
675
|
+
await el.updateComplete;
|
|
676
|
+
|
|
677
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
678
|
+
expect(el.open).toBe(false);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('no cierra el bottom sheet si el usuario arrastra hacia abajo pero lo sube de nuevo antes de soltar', async () => {
|
|
682
|
+
el.handle = true;
|
|
683
|
+
el.open = true;
|
|
684
|
+
await el.updateComplete;
|
|
685
|
+
|
|
686
|
+
const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
|
|
687
|
+
const handle = el.shadowRoot?.querySelector('.handle') as HTMLElement;
|
|
688
|
+
|
|
689
|
+
handle.setPointerCapture = vi.fn();
|
|
690
|
+
handle.releasePointerCapture = vi.fn();
|
|
691
|
+
|
|
692
|
+
vi.spyOn(dialog, 'getBoundingClientRect').mockReturnValue({
|
|
693
|
+
height: 300,
|
|
694
|
+
width: 375,
|
|
695
|
+
x: 0,
|
|
696
|
+
y: 300,
|
|
697
|
+
top: 300,
|
|
698
|
+
bottom: 600,
|
|
699
|
+
left: 0,
|
|
700
|
+
right: 375,
|
|
701
|
+
toJSON: () => {}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const closeSpy = vi.fn();
|
|
705
|
+
el.addEventListener('close', closeSpy);
|
|
706
|
+
|
|
707
|
+
// Inicia arrastre en Y = 400
|
|
708
|
+
handle.dispatchEvent(
|
|
709
|
+
new PointerEvent('pointerdown', {
|
|
710
|
+
bubbles: true,
|
|
711
|
+
clientX: 100,
|
|
712
|
+
clientY: 400,
|
|
713
|
+
pointerId: 1
|
|
714
|
+
})
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// Arrastra hacia abajo (Y = 600, delta = 200px)
|
|
718
|
+
dialog.dispatchEvent(
|
|
719
|
+
new PointerEvent('pointermove', {
|
|
720
|
+
bubbles: true,
|
|
721
|
+
clientX: 100,
|
|
722
|
+
clientY: 600,
|
|
723
|
+
pointerId: 1
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
// Cambia de opinión y lo sube de nuevo cerca del origen (Y = 410, delta = 10px)
|
|
728
|
+
dialog.dispatchEvent(
|
|
729
|
+
new PointerEvent('pointermove', {
|
|
730
|
+
bubbles: true,
|
|
731
|
+
clientX: 100,
|
|
732
|
+
clientY: 410,
|
|
733
|
+
pointerId: 1
|
|
734
|
+
})
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
// Suelta en Y = 410
|
|
738
|
+
handle.dispatchEvent(
|
|
739
|
+
new PointerEvent('pointerup', {
|
|
740
|
+
bubbles: true,
|
|
741
|
+
clientX: 100,
|
|
742
|
+
clientY: 410,
|
|
743
|
+
pointerId: 1
|
|
744
|
+
})
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
await el.updateComplete;
|
|
748
|
+
|
|
749
|
+
expect(closeSpy).not.toHaveBeenCalled();
|
|
750
|
+
expect(el.open).toBe(true);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it('permite abrir y cerrar mediante los métodos show() y close()', async () => {
|
|
754
|
+
expect(el.open).toBe(false);
|
|
755
|
+
el.show();
|
|
756
|
+
await el.updateComplete;
|
|
757
|
+
expect(el.open).toBe(true);
|
|
758
|
+
|
|
759
|
+
const dialog = el.shadowRoot?.querySelector('dialog') as HTMLDialogElement;
|
|
760
|
+
const closePromise = el.close();
|
|
761
|
+
dialog.dispatchEvent(new Event('transitionend'));
|
|
762
|
+
await closePromise;
|
|
763
|
+
await el.updateComplete;
|
|
764
|
+
expect(el.open).toBe(false);
|
|
765
|
+
});
|
|
435
766
|
});
|
|
@@ -164,11 +164,34 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
164
164
|
@query('dialog') private _dialog!: HTMLDialogElement;
|
|
165
165
|
private _isDragging = false;
|
|
166
166
|
private _startY = 0;
|
|
167
|
-
private
|
|
167
|
+
private _lastDeltaY = 0;
|
|
168
168
|
private _sheetHeight = 0;
|
|
169
169
|
private _defaultHeight = 0;
|
|
170
170
|
private _justDragged = false;
|
|
171
171
|
private _skipRequestClose = false;
|
|
172
|
+
private _isClosing = false;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Abre el bottom sheet.
|
|
176
|
+
*/
|
|
177
|
+
show() {
|
|
178
|
+
this._isClosing = false;
|
|
179
|
+
this._isDragging = false;
|
|
180
|
+
this._lastDeltaY = 0;
|
|
181
|
+
if (this._dialog) {
|
|
182
|
+
this._dialog.classList.remove('dragging', 'expanded');
|
|
183
|
+
this._dialog.style.transform = '';
|
|
184
|
+
this._dialog.style.height = '';
|
|
185
|
+
}
|
|
186
|
+
this.open = true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Cierra el bottom sheet ejecutando la animación fluida hacia abajo.
|
|
191
|
+
*/
|
|
192
|
+
close() {
|
|
193
|
+
return this._animateAndClose();
|
|
194
|
+
}
|
|
172
195
|
|
|
173
196
|
override willUpdate(changed: Map<string, unknown>) {
|
|
174
197
|
if (changed.has('open') && this.open !== changed.get('open')) {
|
|
@@ -188,12 +211,18 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
private async _animateAndClose() {
|
|
214
|
+
if (this._isClosing) return false;
|
|
215
|
+
if (!this.open) return false;
|
|
216
|
+
this._isClosing = true;
|
|
217
|
+
|
|
191
218
|
this._skipRequestClose = true;
|
|
192
219
|
const allowed = emitMoniEvent(this, 'moni-request-close', { cancelable: true });
|
|
193
220
|
this._skipRequestClose = false;
|
|
194
|
-
if (!allowed)
|
|
221
|
+
if (!allowed) {
|
|
222
|
+
this._isClosing = false;
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
195
225
|
|
|
196
|
-
this.dispatchEvent(new Event('close', { bubbles: true, composed: true }));
|
|
197
226
|
emitMoniEvent(this, 'moni-close');
|
|
198
227
|
|
|
199
228
|
this._dialog.classList.remove('expanded');
|
|
@@ -209,10 +238,18 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
209
238
|
setTimeout(onEnd, 300);
|
|
210
239
|
});
|
|
211
240
|
|
|
212
|
-
this.
|
|
241
|
+
this._isClosing = false;
|
|
242
|
+
this._isDragging = false;
|
|
243
|
+
this._lastDeltaY = 0;
|
|
244
|
+
if (this._dialog) {
|
|
245
|
+
this._dialog.classList.remove('dragging', 'expanded');
|
|
246
|
+
this._dialog.style.transform = '';
|
|
247
|
+
this._dialog.style.height = '';
|
|
248
|
+
}
|
|
213
249
|
this._skipRequestClose = true;
|
|
214
250
|
this.open = false;
|
|
215
251
|
this._skipRequestClose = false;
|
|
252
|
+
this.dispatchEvent(new Event('close', { bubbles: true, composed: true }));
|
|
216
253
|
emitMoniEvent(this, 'moni-closed');
|
|
217
254
|
return true;
|
|
218
255
|
}
|
|
@@ -250,16 +287,22 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
250
287
|
this._isDragging = true;
|
|
251
288
|
this._startY = e.clientY; // Almacenamos la coordenada inicial Y para calcular el delta luego
|
|
252
289
|
this._sheetHeight = this._dialog.getBoundingClientRect().height; // Capturamos la altura actual
|
|
253
|
-
this.
|
|
290
|
+
this._lastDeltaY = 0;
|
|
254
291
|
|
|
255
292
|
const isExpanded = this._dialog.classList.contains('expanded');
|
|
256
293
|
// Si no estaba expandido previamente, guardamos su altura por defecto para usarla como punto de retorno.
|
|
257
294
|
if (!isExpanded) {
|
|
258
295
|
this._defaultHeight = this._sheetHeight;
|
|
296
|
+
} else if (!this._defaultHeight) {
|
|
297
|
+
this._defaultHeight = Math.min(this._sheetHeight * 0.6, 384);
|
|
259
298
|
}
|
|
260
299
|
|
|
261
300
|
// setPointerCapture enruta todos los eventos subsecuentes a este target
|
|
262
|
-
|
|
301
|
+
try {
|
|
302
|
+
target.setPointerCapture(e.pointerId);
|
|
303
|
+
} catch {
|
|
304
|
+
// Ignorar si el navegador ya maneja el puntero de forma nativa
|
|
305
|
+
}
|
|
263
306
|
this._dialog.classList.add('dragging'); // Desactiva las transiciones CSS para un seguimiento suave 1:1
|
|
264
307
|
}
|
|
265
308
|
|
|
@@ -273,6 +316,7 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
273
316
|
|
|
274
317
|
// Diferencia en píxeles desde donde se inició el click
|
|
275
318
|
const deltaY = e.clientY - this._startY;
|
|
319
|
+
this._lastDeltaY = deltaY;
|
|
276
320
|
const isExpanded = this._dialog.classList.contains('expanded');
|
|
277
321
|
const maxHeight = this._getMaxHeightPx();
|
|
278
322
|
|
|
@@ -291,7 +335,6 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
291
335
|
} else {
|
|
292
336
|
if (deltaY > 0) {
|
|
293
337
|
// Arrastrando hacia abajo desde su estado inicial: lo empujamos visualmente hacia abajo (translate)
|
|
294
|
-
this._currentTranslationY = deltaY;
|
|
295
338
|
this._dialog.style.transform = `translateY(${deltaY}px)`;
|
|
296
339
|
this._dialog.style.height = '';
|
|
297
340
|
} else {
|
|
@@ -306,17 +349,26 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
306
349
|
/**
|
|
307
350
|
* Finaliza la interacción de arrastre, evaluando si el bottom-sheet debe
|
|
308
351
|
* expandirse, regresar a su estado original o cerrarse completamente según la
|
|
309
|
-
* inercia o la
|
|
352
|
+
* inercia, la distancia arrastrada o si se soltó fuera/hacia la parte inferior de la pantalla.
|
|
310
353
|
*/
|
|
311
354
|
private _onPointerUp(e: PointerEvent) {
|
|
312
355
|
if (!this._isDragging) return;
|
|
313
356
|
|
|
314
357
|
const target = e.target as HTMLElement;
|
|
315
|
-
|
|
358
|
+
try {
|
|
359
|
+
if (target && target.hasPointerCapture?.(e.pointerId)) {
|
|
360
|
+
target.releasePointerCapture(e.pointerId);
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
// Ignorar si la captura ya fue liberada o perdida por el navegador
|
|
364
|
+
}
|
|
316
365
|
this._isDragging = false;
|
|
317
366
|
this._dialog.classList.remove('dragging'); // Reactivamos las transiciones CSS
|
|
318
367
|
|
|
319
|
-
|
|
368
|
+
// Evaluamos la posición exacta al momento de soltar
|
|
369
|
+
const hasValidY = typeof e.clientY === 'number' && e.clientY > 0;
|
|
370
|
+
const deltaY = hasValidY ? (e.clientY - this._startY) : this._lastDeltaY;
|
|
371
|
+
|
|
320
372
|
// Si se movió más de 5px, bloqueamos clics accidentales para evitar disparar eventos subyacentes
|
|
321
373
|
if (Math.abs(deltaY) > 5) {
|
|
322
374
|
this._justDragged = true;
|
|
@@ -324,14 +376,24 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
324
376
|
this._justDragged = false;
|
|
325
377
|
}, 50);
|
|
326
378
|
}
|
|
379
|
+
|
|
327
380
|
const isExpanded = this._dialog.classList.contains('expanded');
|
|
328
381
|
const maxHeight = this._getMaxHeightPx();
|
|
382
|
+
const defaultHeight = this._defaultHeight || (this._sheetHeight * 0.6) || 300;
|
|
383
|
+
// Solo se considera off-screen si al SOLTAR el puntero se encuentra en o más allá del límite inferior
|
|
384
|
+
const isOffScreenBottom = hasValidY && typeof window !== 'undefined' && e.clientY >= (window.innerHeight - 30);
|
|
329
385
|
|
|
330
386
|
if (isExpanded) {
|
|
331
387
|
// Evaluamos la altura final tras soltar el ratón/dedo
|
|
332
388
|
const finalHeight = this._sheetHeight - deltaY;
|
|
333
|
-
|
|
334
|
-
|
|
389
|
+
const shouldClose =
|
|
390
|
+
isOffScreenBottom ||
|
|
391
|
+
deltaY > this._sheetHeight * 0.4 ||
|
|
392
|
+
finalHeight < (defaultHeight - 80) ||
|
|
393
|
+
finalHeight < 100;
|
|
394
|
+
|
|
395
|
+
if (shouldClose) {
|
|
396
|
+
// Si se soltó significativamente hacia abajo o fuera de pantalla, lo cerramos
|
|
335
397
|
this._animateAndClose().then((closed) => {
|
|
336
398
|
if (!closed) {
|
|
337
399
|
// Si se canceló el evento, regresamos a su lugar
|
|
@@ -339,18 +401,18 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
339
401
|
this._dialog.style.height = '';
|
|
340
402
|
}
|
|
341
403
|
});
|
|
342
|
-
} else if (finalHeight < (maxHeight +
|
|
404
|
+
} else if (finalHeight < (maxHeight + defaultHeight) / 2) {
|
|
343
405
|
// Si se soltó a medio camino, lo colapsamos a su altura normal
|
|
344
406
|
this._dialog.classList.remove('expanded');
|
|
345
407
|
this._dialog.style.transform = '';
|
|
346
408
|
this._dialog.style.height = '';
|
|
347
409
|
} else {
|
|
348
|
-
// Si no se arrastró
|
|
410
|
+
// Si se subió de nuevo hacia arriba o no se arrastró suficiente, retorna a estado expandido (snap back)
|
|
349
411
|
this._dialog.style.transform = '';
|
|
350
412
|
this._dialog.style.height = '';
|
|
351
413
|
}
|
|
352
414
|
} else {
|
|
353
|
-
if (deltaY < 0) {
|
|
415
|
+
if (deltaY < 0 && !isOffScreenBottom) {
|
|
354
416
|
// Evaluamos el arrastre hacia arriba desde el estado colapsado
|
|
355
417
|
const finalHeight = this._sheetHeight - deltaY;
|
|
356
418
|
if (finalHeight > this._sheetHeight + 80) {
|
|
@@ -360,35 +422,47 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
360
422
|
this._dialog.style.transform = '';
|
|
361
423
|
this._dialog.style.height = '';
|
|
362
424
|
} else {
|
|
363
|
-
// Evaluamos el
|
|
364
|
-
|
|
425
|
+
// Evaluamos si al soltar superó el umbral de descarte o se soltó fuera de la pantalla
|
|
426
|
+
const threshold = Math.min(80, this._sheetHeight * 0.25);
|
|
427
|
+
const shouldClose =
|
|
428
|
+
isOffScreenBottom ||
|
|
429
|
+
deltaY > threshold;
|
|
430
|
+
|
|
431
|
+
if (shouldClose) {
|
|
365
432
|
this._animateAndClose().then((closed) => {
|
|
366
433
|
if (!closed) {
|
|
367
434
|
this._dialog.style.transform = '';
|
|
368
435
|
}
|
|
369
436
|
});
|
|
370
437
|
} else {
|
|
371
|
-
//
|
|
438
|
+
// Si el usuario bajó pero subió de nuevo antes de soltar (o no superó el umbral), regresa a abierto
|
|
372
439
|
this._dialog.style.transform = '';
|
|
373
440
|
}
|
|
374
441
|
}
|
|
375
442
|
}
|
|
443
|
+
|
|
444
|
+
this._lastDeltaY = 0;
|
|
376
445
|
}
|
|
377
446
|
|
|
378
447
|
/**
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
448
|
+
* Maneja la cancelación del puntero o interrupción del SO.
|
|
449
|
+
* Si el usuario ya lo había arrastrado hacia abajo o hacia fuera de la pantalla,
|
|
450
|
+
* evalúa el cierre en lugar de simplemente cancelar y dejar la hoja colgada.
|
|
382
451
|
*/
|
|
383
452
|
private _onPointerCancel(e: PointerEvent) {
|
|
384
|
-
if (
|
|
453
|
+
if (this._isDragging) {
|
|
454
|
+
this._onPointerUp(e);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
385
457
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.
|
|
458
|
+
/**
|
|
459
|
+
* Se ejecuta cuando se pierde la captura de puntero (por ejemplo al arrastrar fuera
|
|
460
|
+
* del viewport del navegador o perder el foco de la ventana).
|
|
461
|
+
*/
|
|
462
|
+
private _onLostPointerCapture(e: PointerEvent) {
|
|
463
|
+
if (this._isDragging) {
|
|
464
|
+
this._onPointerUp(e);
|
|
465
|
+
}
|
|
392
466
|
}
|
|
393
467
|
|
|
394
468
|
/**
|
|
@@ -427,6 +501,14 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
427
501
|
}
|
|
428
502
|
if (changed.has('open')) {
|
|
429
503
|
if (this.open) {
|
|
504
|
+
this._isClosing = false;
|
|
505
|
+
this._isDragging = false;
|
|
506
|
+
this._lastDeltaY = 0;
|
|
507
|
+
if (this._dialog) {
|
|
508
|
+
this._dialog.classList.remove('dragging', 'expanded');
|
|
509
|
+
this._dialog.style.transform = '';
|
|
510
|
+
this._dialog.style.height = '';
|
|
511
|
+
}
|
|
430
512
|
if (changed.get('open') !== undefined) {
|
|
431
513
|
emitMoniEvent(this, 'moni-open');
|
|
432
514
|
}
|
|
@@ -440,8 +522,13 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
440
522
|
setTimeout(() => emitMoniEvent(this, 'moni-opened'), 300);
|
|
441
523
|
}
|
|
442
524
|
} else {
|
|
525
|
+
this._isClosing = false;
|
|
526
|
+
this._isDragging = false;
|
|
527
|
+
this._lastDeltaY = 0;
|
|
443
528
|
if (this._dialog) {
|
|
444
|
-
this._dialog.classList.remove('expanded');
|
|
529
|
+
this._dialog.classList.remove('expanded', 'dragging');
|
|
530
|
+
this._dialog.style.transform = '';
|
|
531
|
+
this._dialog.style.height = '';
|
|
445
532
|
}
|
|
446
533
|
if (this.positioning === 'body' && this._originalParent && this.parentNode === document.body) {
|
|
447
534
|
this._originalParent.insertBefore(this, this._originalSibling);
|
|
@@ -628,6 +715,7 @@ export class MoniBottomSheet extends MoniElement {
|
|
|
628
715
|
@pointermove=${this._onPointerMove}
|
|
629
716
|
@pointerup=${this._onPointerUp}
|
|
630
717
|
@pointercancel=${this._onPointerCancel}
|
|
718
|
+
@lostpointercapture=${this._onLostPointerCapture}
|
|
631
719
|
>
|
|
632
720
|
${this.handle ? html`<div class="handle" aria-hidden="true"></div>` : nothing}
|
|
633
721
|
<header part="header">
|