@almoamendev/ngx-md3 0.3.8 → 0.5.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.
@@ -1,19 +1,19 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, booleanAttribute, effect, Directive, inject, signal, computed, Injectable, InjectionToken, ViewChild, Component, ViewContainerRef, Injector, viewChild, numberAttribute, HostListener, model, contentChild, contentChildren, PLATFORM_ID, afterNextRender, output, ElementRef, HostBinding, TemplateRef } from '@angular/core';
2
+ import { input, booleanAttribute, effect, Directive, inject, signal, computed, Injectable, InjectionToken, viewChild, NgZone, ElementRef, Component, ViewChild, isDevMode, ViewContainerRef, Injector, numberAttribute, HostListener, model, contentChild, contentChildren, PLATFORM_ID, afterNextRender, output, HostBinding, TemplateRef } from '@angular/core';
3
3
  import { DOCUMENT, isPlatformBrowser, NgClass, NgTemplateOutlet } from '@angular/common';
4
4
  import { BreakpointObserver } from '@angular/cdk/layout';
5
5
  import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
6
  import * as i1 from '@angular/router';
7
7
  import { Router, NavigationEnd, RouterLinkActive } from '@angular/router';
8
- import { fromEvent, startWith, map, filter, Subscription, Subject, merge, take } from 'rxjs';
9
- import { ComponentPortal, CdkPortalOutlet } from '@angular/cdk/portal';
8
+ import { fromEvent, startWith, map, filter, Subscription, Subject, take, takeUntil, merge } from 'rxjs';
9
+ import { CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
10
+ import { CdkDialogContainer, Dialog as Dialog$1 } from '@angular/cdk/dialog';
11
+ import { Overlay, OverlayContainer, OverlayConfig } from '@angular/cdk/overlay';
12
+ import { hasModifierKey } from '@angular/cdk/keycodes';
10
13
  import * as i2 from '@angular/cdk/scrolling';
11
14
  import { CdkScrollable } from '@angular/cdk/scrolling';
12
15
  import { Directionality } from '@angular/cdk/bidi';
13
16
  import { FormControlName } from '@angular/forms';
14
- import { CdkDialogContainer, Dialog as Dialog$1 } from '@angular/cdk/dialog';
15
- import { hasModifierKey } from '@angular/cdk/keycodes';
16
- import { Overlay, OverlayContainer, OverlayConfig } from '@angular/cdk/overlay';
17
17
 
18
18
  class TypeDisplay {
19
19
  el;
@@ -397,6 +397,1094 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
397
397
  }]
398
398
  }], ctorParameters: () => [] });
399
399
 
