feat(split-pane): adds split-pane (part 1)

This commit is contained in:
Manuel Mtz-Almeida
2017-09-16 10:29:47 -05:00
parent 1f41fbd78f
commit 9dcd00d9b9
8 changed files with 587 additions and 88 deletions

42
package-lock.json generated
View File

@ -4,6 +4,16 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"JSONStream": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
"integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
"dev": true,
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"abab": { "abab": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz",
@ -1107,8 +1117,8 @@
"integrity": "sha1-4ye1MZThp61dxjR57pCZpSsCSGU=", "integrity": "sha1-4ye1MZThp61dxjR57pCZpSsCSGU=",
"dev": true, "dev": true,
"requires": { "requires": {
"is-text-path": "1.0.1",
"JSONStream": "1.3.1", "JSONStream": "1.3.1",
"is-text-path": "1.0.1",
"lodash": "4.17.4", "lodash": "4.17.4",
"meow": "3.7.0", "meow": "3.7.0",
"split2": "2.1.1", "split2": "2.1.1",
@ -1137,8 +1147,8 @@
"integrity": "sha512-8od6g684Fhi5Vpp4ABRv/RBsW1AY6wSHbJHEK6FGTv+8jvAAnlABniZu/FVmX9TcirkHepaEsa1QGkRvbg0CKw==", "integrity": "sha512-8od6g684Fhi5Vpp4ABRv/RBsW1AY6wSHbJHEK6FGTv+8jvAAnlABniZu/FVmX9TcirkHepaEsa1QGkRvbg0CKw==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-text-path": "1.0.1",
"JSONStream": "1.3.1", "JSONStream": "1.3.1",
"is-text-path": "1.0.1",
"lodash": "4.17.4", "lodash": "4.17.4",
"meow": "3.7.0", "meow": "3.7.0",
"split2": "2.1.1", "split2": "2.1.1",
@ -2881,16 +2891,6 @@
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true "dev": true
}, },
"JSONStream": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
"integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
"dev": true,
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"jsprim": { "jsprim": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@ -4659,15 +4659,6 @@
"any-observable": "0.2.0" "any-observable": "0.2.0"
} }
}, },
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"string-length": { "string-length": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
@ -4688,6 +4679,15 @@
"strip-ansi": "3.0.1" "strip-ansi": "3.0.1"
} }
}, },
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"stringstream": { "stringstream": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",

View File

@ -11,6 +11,7 @@
<body> <body>
<ion-app> <ion-app>
<ion-menu side="left"> <ion-menu side="left">
<ion-header> <ion-header>
<ion-toolbar color="secondary"> <ion-toolbar color="secondary">
<ion-title>Left Menu</ion-title> <ion-title>Left Menu</ion-title>
@ -18,61 +19,19 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-list> <ion-list>
<ion-item>Open Right Menu</ion-item>
<ion-item> <ion-item detail-none>Close Menu</ion-item>
Open Right Menu <ion-item detail-none>Close Menu</ion-item>
</ion-item> <ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item menuClose="left" class="e2eCloseLeftMenu" detail-none> <ion-item detail-none>Close Menu</ion-item>
Close Menu <ion-item detail-none>Close Menu</ion-item>
</ion-item> <ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item menuClose="left" detail-none> <ion-item detail-none>Close Menu</ion-item>
Close Menu <ion-item detail-none>Close Menu</ion-item>
</ion-item> <ion-item detail-none>Close Menu</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
<ion-item menuClose="left" detail-none>
Close Menu
</ion-item>
</ion-list> </ion-list>
</ion-content> </ion-content>
@ -84,35 +43,31 @@
</ion-menu> </ion-menu>
<ion-menu side="right" > <ion-menu side="right">
<ion-header>
<ion-header id="id">
<ion-toolbar> <ion-toolbar>
<ion-title>Hola</ion-title> <ion-title>Hola</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content padding>
hola macho hola macho
</ion-content> </ion-content>
</ion-menu> </ion-menu>
<ion-page main class="show-page"> <ion-page main class="show-page">
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>Menu Basic Test</ion-title> <ion-title>Ionic CDN demo</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content padding>
<ion-button onclick="openLeft()"> <ion-button onclick="openLeft()">Open left menu</ion-button>
Open left menu <ion-button onclick="openRight()">Open right menu</ion-button>
</ion-button>
<ion-button onclick="openRight()">
Open right menu
</ion-button>
</ion-content> </ion-content>
</ion-page> </ion-page>
</ion-app> </ion-app>
@ -130,5 +85,4 @@
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,28 @@
@import "../../themes/ionic.globals.ios";
@import "./split-pane";
// Split Pane
// --------------------------------------------------
/// @prop - Minimum width of the split-pane's side pane
$split-pane-ios-side-min-width: $split-pane-side-min-width !default;
/// @prop - Maximum width of the split-pane's side pane
$split-pane-ios-side-max-width: $split-pane-side-max-width !default;
/// @prop - Border style of the side pane
$split-pane-ios-border: $hairlines-width solid $list-ios-border-color !default;
.split-pane-ios.split-pane-visible >.split-pane-side {
min-width: $split-pane-ios-side-min-width;
max-width: $split-pane-ios-side-max-width;
border-right: $split-pane-ios-border;
border-left: 0;
}
.split-pane-ios.split-pane-visible > .split-pane-side[side=right] {
border-right: 0;
border-left: $split-pane-ios-border;
}

View File

@ -0,0 +1,28 @@
@import "../../themes/ionic.globals.md";
@import "./split-pane";
// Split Pane
// --------------------------------------------------
/// @prop - Minimum width of the split-pane's side pane
$split-pane-md-side-min-width: $split-pane-side-min-width !default;
/// @prop - Maximum width of the split-pane's side pane
$split-pane-md-side-max-width: $split-pane-side-max-width !default;
/// @prop - Border style of the side pane
$split-pane-md-border: 1px solid $list-md-border-color !default;
.split-pane-md.split-pane-visible >.split-pane-side {
min-width: $split-pane-md-side-min-width;
max-width: $split-pane-md-side-max-width;
border-right: $split-pane-md-border;
border-left: 0;
}
.split-pane-md.split-pane-visible > .split-pane-side[side=right] {
border-right: 0;
border-left: $split-pane-md-border;
}

View File

@ -0,0 +1,106 @@
@import "../../themes/ionic.globals";
// Split Pane
// --------------------------------------------------
$split-pane-side-min-width: 270px !default;
$split-pane-side-max-width: 28% !default;
.split-pane {
@include position(0, 0, 0, 0);
position: absolute;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
contain: strict;
}
.split-pane-side:not(ion-menu) {
display: none;
}
.split-pane-visible >.split-pane-side,
.split-pane-visible >.split-pane-main {
@include position(0, 0, 0, 0);
// scss-lint:disable ImportantRule
position: relative;
z-index: 0;
flex: 1;
box-shadow: none !important;
}
.split-pane-visible >.split-pane-side {
flex-shrink: 0;
order: -1;
}
.split-pane-visible >.split-pane-main,
.split-pane-visible >ion-nav.split-pane-side,
.split-pane-visible >ion-tabs.split-pane-side,
.split-pane-visible >ion-menu.menu-enabled {
display: block;
}
.split-pane-visible >ion-split-pane.split-pane-side,
.split-pane-visible >ion-split-pane.split-pane-main {
display: flex;
}
.split-pane-visible >ion-menu.menu-enabled {
>.menu-inner {
@include position-horizontal(0, 0);
// scss-lint:disable ImportantRule
width: auto;
box-shadow: none !important;
transform: none !important;
}
>.ion-backdrop {
// scss-lint:disable ImportantRule
display: hidden !important;
}
}
.split-pane-visible >.split-pane-side[side=start] {
@include multi-dir() {
order: -1;
}
}
.split-pane-visible >.split-pane-side[side=end] {
@include multi-dir() {
order: 1;
}
}
.split-pane-visible >.split-pane-side[side=left] {
@include ltr() {
order: -1;
}
@include rtl() {
order: 1;
}
}
.split-pane-visible >.split-pane-side[side=right] {
@include ltr() {
order: 1;
}
@include rtl() {
order: -1;
}
}

View File

@ -0,0 +1,276 @@
import { Component, State, Element, Event, EventEmitter, Prop, Method, PropDidChange } from '@stencil/core';
// import { assert } from '../../utils/helpers';
const SPLIT_PANE_MAIN = 'split-pane-main';
const SPLIT_PANE_SIDE = 'split-pane-side';
const QUERY: { [key: string]: string } = {
'xs': '(min-width: 0px)',
'sm': '(min-width: 576px)',
'md': '(min-width: 768px)',
'lg': '(min-width: 992px)',
'xl': '(min-width: 1200px)',
'never': ''
};
/**
* @name SplitPane
*
* @description
* SplitPane is a component that makes it possible to create multi-view layout.
* Similar to iPad apps, SplitPane allows UI elements, like Menus, to be
* displayed as the viewport increases.
*
* If the devices screen size is below a certain size, the SplitPane will
* collapse and the menu will become hidden again. This is especially useful when
* creating an app that will be served over a browser or deployed through the app
* store to phones and tablets.
*
* @usage
* To use SplitPane, simply add the component around your root component.
* In this example, we'll be using a sidemenu layout, similar to what is
* provided from the sidemenu starter template.
*
* ```html
* <ion-split-pane>
* <!-- our side menu -->
* <ion-menu>
* <ion-header>
* <ion-toolbar>
* <ion-title>Menu</ion-title>
* </ion-toolbar>
* </ion-header>
* </ion-menu>
*
* <!-- the main content -->
* <ion-nav [root]="root" main></ion-nav>
* </ion-split-pane>
* ```
*
* Here, SplitPane will look for the element with the `main` attribute and make
* that the central component on larger screens. The `main` component can be any
* Ionic component (`ion-nav` or `ion-tabs`) except `ion-menu`.
*
* ### Setting breakpoints
*
* By default, SplitPane will expand when the screen is larger than 768px.
* If you want to customize this, use the `when` input. The `when` input can
* accept any valid media query, as it uses `matchMedia()` underneath.
*
* ```
* <ion-split-pane when="(min-width: 475px)">
*
* <!-- our side menu -->
* <ion-menu>
* ....
* </ion-menu>
*
* <!-- the main content -->
* <ion-nav [root]="root" main></ion-nav>
* </ion-split-pane>
* ```
*
* SplitPane also provides some predefined media queries that can be used.
*
* ```html
* <!-- could be "xs", "sm", "md", "lg", or "xl" -->
* <ion-split-pane when="lg">
* ...
* </ion-split-pane>
* ```
*
*
* | Size | Value | Description |
* |------|-----------------------|-----------------------------------------------------------------------|
* | `xs` | `(min-width: 0px)` | Show the split-pane when the min-width is 0px (meaning, always) |
* | `sm` | `(min-width: 576px)` | Show the split-pane when the min-width is 576px |
* | `md` | `(min-width: 768px)` | Show the split-pane when the min-width is 768px (default break point) |
* | `lg` | `(min-width: 992px)` | Show the split-pane when the min-width is 992px |
* | `xl` | `(min-width: 1200px)` | Show the split-pane when the min-width is 1200px |
*
* You can also pass in boolean values that will trigger SplitPane when the value
* or expression evaluates to true.
*
*
* ```html
* <ion-split-pane [when]="isLarge">
* ...
* </ion-split-pane>
* ```
*
* ```ts
* class MyClass {
* public isLarge = false;
* constructor(){}
* }
* ```
*
* Or
*
* ```html
* <ion-split-pane [when]="shouldShow()">
* ...
* </ion-split-pane>
* ```
*
* ```ts
* class MyClass {
* constructor(){}
* shouldShow(){
* if(conditionA){
* return true
* } else {
* return false
* }
* }
* }
* ```
*
*/
@Component({
tag: 'ion-split-pane',
styleUrls: {
ios: 'split-pane.ios.scss',
md: 'split-pane.md.scss',
wp: 'split-pane.wp.scss'
},
host: {
theme: 'split-pane'
}
})
export class SplitPane {
private rmL: any;
@Element() private ele: HTMLElement;
@State() private visible: boolean = false;
/**
* @input {boolean} If `false`, the split-pane is disabled, ie. the side pane will
* never be displayed. Default `true`.
*/
@Prop() enabled: boolean = true;
/**
* @input {string | boolean} When the split-pane should be shown.
* Can be a CSS media query expression, or a shortcut expression.
* Can also be a boolean expression.
*/
@Prop() when: string | boolean = QUERY['md'];
/**
* @output {any} Expression to be called when the split-pane visibility has changed
*/
@Event() ionChange: EventEmitter;
ionViewDidLoad() {
this._styleChildren();
this._updateQuery();
}
ionViewDidUnload() {
this.rmL && this.rmL();
this.rmL = null;
}
private _styleChildren() {
const children = this.ele.children;
const nu = this.ele.childElementCount;
let foundMain = false;
for (var i = 0; i < nu; i++) {
var child = children[i] as HTMLElement;
var isMain = child.hasAttribute('main');
if (isMain) {
if (foundMain) {
// console.warn('split pane can not have more than one main node');
return;
}
foundMain = true;
}
setPaneClass(child, isMain);
}
// if (!foundMain) {
// console.warn('split pane could not found any main node');
// }
}
@PropDidChange('when')
_updateQuery() {
this.rmL && this.rmL();
this.rmL = null;
// Check if the split-pane is disabled
if (!this.enabled) {
this._setVisible(false);
return;
}
// When query is a boolean
const query = this.when;
if (typeof query === 'boolean') {
this._setVisible(query);
return;
}
// When query is a string, let's find first if it is a shortcut
const defaultQuery = QUERY[query];
const mediaQuery = (defaultQuery)
? defaultQuery
: query;
// Media query is empty or null, we hide it
if (!mediaQuery || mediaQuery.length === 0) {
this._setVisible(false);
return;
}
// Listen on media query
const callback = (q: MediaQueryList) => this._setVisible(q.matches);
const mediaList = window.matchMedia(mediaQuery);
mediaList.addListener(callback);
this.rmL = () => mediaList.removeListener(callback);
this._setVisible(mediaList.matches);
}
private _setVisible(visible: boolean) {
if (this.visible !== visible) {
this.visible = visible;
this.ionChange.emit(this);
}
}
/**
* @hidden
*/
@Method()
isVisible(): boolean {
return this.visible;
}
hostData() {
return {
class: {
'split-pane-visible': this.visible
}
};
}
render() {
return <slot></slot>;
}
}
function setPaneClass(ele: HTMLElement, isMain: boolean) {
let toAdd;
let toRemove;
if (isMain) {
toAdd = SPLIT_PANE_MAIN;
toRemove = SPLIT_PANE_SIDE;
} else {
toAdd = SPLIT_PANE_SIDE;
toRemove = SPLIT_PANE_MAIN;
}
const classList = ele.classList;
classList.add(toAdd);
classList.remove(toRemove);
}

View File

@ -0,0 +1,28 @@
@import "../../themes/ionic.globals.wp";
@import "./split-pane";
// Split Pane
// --------------------------------------------------
/// @prop - Minimum width of the split-pane's side pane
$split-pane-wp-side-min-width: $split-pane-side-min-width !default;
/// @prop - Maximum width of the split-pane's side pane
$split-pane-wp-side-max-width: $split-pane-side-max-width !default;
/// @prop - Border style of the side pane
$split-pane-wp-border: 1px solid $list-wp-border-color !default;
.split-pane-wp.split-pane-visible >.split-pane-side {
min-width: $split-pane-wp-side-min-width;
max-width: $split-pane-wp-side-max-width;
border-right: $split-pane-wp-border;
border-left: 0;
}
.split-pane-wp.split-pane-visible > .split-pane-side[side=right] {
border-right: 0;
border-left: $split-pane-wp-border;
}

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Ionic Item Sliding</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script>
</head>
<body>
<ion-app>
<ion-split-pane>
<ion-menu side="left">
<ion-header>
<ion-toolbar color="secondary">
<ion-title>Left Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>Open Right Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
<ion-item detail-none>Close Menu</ion-item>
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar color="secondary">
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</ion-menu>
<ion-page main class="show-page">
<ion-header>
<ion-toolbar>
<ion-title>Ionic CDN demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-button onclick="openLeft()">Open left menu</ion-button>
<ion-button onclick="openRight()">Open right menu</ion-button>
</ion-content>
</ion-page>
</ion-split-pane>
</ion-app>
<ion-menu-controller></ion-menu-controller>
<script>
const menu = document.querySelector('ion-menu-controller');
function openLeft() {
console.log('Open left menu');
menu.open('left');
}
function openRight() {
console.log('Open right menu');
menu.open('right');
}
</script>
</body>
</html>