├── .editorconfig
├── .eslintrc.json
├── .github
└── workflows
│ ├── build.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── angular.json
├── demo.png
├── package-lock.json
├── package.json
├── projects
├── demo
│ ├── .eslintrc.json
│ ├── src
│ │ ├── app
│ │ │ ├── app.component.css
│ │ │ ├── app.component.html
│ │ │ ├── app.component.ts
│ │ │ └── app.module.ts
│ │ ├── assets
│ │ │ └── angular.svg
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ └── styles.css
│ └── tsconfig.app.json
└── lib
│ ├── .eslintrc.json
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── core
│ │ ├── foldable.module.ts
│ │ ├── if-span.directive.spec.ts
│ │ ├── if-span.directive.ts
│ │ ├── index.ts
│ │ ├── media-queries.ts
│ │ ├── screen-context.ts
│ │ ├── screen-spanning.ts
│ │ ├── split-layout.directive.spec.ts
│ │ ├── split-layout.directive.ts
│ │ ├── window.directive.spec.ts
│ │ └── window.directive.ts
│ └── public-api.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json"
14 | ],
15 | "createDefaultProgram": true
16 | },
17 | "extends": [
18 | "plugin:@angular-eslint/ng-cli-compat",
19 | "plugin:@angular-eslint/ng-cli-compat--formatting-add-on",
20 | "plugin:@angular-eslint/template/process-inline-templates"
21 | ],
22 | "rules": {}
23 | },
24 | {
25 | "files": [
26 | "*.html"
27 | ],
28 | "extends": [
29 | "plugin:@angular-eslint/template/recommended"
30 | ],
31 | "rules": {}
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | build:
12 | strategy:
13 | matrix:
14 | platform: [ubuntu-latest]
15 | node-version: ['14', '>=18']
16 |
17 | name: ${{ matrix.platform }} / Node.js v${{ matrix.node-version }}
18 | runs-on: ${{ matrix.platform }}
19 | steps:
20 | - run: git config --global core.autocrlf false # Preserve line endings
21 | - uses: actions/checkout@v3
22 | - uses: actions/setup-node@v3
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - run: |
26 | npm i -g npm@latest
27 | npm ci
28 | npm run test:ci
29 | env:
30 | CI: true
31 |
32 | build_all:
33 | if: always()
34 | runs-on: ubuntu-latest
35 | needs: build
36 | steps:
37 | - name: Check build matrix status
38 | if: ${{ needs.build.result != 'success' }}
39 | run: exit 1
40 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on:
3 | workflow_dispatch:
4 | repository_dispatch:
5 | types: [release]
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | if: github.ref == 'refs/heads/main'
11 | steps:
12 | - uses: actions/checkout@v3
13 | with:
14 | persist-credentials: false
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 18
18 | - run: |
19 | npm ci
20 | npm run build:lib
21 | npm run build:demo
22 | npm run build:docs
23 | npm run build:lib
24 | env:
25 | CI: true
26 | - run: npx semantic-release
27 | if: success()
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | # Need owner/admin account to bypass branch protection
32 | GIT_COMMITTER_NAME: sinedied
33 | GIT_COMMITTER_EMAIL: noda@free.fr
34 | - uses: peaceiris/actions-gh-pages@v3
35 | with:
36 | personal_token: ${{ secrets.GH_TOKEN }}
37 | publish_dir: ./dist/docs
38 | user_name: sinedied
39 | user_email: noda@free.fr
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 | .angular/
48 |
49 | # Files copied from /
50 | /projects/lib/README.md
51 | /projects/lib/LICENSE
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [5.0.0](https://github.com/sinedied/ngx-foldable/compare/4.0.0...5.0.0) (2022-11-29)
2 |
3 |
4 | ### Features
5 |
6 | * update to Angular 15 ([4cb5766](https://github.com/sinedied/ngx-foldable/commit/4cb5766e6cccb2fd04c35df56bd208c1f5a39cfe))
7 |
8 |
9 | ### BREAKING CHANGES
10 |
11 | * Need Angular version >=15
12 | Need Node.js version >=14.20.0
13 |
14 | # [4.0.0](https://github.com/sinedied/ngx-foldable/compare/3.1.0...4.0.0) (2022-06-03)
15 |
16 |
17 | ### Bug Fixes
18 |
19 | * set minimum Angular version to 14 ([26533ab](https://github.com/sinedied/ngx-foldable/commit/26533ab46527b35385cb9ff89382014ca7f361ff))
20 | * set minimum node version ([7527356](https://github.com/sinedied/ngx-foldable/commit/7527356cf5b6d6787462a72e6c6077d9d433a6e7))
21 |
22 |
23 | ### BREAKING CHANGES
24 |
25 | * set minimum node version to 14.15
26 | * set minimum Angular version to 14
27 |
28 | # [3.1.0](https://github.com/sinedied/ngx-foldable/compare/3.0.0...3.1.0) (2022-06-03)
29 |
30 |
31 | ### Features
32 |
33 | * support new stable API ([30b605d](https://github.com/sinedied/ngx-foldable/commit/30b605d4918ca41a09b3537be16acd25a70d1a91))
34 |
35 | # [3.0.0](https://github.com/sinedied/ngx-foldable/compare/2.0.0...3.0.0) (2021-11-25)
36 |
37 |
38 | ### Features
39 |
40 | * rename spanning mode and media queries ([66c7a62](https://github.com/sinedied/ngx-foldable/commit/66c7a62a93fa890ea8dc2b6f7a03b88253251116))
41 | * upgrade to latest APF ([5ac1ca9](https://github.com/sinedied/ngx-foldable/commit/5ac1ca9c4bc6ce57af6c79e1c4a395f40f18628c))
42 |
43 |
44 | ### BREAKING CHANGES
45 |
46 | * This library now requires Angular v13 or latest.
47 | Use the previous versions of this library if you require compatibility with older Angular version.
48 | * To better match the new viewport browser API, the spanning modes and media queries have been renamed.
49 | It now matches the number of segments on a given axis instead of the fold axis,
50 | which should be less confusing.
51 |
52 | You can migrate by performing these replacements:
53 | - ScreenSpanning.Vertical -> ScreenSpanning.DualHorizontal
54 | - ScreenSpanning.Horizontal -> ScreenSpanning.DualVertical
55 | - 'fold-horizontal' -> 'dual-vertical'
56 | - 'fold-vertical' -> 'dual-horizontal'
57 | - singleFoldHorizontal -> dualVerticalViewport
58 | - singleFoldVertical -> dualHorizontalViewport
59 |
60 | # [2.0.0](https://github.com/sinedied/ngx-foldable/compare/1.1.1...2.0.0) (2021-11-22)
61 |
62 |
63 | ### Features
64 |
65 | * remove old APIs support ([df1ca39](https://github.com/sinedied/ngx-foldable/commit/df1ca390eae33279ef4ba145da6a5653c0231cfe))
66 | * update CSS for new APIs ([afab1c4](https://github.com/sinedied/ngx-foldable/commit/afab1c486462f9f2b3f36929f50c2c7df56d34c8))
67 | * update media queries to support new css segments API ([fc26aae](https://github.com/sinedied/ngx-foldable/commit/fc26aae9ab035715c8e429cc4aee5b7250f9b19f))
68 | * update screenContext to support new visualViewport API ([a444236](https://github.com/sinedied/ngx-foldable/commit/a444236028c6d301e753ef9fb3f798f1108d20bd))
69 |
70 |
71 | ### BREAKING CHANGES
72 |
73 | * remove old APIs support
74 |
75 | ## [1.1.1](https://github.com/sinedied/ngx-foldable/compare/1.1.0...1.1.1) (2021-03-19)
76 |
77 |
78 | ### Bug Fixes
79 |
80 | * layout issues on real devices ([8446001](https://github.com/sinedied/ngx-foldable/commit/844600128fb3af4760b577ef22299842b6f318d7))
81 |
82 | # [1.1.0](https://github.com/sinedied/ngx-foldable/compare/1.0.5...1.1.0) (2021-03-17)
83 |
84 |
85 | ### Bug Fixes
86 |
87 | * inconsistent layout with rtl and fdIfSpan ([1acd46e](https://github.com/sinedied/ngx-foldable/commit/1acd46e32c237624d2e436c6bc1625cad3035f29))
88 | * only allow valid options for SplitLayoutDirective ([3240712](https://github.com/sinedied/ngx-foldable/commit/3240712f21dbd25347cdf69c8f4b53daee340dd7))
89 |
90 |
91 | ### Features
92 |
93 | * add option to reverse window order ([acb3fff](https://github.com/sinedied/ngx-foldable/commit/acb3fff202be180639e2cffcf3e1483e1547d6c0))
94 |
95 | ## [1.0.5](https://github.com/sinedied/ngx-foldable/compare/1.0.4...1.0.5) (2021-03-16)
96 |
97 |
98 | ### Bug Fixes
99 |
100 | * refresh on orientation changes and extra repaints ([0683aa3](https://github.com/sinedied/ngx-foldable/commit/0683aa348d992aa23ed2778d9b65f4bf5b95a44c))
101 | * screen context initialization ([14da071](https://github.com/sinedied/ngx-foldable/commit/14da07174867fcda1b4c3919907250b9ca89f8ca))
102 |
103 | ## [1.0.4](https://github.com/sinedied/ngx-foldable/compare/1.0.3...1.0.4) (2021-03-16)
104 |
105 |
106 | ### Bug Fixes
107 |
108 | * issues when using typescript strict mode ([b84fc9f](https://github.com/sinedied/ngx-foldable/commit/b84fc9f86a0c02bd71fa072f8be5ca1e63db90fb))
109 |
110 | ## [1.0.3](https://github.com/sinedied/ngx-foldable/compare/1.0.2...1.0.3) (2021-03-12)
111 |
112 |
113 | ### Bug Fixes
114 |
115 | * update min angular version ([d383609](https://github.com/sinedied/ngx-foldable/commit/d3836093a9a5eee19bead640062200bb1994d807))
116 |
117 | ## [1.0.2](https://github.com/sinedied/ngx-foldable/compare/1.0.1...1.0.2) (2021-03-12)
118 |
119 |
120 | ### Bug Fixes
121 |
122 | * angular min version ([4aa85c7](https://github.com/sinedied/ngx-foldable/commit/4aa85c78f57c9f817b0a3efa61372340dca58b99))
123 |
124 | ## [1.0.1](https://github.com/sinedied/ngx-foldable/compare/1.0.0...1.0.1) (2021-03-11)
125 |
126 |
127 | ### Bug Fixes
128 |
129 | * docs deployment ([b1c68ac](https://github.com/sinedied/ngx-foldable/commit/b1c68ac7641f2145addef1480f5e669207a349a5))
130 |
131 | # 1.0.0 (2021-03-11)
132 |
133 |
134 | ### Bug Fixes
135 |
136 | * directives export ([536764f](https://github.com/sinedied/ngx-foldable/commit/536764fd1c959501de1a25469281c9fb2537dfeb))
137 | * fdIfSpan init ([7e66b70](https://github.com/sinedied/ngx-foldable/commit/7e66b70c71a84e784eb60cb56744e9c1b9d9a8c3))
138 | * revert naming changes ([3ee5543](https://github.com/sinedied/ngx-foldable/commit/3ee55430887e815dec7ecfd4b5e587f2cdd1abc4))
139 |
140 |
141 | ### Features
142 |
143 | * add RTL support ([8bdb155](https://github.com/sinedied/ngx-foldable/commit/8bdb1554fe44304bda979dc7c184c91df3ded32e))
144 | * add ScreenContext first value and fix span check ([487885f](https://github.com/sinedied/ngx-foldable/commit/487885f02fe3b68141f68382fd3c2318748211a1))
145 | * add SplitLayout directive ([9fadf70](https://github.com/sinedied/ngx-foldable/commit/9fadf702882a53c57f3d8440074f8f3feca82ffe))
146 | * add support for grid and absolute layouts ([582e83e](https://github.com/sinedied/ngx-foldable/commit/582e83eb4176ccb9f9a602c835522ad8f70095df))
147 | * add Window directive ([b2a3632](https://github.com/sinedied/ngx-foldable/commit/b2a3632fa2f950e9b0b3cd237540857e319d8beb))
148 | * demo test ([90ad184](https://github.com/sinedied/ngx-foldable/commit/90ad1844ea8bcbf50c0e7c892ae61635f7b2b993))
149 | * finish demo project ([db13ef1](https://github.com/sinedied/ngx-foldable/commit/db13ef1f6d798c5716dc61bf6f3d60fdb9901c0d))
150 | * implement fdIfSpan directive ([083b648](https://github.com/sinedied/ngx-foldable/commit/083b64890e57b451c42ab42d930ca30a6d2c22e1))
151 | * implement ScreenContext service ([466a9d7](https://github.com/sinedied/ngx-foldable/commit/466a9d7d6f257eccf6590381fc19815dead14e0f))
152 | * initial commit ([9ff473a](https://github.com/sinedied/ngx-foldable/commit/9ff473a1c34bb6be4b3185bb92fd4e0a0fcee7f7))
153 | * initial work on service API ([962620d](https://github.com/sinedied/ngx-foldable/commit/962620d0bd0a9c731e695d5f540e12e3dc9331b4))
154 | * update to ng 11 ([4708aff](https://github.com/sinedied/ngx-foldable/commit/4708aff57cf290991aa6bf7ef77bc2768614847c))
155 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
4 |
5 | Communication through any of this project's channels (GitHub, Slack, IRC, mailing lists, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other disrectpectful conduct.
6 |
7 | We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to this project to do the same.
8 |
9 | If any member of the community violates this code of conduct, the maintainers of this project will take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.
10 |
11 | If you are subject to or witness unacceptable behavior, or have any other concerns, please contact us using any of this project's channels.
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Yohan Lasorsa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📖 ngx-foldable
2 |
3 | [](https://www.npmjs.com/package/ngx-foldable)
4 | 
5 | 
6 | [](https://github.com/sinedied/ngx-foldable/actions)
7 | [](LICENSE)
8 |
9 | > ngx-foldable is a set of components and services to help you build dual-screen experiences for foldable or dual-screen devices, such as the [Surface Duo](https://docs.microsoft.com/dual-screen/web/?WT.mc_id=javascript-9776-yolasors)
10 |
11 |
12 |
13 |
14 |
15 | See the [live demo](https://sinedied.github.io/ngx-foldable/demo/) or read the [full documentation](https://sinedied.github.io/ngx-foldable/).
16 |
17 | ## How to test on your browser
18 |
19 | The dual-screen emulation feature requires latest Microsoft Edge or Google Chrome versions (>= 97).
20 |
21 | If you have older browser versions, you need to enable experimental flags.
22 | Follow [the instructions here](https://devblogs.microsoft.com/surface-duo/build-and-test-dual-screen-web-apps/?WT.mc_id=javascript-9776-yolasors#build-and-test-on-the-desktop) to setup your browser for dual-screen emulation.
23 |
24 | ## Library usage
25 |
26 | Check out the [demo](./projects/demo/src/app) source code to see an example usage of the library.
27 |
28 | Add the library to your Angular project:
29 |
30 | ```sh
31 | npm install ngx-foldable
32 | ```
33 |
34 | Import the library in your app:
35 |
36 | ```ts
37 | import { FoldableModule } from 'ngx-foldable';
38 | ...
39 |
40 | @NgModule({
41 | ...
42 | imports: [
43 | FoldableModule
44 | ...
45 | ],
46 | ...
47 | })
48 | export class AppModule { }
49 | ```
50 |
51 | Use the provided `fdSplitLayout`, `fdWindow` and `fdIfSpan` directives to build your layout:
52 |
53 | ```html
54 |
58 |
59 |
60 |
61 |
62 | This will be displayed on the first window segment of a multi screen or single screen device.
63 |
64 |
This is only visible on a single screen device.
65 |
This is only visible on a multi screen device.
66 |
67 |
68 |
69 |
70 | This will be displayed on the second window segment of a multi screen device.
71 |
72 |
This is only visible on multi screen device, regardless of the orientation.
73 |
This is only visible on dual vertical viewports.
74 |
This is only visible on dual horizontal viewports.
This little demo showcases how ngx-foldable library can help you build dual-screen experiences for foldable or dual-screen devices.
12 |
13 |
14 |
15 | This app is running on a single screen setup.
16 |
17 |
18 |
19 | This app is running on {{ (screenContext$ | async)?.windowSegments?.length }} screens.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
In multi screen mode you can see your device orientation here.
This text will only be visible on multi screen devices.
43 | * @example
44 | *
This text will only be visible on single screen devices.
45 | * This text will only be visible on multi screen devices.
46 | */
47 | @Directive({
48 | selector: '[fdIfSpan]',
49 | })
50 | export class IfSpanDirective implements OnDestroy {
51 | private screenContextSubscription: Subscription | null = null;
52 | private condition: SpanCondition | null = null;
53 | private thenTemplateRef: TemplateRef | null = null;
54 | private elseTemplateRef: TemplateRef | null = null;
55 | private thenViewRef: EmbeddedViewRef | null = null;
56 | private elseViewRef: EmbeddedViewRef | null = null;
57 |
58 | /**
59 | * The spanning mode condition that defines if the template should be shown.
60 | *
61 | * @param condition The spanning mode condition for showing the template.
62 | */
63 | @Input()
64 | set fdIfSpan(condition: SpanCondition) {
65 | if (condition !== this.condition) {
66 | this.condition = condition;
67 | this.updateView();
68 | }
69 | }
70 |
71 | /** A template to show if the span condition evaluates to true. */
72 | @Input()
73 | set fdIfSpanThen(templateRef: TemplateRef | null) {
74 | this.thenTemplateRef = templateRef;
75 | this.thenViewRef = null;
76 | this.updateView();
77 | }
78 |
79 | /** A template to show if the span condition evaluates to false. */
80 | @Input()
81 | set fdIfSpanElse(templateRef: TemplateRef | null) {
82 | this.elseTemplateRef = templateRef;
83 | this.thenViewRef = null;
84 | this.updateView();
85 | }
86 |
87 | constructor(
88 | private screenContext: ScreenContext,
89 | private viewContainer: ViewContainerRef,
90 | templateRef: TemplateRef
91 | ) {
92 | this.thenTemplateRef = templateRef;
93 | this.screenContextSubscription = this.screenContext
94 | .asObservable()
95 | .subscribe(() => this.updateView());
96 | }
97 |
98 | /** ignore */
99 | ngOnDestroy() {
100 | if (this.screenContextSubscription !== null) {
101 | this.screenContextSubscription.unsubscribe();
102 | }
103 | }
104 |
105 | private matchCondition(): boolean {
106 | switch (this.condition) {
107 | case SpanCondition.Multi:
108 | return this.screenContext.isMultiScreen;
109 | case SpanCondition.Horizontal:
110 | return this.screenContext.screenSpanning === ScreenSpanning.DualVertical;
111 | case SpanCondition.Vertical:
112 | return this.screenContext.screenSpanning === ScreenSpanning.DualHorizontal;
113 | default:
114 | return this.screenContext.screenSpanning === ScreenSpanning.None;
115 | }
116 | }
117 |
118 | private updateView() {
119 | const match = this.matchCondition();
120 |
121 | if (match) {
122 | if (!this.thenViewRef) {
123 | this.viewContainer.clear();
124 | this.elseViewRef = null;
125 | if (this.thenTemplateRef) {
126 | this.thenViewRef = this.viewContainer.createEmbeddedView(
127 | this.thenTemplateRef
128 | );
129 | }
130 | }
131 | } else {
132 | if (!this.elseViewRef) {
133 | this.viewContainer.clear();
134 | this.thenViewRef = null;
135 | if (this.elseTemplateRef) {
136 | this.elseViewRef = this.viewContainer.createEmbeddedView(
137 | this.elseTemplateRef
138 | );
139 | }
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/projects/lib/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from './screen-context';
2 | export * from './screen-spanning';
3 | export * from './media-queries';
4 | export * from './split-layout.directive';
5 | export * from './window.directive';
6 | export * from './if-span.directive';
7 | export * from './foldable.module';
8 |
--------------------------------------------------------------------------------
/projects/lib/src/core/media-queries.ts:
--------------------------------------------------------------------------------
1 | /** Media query used to detect dual vertical viewports screen mode. */
2 | export const dualVerticalViewport = '(vertical-viewport-segments: 2)';
3 | /** Media query used to detect dual horizontal viewports screen mode. */
4 | export const dualHorizontalViewport = '(horizontal-viewport-segments: 2)';
5 |
--------------------------------------------------------------------------------
/projects/lib/src/core/screen-context.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, OnDestroy } from '@angular/core';
2 | import { fromEvent, merge, Observable, ReplaySubject } from 'rxjs';
3 | import { map, filter, shareReplay, startWith, takeUntil } from 'rxjs/operators';
4 | import { dualVerticalViewport, dualHorizontalViewport } from './media-queries';
5 | import { ScreenSpanning } from './screen-spanning';
6 |
7 | declare global {
8 | interface Window {
9 | getWindowSegments: () => DOMRect[];
10 | }
11 | }
12 |
13 | /**
14 | * Holds information about the device screen context.
15 | */
16 | export interface ScreenContextData {
17 | /** The list of available window segments. */
18 | readonly windowSegments: DOMRect[];
19 | /** The current screen spanning mode. */
20 | readonly screenSpanning: ScreenSpanning;
21 | /** True is current device have multiple screens available. */
22 | readonly isMultiScreen: boolean;
23 | }
24 |
25 | /**
26 | * This service allows to query and receive updates about current device's
27 | * screen context.
28 | *
29 | * See {@link ScreenContextData}
30 | */
31 | @Injectable({
32 | providedIn: 'root',
33 | })
34 | export class ScreenContext implements ScreenContextData, OnDestroy {
35 | private currentContext: ScreenContextData;
36 | private screenContext$: Observable;
37 | private destroyed$: ReplaySubject = new ReplaySubject(1);
38 |
39 | constructor() {
40 | this.currentContext = this.getScreenContext();
41 | this.screenContext$ = merge(
42 | fromEvent(matchMedia(dualVerticalViewport), 'change'),
43 | fromEvent(matchMedia(dualHorizontalViewport), 'change')
44 | ).pipe(
45 | filter(
46 | () => this.getScreenSpanning() !== this.currentContext.screenSpanning
47 | ),
48 | startWith(1),
49 | map(() => {
50 | this.currentContext = this.getScreenContext();
51 | return this.currentContext;
52 | }),
53 | shareReplay(1),
54 | takeUntil(this.destroyed$)
55 | );
56 | this.screenContext$.subscribe();
57 | }
58 |
59 | /** @ignored */
60 | ngOnDestroy() {
61 | this.destroyed$.next();
62 | this.destroyed$.complete();
63 | }
64 |
65 | /**
66 | * The list of available window segments.
67 | */
68 | get windowSegments(): DOMRect[] {
69 | return this.currentContext.windowSegments;
70 | }
71 |
72 | /**
73 | * The current screen spanning mode.
74 | */
75 | get screenSpanning(): ScreenSpanning {
76 | return this.currentContext.screenSpanning;
77 | }
78 |
79 | /**
80 | * True is current device have multiple screens available.
81 | */
82 | get isMultiScreen(): boolean {
83 | return this.currentContext.isMultiScreen;
84 | }
85 |
86 | /**
87 | * Gets an observable emitting when the screen context changes.
88 | */
89 | asObservable(): Observable {
90 | return this.screenContext$;
91 | }
92 |
93 | /**
94 | * Gets the current screen context.
95 | */
96 | asObject(): ScreenContextData {
97 | return this.currentContext;
98 | }
99 |
100 | private getScreenContext(): ScreenContextData {
101 | const windowSegments = this.getWindowSegments();
102 | const screenSpanning = this.getScreenSpanning();
103 | return {
104 | windowSegments,
105 | screenSpanning,
106 | isMultiScreen: screenSpanning !== ScreenSpanning.None,
107 | };
108 | }
109 |
110 | private getScreenSpanning(): ScreenSpanning {
111 | if (matchMedia(dualVerticalViewport).matches) {
112 | return ScreenSpanning.DualVertical;
113 | } else if (matchMedia(dualHorizontalViewport).matches) {
114 | return ScreenSpanning.DualHorizontal;
115 | }
116 | return ScreenSpanning.None;
117 | }
118 |
119 | private getWindowSegments(): DOMRect[] {
120 | if ('getWindowSegments' in window) {
121 | console.warn('getWindowSegments() is not supported anymore, please update your browser to use the new visualViewport API');
122 | }
123 | if ('visualViewport' in window) {
124 | return (window.visualViewport as any).segments;
125 | }
126 | return [
127 | new DOMRect(
128 | window.pageXOffset,
129 | window.pageYOffset,
130 | window.innerWidth,
131 | window.innerHeight
132 | ),
133 | ];
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/projects/lib/src/core/screen-spanning.ts:
--------------------------------------------------------------------------------
1 | /** Represents the screen spanning mode. */
2 | export type ScreenSpanning = 'dual-horizontal' | 'dual-vertical' | 'none';
3 | /** Enumeration of screen spanning mode values. */
4 | export const ScreenSpanning = {
5 | /** Screen spanning mode is dual horizontal viewports. */
6 | DualHorizontal: 'dual-horizontal' as ScreenSpanning,
7 | /** Screen spanning mode is dual vertical viewports. */
8 | DualVertical: 'dual-vertical' as ScreenSpanning,
9 | /** No screen spanning (single screen mode). */
10 | None: 'none' as ScreenSpanning,
11 | };
12 |
--------------------------------------------------------------------------------
/projects/lib/src/core/split-layout.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component, DebugElement } from '@angular/core';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 | import { Subject } from 'rxjs';
5 | import { ScreenContext, ScreenContextData } from './screen-context';
6 | import { ScreenSpanning } from './screen-spanning';
7 | import { SplitLayoutDirective } from './split-layout.directive';
8 |
9 | @Component({
10 | template: ``,
11 | })
12 | class TestComponent {}
13 |
14 | const getter = (obj: any, prop: string): jasmine.Spy =>
15 | Object.getOwnPropertyDescriptor(obj, prop)?.get as jasmine.Spy;
16 |
17 | describe('SplitLayoutDirective', () => {
18 | let component: TestComponent;
19 | let fixture: ComponentFixture;
20 | let screenContextSpy: jasmine.SpyObj;
21 | let fakeObservable$: Subject;
22 | let el: DebugElement;
23 |
24 | beforeEach(() => {
25 | fakeObservable$ = new Subject();
26 | screenContextSpy = jasmine.createSpyObj(
27 | 'ScreenContext',
28 | ['asObservable', 'asObject'],
29 | ['isMultiScreen', 'screenSpanning', 'windowSegments']
30 | );
31 | screenContextSpy.asObservable.and.returnValue(fakeObservable$);
32 | getter(screenContextSpy, 'isMultiScreen').and.returnValue(false);
33 | getter(screenContextSpy, 'screenSpanning').and.returnValue(
34 | ScreenSpanning.None
35 | );
36 |
37 | fixture = TestBed.configureTestingModule({
38 | declarations: [SplitLayoutDirective, TestComponent],
39 | providers: [{ provide: ScreenContext, useValue: screenContextSpy }],
40 | }).createComponent(TestComponent);
41 |
42 | fakeObservable$.next({} as ScreenContextData);
43 | component = fixture.componentInstance;
44 | });
45 |
46 | it('should not add any css in single screen mode', () => {
47 | fixture.detectChanges();
48 | el = fixture.debugElement.query(By.css('div'));
49 |
50 | expect(el.nativeElement.getAttribute('style')).toBeNull();
51 | });
52 |
53 | it('should add styling in multi screen mode', () => {
54 | getter(screenContextSpy, 'isMultiScreen').and.returnValue(true);
55 | getter(screenContextSpy, 'screenSpanning').and.returnValue(
56 | ScreenSpanning.DualVertical
57 | );
58 | fakeObservable$.next({} as ScreenContextData);
59 | fixture.detectChanges();
60 | el = fixture.debugElement.query(By.css('[fdSplitLayout]'));
61 |
62 | expect(el.nativeElement.getAttribute('style')).not.toBeNull();
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/projects/lib/src/core/split-layout.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | HostBinding,
5 | Input,
6 | OnDestroy,
7 | } from '@angular/core';
8 | import { SafeStyle } from '@angular/platform-browser';
9 | import { Subscription } from 'rxjs';
10 | import { ScreenContext } from './screen-context';
11 | import { ScreenSpanning } from './screen-spanning';
12 |
13 | /**
14 | * Defines how the split layout container should be rendered when multi screen
15 | * is detected.
16 | * See {@link SplitLayoutDirective}
17 | */
18 | export type SplitLayoutMode = 'flex' | 'grid' | 'absolute';
19 | /**
20 | * Enumeration of split layout modes values for use with
21 | * {@link SplitLayoutDirective}.
22 | */
23 | export const SplitLayoutMode = {
24 | Flex: 'flex' as SplitLayoutMode,
25 | Grid: 'grid' as SplitLayoutMode,
26 | Absolute: 'absolute' as SplitLayoutMode,
27 |
28 | /**
29 | * Checks if the given string value is a valid {@link SplitLayoutMode}.
30 | *
31 | * @param value The value to check.
32 | * @return true if the value is a valid {@link SplitLayoutMode}.
33 | */
34 | isValid: (value: string): boolean => {
35 | switch (value) {
36 | case SplitLayoutMode.Flex:
37 | case SplitLayoutMode.Grid:
38 | case SplitLayoutMode.Absolute:
39 | return true;
40 | }
41 | return false;
42 | },
43 | };
44 |
45 | /**
46 | * Defines how the split layout container should order the window segments
47 | * when in horizontal spanning mode vs vertical spanning mode.
48 | * See {@link SplitLayoutDirective}
49 | */
50 | export type WindowOrder = 'normal' | 'reverse';
51 | /**
52 | * Enumeration of window order values for use with
53 | * {@link SplitLayoutDirective}.
54 | */
55 | export const WindowOrder = {
56 | Normal: 'normal' as WindowOrder,
57 | Reverse: 'reverse' as WindowOrder,
58 |
59 | /**
60 | * Checks if the given string value is a valid {@link WindowOrder}.
61 | *
62 | * @param value The value to check.
63 | * @return true if the value is a valid {@link WindowOrder}.
64 | */
65 | isValid: (value: string): boolean => {
66 | switch (value) {
67 | case WindowOrder.Normal:
68 | case WindowOrder.Reverse:
69 | return true;
70 | }
71 | return false;
72 | },
73 | };
74 |
75 | /**
76 | * Defines the text reading direction for the host element.
77 | */
78 | export type ReadingDirection = 'ltr' | 'rtl';
79 | /**
80 | * Enumeration of the text reading direction values.
81 | */
82 | export const ReadingDirection = {
83 | LeftToRight: 'ltr' as ReadingDirection,
84 | RightToLeft: 'rtl' as ReadingDirection,
85 | };
86 |
87 | /**
88 | * Look 'ma, CSS-in-JS with Angular! ಠ_ಠ
89 | *
90 | * @ignore
91 | */
92 | const layoutStyles = {
93 | [SplitLayoutMode.Flex]: {
94 | common: {
95 | display: 'flex',
96 | justifyContent: 'space-between',
97 | height: 'env(viewport-segment-bottom 0 1)',
98 | },
99 | [ScreenSpanning.DualHorizontal]: {
100 | flexDirection: 'row',
101 | },
102 | [ScreenSpanning.DualVertical]: {
103 | flexDirection: 'column',
104 | },
105 | [WindowOrder.Reverse]: {
106 | flexDirection: 'column-reverse',
107 | },
108 | },
109 | [SplitLayoutMode.Grid]: {
110 | common: {
111 | display: 'grid',
112 | height: 'env(viewport-segment-bottom 0 1)',
113 | },
114 | [ScreenSpanning.DualHorizontal]: {
115 | gridTemplateColumns: '1fr 1fr',
116 | gridTemplateAreas: '"segment0 segment1"',
117 | gridAutoFlow: 'row',
118 | columnGap: 'calc(env(viewport-segment-left 1 0) - env(viewport-segment-right 0 0))',
119 | },
120 | [ScreenSpanning.DualVertical]: {
121 | gridTemplateRows: '1fr 1fr',
122 | gridTemplateAreas: '"segment0" "segment1"',
123 | gridAutoFlow: 'row',
124 | rowGap: 'calc(env(viewport-segment-top 0 1) - env(viewport-segment-bottom 0 0))',
125 | },
126 | [WindowOrder.Reverse]: {
127 | gridTemplateRows: '1fr 1fr',
128 | gridTemplateAreas: '"segment0" "segment1"',
129 | gridAutoFlow: 'row',
130 | rowGap: 'calc(env(viewport-segment-top 0 1) - env(viewport-segment-bottom 0 0))',
131 | },
132 | },
133 | [SplitLayoutMode.Absolute]: {
134 | common: {
135 | position: 'relative',
136 | height: 'env(viewport-segment-bottom 0 1)',
137 | },
138 | [ScreenSpanning.DualHorizontal]: {},
139 | [ScreenSpanning.DualVertical]: {},
140 | [WindowOrder.Reverse]: {},
141 | },
142 | };
143 |
144 | /**
145 | * Defines a parent layout container for creating a split layout on multi
146 | * screen devices.
147 | *
148 | * When used on a single screen device, no layout change (CSS) is added.
149 | * You can choose between different {@link SplitLayoutMode} to suit your
150 | * design.
151 | *
152 | * This directive should be used along with {@link WindowDirective}.
153 | *
154 | * @example
155 | *
156 | * Will be displayed on first screen
157 | * Will be displayed on second screen (if available)
158 | *
159 | *
160 | * In addition, you can also choose keep the same window segments order or
161 | * reverse it when the spanning mode change from vertical to horizontal using
162 | * a second optional parameter on the directive:
163 | *
164 | * @example
165 | *
166 | *
167 | * Will be displayed on first screen in vertical spanning mode
168 | * and on the second screen in horizontal spanning mode.
169 | *
170 | *
171 | * Will be displayed on second screen in vertical spanning mode
172 | * and on the first screen in horizontal spanning mode.
173 | *
174 | *
175 | */
176 | @Directive({
177 | selector: '[fdSplitLayout]',
178 | })
179 | export class SplitLayoutDirective implements OnDestroy {
180 | private mode: SplitLayoutMode = SplitLayoutMode.Flex;
181 | private order: WindowOrder = WindowOrder.Normal;
182 | private layoutStyle: SafeStyle = {};
183 | private screenContextSubscription: Subscription | null = null;
184 | private direction: ReadingDirection = 'ltr';
185 |
186 | /**
187 | * Sets the current split layout options to use when multi screen is
188 | * detected.
189 | *
190 | * @param options The split layout options to use.
191 | * Format: `[mode] [order]`
192 | * - The {@link SplitLayoutMode} to use (default is {@link SplitLayoutMode.Flex}).
193 | * - The {@link WindowOrder} to use (default is {@link WindowOrder.Normal}).
194 | */
195 | @Input()
196 | set fdSplitLayout(options: string | undefined) {
197 | this.parseOptions(options || '');
198 | this.updateStyle();
199 | }
200 |
201 | /** @ignore */
202 | @HostBinding('style')
203 | get style(): SafeStyle {
204 | return this.layoutStyle;
205 | }
206 |
207 | constructor(
208 | private element: ElementRef,
209 | private screenContext: ScreenContext
210 | ) {
211 | this.updateStyle();
212 | this.screenContextSubscription = this.screenContext
213 | .asObservable()
214 | .subscribe(() => this.updateStyle());
215 | }
216 |
217 | /**
218 | * The current split layout mode to use when multi screen is detected.
219 | *
220 | * @return The current split layout mode.
221 | */
222 | get layoutMode(): SplitLayoutMode {
223 | return this.mode;
224 | }
225 |
226 | /**
227 | * The window segments order to use when in horizontal spanning mode.
228 | *
229 | * @return The current window order.
230 | */
231 | get windowOrder(): WindowOrder {
232 | return this.order;
233 | }
234 |
235 | /**
236 | * The text reading direction for the host element.
237 | *
238 | * @return The text reading direction.
239 | */
240 | get readingDirection(): ReadingDirection {
241 | return this.direction;
242 | }
243 |
244 | /** @ignore */
245 | ngOnDestroy() {
246 | if (this.screenContextSubscription !== null) {
247 | this.screenContextSubscription.unsubscribe();
248 | }
249 | }
250 |
251 | private parseOptions(options: string) {
252 | let [mode, order] = options.trim().split(' ');
253 | mode = SplitLayoutMode.isValid(mode) ? mode : SplitLayoutMode.Flex;
254 | order = WindowOrder.isValid(order) ? order : WindowOrder.Normal;
255 | this.mode = mode as SplitLayoutMode;
256 | this.order = order as WindowOrder;
257 | }
258 |
259 | private updateStyle() {
260 | const isMultiScreen = this.screenContext.isMultiScreen;
261 | const spanning = this.screenContext.screenSpanning;
262 | const reverse =
263 | spanning === ScreenSpanning.DualVertical &&
264 | this.order === WindowOrder.Reverse;
265 |
266 | this.direction =
267 | (getComputedStyle(this.element.nativeElement)
268 | ?.direction as ReadingDirection) || ReadingDirection.LeftToRight;
269 |
270 | if (isMultiScreen && spanning !== ScreenSpanning.None) {
271 | this.layoutStyle = {
272 | ...layoutStyles[this.mode].common,
273 | ...layoutStyles[this.mode][reverse ? WindowOrder.Reverse : spanning],
274 | };
275 | } else {
276 | this.layoutStyle = {};
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/projects/lib/src/core/window.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { WindowDirective } from './window.directive';
2 | import { Component, DebugElement } from '@angular/core';
3 | import { ComponentFixture, TestBed } from '@angular/core/testing';
4 | import { By } from '@angular/platform-browser';
5 | import { Subject } from 'rxjs';
6 | import { ScreenContext, ScreenContextData } from './screen-context';
7 | import { ScreenSpanning } from './screen-spanning';
8 | import { SplitLayoutDirective } from './split-layout.directive';
9 |
10 | @Component({
11 | template: `
12 |
13 |
14 |
`,
15 | })
16 | class TestComponent {}
17 |
18 | const getter = (obj: any, prop: string): jasmine.Spy =>
19 | Object.getOwnPropertyDescriptor(obj, prop)?.get as jasmine.Spy;
20 |
21 | describe('WindowDirective', () => {
22 | let component: TestComponent;
23 | let fixture: ComponentFixture;
24 | let screenContextSpy: jasmine.SpyObj;
25 | let fakeObservable$: Subject;
26 | let el: DebugElement;
27 |
28 | beforeEach(() => {
29 | fakeObservable$ = new Subject();
30 | screenContextSpy = jasmine.createSpyObj(
31 | 'ScreenContext',
32 | ['asObservable', 'asObject'],
33 | ['isMultiScreen', 'screenSpanning', 'windowSegments']
34 | );
35 | screenContextSpy.asObservable.and.returnValue(fakeObservable$);
36 | getter(screenContextSpy, 'isMultiScreen').and.returnValue(false);
37 | getter(screenContextSpy, 'screenSpanning').and.returnValue(
38 | ScreenSpanning.None
39 | );
40 |
41 | fixture = TestBed.configureTestingModule({
42 | declarations: [WindowDirective, SplitLayoutDirective, TestComponent],
43 | providers: [{ provide: ScreenContext, useValue: screenContextSpy }],
44 | }).createComponent(TestComponent);
45 |
46 | fakeObservable$.next({} as ScreenContextData);
47 | component = fixture.componentInstance;
48 | });
49 |
50 | it('should not add any css in single screen mode', () => {
51 | fixture.detectChanges();
52 | el = fixture.debugElement.query(By.css('section'));
53 |
54 | expect(el.nativeElement.getAttribute('style')).toBeNull();
55 | });
56 |
57 | it('should add styling in multi screen mode', () => {
58 | getter(screenContextSpy, 'isMultiScreen').and.returnValue(true);
59 | getter(screenContextSpy, 'screenSpanning').and.returnValue(
60 | ScreenSpanning.DualVertical
61 | );
62 | fakeObservable$.next({} as ScreenContextData);
63 | fixture.detectChanges();
64 | el = fixture.debugElement.query(By.css('[fdWindow]'));
65 |
66 | expect(el.nativeElement.getAttribute('style')).not.toBeNull();
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/projects/lib/src/core/window.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, Host, HostBinding, Input, OnDestroy } from '@angular/core';
2 | import { SafeStyle } from '@angular/platform-browser';
3 | import { Subscription } from 'rxjs';
4 | import { ScreenContext } from './screen-context';
5 | import { ScreenSpanning } from './screen-spanning';
6 | import {
7 | ReadingDirection,
8 | SplitLayoutDirective,
9 | SplitLayoutMode,
10 | WindowOrder,
11 | } from './split-layout.directive';
12 |
13 | /**
14 | * Look 'ma, CSS-in-JS with Angular! ಠ_ಠ
15 | *
16 | * @ignore
17 | */
18 | const layoutStyles = {
19 | [SplitLayoutMode.Flex]: {
20 | [ScreenSpanning.DualHorizontal]: [
21 | { flex: '0 0 env(viewport-segment-width 0 0)' },
22 | { flex: '0 0 env(viewport-segment-width 1 0)' },
23 | ],
24 | [ScreenSpanning.DualVertical]: [
25 | { flex: '0 0 env(viewport-segment-height 0 0)' },
26 | { flex: '0 0 env(viewport-segment-height 0 1)' },
27 | ],
28 | },
29 | [SplitLayoutMode.Grid]: {
30 | [ScreenSpanning.DualHorizontal]: [
31 | { gridArea: 'segment0' },
32 | { gridArea: 'segment1' },
33 | ],
34 | [ScreenSpanning.DualVertical]: [
35 | {
36 | gridArea: 'segment0',
37 | height: 'env(viewport-segment-height 0 0)'
38 | },
39 | {
40 | gridArea: 'segment1',
41 | height: 'env(viewport-segment-height 0 1)'
42 | },
43 | ],
44 | },
45 | [SplitLayoutMode.Absolute]: {
46 | [ScreenSpanning.DualHorizontal]: [
47 | {
48 | position: 'absolute',
49 | left: 0,
50 | width: 'env(viewport-segment-right 0 0)',
51 | },
52 | {
53 | position: 'absolute',
54 | left: 'env(viewport-segment-left 1 0)',
55 | right: 0,
56 | },
57 | ],
58 | [ScreenSpanning.DualVertical]: [
59 | {
60 | position: 'absolute',
61 | top: 0,
62 | width: '100%',
63 | maxHeight: 'env(viewport-segment-height 0 0)',
64 | },
65 | {
66 | position: 'absolute',
67 | top: 'env(viewport-segment-top 0 1)',
68 | width: '100%',
69 | maxHeight: 'env(viewport-segment-height 0 1)',
70 | },
71 | ],
72 | },
73 | };
74 |
75 | /**
76 | * This directive is used to set specify on which window segment the container
77 | * should be placed on multi screen devices.
78 | *
79 | * When used on a single screen device, no layout change (CSS) is added.
80 | * Only devices with up to two screen are currently supported, meaning that the
81 | * window segment value must be either 0 or 1.
82 | *
83 | * This directive can only be used within a {@link SplitLayoutDirective}.
84 | * If {@link SplitLayoutMode} is set to `absolute`, you can assign multiple
85 | * container element to the same window segment.
86 | *
87 | * Note that if you have set the read direction to Right-To-Left mode (`rtl`)
88 | * in CSS, the first segment will be the rightmost one.
89 | *
90 | * If the {@link WindowOrder} option is set to {@link WindowOrder.Reverse},
91 | * the window segments order will be reversed in horizontal spanning mode.
92 | *
93 | * @example
94 | *
95 | * Will be displayed on first screen
96 | * Will be displayed on second screen (if available)
97 | *