400
+ const BOTTOM_SHEET_DATA = new InjectionToken('MD3_BOTTOM_SHEET_DATA');
401
+ const BOTTOM_SHEET_CONFIG = new InjectionToken('MD3_BOTTOM_SHEET_CONFIG');
402
+ const BOTTOM_SHEET_COMPONENT = new InjectionToken('MD3_BOTTOM_SHEET_COMPONENT');
403
+ /** Long enough to outlast the surface transition, which the transitionend listener usually beats. */
404
+ const BOTTOM_SHEET_EXIT_ANIMATION_FALLBACK_MS = 600;
405
+ const BOTTOM_SHEET_DRAGGING_CLASS = 'md3-bottom-sheet-dragging';
406
+ /** A modal sheet has one height, so it reports the same state a standard sheet does when open. */
407
+ const ALWAYS_EXPANDED = signal('expanded').asReadonly();
408
+ /**
409
+ * What both kinds of bottom sheet have in common: closing once, animating out before the shell
410
+ * goes away, and reporting the result. The two shells differ in what they are hosted by — a CDK
411
+ * overlay or a scaffold outlet — so each subclass owns its own exit and teardown.
412
+ *
413
+ * This class stays the DI token, so a component written for one kind of sheet can be opened as
414
+ * the other without changing how it injects its reference.
415
+ */
416
+ class BottomSheetRef {
417
+ closed = new Subject();
418
+ closeStarted = false;
419
+ closeSettled = false;
420
+ /** Surface hosting the sheet. Filled in by BottomSheetService once the shell is created. */
421
+ sheetInstance;
422
+ /** Filled by BottomSheetService after the sheet component is attached. */
423
+ componentInstance;
424
+ /** Whether the sheet started closing. */
425
+ get isClosing() {
426
+ return this.closeStarted;
427
+ }
428
+ /** Height the sheet is resting at. A modal sheet is only ever fully open. */
429
+ get state() {
430
+ return ALWAYS_EXPANDED;
431
+ }
432
+ /** Closes the sheet. The promise resolves once the exit animation is done. */
433
+ close(result) {
434
+ if (this.closeStarted) {
435
+ return Promise.resolve();
436
+ }
437
+ this.closeStarted = true;
438
+ return this.startCloseAnimation().then(() => this.finishClose(result));
439
+ }
440
+ afterClosed() {
441
+ return this.closed.asObservable();
442
+ }
443
+ /**
444
+ * Wires the shell to this reference. Called by BottomSheetService, since the shell is
445
+ * created before this reference exists and cannot reach it through DI.
446
+ */
447
+ attachContainer(container) {
448
+ this.sheetInstance = container;
449
+ container.dismissed.pipe(take(1)).subscribe(() => this.close());
450
+ }
451
+ /** Moves a standard sheet to its expanded height. Modal sheets have only one height. */
452
+ expand() {
453
+ }
454
+ /** Moves a standard sheet back to its collapsed height. */
455
+ collapse() {
456
+ }
457
+ /** Switches a standard sheet between its two heights. */
458
+ toggle() {
459
+ }
460
+ setCollapsedHeight(height) {
461
+ }
462
+ setExpandedHeight(height) {
463
+ }
464
+ setDismissible(value) {
465
+ }
466
+ /** Resolves when the surface finished animating, with a timeout as a safety net. */
467
+ waitForSurfaceAnimation(fallback) {
468
+ const surface = this.sheetInstance?.surfaceElement ?? fallback ?? null;
469
+ if (!surface) {
470
+ return Promise.resolve();
471
+ }
472
+ return new Promise((resolve) => {
473
+ let isResolved = false;
474
+ const timeoutId = setTimeout(done, BOTTOM_SHEET_EXIT_ANIMATION_FALLBACK_MS);
475
+ function done() {
476
+ if (isResolved) {
477
+ return;
478
+ }
479
+ isResolved = true;
480
+ clearTimeout(timeoutId);
481
+ surface.removeEventListener('transitionend', onTransitionEnd);
482
+ resolve();
483
+ }
484
+ function onTransitionEnd(event) {
485
+ if (event.target === surface) {
486
+ done();
487
+ }
488
+ }
489
+ surface.addEventListener('transitionend', onTransitionEnd);
490
+ });
491
+ }
492
+ /** Emits the result once the sheet is gone. */
493
+ settle(result) {
494
+ if (this.closeSettled) {
495
+ return;
496
+ }
497
+ this.closeStarted = true;
498
+ this.closeSettled = true;
499
+ this.closed.next(result);
500
+ this.closed.complete();
501
+ }
502
+ }
503
+ /**
504
+ * A modal bottom sheet is a single overlay, not a stack like dialogs can be, so this is a
505
+ * smaller version of DialogRef: no hide/show, no full screen variant, closing is always the
506
+ * only way out.
507
+ */
508
+ class ModalBottomSheetRef extends BottomSheetRef {
509
+ cdkRef;
510
+ constructor(cdkRef) {
511
+ super();
512
+ this.cdkRef = cdkRef;
513
+ this.cdkRef.closed.pipe(take(1)).subscribe((result) => this.settle(result));
514
+ this.connectCloseEvents();
515
+ }
516
+ /** Overlay hosting the sheet, useful to reach the panel and the scrim. */
517
+ get overlayRef() {
518
+ return this.cdkRef.overlayRef;
519
+ }
520
+ attachContainer(container) {
521
+ super.attachContainer(container);
522
+ container.dragProgress.pipe(takeUntil(this.closed)).subscribe((progress) => this.trackDragProgress(progress));
523
+ }
524
+ startCloseAnimation() {
525
+ const overlayRef = this.cdkRef.overlayRef;
526
+ // Closing during a drag, from the Escape key for instance, leaves the scrim held
527
+ // where the drag left it, so it is handed back to CSS before the exit animation.
528
+ overlayRef.backdropElement?.classList.remove(BOTTOM_SHEET_DRAGGING_CLASS);
529
+ overlayRef.backdropElement?.style.removeProperty('--md3-drag-remaining');
530
+ overlayRef.overlayElement.classList.add('md3-bottom-sheet-closing');
531
+ overlayRef.backdropElement?.classList.add('md3-bottom-sheet-closing');
532
+ this.sheetInstance?.setActive(false);
533
+ return this.waitForSurfaceAnimation(overlayRef.overlayElement);
534
+ }
535
+ finishClose(result) {
536
+ this.cdkRef.close(result);
537
+ }
538
+ connectCloseEvents() {
539
+ // The CDK config keeps `disableClose` on, so the scrim and Escape key
540
+ // are handled here instead and the sheet can animate out.
541
+ this.cdkRef.backdropClick.pipe(take(1)).subscribe(() => this.close());
542
+ this.cdkRef.keydownEvents.pipe(filter((event) => event.key === 'Escape' && !hasModifierKey(event)), take(1)).subscribe((event) => {
543
+ event.preventDefault();
544
+ this.close();
545
+ });
546
+ }
547
+ /** Fades the scrim along with the sheet being dragged, and hands it back to CSS on release. */
548
+ trackDragProgress(progress) {
549
+ const backdrop = this.cdkRef.overlayRef.backdropElement;
550
+ if (!backdrop || this.closeStarted) {
551
+ return;
552
+ }
553
+ if (progress <= 0) {
554
+ backdrop.classList.remove(BOTTOM_SHEET_DRAGGING_CLASS);
555
+ backdrop.style.removeProperty('--md3-drag-remaining');
556
+ return;
557
+ }
558
+ backdrop.classList.add(BOTTOM_SHEET_DRAGGING_CLASS);
559
+ backdrop.style.setProperty('--md3-drag-remaining', `${1 - progress}`);
560
+ }
561
+ }
562
+ /**
563
+ * A standard bottom sheet lives in a scaffold outlet rather than an overlay, so there is no
564
+ * scrim to fade and nothing to dispose but the component itself. In exchange it can be moved
565
+ * between its two heights while it is open.
566
+ */
567
+ class StandardBottomSheetRef extends BottomSheetRef {
568
+ onClosed;
569
+ sheetComponentRef;
570
+ host;
571
+ constructor(onClosed) {
572
+ super();
573
+ this.onClosed = onClosed;
574
+ }
575
+ get state() {
576
+ return this.host?.state ?? ALWAYS_EXPANDED;
577
+ }
578
+ /** Called by BottomSheetService once the shell has been created in the outlet. */
579
+ attachSheet(componentRef, host) {
580
+ this.sheetComponentRef = componentRef;
581
+ this.host = host;
582
+ this.attachContainer(host);
583
+ }
584
+ expand() {
585
+ this.host?.expand();
586
+ }
587
+ collapse() {
588
+ this.host?.collapse();
589
+ }
590
+ toggle() {
591
+ this.host?.toggle();
592
+ }
593
+ setCollapsedHeight(height) {
594
+ this.host?.setCollapsedHeight(height);
595
+ }
596
+ setExpandedHeight(height) {
597
+ this.host?.setExpandedHeight(height);
598
+ }
599
+ setDismissible(value) {
600
+ this.host?.setDismissible(value);
601
+ }
602
+ /**
603
+ * Tears the sheet down without an exit animation, for when the outlet holding it is going
604
+ * away and there is nothing left to animate in.
605
+ */
606
+ dispose(result) {
607
+ if (this.closeStarted && !this.sheetComponentRef) {
608
+ return;
609
+ }
610
+ this.closeStarted = true;
611
+ this.finishClose(result);
612
+ }
613
+ startCloseAnimation() {
614
+ this.sheetInstance?.setActive(false);
615
+ return this.waitForSurfaceAnimation();
616
+ }
617
+ finishClose(result) {
618
+ this.sheetComponentRef?.destroy();
619
+ this.sheetComponentRef = undefined;
620
+ this.host = undefined;
621
+ this.onClosed();
622
+ this.settle(result);
623
+ }
624
+ }
625
+
626
+ /** Movement shorter than this is still a tap rather than a drag. */
627
+ const DRAG_START_THRESHOLD_PX = 4;
628
+ /** Part of the segment being crossed that a drag has to cover to commit to the next snap point. */
629
+ const SETTLE_DISTANCE_RATIO = 0.4;
630
+ /** Speed, in pixels per millisecond, that carries the sheet to the next snap point on its own. */
631
+ const FLING_VELOCITY = 0.5;
632
+ /** A release this long after the last move is a stop, so the speed before it no longer counts. */
633
+ const VELOCITY_TIMEOUT_MS = 100;
634
+ /**
635
+ * Picks the snap point a released drag settles on, as an index into `points`.
636
+ *
637
+ * A drag only ever moves one point at a time: it commits to the neighbour in the direction of
638
+ * travel once it has covered enough of the gap, or immediately when it was thrown hard enough,
639
+ * and otherwise falls back to where it started. A modal sheet is the two-point case of this —
640
+ * resting and dismissed — which is why generalizing the gesture leaves it behaving the same.
641
+ */
642
+ function settleTarget(input) {
643
+ const { points, origin, offset, velocity, idleTime } = input;
644
+ if (points.length === 0) {
645
+ return 0;
646
+ }
647
+ const originIndex = nearestPointIndex(points, origin);
648
+ if (idleTime < VELOCITY_TIMEOUT_MS && Math.abs(velocity) > FLING_VELOCITY) {
649
+ return clampIndex$1(originIndex + (velocity > 0 ? 1 : -1), points.length);
650
+ }
651
+ const direction = Math.sign(offset - points[originIndex]);
652
+ if (direction === 0) {
653
+ return originIndex;
654
+ }
655
+ const targetIndex = clampIndex$1(originIndex + direction, points.length);
656
+ const segment = Math.abs(points[targetIndex] - points[originIndex]);
657
+ const travelled = Math.abs(offset - points[originIndex]);
658
+ return segment > 0 && travelled >= segment * SETTLE_DISTANCE_RATIO ? targetIndex : originIndex;
659
+ }
660
+ /** Snap point the given offset sits closest to. Ties go to the lower index. */
661
+ function nearestPointIndex(points, offset) {
662
+ let nearest = 0;
663
+ for (let index = 1; index < points.length; index++) {
664
+ if (Math.abs(points[index] - offset) < Math.abs(points[nearest] - offset)) {
665
+ nearest = index;
666
+ }
667
+ }
668
+ return nearest;
669
+ }
670
+ function clampIndex$1(index, length) {
671
+ return Math.min(Math.max(index, 0), length - 1);
672
+ }
673
+
674
+ /**
675
+ * The MD3 bottom sheet surface: the rounded panel, its drag handle and the gesture that moves
676
+ * it. Both shells wrap this — the modal one inside a CDK dialog container, the standard one
677
+ * inside a scaffold outlet — and project their own portal outlet into it, which is what lets
678
+ * the CDK keep the outlet in its own template while the markup lives here once.
679
+ *
680
+ * The surface reports gestures through observables instead of acting on them, because it is
681
+ * created before the sheet's reference exists and cannot reach it through DI.
682
+ */
683
+ class BottomSheetSurface {
684
+ surface = viewChild('surface', /* @ts-ignore */
685
+ ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
686
+ zone = inject(NgZone);
687
+ el = inject(ElementRef);
688
+ dismissedSheet = new Subject();
689
+ draggedSheet = new Subject();
690
+ settledSheet = new Subject();
691
+ mode = input('modal', /* @ts-ignore */
692
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
693
+ showHandle = input(true, /* @ts-ignore */
694
+ ...(ngDevMode ? [{ debugName: "showHandle" }] : /* istanbul ignore next */ []));
695
+ allowGestures = input(true, /* @ts-ignore */
696
+ ...(ngDevMode ? [{ debugName: "allowGestures" }] : /* istanbul ignore next */ []));
697
+ dismissible = input(false, /* @ts-ignore */
698
+ ...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
699
+ initialState = input('collapsed', /* @ts-ignore */
700
+ ...(ngDevMode ? [{ debugName: "initialState" }] : /* istanbul ignore next */ []));
701
+ scheme = input('inherit', /* @ts-ignore */
702
+ ...(ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []));
703
+ direction = input(null, /* @ts-ignore */
704
+ ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
705
+ handleLabel = input('Resize sheet', /* @ts-ignore */
706
+ ...(ngDevMode ? [{ debugName: "handleLabel" }] : /* istanbul ignore next */ []));
707
+ controlsId = input('', /* @ts-ignore */
708
+ ...(ngDevMode ? [{ debugName: "controlsId" }] : /* istanbul ignore next */ []));
709
+ isActive = signal(false, /* @ts-ignore */
710
+ ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
711
+ isDragging = signal(false, /* @ts-ignore */
712
+ ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
713
+ /** How far below the fully expanded position the sheet currently sits, in pixels. */
714
+ dragOffset = signal(0, /* @ts-ignore */
715
+ ...(ngDevMode ? [{ debugName: "dragOffset" }] : /* istanbul ignore next */ []));
716
+ /**
717
+ * Snap offsets in pixels, ascending: expanded, then collapsed for a standard sheet, then
718
+ * dismissed where dragging that far is allowed. Measured rather than derived from the
719
+ * configured CSS lengths, so `em`, `dvh` and the rest resolve themselves.
720
+ */
721
+ snapPoints = signal([0], /* @ts-ignore */
722
+ ...(ngDevMode ? [{ debugName: "snapPoints" }] : /* istanbul ignore next */ []));
723
+ settledIndex = signal(0, /* @ts-ignore */
724
+ ...(ngDevMode ? [{ debugName: "settledIndex" }] : /* istanbul ignore next */ []));
725
+ state = computed(() => {
726
+ return this.settledIndex() === 0 ? 'expanded' : 'collapsed';
727
+ }, /* @ts-ignore */
728
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
729
+ isHandleInteractive = computed(() => {
730
+ return this.mode() === 'standard' && this.allowGestures() && this.showHandle();
731
+ }, /* @ts-ignore */
732
+ ...(ngDevMode ? [{ debugName: "isHandleInteractive" }] : /* istanbul ignore next */ []));
733
+ /**
734
+ * Only set when a drag has to be seen out past where the stylesheet would leave the sheet,
735
+ * so the exit carries on the way the drag was going instead of doubling back.
736
+ */
737
+ exitTransform = signal(null, /* @ts-ignore */
738
+ ...(ngDevMode ? [{ debugName: "exitTransform" }] : /* istanbul ignore next */ []));
739
+ /**
740
+ * Left to the stylesheet unless the component has a position of its own to impose — a drag
741
+ * in progress, or the resting offset of a collapsed standard sheet. The closed and fully
742
+ * open positions stay in CSS so they apply as soon as the element is inserted, which is
743
+ * what the enter transition needs to have somewhere to run from.
744
+ */
745
+ surfaceTransform = computed(() => {
746
+ if (!this.isActive()) {
747
+ return this.exitTransform();
748
+ }
749
+ const offset = this.dragOffset();
750
+ return offset > 0 ? `translateY(${offset}px)` : null;
751
+ }, /* @ts-ignore */
752
+ ...(ngDevMode ? [{ debugName: "surfaceTransform" }] : /* istanbul ignore next */ []));
753
+ /** Pointer currently dragging the sheet, if there is one. */
754
+ pointerId;
755
+ /** Scrollable the drag started over, which owns the gesture while it has room to scroll. */
756
+ scroller = null;
757
+ startY = 0;
758
+ lastY = 0;
759
+ lastTimestamp = 0;
760
+ velocity = 0;
761
+ originOffset = 0;
762
+ isPastThreshold = false;
763
+ /** Set once the sheet is on its way out, so a late resize cannot pull it back into view. */
764
+ isExiting = false;
765
+ dismissed = this.dismissedSheet.asObservable();
766
+ dragProgress = this.draggedSheet.asObservable();
767
+ stateChanges = this.settledSheet.asObservable();
768
+ constructor() {
769
+ effect(() => {
770
+ if (this.mode() === 'standard') {
771
+ this.exitTransform.set('translateY(100%)');
772
+ }
773
+ });
774
+ // Heights are CSS lengths, so anything that resizes the surface — a viewport change, a
775
+ // new expanded height — moves the snap points with it and the sheet has to settle again.
776
+ effect((onCleanup) => {
777
+ const surface = this.el.nativeElement;
778
+ const observer = new ResizeObserver(() => this.remeasure());
779
+ observer.observe(surface);
780
+ onCleanup(() => observer.disconnect());
781
+ });
782
+ }
783
+ get surfaceElement() {
784
+ return this.surface()?.nativeElement ?? null;
785
+ }
786
+ startEnterAnimation() {
787
+ requestAnimationFrame(() => {
788
+ this.measure();
789
+ // A modal sheet has one resting height, so only a standard sheet can open collapsed.
790
+ const opensCollapsed = this.mode() === 'standard' && this.initialState() === 'collapsed';
791
+ this.snapTo(opensCollapsed ? this.collapsedIndex() : 0, false);
792
+ this.isActive.set(true);
793
+ });
794
+ }
795
+ setActive(value) {
796
+ if (!value) {
797
+ this.isExiting = true;
798
+ }
799
+ this.isActive.set(value);
800
+ }
801
+ expand() {
802
+ this.snapTo(0);
803
+ }
804
+ collapse() {
805
+ this.snapTo(this.collapsedIndex());
806
+ }
807
+ toggle() {
808
+ if (this.state() === 'expanded') {
809
+ this.collapse();
810
+ }
811
+ else {
812
+ this.expand();
813
+ }
814
+ }
815
+ destroy() {
816
+ this.releasePointer();
817
+ }
818
+ onHandleKeydown(event) {
819
+ if (event.key === 'ArrowUp') {
820
+ event.preventDefault();
821
+ this.expand();
822
+ }
823
+ // Collapsing is as far as the keyboard goes, even when a drag could dismiss the sheet:
824
+ // there is no undo for a dismissal reached by arrow key.
825
+ if (event.key === 'ArrowDown') {
826
+ event.preventDefault();
827
+ this.collapse();
828
+ }
829
+ }
830
+ onPointerDown(event) {
831
+ // Only the first pointer down drags: a second finger, or a mouse button other than
832
+ // the primary one, leaves context menus and multi-touch alone.
833
+ if (!this.allowGestures() || this.pointerId !== undefined || !event.isPrimary) {
834
+ return;
835
+ }
836
+ if (event.pointerType === 'mouse' && event.button !== 0) {
837
+ return;
838
+ }
839
+ this.measure();
840
+ this.pointerId = event.pointerId;
841
+ this.startY = this.lastY = event.clientY;
842
+ this.lastTimestamp = event.timeStamp;
843
+ this.velocity = 0;
844
+ this.originOffset = this.dragOffset();
845
+ this.isPastThreshold = false;
846
+ this.scroller = this.findScroller(event.target);
847
+ // Tracking a drag at pointer rate has nothing to tell the rest of the application,
848
+ // and the signals it writes schedule their own change detection.
849
+ this.zone.runOutsideAngular(() => {
850
+ this.surfaceElement?.addEventListener('touchmove', this.onTouchMove, { passive: false });
851
+ window.addEventListener('pointermove', this.onPointerMove);
852
+ window.addEventListener('pointerup', this.onPointerUp);
853
+ window.addEventListener('pointercancel', this.onPointerCancel);
854
+ });
855
+ }
856
+ onPointerMove = (event) => {
857
+ if (event.pointerId !== this.pointerId) {
858
+ return;
859
+ }
860
+ if (!this.isPastThreshold && !this.tryStartDrag(event)) {
861
+ return;
862
+ }
863
+ const elapsed = event.timeStamp - this.lastTimestamp;
864
+ if (elapsed > 0) {
865
+ this.velocity = (event.clientY - this.lastY) / elapsed;
866
+ this.lastY = event.clientY;
867
+ this.lastTimestamp = event.timeStamp;
868
+ }
869
+ this.setDragOffset(this.clampToRange(this.originOffset + event.clientY - this.startY));
870
+ };
871
+ onPointerUp = (event) => {
872
+ if (event.pointerId !== this.pointerId) {
873
+ return;
874
+ }
875
+ if (!this.isPastThreshold) {
876
+ this.releasePointer();
877
+ return;
878
+ }
879
+ const points = this.snapPoints();
880
+ const target = settleTarget({
881
+ points,
882
+ origin: this.originOffset,
883
+ offset: this.dragOffset(),
884
+ velocity: this.velocity,
885
+ idleTime: event.timeStamp - this.lastTimestamp,
886
+ });
887
+ this.releasePointer();
888
+ if (target === this.dismissIndex()) {
889
+ this.slideOut(points[target]);
890
+ this.zone.run(() => this.dismissedSheet.next());
891
+ }
892
+ else {
893
+ this.snapTo(target);
894
+ }
895
+ this.suppressClickAfterDrag();
896
+ };
897
+ onPointerCancel = (event) => {
898
+ if (event.pointerId === this.pointerId) {
899
+ const wasDragging = this.isPastThreshold;
900
+ this.releasePointer();
901
+ if (wasDragging) {
902
+ this.snapTo(this.settledIndex());
903
+ }
904
+ }
905
+ };
906
+ /** Keeps the browser from scrolling or overscrolling underneath an ongoing drag. */
907
+ onTouchMove = (event) => {
908
+ if (this.isPastThreshold && event.cancelable) {
909
+ event.preventDefault();
910
+ }
911
+ };
912
+ /**
913
+ * Decides who owns the gesture on the first move that leaves the tap threshold. Downward
914
+ * moves over content that still has somewhere to scroll belong to the content, and so do
915
+ * upward moves once the sheet is already as far up as it goes — in both cases the sheet
916
+ * lets go of the gesture for good.
917
+ */
918
+ tryStartDrag(event) {
919
+ const distance = event.clientY - this.startY;
920
+ if (Math.abs(distance) < DRAG_START_THRESHOLD_PX) {
921
+ return false;
922
+ }
923
+ const isDownward = distance > 0;
924
+ const isBlocked = isDownward
925
+ ? (this.scroller?.scrollTop ?? 0) > 0
926
+ : this.dragOffset() <= this.snapPoints()[0];
927
+ if (isBlocked) {
928
+ this.releasePointer();
929
+ return false;
930
+ }
931
+ // Starting from where the threshold was crossed keeps the sheet from jumping under
932
+ // the pointer on the first frame of the drag.
933
+ this.startY = this.lastY = event.clientY;
934
+ this.isPastThreshold = true;
935
+ this.isDragging.set(true);
936
+ return true;
937
+ }
938
+ /** Re-reads the geometry the snap points are derived from. */
939
+ measure() {
940
+ const expanded = this.el.nativeElement.offsetHeight;
941
+ if (expanded <= 0) {
942
+ return;
943
+ }
944
+ if (this.mode() === 'modal') {
945
+ this.snapPoints.set([0, expanded]);
946
+ return;
947
+ }
948
+ // The peek strip a standard sheet reserves is the height of the element it was placed
949
+ // in — the standard shell's host, which the scaffold row sizes to the collapsed height.
950
+ // Measuring beats resolving the configured CSS length by hand.
951
+ const collapsed = this.el.nativeElement.parentElement?.getBoundingClientRect().height ?? expanded;
952
+ const points = [0, Math.max(0, expanded - collapsed)];
953
+ if (this.dismissible()) {
954
+ points.push(expanded);
955
+ }
956
+ this.snapPoints.set(points);
957
+ }
958
+ /** Settles the sheet again after its geometry moved underneath it. */
959
+ remeasure() {
960
+ if (this.pointerId !== undefined || this.isExiting) {
961
+ return;
962
+ }
963
+ this.measure();
964
+ const points = this.snapPoints();
965
+ const index = Math.min(this.settledIndex(), points.length - 1);
966
+ this.settledIndex.set(index);
967
+ this.dragOffset.set(points[index]);
968
+ }
969
+ snapTo(index, notify = true) {
970
+ const points = this.snapPoints();
971
+ const target = Math.min(Math.max(index, 0), points.length - 1);
972
+ const previous = this.state();
973
+ this.settledIndex.set(target);
974
+ this.setDragOffset(points[target]);
975
+ if (notify && this.state() !== previous) {
976
+ this.zone.run(() => this.settledSheet.next(this.state()));
977
+ }
978
+ }
979
+ /**
980
+ * Sees the drag out of the viewport rather than back to a snap point, so a dismissed sheet
981
+ * keeps going the way it was dragged. The fade comes from the exit animation the sheet's
982
+ * reference starts right after, which finds the surface already on its way out.
983
+ */
984
+ slideOut(offset) {
985
+ this.isExiting = true;
986
+ this.exitTransform.set(`translateY(${offset}px)`);
987
+ this.setDragOffset(offset);
988
+ }
989
+ setDragOffset(offset) {
990
+ const dismissOffset = this.snapPoints()[this.dismissIndex() ?? -1];
991
+ this.dragOffset.set(offset);
992
+ this.draggedSheet.next(dismissOffset > 0 ? Math.min(1, offset / dismissOffset) : 0);
993
+ }
994
+ /** Index a drag has to reach for the sheet to be dismissed, when it can be at all. */
995
+ dismissIndex() {
996
+ if (this.mode() === 'modal') {
997
+ return 1;
998
+ }
999
+ return this.dismissible() ? this.snapPoints().length - 1 : undefined;
1000
+ }
1001
+ collapsedIndex() {
1002
+ return Math.min(1, this.snapPoints().length - 1);
1003
+ }
1004
+ clampToRange(offset) {
1005
+ const points = this.snapPoints();
1006
+ return Math.min(Math.max(offset, points[0]), points[points.length - 1]);
1007
+ }
1008
+ releasePointer() {
1009
+ if (this.pointerId === undefined) {
1010
+ return;
1011
+ }
1012
+ this.pointerId = undefined;
1013
+ this.isPastThreshold = false;
1014
+ this.scroller = null;
1015
+ this.isDragging.set(false);
1016
+ this.surfaceElement?.removeEventListener('touchmove', this.onTouchMove);
1017
+ window.removeEventListener('pointermove', this.onPointerMove);
1018
+ window.removeEventListener('pointerup', this.onPointerUp);
1019
+ window.removeEventListener('pointercancel', this.onPointerCancel);
1020
+ }
1021
+ /**
1022
+ * A drag that ended over a button would otherwise still click it, so the click that
1023
+ * follows the release is swallowed. Nothing else fires in between, and the listener is
1024
+ * dropped right after in case the release happened over something unclickable.
1025
+ */
1026
+ suppressClickAfterDrag() {
1027
+ const surface = this.surfaceElement;
1028
+ if (!surface) {
1029
+ return;
1030
+ }
1031
+ const onClick = (event) => {
1032
+ event.preventDefault();
1033
+ event.stopPropagation();
1034
+ };
1035
+ surface.addEventListener('click', onClick, { capture: true, once: true });
1036
+ setTimeout(() => surface.removeEventListener('click', onClick, { capture: true }));
1037
+ }
1038
+ /** Nearest scrollable between the pointer and the surface, if the drag started over one. */
1039
+ findScroller(target) {
1040
+ const surface = this.surfaceElement;
1041
+ let node = target instanceof HTMLElement ? target : null;
1042
+ while (node && surface?.contains(node)) {
1043
+ if (node.scrollHeight > node.clientHeight && this.isScrollable(node)) {
1044
+ return node;
1045
+ }
1046
+ node = node.parentElement;
1047
+ }
1048
+ return null;
1049
+ }
1050
+ isScrollable(element) {
1051
+ const overflowY = getComputedStyle(element).overflowY;
1052
+ return overflowY === 'auto' || overflowY === 'scroll';
1053
+ }
1054
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetSurface, deps: [], target: i0.ɵɵFactoryTarget.Component });
1055
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: BottomSheetSurface, isStandalone: true, selector: "md3-bottom-sheet-surface", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, showHandle: { classPropertyName: "showHandle", publicName: "showHandle", isSignal: true, isRequired: false, transformFunction: null }, allowGestures: { classPropertyName: "allowGestures", publicName: "allowGestures", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, initialState: { classPropertyName: "initialState", publicName: "initialState", isSignal: true, isRequired: false, transformFunction: null }, scheme: { classPropertyName: "scheme", publicName: "scheme", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, handleLabel: { classPropertyName: "handleLabel", publicName: "handleLabel", isSignal: true, isRequired: false, transformFunction: null }, controlsId: { classPropertyName: "controlsId", publicName: "controlsId", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.md3-mode-standard": "mode() === \"standard\"", "class.md3-mode-modal": "mode() === \"modal\"", "class.md3-hide-handle": "!showHandle()", "class.md3-draggable": "allowGestures()", "class.md3-dragging": "isDragging()", "class.md3-expanded": "state() === \"expanded\"" } }, viewQueries: [{ propertyName: "surface", first: true, predicate: ["surface"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #surface\n class=\"md3-bottom-sheet-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"scheme() === 'dark'\"\n [class.md-scheme-light]=\"scheme() === 'light'\"\n [style.transform]=\"surfaceTransform()\"\n [attr.dir]=\"direction() ?? null\"\n (pointerdown)=\"onPointerDown($event)\">\n @if (isHandleInteractive()) {\n <button\n type=\"button\"\n class=\"md3-bottom-sheet-handle\"\n [attr.aria-label]=\"handleLabel()\"\n [attr.aria-expanded]=\"state() === 'expanded'\"\n [attr.aria-controls]=\"controlsId() || null\"\n (click)=\"toggle()\"\n (keydown)=\"onHandleKeydown($event)\"></button>\n } @else {\n <div class=\"md3-bottom-sheet-handle\" aria-hidden=\"true\"></div>\n }\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;outline:0}:host .md3-bottom-sheet-container{position:relative;display:block;width:100%;overflow:hidden;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-low));border-start-start-radius:var(--md-border-radius-large);border-start-end-radius:var(--md-border-radius-large);box-shadow:var(--md-shadow-1dp);transition:all var(--md-motion-standard-default-spatial-duration) var(--md-motion-standard-default-spatial-easing);transform-origin:bottom;opacity:0;transform:translateY(60%) scaleY(.8)}:host .md3-bottom-sheet-container.md3-active{opacity:1;transform:translateY(0) scaleY(1)}:host .md3-bottom-sheet-container .md3-bottom-sheet-handle{position:absolute;top:.75em;left:50%;width:2em;height:.25em;padding:0;border:0;appearance:none;border-radius:var(--md-border-radius-rounded);background-color:rgb(var(--md-scheme-on-surface-variant));opacity:.4;transform:translate(-50%);pointer-events:none}:host .md3-bottom-sheet-container>::ng-deep *:not(.md3-bottom-sheet-handle){display:grid;grid-template-areas:\"header\" \"body\" \"actions\";grid-template-rows:auto minmax(0,1fr) auto;max-height:inherit;overflow:hidden;padding-top:1.25em}:host.md3-mode-modal .md3-bottom-sheet-container{max-height:70vh}:host.md3-mode-standard{position:absolute;inset-inline:0;bottom:0;height:var(--md3-bottom-sheet-expanded-height, 70dvh);clip-path:inset(-100vh -100vw 0 -100vw)}:host.md3-mode-standard .md3-bottom-sheet-container{height:100%;transform:translateY(100%)}:host.md3-mode-standard .md3-bottom-sheet-container.md3-active{transform:translateY(0)}:host.md3-mode-standard .md3-bottom-sheet-container>::ng-deep *:not(.md3-bottom-sheet-handle){height:100%}:host.md3-hide-handle .md3-bottom-sheet-container .md3-bottom-sheet-handle{display:none}:host.md3-hide-handle .md3-bottom-sheet-container>::ng-deep *:not(.md3-bottom-sheet-handle){padding-top:0}:host.md3-draggable .md3-bottom-sheet-container .md3-bottom-sheet-handle{pointer-events:auto;touch-action:none;cursor:grab}:host.md3-draggable .md3-bottom-sheet-container .md3-bottom-sheet-handle:after{content:\"\";position:absolute;inset:-.625em -1.5em}:host.md3-draggable .md3-bottom-sheet-container .md3-bottom-sheet-handle:focus-visible{opacity:1;outline:.125em solid rgb(var(--md-scheme-secondary));outline-offset:.375em}:host.md3-dragging .md3-bottom-sheet-container{transition:none}:host.md3-dragging .md3-bottom-sheet-container .md3-bottom-sheet-handle{cursor:grabbing}:host.md3-mode-standard.md3-draggable .md3-bottom-sheet-container{touch-action:none;overscroll-behavior:contain}:host.md3-mode-standard.md3-draggable.md3-expanded .md3-bottom-sheet-container{touch-action:pan-y}\n"] });
1056
+ }
1057
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetSurface, decorators: [{
1058
+ type: Component,
1059
+ args: [{ selector: 'md3-bottom-sheet-surface', imports: [], host: {
1060
+ '[class.md3-mode-standard]': 'mode() === "standard"',
1061
+ '[class.md3-mode-modal]': 'mode() === "modal"',
1062
+ '[class.md3-hide-handle]': '!showHandle()',
1063
+ '[class.md3-draggable]': 'allowGestures()',
1064
+ '[class.md3-dragging]': 'isDragging()',
1065
+ '[class.md3-expanded]': 'state() === "expanded"',
1066
+ }, template: "<div\n #surface\n class=\"md3-bottom-sheet-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"scheme() === 'dark'\"\n [class.md-scheme-light]=\"scheme() === 'light'\"\n [style.transform]=\"surfaceTransform()\"\n [attr.dir]=\"direction() ?? null\"\n (pointerdown)=\"onPointerDown($event)\">\n @if (isHandleInteractive()) {\n <button\n type=\"button\"\n class=\"md3-bottom-sheet-handle\"\n [attr.aria-label]=\"handleLabel()\"\n [attr.aria-expanded]=\"state() === 'expanded'\"\n [attr.aria-controls]=\"controlsId() || null\"\n (click)=\"toggle()\"\n (keydown)=\"onHandleKeydown($event)\"></button>\n } @else {\n <div class=\"md3-bottom-sheet-handle\" aria-hidden=\"true\"></div>\n }\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;outline:0}:host .md3-bottom-sheet-container{position:relative;display:block;width:100%;overflow:hidden;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-low));border-start-start-radius:var(--md-border-radius-large);border-start-end-radius:var(--md-border-radius-large);box-shadow:var(--md-shadow-1dp);transition:all var(--md-motion-standard-default-spatial-duration) var(--md-motion-standard-default-spatial-easing);transform-origin:bottom;opacity:0;transform:translateY(60%) scaleY(.8)}:host .md3-bottom-sheet-container.md3-active{opacity:1;transform:translateY(0) scaleY(1)}:host .md3-bottom-sheet-container .md3-bottom-sheet-handle{position:absolute;top:.75em;left:50%;width:2em;height:.25em;padding:0;border:0;appearance:none;border-radius:var(--md-border-radius-rounded);background-color:rgb(var(--md-scheme-on-surface-variant));opacity:.4;transform:translate(-50%);pointer-events:none}:host .md3-bottom-sheet-container>::ng-deep *:not(.md3-bottom-sheet-handle){display:grid;grid-template-areas:\"header\" \"body\" \"actions\";grid-template-rows:auto minmax(0,1fr) auto;max-height:inherit;overflow:hidden;padding-top:1.25em}:host.md3-mode-modal .md3-bottom-sheet-container{max-height:70vh}:host.md3-mode-standard{position:absolute;inset-inline:0;bottom:0;height:var(--md3-bottom-sheet-expanded-height, 70dvh);clip-path:inset(-100vh -100vw 0 -100vw)}:host.md3-mode-standard .md3-bottom-sheet-container{height:100%;transform:translateY(100%)}:host.md3-mode-standard .md3-bottom-sheet-container.md3-active{transform:translateY(0)}:host.md3-mode-standard .md3-bottom-sheet-container>::ng-deep *:not(.md3-bottom-sheet-handle){height:100%}:host.md3-hide-handle .md3-bottom-sheet-container .md3-bottom-sheet-handle{display:none}:host.md3-hide-handle .md3-bottom-sheet-container>::ng-deep *:not(.md3-bottom-sheet-handle){padding-top:0}:host.md3-draggable .md3-bottom-sheet-container .md3-bottom-sheet-handle{pointer-events:auto;touch-action:none;cursor:grab}:host.md3-draggable .md3-bottom-sheet-container .md3-bottom-sheet-handle:after{content:\"\";position:absolute;inset:-.625em -1.5em}:host.md3-draggable .md3-bottom-sheet-container .md3-bottom-sheet-handle:focus-visible{opacity:1;outline:.125em solid rgb(var(--md-scheme-secondary));outline-offset:.375em}:host.md3-dragging .md3-bottom-sheet-container{transition:none}:host.md3-dragging .md3-bottom-sheet-container .md3-bottom-sheet-handle{cursor:grabbing}:host.md3-mode-standard.md3-draggable .md3-bottom-sheet-container{touch-action:none;overscroll-behavior:contain}:host.md3-mode-standard.md3-draggable.md3-expanded .md3-bottom-sheet-container{touch-action:pan-y}\n"] }]
1067
+ }], ctorParameters: () => [], propDecorators: { surface: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], showHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHandle", required: false }] }], allowGestures: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowGestures", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], initialState: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialState", required: false }] }], scheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "scheme", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], handleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "handleLabel", required: false }] }], controlsId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlsId", required: false }] }] } });
1068
+
1069
+ /**
1070
+ * Modal bottom sheet shell. Like Dialog, it extends the CDK dialog container so focus
1071
+ * trapping, focus restoration and the ARIA attributes are handled by the CDK, while the
1072
+ * surface it wraps owns the MD3 panel, the drag handle and the animation. A modal sheet is
1073
+ * always an overlay above the page; the standard variant docks into the scaffold instead and
1074
+ * uses the same surface from a different shell.
1075
+ */
1076
+ class BottomSheet extends CdkDialogContainer {
1077
+ // Static, because BottomSheetService wires this shell to the sheet's reference as soon as
1078
+ // the CDK has created it, which is before any change detection has run over the view.
1079
+ surface;
1080
+ config = inject(BOTTOM_SHEET_CONFIG, { optional: true }) ?? {};
1081
+ get surfaceElement() {
1082
+ return this.surface.surfaceElement;
1083
+ }
1084
+ get dismissed() {
1085
+ return this.surface.dismissed;
1086
+ }
1087
+ get dragProgress() {
1088
+ return this.surface.dragProgress;
1089
+ }
1090
+ get stateChanges() {
1091
+ return this.surface.stateChanges;
1092
+ }
1093
+ startEnterAnimation() {
1094
+ this.surface.startEnterAnimation();
1095
+ }
1096
+ setActive(value) {
1097
+ this.surface.setActive(value);
1098
+ }
1099
+ recaptureFocus() {
1100
+ this._recaptureFocus();
1101
+ }
1102
+ ngOnDestroy() {
1103
+ this.surface.destroy();
1104
+ super.ngOnDestroy();
1105
+ }
1106
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheet, deps: null, target: i0.ɵɵFactoryTarget.Component });
1107
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: BottomSheet, isStandalone: true, selector: "md3-bottom-sheet", viewQueries: [{ propertyName: "surface", first: true, predicate: BottomSheetSurface, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container.\n The portal outlet stays in this template, as the CDK requires, and is projected into the\n shared surface. -->\n<md3-bottom-sheet-surface\n [showHandle]=\"config.handle ?? true\"\n [allowGestures]=\"config.gestures ?? true\"\n [scheme]=\"config.scheme ?? 'inherit'\"\n [direction]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</md3-bottom-sheet-surface>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim{--md3-drag-remaining: 1;position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-standard-slow-spatial-duration) var(--md-motion-standard-slow-spatial-easing);opacity:calc(.32 * var(--md3-drag-remaining))}::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim.md3-bottom-sheet-dragging{transition:none}::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim.md3-bottom-sheet-opening,::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim.md3-bottom-sheet-closing{opacity:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper:has(>.md3-bottom-sheet-panel){padding:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-bottom-sheet-panel{width:100%;max-width:40em;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-bottom-sheet-panel.md3-bottom-sheet-closing{pointer-events:none}:host{display:block;width:100%;max-width:100%;outline:0}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "component", type: BottomSheetSurface, selector: "md3-bottom-sheet-surface", inputs: ["mode", "showHandle", "allowGestures", "dismissible", "initialState", "scheme", "direction", "handleLabel", "controlsId"] }] });
1108
+ }
1109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheet, decorators: [{
1110
+ type: Component,
1111
+ args: [{ selector: 'md3-bottom-sheet', imports: [
1112
+ CdkPortalOutlet,
1113
+ BottomSheetSurface,
1114
+ ], template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container.\n The portal outlet stays in this template, as the CDK requires, and is projected into the\n shared surface. -->\n<md3-bottom-sheet-surface\n [showHandle]=\"config.handle ?? true\"\n [allowGestures]=\"config.gestures ?? true\"\n [scheme]=\"config.scheme ?? 'inherit'\"\n [direction]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</md3-bottom-sheet-surface>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim{--md3-drag-remaining: 1;position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-standard-slow-spatial-duration) var(--md-motion-standard-slow-spatial-easing);opacity:calc(.32 * var(--md3-drag-remaining))}::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim.md3-bottom-sheet-dragging{transition:none}::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim.md3-bottom-sheet-opening,::ng-deep .cdk-overlay-container .md3-bottom-sheet-scrim.md3-bottom-sheet-closing{opacity:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper:has(>.md3-bottom-sheet-panel){padding:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-bottom-sheet-panel{width:100%;max-width:40em;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-bottom-sheet-panel.md3-bottom-sheet-closing{pointer-events:none}:host{display:block;width:100%;max-width:100%;outline:0}\n"] }]
1115
+ }], propDecorators: { surface: [{
1116
+ type: ViewChild,
1117
+ args: [BottomSheetSurface, { static: true }]
1118
+ }] } });
1119
+
1120
+ let nextRegionId = 0;
1121
+ /**
1122
+ * Standard bottom sheet shell: page furniture rather than a dialog. It docks into the
1123
+ * scaffold's sheet region and floats over the content from there, above the bottom bar and
1124
+ * without a scrim, leaving the page scrolling, clickable and focusable behind it. Nothing here
1125
+ * traps focus or handles Escape, which is the whole difference from the modal shell — the
1126
+ * surface, the handle and the gesture are shared.
1127
+ */
1128
+ class StandardBottomSheet {
1129
+ portalOutlet;
1130
+ surface;
1131
+ config = inject(BOTTOM_SHEET_CONFIG, { optional: true }) ?? {};
1132
+ el = inject(ElementRef);
1133
+ /** Named so the drag handle can point `aria-controls` at the region it expands. */
1134
+ regionId = `md3-bottom-sheet-${nextRegionId++}`;
1135
+ showHandle = signal(this.config.handle ?? true, /* @ts-ignore */
1136
+ ...(ngDevMode ? [{ debugName: "showHandle" }] : /* istanbul ignore next */ []));
1137
+ allowGestures = signal(this.config.gestures ?? true, /* @ts-ignore */
1138
+ ...(ngDevMode ? [{ debugName: "allowGestures" }] : /* istanbul ignore next */ []));
1139
+ dismissible = signal(this.config.dismissible ?? false, /* @ts-ignore */
1140
+ ...(ngDevMode ? [{ debugName: "dismissible" }] : /* istanbul ignore next */ []));
1141
+ initialState = signal(this.config.initialState ?? 'collapsed', /* @ts-ignore */
1142
+ ...(ngDevMode ? [{ debugName: "initialState" }] : /* istanbul ignore next */ []));
1143
+ label = signal(this.config.label ?? '', /* @ts-ignore */
1144
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1145
+ labelledBy = signal(this.config.labelledBy ?? '', /* @ts-ignore */
1146
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
1147
+ collapsedHeight = signal(toCssLength(this.config.collapsedHeight, '10em'), /* @ts-ignore */
1148
+ ...(ngDevMode ? [{ debugName: "collapsedHeight" }] : /* istanbul ignore next */ []));
1149
+ expandedHeight = signal(toCssLength(this.config.expandedHeight, '70dvh'), /* @ts-ignore */
1150
+ ...(ngDevMode ? [{ debugName: "expandedHeight" }] : /* istanbul ignore next */ []));
1151
+ /**
1152
+ * Whatever had focus when the sheet opened, so it can be handed back — but only if the
1153
+ * sheet still holds focus when it closes, rather than yanking it off whatever the user
1154
+ * moved on to in the meantime.
1155
+ */
1156
+ previouslyFocused = document.activeElement;
1157
+ constructor() {
1158
+ effect(() => {
1159
+ const element = this.el.nativeElement;
1160
+ element.style.setProperty('--md3-bottom-sheet-collapsed-height', this.collapsedHeight());
1161
+ element.style.setProperty('--md3-bottom-sheet-expanded-height', this.expandedHeight());
1162
+ });
1163
+ if (isDevMode() && !this.config.label && !this.config.labelledBy) {
1164
+ console.warn('A standard bottom sheet without a `label` or `labelledBy` is not exposed as a '
1165
+ + 'landmark to assistive technology. Pass one in the sheet configuration.');
1166
+ }
1167
+ }
1168
+ get state() {
1169
+ return this.surface.state;
1170
+ }
1171
+ get surfaceElement() {
1172
+ return this.surface.surfaceElement;
1173
+ }
1174
+ get dismissed() {
1175
+ return this.surface.dismissed;
1176
+ }
1177
+ get dragProgress() {
1178
+ return this.surface.dragProgress;
1179
+ }
1180
+ get stateChanges() {
1181
+ return this.surface.stateChanges;
1182
+ }
1183
+ startEnterAnimation() {
1184
+ this.surface.startEnterAnimation();
1185
+ }
1186
+ setActive(value) {
1187
+ this.surface.setActive(value);
1188
+ }
1189
+ expand() {
1190
+ this.surface.expand();
1191
+ }
1192
+ collapse() {
1193
+ this.surface.collapse();
1194
+ }
1195
+ toggle() {
1196
+ this.surface.toggle();
1197
+ }
1198
+ setCollapsedHeight(value) {
1199
+ this.collapsedHeight.set(toCssLength(value, '10em'));
1200
+ this.surface.remeasure();
1201
+ }
1202
+ setExpandedHeight(value) {
1203
+ this.expandedHeight.set(toCssLength(value, '70dvh'));
1204
+ this.surface.remeasure();
1205
+ }
1206
+ setDismissible(value) {
1207
+ this.dismissible.set(value);
1208
+ }
1209
+ attachContent(component, injector) {
1210
+ const portal = new ComponentPortal(component, this.config.viewContainerRef ?? null, injector);
1211
+ return this.portalOutlet.attachComponentPortal(portal);
1212
+ }
1213
+ ngOnDestroy() {
1214
+ this.surface.destroy();
1215
+ if (this.el.nativeElement.contains(document.activeElement)) {
1216
+ this.previouslyFocused?.focus();
1217
+ }
1218
+ }
1219
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StandardBottomSheet, deps: [], target: i0.ɵɵFactoryTarget.Component });
1220
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: StandardBottomSheet, isStandalone: true, selector: "md3-standard-bottom-sheet", host: { attributes: { "role": "region" }, properties: { "attr.id": "regionId", "attr.aria-label": "label() || null", "attr.aria-labelledby": "labelledBy() || null" } }, viewQueries: [{ propertyName: "portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, static: true }, { propertyName: "surface", first: true, predicate: BottomSheetSurface, descendants: true, static: true }], ngImport: i0, template: "<md3-bottom-sheet-surface\n mode=\"standard\"\n [showHandle]=\"showHandle()\"\n [allowGestures]=\"allowGestures()\"\n [dismissible]=\"dismissible()\"\n [initialState]=\"initialState()\"\n [controlsId]=\"regionId\"\n [scheme]=\"config.scheme ?? 'inherit'\"\n [direction]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</md3-bottom-sheet-surface>\n", styles: [":host{position:absolute;display:block;inset-inline:0;bottom:0;width:100%;max-width:40em;margin-inline:auto;height:var(--md3-bottom-sheet-collapsed-height, 10em);overflow:visible}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "component", type: BottomSheetSurface, selector: "md3-bottom-sheet-surface", inputs: ["mode", "showHandle", "allowGestures", "dismissible", "initialState", "scheme", "direction", "handleLabel", "controlsId"] }] });
1221
+ }
1222
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StandardBottomSheet, decorators: [{
1223
+ type: Component,
1224
+ args: [{ selector: 'md3-standard-bottom-sheet', imports: [
1225
+ CdkPortalOutlet,
1226
+ BottomSheetSurface,
1227
+ ], host: {
1228
+ 'role': 'region',
1229
+ '[attr.id]': 'regionId',
1230
+ '[attr.aria-label]': 'label() || null',
1231
+ '[attr.aria-labelledby]': 'labelledBy() || null',
1232
+ }, template: "<md3-bottom-sheet-surface\n mode=\"standard\"\n [showHandle]=\"showHandle()\"\n [allowGestures]=\"allowGestures()\"\n [dismissible]=\"dismissible()\"\n [initialState]=\"initialState()\"\n [controlsId]=\"regionId\"\n [scheme]=\"config.scheme ?? 'inherit'\"\n [direction]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</md3-bottom-sheet-surface>\n", styles: [":host{position:absolute;display:block;inset-inline:0;bottom:0;width:100%;max-width:40em;margin-inline:auto;height:var(--md3-bottom-sheet-collapsed-height, 10em);overflow:visible}\n"] }]
1233
+ }], ctorParameters: () => [], propDecorators: { portalOutlet: [{
1234
+ type: ViewChild,
1235
+ args: [CdkPortalOutlet, { static: true }]
1236
+ }], surface: [{
1237
+ type: ViewChild,
1238
+ args: [BottomSheetSurface, { static: true }]
1239
+ }] } });
1240
+ function toCssLength(value, fallback) {
1241
+ if (value === undefined) {
1242
+ return fallback;
1243
+ }
1244
+ return typeof value === 'number' ? `${value}px` : value;
1245
+ }
1246
+
1247
+ /**
1248
+ * Host attached into the scaffold's sheet outlet, where standard bottom sheets are created.
1249
+ * It is attached as soon as the outlet registers and takes no space at any point: the sheets
1250
+ * it holds are positioned against the region around it, so a replacement can open while the
1251
+ * sheet it replaces is still animating out without either of them disturbing the layout.
1252
+ */
1253
+ class BottomSheetOutlet {
1254
+ container;
1255
+ createSheet(injector) {
1256
+ const sheetComponentRef = this.container.createComponent(StandardBottomSheet, { injector });
1257
+ // The shell's view has to exist before its portal outlet can take the content.
1258
+ sheetComponentRef.changeDetectorRef.detectChanges();
1259
+ return sheetComponentRef;
1260
+ }
1261
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetOutlet, deps: [], target: i0.ɵɵFactoryTarget.Component });
1262
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: BottomSheetOutlet, isStandalone: true, selector: "md3-bottom-sheet-outlet", viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: '<ng-container #container></ng-container>', isInline: true, styles: [":host{display:block}\n"] });
1263
+ }
1264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetOutlet, decorators: [{
1265
+ type: Component,
1266
+ args: [{ selector: 'md3-bottom-sheet-outlet', template: '<ng-container #container></ng-container>', styles: [":host{display:block}\n"] }]
1267
+ }], propDecorators: { container: [{
1268
+ type: ViewChild,
1269
+ args: ['container', { read: ViewContainerRef, static: true }]
1270
+ }] } });
1271
+
1272
+ const PANEL_CLASS$1 = 'md3-bottom-sheet-panel';
1273
+ const OPENING_CLASS$1 = 'md3-bottom-sheet-opening';
1274
+ /**
1275
+ * Opens bottom sheets, in either of the two ways MD3 describes them.
1276
+ *
1277
+ * A modal sheet is a CDK dialog anchored to the bottom of the viewport, the same way a regular
1278
+ * dialog is always an overlay, so that path mirrors DialogService. A standard sheet is docked
1279
+ * into the scaffold instead, following the side sheet outlet model: it floats over the content
1280
+ * without a scrim, leaves the rest of the page usable, and needs an outlet to open into.
1281
+ *
1282
+ * One of each may be open at a time, and they replace only their own kind.
1283
+ */
1284
+ class BottomSheetService {
1285
+ cdkDialog = inject(Dialog$1);
1286
+ overlay = inject(Overlay);
1287
+ injector = inject(Injector);
1288
+ /**
1289
+ * Outlets registered from the page scaffold up to whatever took them over last. Standard
1290
+ * sheets always open in the outlet on top.
1291
+ */
1292
+ outlets = [];
1293
+ modalRef;
1294
+ standardRef;
1295
+ /** Registers the outlet standard bottom sheets open into. Called by Scaffold. */
1296
+ registerBottomSheetOutlet(outlet) {
1297
+ if (this.outlets.some((state) => state.outlet === outlet)) {
1298
+ return;
1299
+ }
1300
+ if (outlet.hasAttached()) {
1301
+ outlet.detach();
1302
+ }
1303
+ const portal = new ComponentPortal(BottomSheetOutlet, null, this.injector);
1304
+ this.outlets.push({
1305
+ outlet,
1306
+ hostComponentRef: outlet.attachComponentPortal(portal),
1307
+ });
1308
+ }
1309
+ unregisterBottomSheetOutlet(outlet) {
1310
+ const index = this.outlets.findIndex((state) => state.outlet === outlet);
1311
+ if (index === -1) {
1312
+ return;
1313
+ }
1314
+ // A sheet open in this outlet goes with it, without an exit animation: the outlet is
1315
+ // being torn down, so there is nothing left to animate in.
1316
+ if (index === this.outlets.length - 1) {
1317
+ this.standardRef?.dispose();
1318
+ }
1319
+ this.outlets[index].outlet.detach();
1320
+ this.outlets.splice(index, 1);
1321
+ }
1322
+ open(component, config = {}) {
1323
+ const sheetConfig = this.mergeConfig(config);
1324
+ return sheetConfig.type === 'standard'
1325
+ ? this.openStandard(component, sheetConfig)
1326
+ : this.openModal(component, sheetConfig);
1327
+ }
1328
+ /**
1329
+ * Closes the bottom sheet that is open. A modal sheet goes first when both kinds are up,
1330
+ * since it is the one covering the page.
1331
+ */
1332
+ close(result) {
1333
+ if (this.modalRef) {
1334
+ this.modalRef.close(result);
1335
+ return;
1336
+ }
1337
+ this.standardRef?.close(result);
1338
+ }
1339
+ openModal(component, sheetConfig) {
1340
+ // Opening a new modal sheet always replaces the one that is open.
1341
+ this.modalRef?.close();
1342
+ let sheetRef;
1343
+ const cdkRef = this.cdkDialog.open(component, {
1344
+ // The MD3 shell replaces the default CDK container, so the overlay,
1345
+ // focus trap and focus restoration come from the CDK while the
1346
+ // shell only renders the surface.
1347
+ container: {
1348
+ type: BottomSheet,
1349
+ providers: () => [
1350
+ { provide: BOTTOM_SHEET_CONFIG, useValue: sheetConfig },
1351
+ ],
1352
+ },
1353
+ // These tokens make the sheet controllable from the dynamic
1354
+ // component without coupling that component to the service.
1355
+ providers: (cdkDialogRef) => {
1356
+ sheetRef = new ModalBottomSheetRef(cdkDialogRef);
1357
+ return [
1358
+ { provide: BottomSheetRef, useValue: sheetRef },
1359
+ { provide: BOTTOM_SHEET_DATA, useValue: sheetConfig.data },
1360
+ { provide: BOTTOM_SHEET_CONFIG, useValue: sheetConfig },
1361
+ { provide: BOTTOM_SHEET_COMPONENT, useValue: component },
1362
+ ];
1363
+ },
1364
+ data: sheetConfig.data,
1365
+ role: 'dialog',
1366
+ ariaModal: true,
1367
+ direction: sheetConfig.direction ?? undefined,
1368
+ viewContainerRef: sheetConfig.viewContainerRef,
1369
+ injector: sheetConfig.injector,
1370
+ hasBackdrop: true,
1371
+ backdropClass: ['md3-bottom-sheet-scrim', OPENING_CLASS$1],
1372
+ panelClass: [PANEL_CLASS$1, OPENING_CLASS$1],
1373
+ positionStrategy: this.overlay.position()
1374
+ .global()
1375
+ .centerHorizontally()
1376
+ .bottom('0'),
1377
+ // A modal bottom sheet blocks page scrolling for as long as it is open.
1378
+ scrollStrategy: this.overlay.scrollStrategies.block(),
1379
+ // Scrim and Escape handling lives in the reference so the sheet
1380
+ // can play its exit animation before the overlay is disposed.
1381
+ disableClose: true,
1382
+ });
1383
+ sheetRef.attachContainer(cdkRef.containerInstance);
1384
+ sheetRef.componentInstance = cdkRef.componentInstance ?? undefined;
1385
+ if (cdkRef.componentRef) {
1386
+ this.bindDataToInputs(cdkRef.componentRef, sheetConfig);
1387
+ }
1388
+ this.modalRef = sheetRef;
1389
+ this.startOpenAnimation(sheetRef);
1390
+ sheetRef.afterClosed().subscribe(() => {
1391
+ if (this.modalRef === sheetRef) {
1392
+ this.modalRef = undefined;
1393
+ }
1394
+ });
1395
+ return sheetRef;
1396
+ }
1397
+ openStandard(component, sheetConfig) {
1398
+ const host = this.activeOutlet();
1399
+ if (!host) {
1400
+ throw new Error('No bottom sheet outlet is registered. A standard bottom sheet docks into the '
1401
+ + 'scaffold, so the layout has to be in place before one can open.');
1402
+ }
1403
+ // Opening a new standard sheet always replaces the one that is open.
1404
+ this.standardRef?.close();
1405
+ const sheetRef = new StandardBottomSheetRef(() => {
1406
+ if (this.standardRef === sheetRef) {
1407
+ this.standardRef = undefined;
1408
+ }
1409
+ });
1410
+ const injector = this.createInjector(component, sheetConfig, sheetRef);
1411
+ const sheetComponentRef = host.hostComponentRef.instance.createSheet(injector);
1412
+ const contentComponentRef = sheetComponentRef.instance.attachContent(component, injector);
1413
+ sheetComponentRef.changeDetectorRef.detectChanges();
1414
+ sheetRef.attachSheet(sheetComponentRef, sheetComponentRef.instance);
1415
+ this.bindDataToInputs(contentComponentRef, sheetConfig);
1416
+ sheetRef.componentInstance = contentComponentRef.instance;
1417
+ this.standardRef = sheetRef;
1418
+ sheetComponentRef.instance.startEnterAnimation();
1419
+ return sheetRef;
1420
+ }
1421
+ activeOutlet() {
1422
+ return this.outlets[this.outlets.length - 1];
1423
+ }
1424
+ createInjector(component, config, sheetRef) {
1425
+ return Injector.create({
1426
+ parent: config.injector,
1427
+ providers: [
1428
+ { provide: BottomSheetRef, useValue: sheetRef },
1429
+ { provide: BOTTOM_SHEET_DATA, useValue: config.data },
1430
+ { provide: BOTTOM_SHEET_CONFIG, useValue: config },
1431
+ { provide: BOTTOM_SHEET_COMPONENT, useValue: component },
1432
+ ],
1433
+ });
1434
+ }
1435
+ startOpenAnimation(sheetRef) {
1436
+ const overlayRef = sheetRef.overlayRef;
1437
+ const panel = overlayRef.overlayElement;
1438
+ const backdrop = overlayRef.backdropElement;
1439
+ // The overlay is inserted already rendered. Keep an initial class for
1440
+ // one frame, force style calculation, then remove it so CSS
1441
+ // transitions have a real from/to state.
1442
+ requestAnimationFrame(() => {
1443
+ setTimeout(() => {
1444
+ panel.getBoundingClientRect();
1445
+ panel.classList.remove(OPENING_CLASS$1);
1446
+ backdrop?.classList.remove(OPENING_CLASS$1);
1447
+ }, 100);
1448
+ });
1449
+ sheetRef.sheetInstance?.startEnterAnimation();
1450
+ }
1451
+ mergeConfig(config) {
1452
+ return {
1453
+ ...config,
1454
+ data: config.data,
1455
+ bindDataToInputs: config.bindDataToInputs ?? false,
1456
+ type: config.type ?? 'modal',
1457
+ handle: config.handle ?? true,
1458
+ gestures: config.gestures ?? true,
1459
+ dismissible: config.dismissible ?? false,
1460
+ initialState: config.initialState ?? 'collapsed',
1461
+ scheme: config.scheme ?? 'inherit',
1462
+ direction: config.direction ?? null,
1463
+ viewContainerRef: config.viewContainerRef,
1464
+ injector: config.injector ?? this.injector,
1465
+ };
1466
+ }
1467
+ bindDataToInputs(componentRef, config) {
1468
+ if (!config.bindDataToInputs || !this.canBindDataToInputs(config.data)) {
1469
+ return;
1470
+ }
1471
+ Object.entries(config.data).forEach(([inputName, inputValue]) => {
1472
+ componentRef.setInput(inputName, inputValue);
1473
+ });
1474
+ }
1475
+ canBindDataToInputs(data) {
1476
+ return typeof data === 'object' && data !== null && !Array.isArray(data);
1477
+ }
1478
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1479
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetService, providedIn: 'root' });
1480
+ }
1481
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: BottomSheetService, decorators: [{
1482
+ type: Injectable,
1483
+ args: [{
1484
+ providedIn: 'root',
1485
+ }]
1486
+ }] });
1487
+
400
1488
  const SIDE_SHEET_DATA = new InjectionToken('MD3_SIDE_SHEET_DATA');
