split-pane.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import { ContentChildren, Directive, ElementRef, EventEmitter, Input, NgZone, Output, Renderer, forwardRef } from '@angular/core';
  2. import { Ion } from '../ion';
  3. import { isTrueProperty } from '../../util/util';
  4. import { Config } from '../../config/config';
  5. import { Platform } from '../../platform/platform';
  6. const QUERY = {
  7. xs: '(min-width: 0px)',
  8. sm: '(min-width: 576px)',
  9. md: '(min-width: 768px)',
  10. lg: '(min-width: 992px)',
  11. xl: '(min-width: 1200px)',
  12. never: ''
  13. };
  14. /**
  15. * @hidden
  16. */
  17. export class RootNode {
  18. }
  19. /**
  20. * @name SplitPane
  21. *
  22. * @description
  23. * SplitPane is a component that makes it possible to create multi-view layout.
  24. * Similar to iPad apps, SplitPane allows UI elements, like Menus, to be
  25. * displayed as the viewport increases.
  26. *
  27. * If the devices screen size is below a certain size, the SplitPane will
  28. * collapse and the menu will become hidden again. This is especially useful when
  29. * creating an app that will be served over a browser or deployed through the app
  30. * store to phones and tablets.
  31. *
  32. * @usage
  33. * To use SplitPane, simply add the component around your root component.
  34. * In this example, we'll be using a sidemenu layout, similar to what is
  35. * provided from the sidemenu starter template.
  36. *
  37. * ```html
  38. * <ion-split-pane>
  39. * <!-- our side menu -->
  40. * <ion-menu [content]="content">
  41. * <ion-header>
  42. * <ion-toolbar>
  43. * <ion-title>Menu</ion-title>
  44. * </ion-toolbar>
  45. * </ion-header>
  46. * </ion-menu>
  47. *
  48. * <!-- the main content -->
  49. * <ion-nav [root]="root" main #content></ion-nav>
  50. * </ion-split-pane>
  51. * ```
  52. *
  53. * Here, SplitPane will look for the element with the `main` attribute and make
  54. * that the central component on larger screens. The `main` component can be any
  55. * Ionic component (`ion-nav` or `ion-tabs`) except `ion-menu`.
  56. *
  57. * ### Setting breakpoints
  58. *
  59. * By default, SplitPane will expand when the screen is larger than 768px.
  60. * If you want to customize this, use the `when` input. The `when` input can
  61. * accept any valid media query, as it uses `matchMedia()` underneath.
  62. *
  63. * ```
  64. * <ion-split-pane when="(min-width: 475px)">
  65. *
  66. * <!-- our side menu -->
  67. * <ion-menu [content]="content">
  68. * ....
  69. * </ion-menu>
  70. *
  71. * <!-- the main content -->
  72. * <ion-nav [root]="root" main #content></ion-nav>
  73. * </ion-split-pane>
  74. * ```
  75. *
  76. * SplitPane also provides some predefined media queries that can be used.
  77. *
  78. * ```html
  79. * <!-- could be "xs", "sm", "md", "lg", or "xl" -->
  80. * <ion-split-pane when="lg">
  81. * ...
  82. * </ion-split-pane>
  83. * ```
  84. *
  85. *
  86. * | Size | Value | Description |
  87. * |------|-----------------------|-----------------------------------------------------------------------|
  88. * | `xs` | `(min-width: 0px)` | Show the split-pane when the min-width is 0px (meaning, always) |
  89. * | `sm` | `(min-width: 576px)` | Show the split-pane when the min-width is 576px |
  90. * | `md` | `(min-width: 768px)` | Show the split-pane when the min-width is 768px (default break point) |
  91. * | `lg` | `(min-width: 992px)` | Show the split-pane when the min-width is 992px |
  92. * | `xl` | `(min-width: 1200px)` | Show the split-pane when the min-width is 1200px |
  93. *
  94. * You can also pass in boolean values that will trigger SplitPane when the value
  95. * or expression evaluates to true.
  96. *
  97. *
  98. * ```html
  99. * <ion-split-pane [when]="isLarge">
  100. * ...
  101. * </ion-split-pane>
  102. * ```
  103. *
  104. * ```ts
  105. * class MyClass {
  106. * public isLarge = false;
  107. * constructor(){}
  108. * }
  109. * ```
  110. *
  111. * Or
  112. *
  113. * ```html
  114. * <ion-split-pane [when]="shouldShow()">
  115. * ...
  116. * </ion-split-pane>
  117. * ```
  118. *
  119. * ```ts
  120. * class MyClass {
  121. * constructor(){}
  122. * shouldShow(){
  123. * if(conditionA){
  124. * return true
  125. * } else {
  126. * return false
  127. * }
  128. * }
  129. * }
  130. * ```
  131. *
  132. */
  133. export class SplitPane extends Ion {
  134. constructor(_zone, _plt, config, elementRef, renderer) {
  135. super(config, elementRef, renderer, 'split-pane');
  136. this._zone = _zone;
  137. this._plt = _plt;
  138. this._init = false;
  139. this._visible = false;
  140. this._isEnabled = true;
  141. this._mediaQuery = QUERY['md'];
  142. /**
  143. * @hidden
  144. */
  145. this.sideContent = null;
  146. /**
  147. * @hidden
  148. */
  149. this.mainContent = null;
  150. /**
  151. * @output {any} Expression to be called when the split-pane visibility has changed
  152. */
  153. this.ionChange = new EventEmitter();
  154. }
  155. /**
  156. * @hidden
  157. */
  158. set _setchildren(query) {
  159. const children = this._children = query.filter((child => child !== this));
  160. children.forEach(child => {
  161. var isMain = child.initPane();
  162. this._setPaneCSSClass(child.getElementRef(), isMain);
  163. });
  164. }
  165. /**
  166. * @input {string | boolean} When the split-pane should be shown.
  167. * Can be a CSS media query expression, or a shortcut expression.
  168. * Can also be a boolean expression.
  169. */
  170. set when(query) {
  171. if (typeof query === 'boolean') {
  172. this._mediaQuery = query;
  173. }
  174. else {
  175. const defaultQuery = QUERY[query];
  176. this._mediaQuery = (defaultQuery)
  177. ? defaultQuery
  178. : query;
  179. }
  180. this._update();
  181. }
  182. get when() {
  183. return this._mediaQuery;
  184. }
  185. /**
  186. * @input {boolean} If `false`, the split-pane is disabled, ie. the side pane will
  187. * never be displayed. Default `true`.
  188. */
  189. set enabled(val) {
  190. this._isEnabled = isTrueProperty(val);
  191. this._update();
  192. }
  193. get enabled() {
  194. return this._isEnabled;
  195. }
  196. /**
  197. * @hidden
  198. */
  199. _register(node, isMain, callback) {
  200. if (this.getElementRef().nativeElement !== node.getElementRef().nativeElement.parentNode) {
  201. return false;
  202. }
  203. this._setPaneCSSClass(node.getElementRef(), isMain);
  204. if (callback) {
  205. this.ionChange.subscribe(callback);
  206. }
  207. if (isMain) {
  208. if (this.mainContent) {
  209. console.error('split pane: main content was already set');
  210. }
  211. this.mainContent = node;
  212. }
  213. return true;
  214. }
  215. /**
  216. * @hidden
  217. */
  218. ngAfterViewInit() {
  219. this._init = true;
  220. this._update();
  221. }
  222. /**
  223. * @hidden
  224. */
  225. _update() {
  226. if (!this._init) {
  227. return;
  228. }
  229. // Unlisten
  230. this._rmListener && this._rmListener();
  231. this._rmListener = null;
  232. // Check if the split-pane is disabled
  233. if (!this._isEnabled) {
  234. this._setVisible(false);
  235. return;
  236. }
  237. const query = this._mediaQuery;
  238. if (typeof query === 'boolean') {
  239. this._setVisible(query);
  240. return;
  241. }
  242. if (query && query.length > 0) {
  243. // Listen
  244. const callback = (query) => this._setVisible(query.matches);
  245. const mediaList = this._plt.win().matchMedia(query);
  246. mediaList.addListener(callback);
  247. this._setVisible(mediaList.matches);
  248. this._rmListener = function () {
  249. mediaList.removeListener(callback);
  250. };
  251. }
  252. else {
  253. this._setVisible(false);
  254. }
  255. }
  256. /**
  257. * @hidden
  258. */
  259. _updateChildren() {
  260. this.mainContent = null;
  261. this.sideContent = null;
  262. const visible = this._visible;
  263. this._children.forEach(child => child.paneChanged && child.paneChanged(visible));
  264. }
  265. /**
  266. * @hidden
  267. */
  268. _setVisible(visible) {
  269. if (this._visible === visible) {
  270. return;
  271. }
  272. this._visible = visible;
  273. this.setElementClass('split-pane-visible', visible);
  274. this._updateChildren();
  275. this._zone.run(() => {
  276. this.ionChange.emit(this);
  277. });
  278. }
  279. /**
  280. * @hidden
  281. */
  282. isVisible() {
  283. return this._visible;
  284. }
  285. /**
  286. * @hidden
  287. */
  288. setElementClass(className, add) {
  289. this._renderer.setElementClass(this._elementRef.nativeElement, className, add);
  290. }
  291. /**
  292. * @hidden
  293. */
  294. _setPaneCSSClass(elementRef, isMain) {
  295. const ele = elementRef.nativeElement;
  296. this._renderer.setElementClass(ele, 'split-pane-main', isMain);
  297. this._renderer.setElementClass(ele, 'split-pane-side', !isMain);
  298. }
  299. /**
  300. * @hidden
  301. */
  302. ngOnDestroy() {
  303. (void 0) /* assert */;
  304. this._rmListener && this._rmListener();
  305. this._rmListener = null;
  306. }
  307. /**
  308. * @hidden
  309. */
  310. initPane() {
  311. return true;
  312. }
  313. }
  314. SplitPane.decorators = [
  315. { type: Directive, args: [{
  316. selector: 'ion-split-pane',
  317. providers: [{ provide: RootNode, useExisting: forwardRef(() => SplitPane) }]
  318. },] },
  319. ];
  320. /** @nocollapse */
  321. SplitPane.ctorParameters = () => [
  322. { type: NgZone, },
  323. { type: Platform, },
  324. { type: Config, },
  325. { type: ElementRef, },
  326. { type: Renderer, },
  327. ];
  328. SplitPane.propDecorators = {
  329. '_setchildren': [{ type: ContentChildren, args: [RootNode, { descendants: false },] },],
  330. 'when': [{ type: Input },],
  331. 'enabled': [{ type: Input },],
  332. 'ionChange': [{ type: Output },],
  333. };
  334. //# sourceMappingURL=split-pane.js.map