@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,
|
|
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,
|
|
9
|
-
import {
|
|
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
|