401
1489
  const SIDE_SHEET_CONFIG = new InjectionToken('MD3_SIDE_SHEET_CONFIG');
402
1490
  const SIDE_SHEET_COMPONENT = new InjectionToken('MD3_SIDE_SHEET_COMPONENT');
@@ -753,13 +1841,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
753
1841
  class Scaffold {
754
1842
  startOutlet = viewChild('startOutlet', { ...(ngDevMode ? { debugName: "startOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
755
1843
  endOutlet = viewChild('endOutlet', { ...(ngDevMode ? { debugName: "endOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
1844
+ bottomOutlet = viewChild('bottomOutlet', { ...(ngDevMode ? { debugName: "bottomOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
756
1845
  panesContainer = viewChild('panesContainer', /* @ts-ignore */
757
1846
  ...(ngDevMode ? [{ debugName: "panesContainer" }] : /* istanbul ignore next */ []));
758
1847
  sheets = inject(SheetsService);
1848
+ bottomSheets = inject(BottomSheetService);
759
1849
  layout = inject(LayoutService);
760
1850
  ngAfterViewInit() {
761
1851
  const startOutlet = this.startOutlet();
762
1852
  const endOutlet = this.endOutlet();
1853
+ const bottomOutlet = this.bottomOutlet();
763
1854
  const panesContainer = this.panesContainer();
764
1855
  if (startOutlet) {
765
1856
  this.sheets.registerSideSheetOutlet('start', startOutlet);
@@ -767,6 +1858,9 @@ class Scaffold {
767
1858
  if (endOutlet) {
768
1859
  this.sheets.registerSideSheetOutlet('end', endOutlet);
769
1860
  }
1861
+ if (bottomOutlet) {
1862
+ this.bottomSheets.registerBottomSheetOutlet(bottomOutlet);
1863
+ }
770
1864
  if (panesContainer) {
771
1865
  this.layout.registerPanesContainer(panesContainer.nativeElement);
772
1866
  }
@@ -774,23 +1868,27 @@ class Scaffold {
774
1868
  ngOnDestroy() {
775
1869
  const startOutlet = this.startOutlet();
776
1870
  const endOutlet = this.endOutlet();
1871
+ const bottomOutlet = this.bottomOutlet();
777
1872
  if (startOutlet) {
778
1873
  this.sheets.unregisterSideSheetOutlet('start', startOutlet);
779
1874
  }
780
1875
  if (endOutlet) {
781
1876
  this.sheets.unregisterSideSheetOutlet('end', endOutlet);
782
1877
  }
1878
+ if (bottomOutlet) {
1879
+ this.bottomSheets.unregisterBottomSheetOutlet(bottomOutlet);
1880
+ }
783
1881
  this.layout.unregisterPanesContainer();
784
1882
  }
785
1883
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Scaffold, deps: [], target: i0.ɵɵFactoryTarget.Component });
786
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Scaffold, isStandalone: true, selector: "md3-scaffold", viewQueries: [{ propertyName: "startOutlet", first: true, predicate: ["startOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "endOutlet", first: true, predicate: ["endOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "panesContainer", first: true, predicate: ["panesContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-content select=\"[md3-scaffold-bar=top]\"></ng-content>\n<ng-content select=\"[md3-scaffold-rail=leading]\"></ng-content>\n\n<div class=\"md3-panes-container\" #panesContainer>\n <div class=\"md3-pane md3-pane-start\">\n <ng-content select=\"[md3-scaffold-pane=start]\"></ng-content>\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-main\">\n <ng-content select=\"[md3-scaffold-pane=main]\"></ng-content>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-content select=\"[md3-scaffold-pane=end]\"></ng-content>\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n</div>\n\n<ng-content select=\"[md3-scaffold-rail=trailing]\"></ng-content>\n<ng-content select=\"[md3-scaffold-bar=bottom]\"></ng-content>", styles: [":host{display:grid;width:100%;height:100dvh;min-height:0em;overflow:hidden;grid-template-areas:\"top top top\" \"start content end\" \"bottom bottom bottom\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto minmax(0,1fr) auto}:host ::ng-deep [md3-scaffold-bar=top]{grid-area:top;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host ::ng-deep [md3-scaffold-rail=leading]{grid-area:start;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-rail=trailing]{grid-area:end;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-bar=bottom]{grid-area:bottom;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host .md3-panes-container{--background-color: rgb(var(--md-scheme-surface));--foreground-color: rgb(var(--md-scheme-on-surface));grid-area:content;min-width:0em;min-height:0em;overflow:hidden;background-color:var(--background-color);color:var(--foreground-color);display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr)}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
1884
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Scaffold, isStandalone: true, selector: "md3-scaffold", viewQueries: [{ propertyName: "startOutlet", first: true, predicate: ["startOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "endOutlet", first: true, predicate: ["endOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "bottomOutlet", first: true, predicate: ["bottomOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "panesContainer", first: true, predicate: ["panesContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-content select=\"[md3-scaffold-bar=top]\"></ng-content>\n<ng-content select=\"[md3-scaffold-rail=leading]\"></ng-content>\n\n<div class=\"md3-panes-container\" #panesContainer>\n <div class=\"md3-pane md3-pane-start\">\n <ng-content select=\"[md3-scaffold-pane=start]\"></ng-content>\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-main\">\n <ng-content select=\"[md3-scaffold-pane=main]\"></ng-content>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-content select=\"[md3-scaffold-pane=end]\"></ng-content>\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n</div>\n\n<!-- Standard bottom sheets dock here, between the content and the bottom bar. The region\n measures zero until one opens, so it reserves nothing the rest of the time. -->\n<div class=\"md3-bottom-sheet-region\">\n <ng-template #bottomOutlet cdkPortalOutlet></ng-template>\n</div>\n\n<ng-content select=\"[md3-scaffold-rail=trailing]\"></ng-content>\n<ng-content select=\"[md3-scaffold-bar=bottom]\"></ng-content>", styles: [":host{display:grid;width:100%;height:100dvh;min-height:0em;overflow:hidden;grid-template-areas:\"top top top\" \"start content end\" \"start sheet end\" \"bottom bottom bottom\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto minmax(0,1fr) auto auto}:host ::ng-deep [md3-scaffold-bar=top]{grid-area:top;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host ::ng-deep [md3-scaffold-rail=leading]{grid-area:start;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-rail=trailing]{grid-area:end;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-bar=bottom]{grid-area:bottom;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host .md3-bottom-sheet-region{grid-area:sheet;justify-self:stretch;min-width:0em;height:0em;position:relative;overflow:visible;z-index:4}:host .md3-panes-container{--background-color: rgb(var(--md-scheme-surface));--foreground-color: rgb(var(--md-scheme-on-surface));grid-area:content;min-width:0em;min-height:0em;overflow:hidden;background-color:var(--background-color);color:var(--foreground-color);display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr)}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
787
1885
  }
788
1886
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Scaffold, decorators: [{
789
1887
  type: Component,
790
1888
  args: [{ selector: 'md3-scaffold', imports: [
791
1889
  CdkPortalOutlet,
792
- ], template: "<ng-content select=\"[md3-scaffold-bar=top]\"></ng-content>\n<ng-content select=\"[md3-scaffold-rail=leading]\"></ng-content>\n\n<div class=\"md3-panes-container\" #panesContainer>\n <div class=\"md3-pane md3-pane-start\">\n <ng-content select=\"[md3-scaffold-pane=start]\"></ng-content>\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-main\">\n <ng-content select=\"[md3-scaffold-pane=main]\"></ng-content>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-content select=\"[md3-scaffold-pane=end]\"></ng-content>\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n</div>\n\n<ng-content select=\"[md3-scaffold-rail=trailing]\"></ng-content>\n<ng-content select=\"[md3-scaffold-bar=bottom]\"></ng-content>", styles: [":host{display:grid;width:100%;height:100dvh;min-height:0em;overflow:hidden;grid-template-areas:\"top top top\" \"start content end\" \"bottom bottom bottom\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto minmax(0,1fr) auto}:host ::ng-deep [md3-scaffold-bar=top]{grid-area:top;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host ::ng-deep [md3-scaffold-rail=leading]{grid-area:start;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-rail=trailing]{grid-area:end;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-bar=bottom]{grid-area:bottom;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host .md3-panes-container{--background-color: rgb(var(--md-scheme-surface));--foreground-color: rgb(var(--md-scheme-on-surface));grid-area:content;min-width:0em;min-height:0em;overflow:hidden;background-color:var(--background-color);color:var(--foreground-color);display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr)}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"] }]
793
- }], propDecorators: { startOutlet: [{ type: i0.ViewChild, args: ['startOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], endOutlet: [{ type: i0.ViewChild, args: ['endOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], panesContainer: [{ type: i0.ViewChild, args: ['panesContainer', { isSignal: true }] }] } });
1890
+ ], template: "<ng-content select=\"[md3-scaffold-bar=top]\"></ng-content>\n<ng-content select=\"[md3-scaffold-rail=leading]\"></ng-content>\n\n<div class=\"md3-panes-container\" #panesContainer>\n <div class=\"md3-pane md3-pane-start\">\n <ng-content select=\"[md3-scaffold-pane=start]\"></ng-content>\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-main\">\n <ng-content select=\"[md3-scaffold-pane=main]\"></ng-content>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-content select=\"[md3-scaffold-pane=end]\"></ng-content>\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n</div>\n\n<!-- Standard bottom sheets dock here, between the content and the bottom bar. The region\n measures zero until one opens, so it reserves nothing the rest of the time. -->\n<div class=\"md3-bottom-sheet-region\">\n <ng-template #bottomOutlet cdkPortalOutlet></ng-template>\n</div>\n\n<ng-content select=\"[md3-scaffold-rail=trailing]\"></ng-content>\n<ng-content select=\"[md3-scaffold-bar=bottom]\"></ng-content>", styles: [":host{display:grid;width:100%;height:100dvh;min-height:0em;overflow:hidden;grid-template-areas:\"top top top\" \"start content end\" \"start sheet end\" \"bottom bottom bottom\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto minmax(0,1fr) auto auto}:host ::ng-deep [md3-scaffold-bar=top]{grid-area:top;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host ::ng-deep [md3-scaffold-rail=leading]{grid-area:start;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-rail=trailing]{grid-area:end;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:3}:host ::ng-deep [md3-scaffold-bar=bottom]{grid-area:bottom;justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;overflow:hidden;z-index:2}:host .md3-bottom-sheet-region{grid-area:sheet;justify-self:stretch;min-width:0em;height:0em;position:relative;overflow:visible;z-index:4}:host .md3-panes-container{--background-color: rgb(var(--md-scheme-surface));--foreground-color: rgb(var(--md-scheme-on-surface));grid-area:content;min-width:0em;min-height:0em;overflow:hidden;background-color:var(--background-color);color:var(--foreground-color);display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr)}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0em;min-height:0em;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"] }]
1891
+ }], propDecorators: { startOutlet: [{ type: i0.ViewChild, args: ['startOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], endOutlet: [{ type: i0.ViewChild, args: ['endOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], bottomOutlet: [{ type: i0.ViewChild, args: ['bottomOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], panesContainer: [{ type: i0.ViewChild, args: ['panesContainer', { isSignal: true }] }] } });
794
1892
 
795
1893
  class ScaffoldBar {
796
1894
  region = input.required({ ...(ngDevMode ? { debugName: "region" } : /* istanbul ignore next */ {}), alias: 'md3-scaffold-bar' });
@@ -5970,5 +7068,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5970
7068
  * Generated bundle index. Do not edit.
5971
7069
  */
5972
7070
 
5973
- export { AppBar, AppBarLogo, Avatar, Badge, Button, ButtonGroup, CAROUSEL_STRATEGIES, Card, Carousel, CarouselItem, Checkbox, ChipAvatar, Chips, CircularProgressIndicator, DIALOG_COMPONENT, DIALOG_CONFIG, DIALOG_DATA, Dialog, DialogActions, DialogBody, DialogHeader, DialogRef, DialogService, Divider, FloatingActionButton, FullScreenDialog, FullScreenDialogHeader, Grid, GridItem, IconButton, IconElement, InputElement, LayoutService, LinearProgressIndicator, List, ListItem, ListItemPrimaryAction, ListLeading, ListSlot, LoadingIndicator, MENU_COMPONENT, MENU_CONFIG, MENU_DATA, MaterialIcon, Menu, MenuGroup, MenuItem, MenuRef, MenuService, NavigationBar, NavigationGroup, NavigationItem, NavigationRail, RadioButton, SIDE_SHEET_COMPONENT, SIDE_SHEET_CONFIG, SIDE_SHEET_DATA, SNACKBAR_ACTION_LABEL, SNACKBAR_CONFIG, SNACKBAR_MESSAGE, Scaffold, ScaffoldBar, ScaffoldPane, ScaffoldRail, SheetsService, SideSheetActions, SideSheetBody, SideSheetHeader, SideSheetRef, Slider, Snackbar, SnackbarRef, SnackbarService, SplitButton, StateComponent, SupportingText, Switch, TextField, TypeBody, TypeDisplay, TypeHeadline, TypeLabel, TypeTitle, buildGeometry, clampIndex, indexForScrollOffset, multiBrowseStrategy, parseCarouselAspectRatio, resolveItemGeometry, resolveState, scrollOffsetForIndex, sizeBandFor };
7071
+ export { AppBar, AppBarLogo, Avatar, BOTTOM_SHEET_COMPONENT, BOTTOM_SHEET_CONFIG, BOTTOM_SHEET_DATA, Badge, BottomSheetRef, BottomSheetService, Button, ButtonGroup, CAROUSEL_STRATEGIES, Card, Carousel, CarouselItem, Checkbox, ChipAvatar, Chips, CircularProgressIndicator, DIALOG_COMPONENT, DIALOG_CONFIG, DIALOG_DATA, Dialog, DialogActions, DialogBody, DialogHeader, DialogRef, DialogService, Divider, FloatingActionButton, FullScreenDialog, FullScreenDialogHeader, Grid, GridItem, IconButton, IconElement, InputElement, LayoutService, LinearProgressIndicator, List, ListItem, ListItemPrimaryAction, ListLeading, ListSlot, LoadingIndicator, MENU_COMPONENT, MENU_CONFIG, MENU_DATA, MaterialIcon, Menu, MenuGroup, MenuItem, MenuRef, MenuService, ModalBottomSheetRef, NavigationBar, NavigationGroup, NavigationItem, NavigationRail, RadioButton, SIDE_SHEET_COMPONENT, SIDE_SHEET_CONFIG, SIDE_SHEET_DATA, SNACKBAR_ACTION_LABEL, SNACKBAR_CONFIG, SNACKBAR_MESSAGE, Scaffold, ScaffoldBar, ScaffoldPane, ScaffoldRail, SheetsService, SideSheetActions, SideSheetBody, SideSheetHeader, SideSheetRef, Slider, Snackbar, SnackbarRef, SnackbarService, SplitButton, StandardBottomSheetRef, StateComponent, SupportingText, Switch, TextField, TypeBody, TypeDisplay, TypeHeadline, TypeLabel, TypeTitle, buildGeometry, clampIndex, indexForScrollOffset, multiBrowseStrategy, parseCarouselAspectRatio, resolveItemGeometry, resolveState, scrollOffsetForIndex, sizeBandFor };
5974
7072
  //# sourceMappingURL=almoamendev-ngx-md3.mjs.map