MOBILE-3661 split-view: Support nested split views
parent
ae76e3da02
commit
6d148229f1
|
@ -1,5 +1,5 @@
|
||||||
<ion-content>
|
<ion-content class="menu">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
<ion-router-outlet></ion-router-outlet>
|
<ion-router-outlet class="content"></ion-router-outlet>
|
||||||
<!-- @todo placeholder -->
|
<!-- @todo placeholder -->
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
@import "~theme/breakpoints";
|
|
||||||
|
|
||||||
// @todo RTL layout
|
// @todo RTL layout
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
--side-width: 100%;
|
--menu-min-width: 270px;
|
||||||
--side-min-width: 270px;
|
--menu-max-width: 28%;
|
||||||
--side-max-width: 28%;
|
--menu-display: flex;
|
||||||
|
--content-display: block;
|
||||||
|
--border-width: 1;
|
||||||
|
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -19,16 +19,28 @@
|
||||||
contain: strict;
|
contain: strict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host(.menu-only) {
|
||||||
|
--menu-min-width: 0;
|
||||||
|
--menu-max-width: 100%;
|
||||||
|
--content-display: none;
|
||||||
|
--border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.content-only) {
|
||||||
|
--menu-display: none;
|
||||||
|
--border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
:host-context(ion-app.md) {
|
:host-context(ion-app.md) {
|
||||||
--border: 1px solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .13))));
|
--border: calc(var(--border-width) * 1px) solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, .13))));
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(ion-app.ios) {
|
:host-context(ion-app.ios) {
|
||||||
--border: .55px solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-250, #c8c7cc)));
|
--border: calc(var(--border-width) * .55px) solid var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-250, #c8c7cc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-content,
|
.menu,
|
||||||
ion-router-outlet {
|
.content {
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -38,49 +50,25 @@ ion-router-outlet {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-content {
|
.menu {
|
||||||
display: flex;
|
display: var(--menu-display);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
order: -1;
|
order: -1;
|
||||||
border-left: unset;
|
border-left: unset;
|
||||||
border-right: unset;
|
border-right: unset;
|
||||||
border-inline-start: 0;
|
border-inline-start: 0;
|
||||||
border-inline-end: 0;
|
border-inline-end: var(--border);
|
||||||
|
min-width: var(--menu-min-width);
|
||||||
|
max-width: var(--menu-max-width);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-router-outlet {
|
.content {
|
||||||
|
display: var(--content-display);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: none;
|
|
||||||
|
|
||||||
::ng-deep ion-header {
|
::ng-deep ion-header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host(.outlet-activated) {
|
|
||||||
|
|
||||||
ion-router-outlet {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-content {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: $breakpoint-tablet) {
|
|
||||||
|
|
||||||
ion-content {
|
|
||||||
border-inline-end: var(--border);
|
|
||||||
min-width: var(--side-min-width);
|
|
||||||
max-width: var(--side-max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(.outlet-activated) ion-content {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,10 +12,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AfterViewInit, Component, HostBinding, OnDestroy, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, HostBinding, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { IonRouterOutlet } from '@ionic/angular';
|
import { IonRouterOutlet } from '@ionic/angular';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
enum CoreSplitViewMode {
|
||||||
|
Default = '', // Shows both menu and content.
|
||||||
|
MenuOnly = 'menu-only', // Hides content.
|
||||||
|
ContentOnly = 'content-only', // Hides menu.
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-split-view',
|
selector: 'core-split-view',
|
||||||
templateUrl: 'split-view.html',
|
templateUrl: 'split-view.html',
|
||||||
|
@ -24,19 +31,25 @@ import { Subscription } from 'rxjs';
|
||||||
export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
|
export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(IonRouterOutlet) outlet!: IonRouterOutlet;
|
@ViewChild(IonRouterOutlet) outlet!: IonRouterOutlet;
|
||||||
@HostBinding('class.outlet-activated') outletActivated = false;
|
@HostBinding('class') classes = '';
|
||||||
|
|
||||||
|
private isNestedSplitView = false;
|
||||||
private subscriptions?: Subscription[];
|
private subscriptions?: Subscription[];
|
||||||
|
|
||||||
|
constructor(private element: ElementRef<HTMLElement>) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.outletActivated = this.outlet.isActivated;
|
this.isNestedSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view');
|
||||||
this.subscriptions = [
|
this.subscriptions = [
|
||||||
this.outlet.activateEvents.subscribe(() => this.outletActivated = true),
|
this.outlet.activateEvents.subscribe(() => this.updateClasses()),
|
||||||
this.outlet.deactivateEvents.subscribe(() => this.outletActivated = false),
|
this.outlet.deactivateEvents.subscribe(() => this.updateClasses()),
|
||||||
|
CoreScreen.instance.layoutObservable.subscribe(() => this.updateClasses()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
this.updateClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,4 +59,31 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
|
||||||
this.subscriptions?.forEach(subscription => subscription.unsubscribe());
|
this.subscriptions?.forEach(subscription => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update host classes.
|
||||||
|
*/
|
||||||
|
private updateClasses(): void {
|
||||||
|
this.classes = this.getCurrentMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current mode. Depending on the layout, outlet status, and whether this split view
|
||||||
|
* is nested or not, this method will indicate which parts of the split view should be visible.
|
||||||
|
*
|
||||||
|
* @return Split view mode.
|
||||||
|
*/
|
||||||
|
private getCurrentMode(): CoreSplitViewMode {
|
||||||
|
if (this.isNestedSplitView) {
|
||||||
|
return CoreSplitViewMode.MenuOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CoreScreen.instance.isMobile) {
|
||||||
|
return this.outlet.isActivated
|
||||||
|
? CoreSplitViewMode.ContentOnly
|
||||||
|
: CoreSplitViewMode.MenuOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreSplitViewMode.Default;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Layout Breakpoints
|
|
||||||
*
|
|
||||||
* https://ionicframework.com/docs/layout/grid#default-breakpoints
|
|
||||||
*/
|
|
||||||
|
|
||||||
$breakpoint-xs: 0px;
|
|
||||||
$breakpoint-sm: 576px;
|
|
||||||
$breakpoint-md: 768px;
|
|
||||||
$breakpoint-lg: 992px;
|
|
||||||
$breakpoint-xl: 1200px;
|
|
||||||
|
|
||||||
$breakpoint-tablet: $breakpoint-lg;
|
|
Loading…
Reference in New Issue