Repository: MrFrankel/ngx-popper
Branch: master
Commit: d53971a125df
Files: 38
Total size: 80.2 KB
Directory structure:
gitextract_bco3m_wf/
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── _config.yml
├── example/
│ ├── app/
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── index.ts
│ ├── index.html
│ ├── tsconfig.json
│ └── webpack.config.js
├── jest.config.js
├── ng-package.json
├── package.json
├── public_api.ts
├── puppeteer_environment.js
├── setup.js
├── src/
│ ├── index.ts
│ ├── popper-content.css
│ ├── popper-content.ts
│ ├── popper-directive.ts
│ ├── popper-model.ts
│ └── popper.module.ts
├── teardown.js
├── test/
│ ├── __tests__/
│ │ └── basic.test.js
│ ├── app/
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── index.ts
│ ├── index.html
│ ├── tsconfig.json
│ ├── utils.js
│ └── webpack.config.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules/
*.iml
*.xml
.idea
dist
dist-tsc
test_dist
================================================
FILE: .npmignore
================================================
.idea
example
node_modules
/src
/*.*
./dist/dist-tsc
./dist/example
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '10'
dist: trusty
addons:
apt:
packages:
# This is required to run new chrome on old trusty
- libnss3
notifications:
email: false
cache:
directories:
- node_modules
# allow headful tests
before_install:
# Enable user namespace cloning
- "sysctl kernel.unprivileged_userns_clone=1"
# Launch XVFB
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016-2017 Jacob Harasimo.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# ngx-popper
[](https://www.npmjs.com/package/ngx-popper)
[](https://www.npmjs.com/package/ngx-popper)
[](https://github.com/MrFrankel/ngx-popper/blob/master/LICENSE) [](https://greenkeeper.io/)
<img src="http://badge-size.now.sh/https://unpkg.com/ngx-popper@6.0.7/bundles/ngx-popper.umd.js?compression=brotli" alt="Stable Release Size"/>
<img src="http://badge-size.now.sh/https://unpkg.com/ngx-popper@6.0.7/bundles/ngx-popper.umd.js?compression=gzip" alt="Stable Release Size"/>
[](https://travis-ci.org/MrFrankel/ngx-popper)
ngx-popper is an angular wrapper for the [Popper.js](https://popper.js.org/) library.
## Changes
As of version 6.0.0 popper content runs in onPush change detection strategy, therefore forceChangeDetection is no longer necessary and is removed
As of version 4.0.0 ngx-popper now use innerHTML binding for string popper i.e:
```HTML
<div popper="some text"></div>
```
This should make no difference but you should be aware.
As of version 4.0.0 popper.model is now popper-model, due to some angular-cli issues, if you are referencing this please update your references.
### Installation
node and npm are required to run this package.
1. Use npm/yarn to install the package:
```terminal
$ npm install popper.js --save
$ npm install ngx-popper --save
```
Or
```terminal
$ yarn add popper.js --save
$ yarn add ngx-popper --save
```
2. You simply add into your module `NgxPopperModule`:
```typescript
import {NgxPopperModule} from 'ngx-popper';
@NgModule({
// ...
imports: [
// ...
NgxPopperModule
]
})
```
SystemJS
```
System.config({
paths: {
// paths serve as alias
'npm:': 'libs/'
},
// map tells the System loader where to look for things
map: {
... ,
'ngx-popper': 'npm:ngx-popper',
'popper-directive.js': 'npm:ngx-popper',
'popper.module': 'npm:ngx-popper',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
... ,
'ngx-popper': {
main: 'index.js',
defaultExtension: 'js'
},
'popper.js': {
main: 'popper-directive.js',
defaultExtension: 'js'
},
'popper.module': {
main: './popper.module.js',
defaultExtension: 'js'
}
}
});
```
3. Add to view:
```HTML
<div [popper]="popper1Content"
[popperShowOnStart]="true"
[popperTrigger]="'click'"
[popperHideOnClickOutside]="true"
[popperHideOnScroll]="true"
[popperPlacement]="'bottom'">
<p class="bold">Hey!</p>
<p class="thin">Choose where to put your popper!</p>
</div>
<popper-content #popper1Content>
<p class="bold">Popper on bottom</p>
</popper-content>
```
4. As text:
```HTML
<div [popper]="'As text'"
[popperTrigger]="'hover'"
[popperPlacement]="'bottom'"
(popperOnShown)="onShown($event)">
<p class="bold">Pop</p>
<p class="thin">on the bottom</p>
</div>
```
```HTML
<div popper="{{someTextProperty}}"
[popperTrigger]="'hover'"
[popperPlacement]="'bottom'"
[popperStyles]="{'background-color: 'blue''}",
(popperOnShown)="onShown($event)">
<p class="bold">Pop</p>
<p class="thin">on the bottom</p>
</div>
```
5. Position fixed, breaking overflow:
```HTML
<div [popper]="'As text'"
[popperTrigger]="'hover'"
[popperPlacement]="'bottom'"
[popperPositionFixed]="true"
(popperOnShown)="onShown($event)">
</div>
```
6. Specific target:
```HTML
<div class="example">
<div #popperTargetElement></div>
<div [popper]="'As text'"
[popperTrigger]="'hover'"
[popperPlacement]="'bottom'"
[popperTarget]="popperTargetElement.nativeElement"
(popperOnShown)="onShown($event)">
</div>
```
7. hide/show programmatically:
```HTML
<div [popper]="tooltipcontent"
[popperTrigger]="'hover'"
[popperPlacement]="'bottom'"
[popperApplyClass]="'popperSpecialStyle'">
<p class="bold">Pop</p>
<p class="thin">on the bottom</p>
</div>
<popper-content #tooltipcontent>
<div>
<p>This is a tooltip with text</p>
<span (click)="tooltipcontent.hide()">Close</span>
</div>
</popper-content>
```
8. Attributes map:
| Option | Type | Default | Description |
|:----------------------- |:---------------- |:--------- | :------------------------------------------------------------------------------------------------------ |
| popperDisableAnimation | boolean | false | Disable the default animation on show/hide |
| popperDisableStyle | boolean | false | Disable the default styling |
| popperDisabled | boolean | false | Disable the popper, ignore all events |
| popperDelay | number | 0 | Delay time until popper it shown |
| popperTimeout | number | 0 | Set delay before the popper is hidden |
| popperTimeoutAfterShow | number | 0 | Set a time on which the popper will be hidden after it is shown |
| popperPlacement | Placement(string) | auto | The placement to show the popper relative to the reference element |
| popperTarget | HtmlElement | auto | Specify a different reference element other the the one hosting the directive |
| popperBoundaries | string(selector) | undefined | Specify a selector to serve as the boundaries of the element |
| popperShowOnStart | boolean | false | Popper default to show |
| popperTrigger | Trigger(string) | hover | Trigger/Event on which to show/hide the popper |
| popperPositionFixed | boolean | false | Set the popper element to use position: fixed |
| popperAppendTo | string | undefined | append The popper-content element to a given selector, if multiple will apply to first |
| popperPreventOverflow | boolean | undefined | Prevent the popper from being positioned outside the boundary |
| popperHideOnClickOutside | boolean | true | Popper will hide on a click outside |
| popperHideOnScroll | boolean | false | Popper will hide on scroll |
| popperHideOnMouseLeave | boolean | false | Popper will hide on mouse leave |
| popperModifiers | popperModifier | undefined | popper.js custom modifiers hock |
| popperApplyClass | string | undefined | list of comma separated class to apply on ngpx__container |
| popperStyles | Object | undefined | Apply the styles object, aligned with ngStyles |
| popperApplyArrowClass | string | undefined | list of comma separated class to apply on ngpx__arrow |
| popperOnShown | EventEmitter<> | $event | Event handler when popper is shown |
| popperOnHidden | EventEmitter<> | $event | Event handler when popper is hidden |
| popperOnUpdate | EventEmitter<> | $event | Event handler when popper is updated |
| popperAriaDescribeBy | string | undefined | Define value for aria-describeby attribute |
| popperAriaRole | string | popper | Define value for aria-role attribute |
9. Override defaults:
Ngx-popper comes with a few default properties you can override in default to effect all instances
These are overridden by any child attributes.
```JavaScript
NgModule({
imports: [
BrowserModule,
FormsModule,
NgxPopperModule.forRoot({placement: 'bottom'})],
declarations: [AppComponent],
providers: [],
bootstrap: [AppComponent]
})
```
| Options | Type | Default |
|:------------------- |:---------------- |:--------- |
| showDelay | number | 0 |
| disableAnimation | boolean | false |
| disableDefaultStyling | boolean | false |
| placement | Placement(string) | auto |
| boundariesElement | string(selector) | undefined |
| trigger | Trigger(string) | hover |
| popperModifiers | popperModifier | undefined |
| positionFixed | boolean | false |
| hideOnClickOutside | boolean | true |
| hideOnMouseLeave | boolean | false |
| hideOnScroll | boolean | false |
| applyClass | string | undefined |
| styles | Object | undefined |
| applyArrowClass | string | undefined |
| ariaDescribeBy | string | undefined |
| ariaRole | string | undefined |
| appendTo | string | undefined |
| preventOverflow | boolean | undefined |
10. popperPlacement:
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'top-start'
| 'bottom-start'
| 'left-start'
| 'right-start'
| 'top-end'
| 'bottom-end'
| 'left-end'
| 'right-end'
| 'auto'
| 'auto-start'
| 'auto-end'
| Function
11. popperTrigger:
| 'click'
| 'mousedown'
| 'hover'
| 'none'
### Demo
<a href="https://mrfrankel.github.io/ngx-popper/">Demo</a>
### Contribute
Hell ya!!!
```terminal
$ npm install
$ npm run build
$ npm run dev //run example
$ npm run start_test //run tests
```
================================================
FILE: _config.yml
================================================
theme: jekyll-theme-cayman
================================================
FILE: example/app/app.component.css
================================================
p.thin {
font-weight: 100;
margin: 0;
line-height: 1.2em;
}
p.bold {
font-weight: 900;
margin: 0;
margin-top: -5px;
}
.rel {
width: 30%;
margin: 0 auto;
position: relative;
text-align: center;
padding: 20px;
border-style: dotted;
border-color: white;
border-width: medium;
}
.ngxp__container {
width: 150px;
background: #FFC107;
color: black;
padding: 10px;
text-align: center;
}
#example10positionSelector {
margin-top: 1em;
}
.ngxp__container[x-placement^="top"] .ngxp__arrow {
border-color: #FFC107 transparent transparent transparent
}
.ngxp__container[x-placement^="bottom"] .ngxp__arrow {
border-color: transparent transparent #FFC107 transparent
}
.ngxp__container[x-placement^="right"] .ngxp__arrow {
border-color: transparent #FFC107 transparent transparent
}
.ngxp__container[x-placement^="left"] .ngxp__arrow {
border-color: transparent transparent transparent #FFC107
}
.example2__fake-body {
overflow-x: scroll;
height: 450px;
flex: 1;
}
.example2__scroll-box {
height: 200%;
display: flex;
align-content: center;
align-items: center;
}
#example3 .reference1:hover {
background: rgba(255, 255, 255, 0.2);
}
#example4 .ngxp__container1 {
height: 150px;
}
p.thin {
font-weight: 100;
margin: 0;
line-height: 1.2em;
}
p.bold {
font-weight: 900;
margin: 0;
margin-top: -5px;
}
.rel {
width: 30%;
margin: 0 auto;
position: relative;
text-align: center;
padding: 20px;
border-style: dotted;
border-color: white;
border-width: medium;
}
/*
Spectral by HTML5 UP
html5up.net | @n33co
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
/* Reset */
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after, q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
-webkit-text-size-adjust: none;
}
/* Box Model */
*, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/* Basic */
@-ms-viewport {
width: device-width;
}
body {
background: #2e3842;
}
body.is-loading *, body.is-loading *:before, body.is-loading *:after {
-moz-animation: none !important;
-webkit-animation: none !important;
-ms-animation: none !important;
å animation: none !important;
-moz-transition: none !important;
-webkit-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}
body, input, select, textarea {
color: #fff;
font-family: "Open Sans", Helvetica, sans-serif;
font-size: 15pt;
font-weight: 400;
letter-spacing: 0.075em;
line-height: 1.65em;
}
@media screen and (max-width: 1680px) {
body, input, select, textarea {
font-size: 13pt;
}
}
@media screen and (max-width: 1280px) {
body, input, select, textarea {
font-size: 12pt;
}
}
@media screen and (max-width: 736px) {
body, input, select, textarea {
font-size: 11pt;
letter-spacing: 0.0375em;
}
}
a {
-moz-transition: color 0.2s ease, border-bottom-color 0.2s ease;
-webkit-transition: color 0.2s ease, border-bottom-color 0.2s ease;
-ms-transition: color 0.2s ease, border-bottom-color 0.2s ease;
transition: color 0.2s ease, border-bottom-color 0.2s ease;
border-bottom: dotted 1px;
color: inherit;
text-decoration: none;
}
a:hover {
border-bottom-color: transparent;
}
strong, b {
color: #fff;
font-weight: 600;
}
em, i {
font-style: italic;
}
p {
margin: 0 0 2em 0;
}
h1, h2, h3, h4, h5, h6 {
color: #fff;
font-weight: 800;
letter-spacing: 0.225em;
line-height: 1em;
margin: 0 0 1em 0;
text-transform: uppercase;
}
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
color: inherit;
text-decoration: none;
}
h2 {
font-size: 1.35em;
line-height: 1.75em;
}
@media screen and (max-width: 736px) {
h2 {
font-size: 1.1em;
line-height: 1.65em;
}
}
h3 {
font-size: 1.15em;
line-height: 1.75em;
}
@media screen and (max-width: 736px) {
h3 {
font-size: 1em;
line-height: 1.65em;
}
}
h4 {
font-size: 1em;
line-height: 1.5em;
}
h5 {
font-size: 0.8em;
line-height: 1.5em;
}
h6 {
font-size: 0.7em;
line-height: 1.5em;
}
sub {
font-size: 0.8em;
position: relative;
top: 0.5em;
}
sup {
font-size: 0.8em;
position: relative;
top: -0.5em;
}
hr {
border: 0;
border-bottom: solid 2px #fff;
margin: 3em 0;
}
hr.major {
margin: 4.5em 0;
}
blockquote {
border-left: solid 4px #fff;
font-style: italic;
margin: 0 0 2em 0;
padding: 0.5em 0 0.5em 2em;
}
code {
background: rgba(144, 144, 144, 0.25);
border-radius: 3px;
font-family: "Courier New", monospace;
font-size: 0.9em;
letter-spacing: 0;
margin: 0 0.25em;
padding: 0.25em 0.65em;
}
pre {
-webkit-overflow-scrolling: touch;
font-family: "Courier New", monospace;
font-size: 0.8em;
margin: 0 0 2em 0;
text-align: left;
}
pre code {
display: block;
line-height: 1.75em;
padding: 1em 1.5em;
overflow-x: auto;
}
header p {
color: rgba(255, 255, 255, 0.5);
position: relative;
top: -0.25em;
}
header h3 + p {
font-size: 1.1em;
}
header h4 + p, header h5 + p, header h6 + p {
font-size: 0.9em;
}
header.major {
margin: 0 0 3.5em 0;
}
header.major h2, header.major h3, header.major h4, header.major h5, header.major h6 {
border-bottom: solid 2px #fff;
display: inline-block;
padding-bottom: 1em;
position: relative;
}
header.major h2:after, header.major h3:after, header.major h4:after, header.major h5:after, header.major h6:after {
content: '';
display: block;
height: 1px;
}
header.major p {
color: #fff;
top: 0;
}
@media screen and (max-width: 736px) {
header.major {
margin: 0 0 2em 0;
}
}
@media screen and (max-width: 980px) {
header br {
display: none;
}
}
/* Form */
form {
margin: 0 0 2em 0;
}
label {
color: #fff;
display: block;
font-size: 0.9em;
font-weight: 600;
margin: 0 0 1em 0;
}
@media screen and (max-width: 736px) {
.wrapper.style1 .features li {
border-top-color: rgba(0, 0, 0, 0.125);
}
}
.wrapper.style2 {
background-color: #2e3842;
}
select {
-moz-appearance: none;
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
background: rgba(144, 144, 144, 0.25);
border-radius: 3px;
border: none;
color: inherit;
display: block;
outline: 0;
padding: 0 1em;
text-decoration: none;
width: 100%;
}
::-webkit-input-placeholder {
color: rgba(255, 255, 255, 0.5) !important;
opacity: 1.0;
}
:-moz-placeholder {
color: rgba(255, 255, 255, 0.5) !important;
opacity: 1.0;
}
::-moz-placeholder {
color: rgba(255, 255, 255, 0.5) !important;
opacity: 1.0;
}
:-ms-input-placeholder {
color: rgba(255, 255, 255, 0.5) !important;
opacity: 1.0;
}
.formerize-placeholder {
color: rgba(255, 255, 255, 0.5) !important;
opacity: 1.0;
}
/* Spotlight */
.spotlight {
display: -moz-flex;
display: -webkit-flex;
display: -ms-flex;
display: flex;
}
.spotlight:nth-child(2n) {
-moz-flex-direction: row-reverse;
-webkit-flex-direction: row-reverse;
-ms-flex-direction: row-reverse;
flex-direction: row-reverse;
}
.spotlight:nth-child(1) {
background-color: rgba(0, 0, 0, 0.075);
}
.spotlight:nth-child(2) {
background-color: rgba(0, 0, 0, 0.15);
}
.spotlight:nth-child(3) {
background-color: rgba(0, 0, 0, 0.225);
}
.spotlight:nth-child(4) {
background-color: rgba(0, 0, 0, 0.3);
}
.spotlight:nth-child(5) {
background-color: rgba(0, 0, 0, 0.375);
}
.spotlight:nth-child(6) {
background-color: rgba(0, 0, 0, 0.45);
}
.spotlight:nth-child(7) {
background-color: rgba(0, 0, 0, 0.525);
}
.spotlight:nth-child(8) {
background-color: rgba(0, 0, 0, 0.6);
}
.spotlight:nth-child(9) {
background-color: rgba(0, 0, 0, 0.675);
}
.spotlight:nth-child(10) {
background-color: rgba(0, 0, 0, 0.75);
}
@media screen and (max-width: 1280px) {
.spotlight .image {
width: 45%;
}
.spotlight .content {
width: 55%;
}
}
@media screen and (max-width: 980px) {
.spotlight {
display: block;
}
.spotlight br {
display: none;
}
.spotlight .image, .spotlight .example {
width: 100%;
}
.spotlight .content {
padding: 4em 3em 2em 3em;
max-width: none;
text-align: center;
width: 100%;
}
}
@media screen and (max-width: 736px) {
.spotlight .content {
padding: 3em 2em 1em 2em;
}
}
/* Wrapper */
.wrapper {
padding: 6em 0 4em 0;
}
.wrapper > .inner {
width: 60em;
margin: 0 auto;
}
@media screen and (max-width: 1280px) {
.wrapper > .inner {
width: 90%;
}
}
@media screen and (max-width: 980px) {
.wrapper > .inner {
width: 100%;
}
}
.wrapper.alt {
padding: 0;
}
.wrapper.style2 {
background-color: #2e3842;
}
/* Spotlight */
.spotlight {
display: -moz-flex;
display: -webkit-flex;
display: -ms-flex;
display: flex;
}
.spotlight .image {
-moz-order: 1;
-webkit-order: 1;
-ms-order: 1;
order: 1;
border-radius: 0;
width: 40%;
}
.spotlight .image img {
border-radius: 0;
width: 100%;
}
.spotlight .example {
-moz-order: 1;
-webkit-order: 1;
-ms-order: 1;
order: 1;
position: relative;
min-height: 450px;
width: 40%;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-content: center;
align-items: center;
}
.spotlight .content {
padding: 2em 4em 0.1em 4em;
-moz-order: 2;
-webkit-order: 2;
-ms-order: 2;
order: 2;
max-width: 48em;
width: 60%;
}
.spotlight:nth-child(2n) {
-moz-flex-direction: row-reverse;
-webkit-flex-direction: row-reverse;
-ms-flex-direction: row-reverse;
flex-direction: row-reverse;
}
.spotlight:nth-child(1) {
background-color: rgba(0, 0, 0, 0.075);
}
.spotlight:nth-child(2) {
background-color: rgba(0, 0, 0, 0.15);
}
.spotlight:nth-child(3) {
background-color: rgba(0, 0, 0, 0.225);
}
.spotlight:nth-child(4) {
background-color: rgba(0, 0, 0, 0.3);
}
.spotlight:nth-child(5) {
background-color: rgba(0, 0, 0, 0.375);
}
.spotlight:nth-child(6) {
background-color: rgba(0, 0, 0, 0.45);
}
.spotlight:nth-child(7) {
background-color: rgba(0, 0, 0, 0.525);
}
.spotlight:nth-child(8) {
background-color: rgba(0, 0, 0, 0.6);
}
.spotlight:nth-child(9) {
background-color: rgba(0, 0, 0, 0.675);
}
.spotlight:nth-child(10) {
background-color: rgba(0, 0, 0, 0.75);
}
@media screen and (max-width: 1280px) {
.spotlight .image {
width: 45%;
}
.spotlight .content {
width: 55%;
}
}
@media screen and (max-width: 980px) {
.spotlight {
display: block;
}
.spotlight br {
display: none;
}
.spotlight .image, .spotlight .example {
width: 100%;
}
.spotlight .content {
padding: 4em 3em 2em 3em;
max-width: none;
text-align: center;
width: 100%;
}
}
@media screen and (max-width: 736px) {
.spotlight .content {
padding: 3em 2em 1em 2em;
}
}
@media screen and (max-width: 980px) {
.wrapper {
padding: 4em 3em 2em 3em;
}
}
@media screen and (max-width: 736px) {
.wrapper {
padding: 3em 2em 1em 2em;
}
}
================================================
FILE: example/app/app.component.html
================================================
<section id="two" class="wrapper alt style2">
<section class="spotlight">
<div class="example">
<div #popper1
[popper]="popper1Content"
[popperTrigger]="'click'"
[popperShowOnStart]="true"
[popperPlacement]="example1select" class="rel" id="example10reference1">
<p class="bold">Hey!</p>
<p class="thin">Choose where to put your popper!</p>
<select [(ngModel)]="example1select" (change)="changeExample1(popper1Content)"
id="example10positionSelector">
<option value="top">Top</option>
<option value="right">Right</option>
<option value="bottom">Bottom</option>
<option value="left">Left</option>
</select>
</div>
<popper-content #popper1Content>
<p class="bold" >Popper on <b id="example10currentPosition" class="currentPosition">{{example1select}}</b></p>
</popper-content>
</div>
<div class="content">
<a class="anchor-wrapper" href="#example10"><h2 id="" tabindex="0"><a name="example10"></a>Popper on your side!<i
class="fa fa-link link-anchor"></i></h2></a>
<p id="example10Title">
What are you waiting for? Select a popper from that dropdown.<br>
Placing poppers around elements is just that easy!
</p>
<div id="example10code">
</div>
</div>
</section>
<section class="spotlight">
<div class="example">
<div class="example2__fake-body">
<div class="example2__scroll-box">
<div class="rel" id="example2reference1"
#popper2
[popper]="popper2Content"
[popperShowOnStart]="true">
<p class="bold">Scroll me</p>
<p class="thin">up and down</p>
</div>
<popper-content
#popper2Content id="example2popper1">
<p class="bold">I follow it</p>
<p class="thin">staying between boundaries</p>
</popper-content>
</div>
</div>
</div>
<div class="content">
<h2 id="example2" tabindex="0">Popper on scrolling container</h2>
<p>
In this example we have a relative div which contains a div with <code>overflow: scroll</code>.
<br> Inside it, there are our popper and reference elements.
</p>
</div>
</section>
<section class="spotlight">
<div id="example3Container" class="example">
<div
#popper3
[popper]="popper3Content"
[popperShowOnStart]="true"
[popperPlacement]="'right'"
[popperBoundaries]="'#example3Container'"
[popperModifiers]="example3modifiers"
[ng2-draggable]
(mouseover)="popper3Content.update()" class="rel" id="example3reference1">
<p class="bold">Drag me</p>
<p class="thin">on the edges</p>
</div>
</div>
<popper-content #popper3Content id="example3popper1">
<p class="bold">Flipping popper</p>
<p class="thin">which never flips to right</p>
</popper-content>
<div class="content">
<h2 id="example3" tabindex="0">Custom flip behavior</h2>
<p>
Try dragging the reference element on the left side, its popper will move on its bottom edge. Then,
try to move the reference element on the bottom left corner, it will move on its top edge.
</p>
</div>
</section>
<section class="spotlight">
<div class="example">
<div class="rel" id="example4reference1"
#popper4
[popper]="popper4Content"
[popperShowOnStart]="true"
[popperPlacement]="'left-start'">
<p class="bold">Reference</p>
</div>
<popper-content #popper4Content>
<div style="height: 150px" id="example4popper1">
<p class="bold">Shifted popper</p>
<p class="thin">on start</p>
</div>
</popper-content>
</div>
<div class="content">
<h2 id="example5" tabindex="0">Shifted poppers</h2>
<p>
Shift your poppers on start or end of its reference element side.
</p>
</div>
</section>
<section class="spotlight">
<div class="example">
<div class="rel" id="example5reference1"
#popper5
[popper]="popper5Content"
[popperShowOnStart]="true"
[popperPlacement]="'bottom'">
<p class="bold">Pop</p>
<p class="thin">on the bottom</p>
</div>
<popper-content #popper5Content>
<div id="example5popper1">
<p class="bold">Popper on bottom</p>
<p class="thin">Flips when hits viewport</p>
</div>
</popper-content>
</div>
<div class="content">
<h2 id="example4" tabindex="0">Viewport boundaries</h2>
<p>
By default, poppers use as boundaries the page viewport.
<br> Scroll the page to see the popper flip when hits the page viewport margins.
</p>
</div>
</section>
</section>
================================================
FILE: example/app/app.component.ts
================================================
import {Component, ElementRef, ViewEncapsulation, OnInit, ViewChild} from '@angular/core';
import {PopperContent} from '../../dist';
/**
* This class represents the main application component.
*/
@Component({
selector: 'demo-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent implements OnInit {
example3modifiers = {
flip: {
behavior: ['right', 'bottom', 'top']
}
};
example1select: string = 'top';
@ViewChild('popper3Content') popper3Content: any;
constructor(private elem: ElementRef) {
}
ngOnInit() {
setInterval(() => {
this.popper3Content.update();
}, 10);
}
changeExample1(popperRef: PopperContent) {
setTimeout(() => {
this.elem.nativeElement.querySelector('#example10reference1').dispatchEvent(new Event('click'));
}, 100)
}
}
================================================
FILE: example/app/app.module.ts
================================================
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app.component';
import {NgxPopperModule, Triggers} from '../../dist';
import {Draggable} from 'ng2draggable/draggable.directive';
@NgModule({
imports: [
BrowserModule,
FormsModule,
NgxPopperModule.forRoot({
trigger: Triggers.CLICK,
hideOnClickOutside: false
})],
declarations: [
Draggable,
AppComponent],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
================================================
FILE: example/app/index.ts
================================================
import 'zone.js';
import 'reflect-metadata';
import '@angular/common';
import '@angular/compiler';
import '@angular/core';
import '@angular/forms';
import '@angular/platform-browser-dynamic';
import '@angular/platform-browser';
import 'rxjs';
import { enableProdMode } from '@angular/core';
// The browser platform with a compiler
//import {platformBrowser} from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// The app module
import { AppModule } from './app.module';
enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);
================================================
FILE: example/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ngx-popper</title>
</head>
<body>
<demo-app></demo-app>
</body>
</html>
================================================
FILE: example/tsconfig.json
================================================
{
"compilerOptions": {
"outDir": "./dist-tsc",
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"skipLibCheck": true,
"lib": [
"es7",
"dom",
"es2017.object",
"es2015.iterable",
"ScriptHost"
]
},
"typeRoots": [
"../node_modules/@types",
"../node_modules"
],
"types": [
"node"
],
"include": [
"."
],
"exclude": [
"../dist",
"../dist-tsc",
"../test"
],
"angularCompilerOptions": {
"preserveWhiteSpace": false
}
}
================================================
FILE: example/webpack.config.js
================================================
const path = require('path');
const {CheckerPlugin} = require('awesome-typescript-loader');
const webpack = require('webpack');
const chalk = require('chalk');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
module.exports = {
entry: './example/app/index.ts',
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.html', '.css']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.ts?$/,
use: ['awesome-typescript-loader?configFileName="./tsconfig.json"', 'angular2-template-loader'],
// exclude: [/node_modules/, /dist/, /dist-tsc/, /test/, /public_api/]
},
{
test: /\.(html|css)$/,
use: 'raw-loader',
// exclude: [/node_modules/, /dist/, /dist-tsc/, /test/]
}
]
},
plugins: [
new CheckerPlugin(),
new HtmlWebpackPlugin({
inject: true,
template: './example/index.html'
}),
new ProgressBarPlugin({
format: ' build [' + chalk.blue.bold(':bar') + ']' + chalk.green.bold(':percent') + ' (:elapsed seconds) => :msg... ',
clear: false
}),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
// https: true,
hot: true,
stats: 'errors-only',
port: 8888,
inline: true,
historyApiFallback: {
index: './example/'
},
open: false
}
};
================================================
FILE: jest.config.js
================================================
module.exports = {
globalSetup: './setup.js',
globalTeardown: './teardown.js',
testEnvironment: './puppeteer_environment.js',
};
================================================
FILE: ng-package.json
================================================
{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"dest": "dist",
"workingDirectory": ".ng_build",
"lib": {
"entryFile": "public_api.ts"
}
}
================================================
FILE: package.json
================================================
{
"name": "ngx-popper",
"version": "7.0.0",
"description": "ngx-popper is an Angular wrapper for popper.js",
"directories": {
"test": "test"
},
"scripts": {
"test-server": "http-server test_dist -p 8888",
"create_test_server": "rm -rf test_dist && webpack --config ./test/webpack.config.js && npm run test-server",
"test": "npm install && npm run start_test",
"run_jest": "jest",
"start_test": "npm run build && start-server-and-test create_test_server http://localhost:8888 run_jest",
"build": "ng-packagr -p ng-package.json",
"deploy": "npm run build && npm publish --access=public dist",
"dev": "webpack-dev-server --config ./example/webpack.config.js"
},
"repository": {
"type": "git",
"url": "git@github.com:MrFrankel/ngx-popper"
},
"keywords": [
"Angular",
"ngx",
"ngx-popper",
"ngx-tooltip",
"popper",
"popperjs"
],
"engines": {
"node": ">=6.0.0",
"npm": ">=3.4.0"
},
"author": "MrFrankel",
"license": "MIT",
"bugs": {
"url": "/issues"
},
"homepage": "",
"devDependencies": {
"@angular/animations": "^7.2.0",
"@angular/cli": "^7.3.9",
"@angular/common": "^7.2.0",
"@angular/compiler": "^7.2.0",
"@angular/compiler-cli": "^7.2.15",
"@angular/core": "^7.2.0",
"@angular/forms": "^7.2.0",
"@angular/http": "^7.2.0",
"@angular/platform-browser": "^7.2.0",
"@angular/platform-browser-dynamic": "^7.2.0",
"@angular/platform-server": "^7.2.0",
"@ngtools/webpack": "^7.2.1",
"@types/node": "^10.12.2",
"angular2-template-loader": "^0.6.2",
"awesome-typescript-loader": "^5.2.1",
"chalk": "^2.3.0",
"css-loader": "^1.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"http-server": "^0.11.1",
"install": "^0.12.2",
"jest": "^23.0.0",
"jest-puppeteer": "^3.8.0",
"ng-packagr": "^4.5.0",
"ng2draggable": "^1.3.2",
"popper.js": "^1.14.3",
"progress-bar-webpack-plugin": "^1.10.0",
"puppeteer": "^1.15.0",
"raw-loader": "^0.5.1",
"reflect-metadata": "^0.1.12",
"rxjs": "^6.3.3",
"script-loader": "^0.7.2",
"standard-version": "^4.3.0",
"start-server-and-test": "^1.7.11",
"style-loader": "^0.23.1",
"tsickle": "^0.34.0",
"typescript": "^3.1.6",
"webpack": "^4.24.0",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14",
"webpack-shell-plugin": "^0.5.0",
"zone.js": "^0.8.20"
},
"peerDependencies": {
"popper.js": "^1.14.3"
}
}
================================================
FILE: public_api.ts
================================================
export * from "./src/index";
================================================
FILE: puppeteer_environment.js
================================================
const chalk = require('chalk');
const NodeEnvironment = require('jest-environment-node');
const puppeteer = require('puppeteer');
const fs = require('fs');
const os = require('os');
const path = require('path');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
class PuppeteerEnvironment extends NodeEnvironment {
constructor(config) {
super(config)
}
async setup() {
console.log(chalk.yellow('Setup Test Environment.'));
await super.setup();
const wsEndpoint = fs.readFileSync(path.join(DIR, 'wsEndpoint'), 'utf8');
if (!wsEndpoint) {
throw new Error('wsEndpoint not found')
}
this.global.__BROWSER__ = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
})
}
async teardown() {
console.log(chalk.yellow('Teardown Test Environment.'));
await super.teardown()
}
runScript(script) {
return super.runScript(script)
}
}
module.exports = PuppeteerEnvironment;
================================================
FILE: setup.js
================================================
const chalk = require('chalk');
const puppeteer = require('puppeteer');
const fs = require('fs');
const mkdirp = require('mkdirp');
const os = require('os');
const path = require('path');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function() {
console.log(chalk.green('Setup Puppeteer'));
const browser = await puppeteer.launch({devtools: true});
global.__BROWSER__ = browser;
mkdirp.sync(DIR);
fs.writeFileSync(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());
};
================================================
FILE: src/index.ts
================================================
export * from './popper-model';
export * from './popper-directive';
export * from './popper-content';
export * from './popper.module';
================================================
FILE: src/popper-content.css
================================================
.ngxp__container {
display: none;
position: absolute;
border-radius: 3px;
border: 1px solid grey;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
padding: 10px;
}
.ngxp__container.ngxp__animation {
-webkit-animation: ngxp-fadeIn 150ms ease-out;
-moz-animation: ngxp-fadeIn 150ms ease-out;
-o-animation: ngxp-fadeIn 150ms ease-out;
animation: ngxp-fadeIn 150ms ease-out;
}
.ngxp__container > .ngxp__arrow {
border-color: grey;
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
}
.ngxp__container[x-placement^="top"],
.ngxp__container[x-placement^="bottom"],
.ngxp__container[x-placement^="right"],
.ngxp__container[x-placement^="left"] {
display: block;
}
.ngxp__container[x-placement^="top"] {
margin-bottom: 5px;
}
.ngxp__container[x-placement^="top"] > .ngxp__arrow {
border-width: 5px 5px 0 5px;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.ngxp__container[x-placement^="top"] > .ngxp__arrow.__force-arrow {
border-right-color: transparent !important;
border-bottom-color: transparent !important;
border-left-color: transparent !important;
}
.ngxp__container[x-placement^="bottom"] {
margin-top: 5px;
}
.ngxp__container[x-placement^="bottom"] > .ngxp__arrow {
border-width: 0 5px 5px 5px;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.ngxp__container[x-placement^="bottom"] > .ngxp__arrow.__force-arrow {
border-top-color: transparent !important;
border-right-color: transparent !important;
border-left-color: transparent !important;
}
.ngxp__container[x-placement^="right"] {
margin-left: 5px;
}
.ngxp__container[x-placement^="right"] > .ngxp__arrow {
border-width: 5px 5px 5px 0;
border-top-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.ngxp__container[x-placement^="right"] > .ngxp__arrow.__force-arrow {
border-top-color: transparent !important;
border-bottom-color: transparent !important;
border-left-color: transparent !important;
}
.ngxp__container[x-placement^="left"] {
margin-right: 5px;
}
.ngxp__container[x-placement^="left"] > .ngxp__arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent;
border-bottom-color: transparent;
border-right-color: transparent;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.ngxp__container[x-placement^="left"] > .ngxp__arrow.__force-arrow {
border-top-color: transparent !important;
border-bottom-color: transparent !important;
border-right-color: transparent !important;
}
@-webkit-keyframes ngxp-fadeIn {
0% {
display: none;
opacity: 0;
}
1% {
display: block;
opacity: 0;
}
100% {
display: block;
opacity: 1;
}
}
@keyframes ngxp-fadeIn {
0% {
display: none;
opacity: 0;
}
1% {
display: block;
opacity: 0;
}
100% {
display: block;
opacity: 1;
}
}
================================================
FILE: src/popper-content.ts
================================================
import {
ChangeDetectionStrategy, ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
OnDestroy,
Renderer2,
ViewChild,
ViewContainerRef,
ViewEncapsulation,
} from "@angular/core"
import Popper from 'popper.js'
import {Placements, PopperContentOptions, Triggers} from './popper-model'
@Component({
selector: "popper-content",
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div #popperViewRef
[class.ngxp__container]="!popperOptions.disableDefaultStyling"
[class.ngxp__animation]="!popperOptions.disableAnimation"
[style.display]="displayType"
[style.opacity]="opacity"
[ngStyle]="popperOptions.styles"
[ngClass]="extractAppliedClassListExpr(popperOptions.applyClass)"
attr.aria-hidden="{{ariaHidden}}"
[attr.aria-describedby]="popperOptions.ariaDescribe || null"
attr.role="{{popperOptions.ariaRole}}">
<div class="ngxp__inner" *ngIf="text" [innerHTML]="text">
<ng-content></ng-content>
</div>
<div class="ngxp__inner" *ngIf="!text">
<ng-content></ng-content>
</div>
<div class="ngxp__arrow" [style.border-color]="arrowColor" [class.__force-arrow]="arrowColor"
[ngClass]="extractAppliedClassListExpr(popperOptions.applyArrowClass)"></div>
</div>
`,
styleUrls: ['./popper-content.css'],
})
export class PopperContent implements OnDestroy {
popperOptions: PopperContentOptions = <PopperContentOptions>{
disableAnimation: false,
disableDefaultStyling: false,
placement: Placements.Auto,
boundariesElement: '',
trigger: Triggers.HOVER,
positionFixed: false,
appendToBody: false,
popperModifiers: {}
};
referenceObject: HTMLElement;
isMouseOver: boolean = false;
onHidden = new EventEmitter();
text: string;
popperInstance: Popper;
displayType: string = "none";
opacity: number = 0;
ariaHidden: string = 'true';
arrowColor: string | null = null;
onUpdate: Function;
state: boolean = true;
private globalResize: any;
@ViewChild("popperViewRef")
popperViewRef: ElementRef;
@HostListener('mouseover')
onMouseOver() {
this.isMouseOver = true;
}
@HostListener('mouseleave')
showOnLeave() {
this.isMouseOver = false;
if (this.popperOptions.trigger !== Triggers.HOVER && !this.popperOptions.hideOnMouseLeave) {
return;
}
this.hide();
}
onDocumentResize() {
this.update();
}
constructor(
public elemRef: ElementRef,
private renderer: Renderer2,
private viewRef: ViewContainerRef,
private CDR: ChangeDetectorRef) {
}
ngOnDestroy() {
this.clean();
if(this.popperOptions.appendTo && this.elemRef && this.elemRef.nativeElement && this.elemRef.nativeElement.parentNode){
this.viewRef.detach();
this.elemRef.nativeElement.parentNode.removeChild(this.elemRef.nativeElement);
}
}
clean() {
this.toggleVisibility(false);
if (!this.popperInstance) {
return;
}
(this.popperInstance as any).disableEventListeners();
this.popperInstance.destroy();
}
show(): void {
if (!this.referenceObject) {
return;
}
const appendToParent = this.popperOptions.appendTo && document.querySelector(this.popperOptions.appendTo);
if (appendToParent && this.elemRef.nativeElement.parentNode !== appendToParent) {
this.elemRef.nativeElement.parentNode && this.elemRef.nativeElement.parentNode.removeChild(this.elemRef.nativeElement);
appendToParent.appendChild(this.elemRef.nativeElement);
}
let popperOptions: Popper.PopperOptions = <Popper.PopperOptions>{
placement: this.popperOptions.placement,
positionFixed: this.popperOptions.positionFixed,
modifiers: {
arrow: {
element: this.popperViewRef.nativeElement.querySelector('.ngxp__arrow')
}
}
};
if (this.onUpdate) {
popperOptions.onUpdate = this.onUpdate as any;
}
let boundariesElement = this.popperOptions.boundariesElement && document.querySelector(this.popperOptions.boundariesElement);
if (popperOptions.modifiers && boundariesElement) {
popperOptions.modifiers.preventOverflow = {boundariesElement};
}
if (popperOptions.modifiers && this.popperOptions.preventOverflow !== undefined) {
popperOptions.modifiers.preventOverflow = popperOptions.modifiers.preventOverflow || {};
popperOptions.modifiers.preventOverflow.enabled = this.popperOptions.preventOverflow;
if (!popperOptions.modifiers.preventOverflow.enabled) {
popperOptions.modifiers.hide = {enabled: false};
}
}
this.determineArrowColor();
popperOptions.modifiers = Object.assign(popperOptions.modifiers, this.popperOptions.popperModifiers);
this.popperInstance = new Popper(
this.referenceObject,
this.popperViewRef.nativeElement,
popperOptions,
);
(this.popperInstance as any).enableEventListeners();
this.scheduleUpdate();
this.toggleVisibility(true);
this.globalResize = this.renderer.listen('document', 'resize', this.onDocumentResize.bind(this))
}
private determineArrowColor() {
['background-color', 'backgroundColor'].some((clr) => {
if (!this.popperOptions.styles) {
return false;
}
if (this.popperOptions.styles.hasOwnProperty(clr)) {
this.arrowColor = this.popperOptions.styles[clr];
return true;
}
return false;
})
}
update(): void {
this.popperInstance && (this.popperInstance as any).update();
}
scheduleUpdate(): void {
this.popperInstance && (this.popperInstance as any).scheduleUpdate();
}
hide(): void {
if (this.popperInstance) {
this.popperInstance.destroy();
}
this.toggleVisibility(false);
this.onHidden.emit();
}
toggleVisibility(state: boolean) {
if (!state) {
this.opacity = 0;
this.displayType = "none";
this.ariaHidden = 'true';
this.state = false;
}
else {
this.opacity = 1;
this.displayType = "block";
this.ariaHidden = 'false';
this.state = true;
}
if (!this.CDR['destroyed']) {
this.CDR.detectChanges();
}
}
extractAppliedClassListExpr(classList?: string): Object | null {
if (!classList || typeof classList !== 'string') {
return null;
}
try {
return classList
.replace(/ /, '')
.split(',')
.reduce((acc, clss) => {
acc[clss] = true;
return acc;
}, {})
}
catch (e) {
return null;
}
}
private clearGlobalResize() {
this.globalResize && typeof this.globalResize === 'function' && this.globalResize();
}
}
================================================
FILE: src/popper-directive.ts
================================================
import {
Directive,
ComponentRef,
ViewContainerRef,
ComponentFactoryResolver,
Input,
OnChanges,
SimpleChange,
Output,
OnDestroy,
EventEmitter, OnInit, Renderer2, ChangeDetectorRef, Inject, ElementRef
} from '@angular/core';
import {Placement, Placements, PopperContentOptions, Trigger, Triggers} from './popper-model';
import {PopperContent} from './popper-content';
@Directive({
selector: '[popper]',
exportAs: 'popper'
})
export class PopperController implements OnInit, OnDestroy, OnChanges {
private popperContentClass = PopperContent;
private popperContentRef: ComponentRef<PopperContent>;
private shown: boolean = false;
private scheduledShowTimeout: any;
private scheduledHideTimeout: any;
private subscriptions: any[] = [];
private eventListeners: any[] = [];
private globalEventListeners: any[] = [];
private popperContent: PopperContent;
constructor(private viewContainerRef: ViewContainerRef,
private changeDetectorRef: ChangeDetectorRef,
private resolver: ComponentFactoryResolver,
private elementRef: ElementRef,
private renderer: Renderer2,
@Inject('popperDefaults') private popperDefaults: PopperContentOptions = {}) {
PopperController.baseOptions = {...PopperController.baseOptions, ...this.popperDefaults};
}
public static baseOptions: PopperContentOptions = <PopperContentOptions>{
showDelay: 0,
placement: Placements.Auto,
hideOnClickOutside: true,
hideOnMouseLeave: false,
hideOnScroll: false,
showTrigger: Triggers.HOVER,
appendTo: undefined,
ariaRole: 'popper',
ariaDescribe: '',
styles: {}
};
@Input('popper')
content: string | PopperContent;
@Input('popperDisabled')
disabled: boolean;
@Input('popperPlacement')
placement: Placement;
@Input('popperTrigger')
showTrigger: Trigger | undefined;
@Input('popperTarget')
targetElement: HTMLElement;
@Input('popperDelay')
showDelay: number | undefined;
@Input('popperTimeout')
hideTimeout: number = 0;
@Input('popperTimeoutAfterShow')
timeoutAfterShow: number = 0;
@Input('popperBoundaries')
boundariesElement: string;
@Input('popperShowOnStart')
showOnStart: boolean;
@Input('popperCloseOnClickOutside')
closeOnClickOutside: boolean;
@Input('popperHideOnClickOutside')
hideOnClickOutside: boolean | undefined;
@Input('popperHideOnScroll')
hideOnScroll: boolean | undefined;
@Input('popperHideOnMouseLeave')
hideOnMouseLeave: boolean | undefined;
@Input('popperPositionFixed')
positionFixed: boolean;
@Input('popperModifiers')
popperModifiers: {};
@Input('popperDisableStyle')
disableStyle: boolean;
@Input('popperDisableAnimation')
disableAnimation: boolean;
@Input('popperApplyClass')
applyClass: string;
@Input('popperApplyArrowClass')
applyArrowClass: string;
@Input('popperAriaDescribeBy')
ariaDescribe: string | undefined;
@Input('popperAriaRole')
ariaRole: string | undefined;
@Input('popperStyles')
styles: Object | undefined;
@Input('popperAppendTo')
appendTo: string;
@Input('popperPreventOverflow')
preventOverflow: boolean;
@Output()
popperOnShown: EventEmitter<PopperController> = new EventEmitter<PopperController>();
@Output()
popperOnHidden: EventEmitter<PopperController> = new EventEmitter<PopperController>();
@Output()
popperOnUpdate: EventEmitter<any> = new EventEmitter<any>();
hideOnClickOutsideHandler($event: MouseEvent): void {
if (this.disabled || !this.hideOnClickOutside || $event.srcElement &&
$event.srcElement === this.popperContent.elemRef.nativeElement ||
this.popperContent.elemRef.nativeElement.contains($event.srcElement)) {
return;
}
this.scheduledHide($event, this.hideTimeout);
}
hideOnScrollHandler($event: MouseEvent): void {
if (this.disabled || !this.hideOnScroll) {
return;
}
this.scheduledHide($event, this.hideTimeout);
}
applyTriggerListeners() {
switch (this.showTrigger) {
case Triggers.CLICK:
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'click', this.toggle.bind(this)));
break;
case Triggers.MOUSEDOWN:
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'mousedown', this.toggle.bind(this)));
break;
case Triggers.HOVER:
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'mouseenter', this.scheduledShow.bind(this, this.showDelay)));
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'touchend', this.scheduledHide.bind(this, null, this.hideTimeout)));
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'touchcancel', this.scheduledHide.bind(this, null, this.hideTimeout)));
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'mouseleave', this.scheduledHide.bind(this, null, this.hideTimeout)));
break;
}
if (this.showTrigger !== Triggers.HOVER && this.hideOnMouseLeave) {
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'touchend', this.scheduledHide.bind(this, null, this.hideTimeout)));
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'touchcancel', this.scheduledHide.bind(this, null, this.hideTimeout)));
this.eventListeners.push(this.renderer.listen(this.elementRef.nativeElement, 'mouseleave', this.scheduledHide.bind(this, null, this.hideTimeout)));
}
}
static assignDefined(target: any, ...sources: any[]) {
for (const source of sources) {
for (const key of Object.keys(source)) {
const val = source[key];
if (val !== undefined) {
target[key] = val;
}
}
}
return target;
}
ngOnInit() {
//Support legacy prop
this.hideOnClickOutside = typeof this.hideOnClickOutside === 'undefined' ?
this.closeOnClickOutside : this.hideOnClickOutside;
if (typeof this.content === 'string') {
const text = this.content;
this.popperContent = this.constructContent();
this.popperContent.text = text;
}
else {
this.popperContent = this.content;
}
const popperRef = this.popperContent;
popperRef.referenceObject = this.getRefElement();
this.setContentProperties(popperRef);
this.setDefaults();
this.applyTriggerListeners();
if (this.showOnStart) {
this.scheduledShow();
}
}
ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
if (changes['popperDisabled'] && changes['popperDisabled'].currentValue) {
this.hide();
}
if (changes['content']
&& !changes['content'].firstChange
&& typeof changes['content'].currentValue === 'string') {
this.popperContent.text = changes['content'].currentValue;
}
if (changes['applyClass']
&& !changes['applyClass'].firstChange
&& typeof changes['applyClass'].currentValue === 'string') {
this.popperContent.popperOptions.applyClass = changes['applyClass'].currentValue;
}
if (changes['applyArrowClass']
&& !changes['applyArrowClass'].firstChange
&& typeof changes['applyArrowClass'].currentValue === 'string') {
this.popperContent.popperOptions.applyArrowClass = changes['applyArrowClass'].currentValue;
}
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe && sub.unsubscribe());
this.subscriptions.length = 0;
this.clearEventListeners();
this.clearGlobalEventListeners();
clearTimeout(this.scheduledShowTimeout);
clearTimeout(this.scheduledHideTimeout);
this.popperContent && this.popperContent.clean();
}
toggle() {
if (this.disabled) {
return;
}
this.shown ? this.scheduledHide(null, this.hideTimeout) : this.scheduledShow();
}
show() {
if (this.shown) {
this.overrideHideTimeout();
return;
}
this.shown = true;
const popperRef = this.popperContent;
const element = this.getRefElement();
if (popperRef.referenceObject !== element) {
popperRef.referenceObject = element;
}
this.setContentProperties(popperRef);
popperRef.show();
this.popperOnShown.emit(this);
if (this.timeoutAfterShow > 0) {
this.scheduledHide(null, this.timeoutAfterShow);
}
this.globalEventListeners.push(this.renderer.listen('document', 'touchend', this.hideOnClickOutsideHandler.bind(this)));
this.globalEventListeners.push(this.renderer.listen('document', 'click', this.hideOnClickOutsideHandler.bind(this)));
this.globalEventListeners.push(this.renderer.listen(this.getScrollParent(this.getRefElement()), 'scroll', this.hideOnScrollHandler.bind(this)));
}
hide() {
if (this.disabled) {
return;
}
if (!this.shown) {
this.overrideShowTimeout();
return;
}
this.shown = false;
if (this.popperContentRef) {
this.popperContentRef.instance.hide();
}
else {
this.popperContent.hide();
}
this.popperOnHidden.emit(this);
this.clearGlobalEventListeners();
}
scheduledShow(delay: number | undefined = this.showDelay) {
if (this.disabled) {
return;
}
this.overrideHideTimeout();
this.scheduledShowTimeout = setTimeout(() => {
this.show();
this.applyChanges();
}, delay)
}
scheduledHide($event: any = null, delay: number = this.hideTimeout) {
if (this.disabled) {
return;
}
this.overrideShowTimeout();
this.scheduledHideTimeout = setTimeout(() => {
const toElement = $event ? $event.toElement : null;
const popperContentView = this.popperContent.popperViewRef ? this.popperContent.popperViewRef.nativeElement : false;
if (!popperContentView || popperContentView === toElement || popperContentView.contains(toElement) || (this.content as PopperContent).isMouseOver) {
return;
}
this.hide();
this.applyChanges();
}, delay);
}
getRefElement() {
return this.targetElement || this.viewContainerRef.element.nativeElement;
}
private applyChanges() {
this.changeDetectorRef.markForCheck();
this.changeDetectorRef.detectChanges();
}
private setDefaults() {
this.showDelay = typeof this.showDelay === 'undefined' ? PopperController.baseOptions.showDelay : this.showDelay;
this.showTrigger = typeof this.showTrigger === 'undefined' ? PopperController.baseOptions.trigger : this.showTrigger;
this.hideOnClickOutside = typeof this.hideOnClickOutside === 'undefined' ? PopperController.baseOptions.hideOnClickOutside : this.hideOnClickOutside;
this.hideOnScroll = typeof this.hideOnScroll === 'undefined' ? PopperController.baseOptions.hideOnScroll : this.hideOnScroll;
this.hideOnMouseLeave = typeof this.hideOnMouseLeave === 'undefined' ? PopperController.baseOptions.hideOnMouseLeave : this.hideOnMouseLeave;
this.ariaRole = typeof this.ariaRole === 'undefined' ? PopperController.baseOptions.ariaRole : this.ariaRole;
this.ariaDescribe = typeof this.ariaDescribe === 'undefined' ? PopperController.baseOptions.ariaDescribe : this.ariaDescribe;
this.styles = typeof this.styles === 'undefined' ? Object.assign({}, PopperController.baseOptions.styles) : this.styles;
}
private clearEventListeners() {
this.eventListeners.forEach(evt => {
evt && typeof evt === 'function' && evt();
});
this.eventListeners.length = 0;
}
private clearGlobalEventListeners() {
this.globalEventListeners.forEach(evt => {
evt && typeof evt === 'function' && evt();
});
this.globalEventListeners.length = 0;
}
private overrideShowTimeout() {
if (this.scheduledShowTimeout) {
clearTimeout(this.scheduledShowTimeout);
this.scheduledHideTimeout = 0;
}
}
private overrideHideTimeout() {
if (this.scheduledHideTimeout) {
clearTimeout(this.scheduledHideTimeout);
this.scheduledHideTimeout = 0;
}
}
private constructContent(): PopperContent {
const factory = this.resolver.resolveComponentFactory(this.popperContentClass);
this.popperContentRef = this.viewContainerRef.createComponent(factory);
return this.popperContentRef.instance as PopperContent;
}
private setContentProperties(popperRef: PopperContent) {
popperRef.popperOptions = PopperController.assignDefined(popperRef.popperOptions, PopperController.baseOptions, {
showDelay: this.showDelay,
disableAnimation: this.disableAnimation,
disableDefaultStyling: this.disableStyle,
placement: this.placement,
boundariesElement: this.boundariesElement,
trigger: this.showTrigger,
positionFixed: this.positionFixed,
popperModifiers: this.popperModifiers,
ariaDescribe: this.ariaDescribe,
ariaRole: this.ariaRole,
applyClass: this.applyClass,
applyArrowClass: this.applyArrowClass,
hideOnMouseLeave: this.hideOnMouseLeave,
styles: this.styles,
appendTo: this.appendTo,
preventOverflow: this.preventOverflow,
});
popperRef.onUpdate = this.onPopperUpdate.bind(this);
this.subscriptions.push(popperRef.onHidden.subscribe(this.hide.bind(this)));
}
private getScrollParent(node) {
const isElement = node instanceof HTMLElement;
const overflowY = isElement && window.getComputedStyle(node).overflowY;
const isScrollable = overflowY !== 'visible' && overflowY !== 'hidden';
if (!node) {
return null;
} else if (isScrollable && node.scrollHeight >= node.clientHeight) {
return node;
}
return this.getScrollParent(node.parentNode) || document;
}
private onPopperUpdate(event) {
this.popperOnUpdate.emit(event);
}
}
================================================
FILE: src/popper-model.ts
================================================
export type Trigger =
| 'click'
| 'mousedown'
| 'hover'
| 'none' ;
export class Triggers {
static CLICK: Trigger = 'click';
static HOVER: Trigger = 'hover';
static MOUSEDOWN: Trigger = 'mousedown';
static NONE: Trigger = 'none';
}
export type Placement =
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'top-start'
| 'bottom-start'
| 'left-start'
| 'right-start'
| 'top-end'
| 'bottom-end'
| 'left-end'
| 'right-end'
| 'auto'
| 'auto-start'
| 'auto-end'
| Function
export class Placements {
static Top: Placement = 'top';
static Bottom: Placement = 'bottom';
static Left: Placement = 'left';
static Right: Placement = 'right';
static TopStart: Placement = 'top-start';
static BottomStart: Placement = 'bottom-start';
static LeftStart: Placement = 'left-start';
static RightStart: Placement = 'right-start';
static TopEnd: Placement = 'top-end';
static BottomEnd: Placement = 'bottom-end';
static LeftEnd: Placement = 'left-end';
static RightEnd: Placement = 'right-end';
static Auto: Placement = 'auto';
static AutoStart: Placement = 'auto-start';
static AutoEnd: Placement = 'auto-end';
}
export interface PopperContentOptions {
showDelay?: number;
disableAnimation?: boolean;
disableDefaultStyling?: boolean;
placement?: Placement;
boundariesElement?: string;
trigger?: Trigger;
positionFixed?: boolean;
hideOnClickOutside?: boolean;
hideOnMouseLeave?: boolean;
hideOnScroll?: boolean;
popperModifiers?: {};
ariaRole?: string;
ariaDescribe?: string;
applyClass?: string;
applyArrowClass?: string;
styles?: Object;
appendTo?: string;
preventOverflow?: boolean;
}
================================================
FILE: src/popper.module.ts
================================================
import {CommonModule} from "@angular/common";
import {ModuleWithProviders, NgModule} from "@angular/core";
import {PopperContentOptions} from './popper-model';
import {PopperController} from './popper-directive';
import {PopperContent} from './popper-content';
@NgModule({
imports: [
CommonModule
],
declarations: [
PopperController,
PopperContent
],
exports: [
PopperController,
PopperContent
],
entryComponents: [
PopperContent
]
})
export class NgxPopperModule {
ngDoBootstrap() {
}
public static forRoot(popperBaseOptions: PopperContentOptions = {}): ModuleWithProviders {
return {ngModule: NgxPopperModule, providers: [{provide: 'popperDefaults', useValue: popperBaseOptions}]};
}
}
================================================
FILE: teardown.js
================================================
const chalk = require('chalk');
const puppeteer = require('puppeteer');
const rimraf = require('rimraf');
const os = require('os');
const path = require('path');
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
module.exports = async function() {
console.log(chalk.green('Teardown Puppeteer'));
await global.__BROWSER__.close();
rimraf.sync(DIR);
};
================================================
FILE: test/__tests__/basic.test.js
================================================
const utils = require('../utils');
const timeout = 20000;
let testCounter = 0;
jest.setTimeout(timeout);
describe(
'/basic tests',
() => {
let page;
beforeEach(async () => {
testCounter++;
await page.evaluate((count) => {
const elm = document.querySelector('test-app > div');
elm.id = `popper${count}`;
elm.style.display = 'block';
const elm2 = document.querySelector('body > .ngxp__container');
elm2 && elm2.parentNode.removeChild(elm2);
}, testCounter - 1)
});
afterEach(async () => {
await page.evaluate(() => {
const elm = document.querySelector('test-app > div');
elm.parentNode.removeChild(elm);
const elm2 = document.querySelector('body > .ngxp__container');
elm2 && elm2.parentNode.removeChild(elm2);
});
});
beforeAll(async () => {
page = await global.__BROWSER__.newPage();
await page.goto('http://localhost:8888');
await page.waitForSelector('popper-content');
console.log('page ready');
}, timeout);
afterAll(async () => {
await page.close()
});
it('should show popper on start', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
expect(popperText.trim()).toEqual('testing');
// let popperBox = await popper.boundingBox();
// let targetBox = await popperTarget.boundingBox();
// await page.evaluate(() => {debugger});
// expect((targetBox.y + targetBox.height) - popperBox.y).toEqual(18);
});
it('should show popper on click', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.click(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.y - (targetBox.y + targetBox.height)).toEqual(5);
});
it('should on right', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.click(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
});
it('should open to top', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.click(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
//await utils.pause(page);
expect(targetBox.y - (popperContainerBox.y + popperContainerBox.height)).toEqual(5);
});
it('left placement should flip to right', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.click(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
});
it('should show/hover in hover', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.hover(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
await page.hover('p');
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
});
it('should not show popper with trigger none', async () => {
await page.click(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.hover(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
});
it('should show popper-content component on right', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.click(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
});
it('should show popper-content component on right after delay', async () => {
await page.waitForSelector(`.ngxp__container`, {
visible: false
});
await page.click(`.popperTarget`);
await page.waitFor(4500);
//utils.pause(page);
await page.waitForSelector(`.ngxp__container`, {
visible: true,
timeout: 1000
});
const popperText = await utils.getPopperText(page);
const popperContainerBox = await utils.getPopperBoundingBox(page);
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
});
it('should show popper on the right and attached to body', async () => {
// utils.pause(page);
await page.waitForSelector(`popper-content`, {
visible: false
});
await page.hover(`.popperTarget`);
await page.waitForSelector(`.ngxp__container`, {
visible: true
});
const popperText = await utils.getPopperText(page, 'body');
const popperContainerBox = await utils.getPopperBoundingBox(page, 'body');
const targetBox = await utils.getTargetBoundingBox(page);
expect(popperText.trim()).toEqual('testing');
expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
});
// it('should show popper on the right with position fixed and attached to body', async () => {
// await page.waitForSelector(`.ngxp__container`, {
// visible: false
// });
// await page.hover(`.popperTarget`);
// await page.waitForSelector(`.ngxp__container`, {
// visible: true
// });
// const popper = await page.$('.ngxp__container');
// const popperText = await utils.getPopperText(page, 'body');
// const popperContainerBox = await utils.getPopperBoundingBox(page, 'body');
// const targetBox = await utils.getTargetBoundingBox(page);
// expect(popperText.trim()).toEqual('testing');
// expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
// })
//
// it('should show popper on the right with show on start and attached to body', async () => {
//
// await page.waitForSelector(`.ngxp__container`, {
// visible: true
// });
// const popperText = await utils.getPopperText(page, 'body');
// const popperContainerBox = await utils.getPopperBoundingBox(page, 'body');
// const targetBox = await utils.getTargetBoundingBox(page);
//
// expect(popperText.trim()).toEqual('testing');
// expect(popperContainerBox.x - (targetBox.x + targetBox.width)).toEqual(5);
// })
}
);
================================================
FILE: test/app/app.component.css
================================================
test-app > div {
display: none;
}
.popperTarget {
display: inline-block;
height: 50px;
width: 50px;
background: green;
}
popper-content .ngxp__container {
background-color: blue;
color: white;
}
================================================
FILE: test/app/app.component.html
================================================
<p>ngx-popper testing started...</p>
<div>
<div class="popperTarget" popper="testing" popperPlacement="bottom" [popperShowOnStart]="true">popper</div>
</div>
<div>
<div class="popperTarget" popper="testing" popperPlacement="bottom" [popperTrigger]="'click'">click</div>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'click'" [popperPlacement]="'right'">click</div>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'click'" [popperPlacement]="'top'">click</div>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'click'" [popperPlacement]="'left'">click</div>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'hover'" [popperPlacement]="'right'" >click</div>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'none'" [popperPlacement]="'right'">click</div>
</div>
<div>
<div class="popperTarget" [popper]="popper1" [popperTrigger]="'click'" [popperPlacement]="'right'">click</div>
<popper-content #popper1>testing</popper-content>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'click'" [popperDelay]="5000" [popperPlacement]="'right'">click</div>
</div>
<div>
<div class="popperTarget" popper="testing" [popperTrigger]="'hover'" [popperPlacement]="'right'" [popperAppendTo]="'body'">click</div>
</div>
<!--<div>-->
<!--<div class="popperTarget" popper="testing" [popperTrigger]="'hover'" [popperPlacement]="'right'" [popperAppendToBody]="true" [popperPositionFixed]="true">click</div>-->
<!--</div>-->
<!--<div>-->
<!--<div class="popperTarget" popper="testing" [popperShowOnStart]="true" [popperPlacement]="'right'" [popperAppendToBody]="true">click</div>-->
<!--</div>-->
================================================
FILE: test/app/app.component.ts
================================================
import {Component, ViewEncapsulation} from '@angular/core';
/**
* This class represents the main application component.
*/
@Component({
selector: 'test-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
constructor() {
}
}
================================================
FILE: test/app/app.module.ts
================================================
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent} from './app.component';
import {NgxPopperModule, Triggers} from '../../dist';
@NgModule({
imports: [
BrowserModule,
FormsModule,
NgxPopperModule.forRoot({
trigger: Triggers.CLICK,
hideOnClickOutside: false
})],
declarations: [
AppComponent],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
================================================
FILE: test/app/index.ts
================================================
import 'zone.js';
import 'reflect-metadata';
import '@angular/common';
import '@angular/compiler';
import '@angular/core';
import '@angular/forms';
import '@angular/platform-browser-dynamic';
import '@angular/platform-browser';
import 'rxjs';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// The app module
import { AppModule } from './app.module';
enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);
================================================
FILE: test/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ngx-popper</title>
</head>
<body>
<test-app></test-app>
</body>
</html>
================================================
FILE: test/tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"skipLibCheck": true,
"lib": [
"es7",
"dom",
"es2017.object",
"es2015.iterable",
"ScriptHost"
]
},
"typeRoots": [
"../node_modules/@types",
"../node_modules"
],
"types": [
"node"
],
"include": [
"."
],
"exclude": [
"../node_modules",
"../dist",
"../dist-tsc",
"../example",
"../example-cli"
],
"angularCompilerOptions": {
"preserveWhiteSpace": false
}
}
================================================
FILE: test/utils.js
================================================
module.exports = {
curPage: {},
getPopperText: async (page, parent) => {
let popper = await page.$(parent || 'popper-content');
let popperInner = await popper.$('.ngxp__inner');
let popperText = await popperInner.getProperty('innerText');
return await popperText.jsonValue();
},
getPopperBoundingBox: async (page, parent) => {
let popper = await page.$(parent || 'popper-content');
let popperContainer = await popper.$('.ngxp__container');
return await popperContainer.boundingBox();
},
getTargetBoundingBox: async (page) => {
let popperTarget = await page.$('.popperTarget');
return await popperTarget.boundingBox();
},
pause: async (page) => {
jest.setTimeout(500000);
return await page.evaluate(() => {
debugger;
});
}
};
================================================
FILE: test/webpack.config.js
================================================
const path = require('path');
const {CheckerPlugin} = require('awesome-typescript-loader');
//const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const chalk = require('chalk');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
module.exports = {
entry: './test/app/index.ts',
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../test_dist')
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.html', '.css']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.ts?$/,
use: ['awesome-typescript-loader?configFileName="./tsconfig.json"', 'angular2-template-loader'],
exclude: [/__tests__/]
},
{
test: /\.(html|css)$/,
use: 'raw-loader',
exclude: [/__tests__/]
}
]
},
plugins: [
new CheckerPlugin(),
new HtmlWebpackPlugin({
inject: true,
template: './test/index.html'
}),
new ProgressBarPlugin({
format: ' build [' + chalk.blue.bold(':bar') + ']' + chalk.green.bold(':percent') + ' (:elapsed seconds) => :msg... ',
clear: false
}),
// new OpenBrowserPlugin({
// url: `http://localhost:8888`
// })
],
devServer: {
// https: true,
//hot: true,
stats: 'errors-only',
port: 8888,
inline: true,
historyApiFallback: {
index: './test/'
},
open: false
}
};
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"outDir": "./dist-tsc",
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"skipLibCheck": true,
"lib": [
"es7",
"dom",
"es2017.object",
"es2015.iterable",
"ScriptHost"
]
},
"typeRoots": [
"./node_modules/@types",
"./node_modules"
],
"types": [
"node"
],
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"dist-tsc",
"test",
"example",
"example-cli"
],
"angularCompilerOptions": {
"preserveWhiteSpace": false
}
}
gitextract_bco3m_wf/ ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── _config.yml ├── example/ │ ├── app/ │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ └── index.ts │ ├── index.html │ ├── tsconfig.json │ └── webpack.config.js ├── jest.config.js ├── ng-package.json ├── package.json ├── public_api.ts ├── puppeteer_environment.js ├── setup.js ├── src/ │ ├── index.ts │ ├── popper-content.css │ ├── popper-content.ts │ ├── popper-directive.ts │ ├── popper-model.ts │ └── popper.module.ts ├── teardown.js ├── test/ │ ├── __tests__/ │ │ └── basic.test.js │ ├── app/ │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ └── index.ts │ ├── index.html │ ├── tsconfig.json │ ├── utils.js │ └── webpack.config.js └── tsconfig.json
SYMBOL INDEX (64 symbols across 11 files)
FILE: example/app/app.component.ts
class AppComponent (line 12) | class AppComponent implements OnInit {
method constructor (line 21) | constructor(private elem: ElementRef) {
method ngOnInit (line 24) | ngOnInit() {
method changeExample1 (line 30) | changeExample1(popperRef: PopperContent) {
FILE: example/app/app.module.ts
class AppModule (line 23) | class AppModule {
FILE: puppeteer_environment.js
constant DIR (line 8) | const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
class PuppeteerEnvironment (line 10) | class PuppeteerEnvironment extends NodeEnvironment {
method constructor (line 11) | constructor(config) {
method setup (line 15) | async setup() {
method teardown (line 27) | async teardown() {
method runScript (line 32) | runScript(script) {
FILE: setup.js
constant DIR (line 8) | const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
FILE: src/popper-content.ts
class PopperContent (line 44) | class PopperContent implements OnDestroy {
method onMouseOver (line 85) | onMouseOver() {
method showOnLeave (line 90) | showOnLeave() {
method onDocumentResize (line 98) | onDocumentResize() {
method constructor (line 102) | constructor(
method ngOnDestroy (line 109) | ngOnDestroy() {
method clean (line 117) | clean() {
method show (line 127) | show(): void {
method determineArrowColor (line 178) | private determineArrowColor() {
method update (line 191) | update(): void {
method scheduleUpdate (line 195) | scheduleUpdate(): void {
method hide (line 199) | hide(): void {
method toggleVisibility (line 208) | toggleVisibility(state: boolean) {
method extractAppliedClassListExpr (line 226) | extractAppliedClassListExpr(classList?: string): Object | null {
method clearGlobalResize (line 244) | private clearGlobalResize() {
FILE: src/popper-directive.ts
class PopperController (line 20) | class PopperController implements OnInit, OnDestroy, OnChanges {
method constructor (line 31) | constructor(private viewContainerRef: ViewContainerRef,
method hideOnClickOutsideHandler (line 137) | hideOnClickOutsideHandler($event: MouseEvent): void {
method hideOnScrollHandler (line 146) | hideOnScrollHandler($event: MouseEvent): void {
method applyTriggerListeners (line 153) | applyTriggerListeners() {
method assignDefined (line 175) | static assignDefined(target: any, ...sources: any[]) {
method ngOnInit (line 187) | ngOnInit() {
method ngOnChanges (line 210) | ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
method ngOnDestroy (line 233) | ngOnDestroy() {
method toggle (line 243) | toggle() {
method show (line 250) | show() {
method hide (line 273) | hide() {
method scheduledShow (line 293) | scheduledShow(delay: number | undefined = this.showDelay) {
method scheduledHide (line 304) | scheduledHide($event: any = null, delay: number = this.hideTimeout) {
method getRefElement (line 320) | getRefElement() {
method applyChanges (line 324) | private applyChanges() {
method setDefaults (line 329) | private setDefaults() {
method clearEventListeners (line 340) | private clearEventListeners() {
method clearGlobalEventListeners (line 347) | private clearGlobalEventListeners() {
method overrideShowTimeout (line 354) | private overrideShowTimeout() {
method overrideHideTimeout (line 361) | private overrideHideTimeout() {
method constructContent (line 368) | private constructContent(): PopperContent {
method setContentProperties (line 374) | private setContentProperties(popperRef: PopperContent) {
method getScrollParent (line 397) | private getScrollParent(node) {
method onPopperUpdate (line 411) | private onPopperUpdate(event) {
FILE: src/popper-model.ts
type Trigger (line 1) | type Trigger =
class Triggers (line 7) | class Triggers {
type Placement (line 14) | type Placement =
class Placements (line 32) | class Placements {
type PopperContentOptions (line 50) | interface PopperContentOptions {
FILE: src/popper.module.ts
class NgxPopperModule (line 23) | class NgxPopperModule {
method ngDoBootstrap (line 24) | ngDoBootstrap() {
method forRoot (line 27) | public static forRoot(popperBaseOptions: PopperContentOptions = {}): M...
FILE: teardown.js
constant DIR (line 7) | const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
FILE: test/app/app.component.ts
class AppComponent (line 12) | class AppComponent {
method constructor (line 14) | constructor() {
FILE: test/app/app.module.ts
class AppModule (line 21) | class AppModule {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (88K chars).
[
{
"path": ".gitignore",
"chars": 55,
"preview": "node_modules/\n*.iml\n*.xml\n.idea\ndist\ndist-tsc\ntest_dist"
},
{
"path": ".npmignore",
"chars": 67,
"preview": ".idea\nexample\nnode_modules\n/src\n/*.*\n./dist/dist-tsc\n./dist/example"
},
{
"path": ".travis.yml",
"chars": 403,
"preview": "language: node_js\nnode_js:\n - '10'\ndist: trusty\naddons:\n apt:\n packages:\n # This is required to run new chrome o"
},
{
"path": "LICENSE",
"chars": 1088,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2017 Jacob Harasimo.\n\nPermission is hereby granted, free of charge, to any per"
},
{
"path": "README.md",
"chars": 12313,
"preview": "# ngx-popper \n\n[](https://www.npmjs.com/package/ng"
},
{
"path": "_config.yml",
"chars": 26,
"preview": "theme: jekyll-theme-cayman"
},
{
"path": "example/app/app.component.css",
"chars": 11846,
"preview": "p.thin {\n font-weight: 100;\n margin: 0;\n line-height: 1.2em;\n}\n\np.bold {\n font-weight: 900;\n margin: 0;\n margin-to"
},
{
"path": "example/app/app.component.html",
"chars": 5000,
"preview": "<section id=\"two\" class=\"wrapper alt style2\">\n <section class=\"spotlight\">\n <div class=\"example\">\n <div #popper"
},
{
"path": "example/app/app.component.ts",
"chars": 907,
"preview": "import {Component, ElementRef, ViewEncapsulation, OnInit, ViewChild} from '@angular/core';\nimport {PopperContent} from '"
},
{
"path": "example/app/app.module.ts",
"chars": 592,
"preview": "import {NgModule} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {BrowserModule} from '@angula"
},
{
"path": "example/app/index.ts",
"chars": 603,
"preview": "import 'zone.js';\nimport 'reflect-metadata';\nimport '@angular/common';\nimport '@angular/compiler';\nimport '@angular/core"
},
{
"path": "example/index.html",
"chars": 147,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>ngx-popper</title>\n</head>\n\n<body>\n <demo-app>"
},
{
"path": "example/tsconfig.json",
"chars": 691,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"./dist-tsc\",\n \"module\": \"commonjs\",\n \"target\": \"es5\",\n \"sourceMap\": tru"
},
{
"path": "example/webpack.config.js",
"chars": 1486,
"preview": "const path = require('path');\nconst {CheckerPlugin} = require('awesome-typescript-loader');\nconst webpack = require('web"
},
{
"path": "jest.config.js",
"chars": 134,
"preview": "module.exports = {\n globalSetup: './setup.js',\n globalTeardown: './teardown.js',\n testEnvironment: './puppeteer_envir"
},
{
"path": "ng-package.json",
"chars": 169,
"preview": "{\n \"$schema\": \"./node_modules/ng-packagr/ng-package.schema.json\",\n \"dest\": \"dist\",\n \"workingDirectory\": \".ng_build\",\n"
},
{
"path": "package.json",
"chars": 2536,
"preview": "{\n \"name\": \"ngx-popper\",\n \"version\": \"7.0.0\",\n \"description\": \"ngx-popper is an Angular wrapper for popper.js\",\n \"di"
},
{
"path": "public_api.ts",
"chars": 28,
"preview": "export * from \"./src/index\";"
},
{
"path": "puppeteer_environment.js",
"chars": 956,
"preview": "const chalk = require('chalk');\nconst NodeEnvironment = require('jest-environment-node');\nconst puppeteer = require('pup"
},
{
"path": "setup.js",
"chars": 526,
"preview": "const chalk = require('chalk');\nconst puppeteer = require('puppeteer');\nconst fs = require('fs');\nconst mkdirp = require"
},
{
"path": "src/index.ts",
"chars": 134,
"preview": "export * from './popper-model';\nexport * from './popper-directive';\nexport * from './popper-content';\nexport * from './p"
},
{
"path": "src/popper-content.css",
"chars": 3229,
"preview": ".ngxp__container {\n display: none;\n position: absolute;\n border-radius: 3px;\n border: 1px solid grey;\n box-shadow: "
},
{
"path": "src/popper-content.ts",
"chars": 6799,
"preview": "import {\n ChangeDetectionStrategy, ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n OnDe"
},
{
"path": "src/popper-directive.ts",
"chars": 13806,
"preview": "import {\n Directive,\n ComponentRef,\n ViewContainerRef,\n ComponentFactoryResolver,\n Input,\n OnChanges,\n SimpleChan"
},
{
"path": "src/popper-model.ts",
"chars": 1673,
"preview": "export type Trigger =\n | 'click'\n | 'mousedown'\n | 'hover'\n | 'none' ;\n\nexport class Triggers {\n static CLICK: Trig"
},
{
"path": "src/popper.module.ts",
"chars": 741,
"preview": "import {CommonModule} from \"@angular/common\";\nimport {ModuleWithProviders, NgModule} from \"@angular/core\";\nimport {Poppe"
},
{
"path": "teardown.js",
"chars": 375,
"preview": "const chalk = require('chalk');\nconst puppeteer = require('puppeteer');\nconst rimraf = require('rimraf');\nconst os = req"
},
{
"path": "test/__tests__/basic.test.js",
"chars": 8596,
"preview": "const utils = require('../utils');\nconst timeout = 20000;\nlet testCounter = 0;\n\njest.setTimeout(timeout);\ndescribe(\n '/"
},
{
"path": "test/app/app.component.css",
"chars": 210,
"preview": "test-app > div {\n display: none;\n}\n\n.popperTarget {\n display: inline-block;\n height: 50px;\n width: 50px;\n backgroun"
},
{
"path": "test/app/app.component.html",
"chars": 1747,
"preview": "<p>ngx-popper testing started...</p>\n<div>\n <div class=\"popperTarget\" popper=\"testing\" popperPlacement=\"bottom\" [popper"
},
{
"path": "test/app/app.component.ts",
"chars": 337,
"preview": "import {Component, ViewEncapsulation} from '@angular/core';\n\n/**\n * This class represents the main application component"
},
{
"path": "test/app/app.module.ts",
"chars": 517,
"preview": "import {NgModule} from '@angular/core';\nimport {FormsModule} from '@angular/forms';\nimport {BrowserModule} from '@angula"
},
{
"path": "test/app/index.ts",
"chars": 502,
"preview": "import 'zone.js';\nimport 'reflect-metadata';\nimport '@angular/common';\nimport '@angular/compiler';\nimport '@angular/core"
},
{
"path": "test/index.html",
"chars": 147,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>ngx-popper</title>\n</head>\n\n<body>\n <test-app>"
},
{
"path": "test/tsconfig.json",
"chars": 711,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"target\": \"es5\",\n \"sourceMap\": true,\n \"declaration\": true,\n"
},
{
"path": "test/utils.js",
"chars": 797,
"preview": "module.exports = {\n curPage: {},\n getPopperText: async (page, parent) => {\n let popper = await page.$(parent || 'po"
},
{
"path": "test/webpack.config.js",
"chars": 1476,
"preview": "const path = require('path');\nconst {CheckerPlugin} = require('awesome-typescript-loader');\n//const OpenBrowserPlugin = "
},
{
"path": "tsconfig.json",
"chars": 736,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"./dist-tsc\",\n \"module\": \"commonjs\",\n \"target\": \"es5\",\n \"sourceMap\": tru"
}
]
About this extraction
This page contains the full source code of the MrFrankel/ngx-popper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (80.2 KB), approximately 22.5k tokens, and a symbol index with 64 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.