├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── browserslist ├── demo.gif ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── projects └── ngx-overflow-shadow │ ├── README.md │ ├── demo.gif │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── ngx-overflow-shadow.directive.ts │ │ └── ngx-overflow-shadow.module.ts │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ └── tslint.json ├── src ├── app │ ├── app.component.html │ ├── app.component.less │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app.module.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.less └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.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 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.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 | /.angular/cache 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NgxOverflowShadow 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/ngx-overflow-shadow.svg)](https://www.npmjs.com/package/ngx-notification-msg) 4 | [![NPM Downloads](https://img.shields.io/npm/dt/ngx-overflow-shadow.svg)](https://www.npmjs.com/package/ngx-notification-msg) 5 | 6 | - A simple Angular directive to be used on any scrollable container for adding a shadow on the top/bottom side of the container. 7 | 8 | ## Demo 9 | 10 | - A simple usage example can be found under `src/app` directory of this repository. 11 | 12 | - You may also visit the online usage example on https://maormoshe.github.io/OverflowShadowLibrary/ 13 | 14 | ![](demo.gif) 15 | 16 | ## Installation 17 | 18 | 1. Download from npm: 19 | `npm install ngx-overflow-shadow --save` 20 | 21 | 2. Import the `NgxOverflowShadowModule` module: 22 | `import {NgxOverflowShadowModule} from 'ngx-overflow-shadow'` 23 | 24 | 3. Add `NgxOverflowShadowModule` to your module imports: 25 | ```ts 26 | @NgModule({ ... imports: [ ... NgxOverflowShadowModule ] }) 27 | ``` 28 | 29 | ## API 30 | 31 | Put the `ngxOverflowShadow` directive selector on any scrollable container. 32 | 33 | ### Input() 34 | 35 | | Name | Type | Default | Description | 36 | |----------------|:----------:|:--------------------------------:|:-------------------------------------------------------------------------------| 37 | | top | boolean | false | Indicator for top shadow display. | 38 | | bottom | boolean | true | Indicator for bottom shadow display. | 39 | | shadowStyle | string | 0 0 8px 1px rgba(0, 0, 0, 0.5) | The box-shadow style you want to apply on the top/bottom side of the container.| 40 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "OverflowShadowLibrary": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "less" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "os", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/OverflowShadowLibrary", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": true, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "src/styles.less" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "fileReplacements": [ 38 | { 39 | "replace": "src/environments/environment.ts", 40 | "with": "src/environments/environment.prod.ts" 41 | } 42 | ], 43 | "optimization": true, 44 | "outputHashing": "all", 45 | "sourceMap": false, 46 | "namedChunks": false, 47 | "extractLicenses": true, 48 | "vendorChunk": false, 49 | "buildOptimizer": true, 50 | "budgets": [ 51 | { 52 | "type": "initial", 53 | "maximumWarning": "2mb", 54 | "maximumError": "5mb" 55 | }, 56 | { 57 | "type": "anyComponentStyle", 58 | "maximumWarning": "6kb" 59 | } 60 | ] 61 | } 62 | } 63 | }, 64 | "serve": { 65 | "builder": "@angular-devkit/build-angular:dev-server", 66 | "options": { 67 | "browserTarget": "OverflowShadowLibrary:build" 68 | }, 69 | "configurations": { 70 | "production": { 71 | "browserTarget": "OverflowShadowLibrary:build:production" 72 | } 73 | } 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "OverflowShadowLibrary:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "main": "src/test.ts", 85 | "polyfills": "src/polyfills.ts", 86 | "tsConfig": "tsconfig.spec.json", 87 | "karmaConfig": "karma.conf.js", 88 | "assets": [ 89 | "src/favicon.ico", 90 | "src/assets" 91 | ], 92 | "styles": [ 93 | "src/styles.less" 94 | ], 95 | "scripts": [] 96 | } 97 | }, 98 | "e2e": { 99 | "builder": "@angular-devkit/build-angular:protractor", 100 | "options": { 101 | "protractorConfig": "e2e/protractor.conf.js", 102 | "devServerTarget": "OverflowShadowLibrary:serve" 103 | }, 104 | "configurations": { 105 | "production": { 106 | "devServerTarget": "OverflowShadowLibrary:serve:production" 107 | } 108 | } 109 | } 110 | } 111 | }, 112 | "NgxOverflowShadow": { 113 | "projectType": "library", 114 | "root": "projects/ngx-overflow-shadow", 115 | "sourceRoot": "projects/ngx-overflow-shadow/src", 116 | "prefix": "Ngx", 117 | "architect": { 118 | "build": { 119 | "builder": "@angular-devkit/build-angular:ng-packagr", 120 | "options": { 121 | "tsConfig": "projects/ngx-overflow-shadow/tsconfig.lib.json", 122 | "project": "projects/ngx-overflow-shadow/ng-package.json" 123 | } 124 | , "configurations": { 125 | "production": { 126 | "tsConfig": "projects/ngx-overflow-shadow/tsconfig.lib.prod.json" 127 | } 128 | } 129 | }, 130 | "test": { 131 | "builder": "@angular-devkit/build-angular:karma", 132 | "options": { 133 | "main": "projects/ngx-overflow-shadow/src/test.ts", 134 | "tsConfig": "projects/ngx-overflow-shadow/tsconfig.spec.json", 135 | "karmaConfig": "projects/ngx-overflow-shadow/karma.conf.js" 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maormoshe/OverflowShadowLibrary/81ce0f3c8901393acd37a2a3a5f0a605f8ce6a8c/demo.gif -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to OverflowShadowLibrary!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('os-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/OverflowShadowLibrary'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overflow-shadow-library", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --port 4201 --open", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "build:directive": "ng build NgxOverflowShadow --configuration=\"production\"", 12 | "publish:directive": "cd dist/ngx-overflow-shadow && npm publish", 13 | "build:n:publish:demo": "ng build --configuration=\"production\" --base-href /OverflowShadowLibrary/ && git add -A && git commit -am \"new demo\" && npm run publish:demo && git checkout master", 14 | "publish:demo": "git subtree push --prefix dist/OverflowShadowLibrary origin gh-pages" 15 | }, 16 | "private": true, 17 | "dependencies": { 18 | "@angular/animations": "^16.2.2", 19 | "@angular/common": "^16.2.2", 20 | "@angular/compiler": "^16.2.2", 21 | "@angular/core": "^16.2.2", 22 | "@angular/forms": "^16.2.2", 23 | "@angular/platform-browser": "^16.2.2", 24 | "@angular/platform-browser-dynamic": "^16.2.2", 25 | "@angular/router": "^16.2.2", 26 | "rxjs": "~6.6.7", 27 | "tslib": "^2.0.0", 28 | "zone.js": "~0.13.1" 29 | }, 30 | "devDependencies": { 31 | "@angular-devkit/build-angular": "^16.2.0", 32 | "@angular/cli": "^16.2.0", 33 | "@angular/compiler-cli": "^16.2.2", 34 | "@angular/language-service": "^16.2.2", 35 | "@types/jasmine": "~3.6.0", 36 | "@types/jasminewd2": "~2.0.3", 37 | "@types/node": "^12.11.1", 38 | "codelyzer": "^6.0.0", 39 | "jasmine-core": "~4.0.0", 40 | "jasmine-spec-reporter": "~5.0.0", 41 | "karma": "~6.3.3", 42 | "karma-chrome-launcher": "~3.1.0", 43 | "karma-coverage-istanbul-reporter": "~2.0.1", 44 | "karma-jasmine": "~5.0.0", 45 | "karma-jasmine-html-reporter": "^2.0.0", 46 | "ng-packagr": "^16.2.2", 47 | "protractor": "~7.0.0", 48 | "ts-node": "~7.0.0", 49 | "tslint": "~6.1.3", 50 | "typescript": "^4.9.5" 51 | } 52 | } -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/README.md: -------------------------------------------------------------------------------- 1 | # NgxOverflowShadow 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/ngx-overflow-shadow.svg)](https://www.npmjs.com/package/ngx-notification-msg) 4 | [![NPM Downloads](https://img.shields.io/npm/dt/ngx-overflow-shadow.svg)](https://www.npmjs.com/package/ngx-notification-msg) 5 | 6 | - A simple Angular directive to be used on any scrollable container for adding a shadow on the top/bottom side of the container. 7 | 8 | ## Demo 9 | 10 | - A simple usage example can be found under `src/app` directory of this repository. 11 | 12 | - You may also visit the online usage example on https://maormoshe.github.io/OverflowShadowLibrary/ 13 | 14 | ![](demo.gif) 15 | 16 | ## Installation 17 | 18 | 1. Download from npm: 19 | `npm install ngx-overflow-shadow --save` 20 | 21 | 2. Import the `NgxOverflowShadowModule` module: 22 | `import {NgxOverflowShadowModule} from 'ngx-overflow-shadow'` 23 | 24 | 3. Add `NgxOverflowShadowModule` to your module imports: 25 | ```ts 26 | @NgModule({ ... imports: [ ... NgxOverflowShadowModule ] }) 27 | ``` 28 | 29 | ## API 30 | 31 | Put the `ngxOverflowShadow` directive selector on any scrollable container. 32 | 33 | ### Input() 34 | 35 | | Name | Type | Default | Description | 36 | |----------------|:----------:|:--------------------------------:|:-------------------------------------------------------------------------------| 37 | | top | boolean | false | Indicator for top shadow display. | 38 | | bottom | boolean | true | Indicator for bottom shadow display. | 39 | | shadowStyle | string | 0 0 8px 1px rgba(0, 0, 0, 0.5) | The box-shadow style you want to apply on the top/bottom side of the container.| 40 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maormoshe/OverflowShadowLibrary/81ce0f3c8901393acd37a2a3a5f0a605f8ce6a8c/projects/ngx-overflow-shadow/demo.gif -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/ngx-overflow-shadow'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-overflow-shadow", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-overflow-shadow", 3 | "version": "0.2.3", 4 | "description": "A simple Angular directive to be used on any scrollable container for adding a shadow on the top/bottom side of the container.", 5 | "author": "Maor Moshe", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/maormoshe/OverflowShadowLibrary.git" 9 | }, 10 | "homepage": "https://github.com/maormoshe/OverflowShadowLibrary", 11 | "keywords": [ 12 | "angular", 13 | "javascript", 14 | "typescript", 15 | "directive", 16 | "overflow", 17 | "shadow", 18 | "scroll" 19 | ], 20 | "peerDependencies": { 21 | "@angular/common": "15 - 16", 22 | "@angular/core": "15 - 16" 23 | }, 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/src/lib/ngx-overflow-shadow.directive.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Directive, ElementRef, Input, OnDestroy, Renderer2} from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[ngxOverflowShadow]' 5 | }) 6 | export class NgxOverflowShadowDirective implements AfterViewInit, OnDestroy { 7 | @Input() top = false; 8 | @Input() bottom = true; 9 | @Input() shadowStyle = '0 0 8px 1px rgba(0, 0, 0, 0.5)'; 10 | 11 | topShadowDiv: HTMLElement; 12 | bottomShadowDiv: HTMLElement; 13 | 14 | private topIntersectionObserver: IntersectionObserver; 15 | private bottomIntersectionObserver: IntersectionObserver; 16 | 17 | constructor(private readonly renderer: Renderer2, 18 | private readonly elementRef: ElementRef) { 19 | } 20 | 21 | ngAfterViewInit(): void { 22 | this.init(); 23 | } 24 | 25 | ngOnDestroy(): void { 26 | if (this.top) { 27 | this.topIntersectionObserver.disconnect(); 28 | } 29 | if (this.bottom) { 30 | this.bottomIntersectionObserver.disconnect(); 31 | } 32 | } 33 | 34 | private init(): void { 35 | if (this.top) { 36 | this.initTopShadow(); 37 | } 38 | if (this.bottom) { 39 | this.initBottomShadow(); 40 | } 41 | } 42 | 43 | private initTopShadow(): void { 44 | const {nativeElement} = this.elementRef; 45 | 46 | this.createTopShadowDiv(nativeElement); 47 | this.createTopIntersectionObserver(nativeElement); 48 | this.createTopPlaceholderDiv(nativeElement); 49 | } 50 | 51 | private initBottomShadow(): void { 52 | const {nativeElement} = this.elementRef; 53 | 54 | this.createBottomShadowDiv(nativeElement); 55 | this.createBottomIntersectionObserver(nativeElement); 56 | this.createBottomPlaceholderDiv(nativeElement); 57 | } 58 | 59 | private createTopShadowDiv(nativeElement: HTMLElement): void { 60 | this.topShadowDiv = this.renderer.createElement('div'); 61 | this.renderer.setStyle(this.topShadowDiv, 'position', 'sticky'); 62 | this.renderer.setStyle(this.topShadowDiv, 'top', 0); 63 | this.renderer.insertBefore(nativeElement, this.topShadowDiv, nativeElement.firstChild); 64 | } 65 | 66 | private createBottomShadowDiv(nativeElement: HTMLElement): void { 67 | this.bottomShadowDiv = this.renderer.createElement('div'); 68 | this.renderer.setStyle(this.bottomShadowDiv, 'position', 'sticky'); 69 | this.renderer.setStyle(this.bottomShadowDiv, 'bottom', 0); 70 | this.renderer.appendChild(nativeElement, this.bottomShadowDiv); 71 | } 72 | 73 | private createTopIntersectionObserver(nativeElement: HTMLElement): void { 74 | this.topIntersectionObserver = new IntersectionObserver(this.callback.bind(this, this.topShadowDiv), {root: nativeElement}); 75 | } 76 | 77 | private createBottomIntersectionObserver(nativeElement: HTMLElement): void { 78 | this.bottomIntersectionObserver = new IntersectionObserver(this.callback.bind(this, this.bottomShadowDiv), {root: nativeElement}); 79 | } 80 | 81 | private createTopPlaceholderDiv(nativeElement: HTMLElement): void { 82 | const topPlaceholderDiv = this.renderer.createElement('div'); 83 | 84 | this.renderer.insertBefore(nativeElement, topPlaceholderDiv, nativeElement.firstChild); 85 | this.topIntersectionObserver.observe(topPlaceholderDiv); 86 | } 87 | 88 | private createBottomPlaceholderDiv(nativeElement: HTMLElement): void { 89 | const bottomPlaceholderDiv = this.renderer.createElement('div'); 90 | 91 | this.renderer.appendChild(nativeElement, bottomPlaceholderDiv); 92 | this.bottomIntersectionObserver.observe(bottomPlaceholderDiv); 93 | } 94 | 95 | private callback(shadowDiv: HTMLElement, entries: IntersectionObserverEntry[]): void { 96 | const isIntersecting = entries.find((entry) => entry.isIntersecting); 97 | 98 | if (isIntersecting) { 99 | this.renderer.removeStyle(shadowDiv, 'boxShadow'); 100 | } else { 101 | this.renderer.setStyle(shadowDiv, 'boxShadow', this.shadowStyle); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/src/lib/ngx-overflow-shadow.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {NgxOverflowShadowDirective} from './ngx-overflow-shadow.directive'; 3 | 4 | @NgModule({ 5 | declarations: [NgxOverflowShadowDirective], 6 | exports: [NgxOverflowShadowDirective] 7 | }) 8 | export class NgxOverflowShadowModule { 9 | } 10 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-overflow-shadow 3 | */ 4 | 5 | export * from './lib/ngx-overflow-shadow.directive'; 6 | export * from './lib/ngx-overflow-shadow.module'; 7 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting(), { 15 | teardown: { destroyAfterEach: false } 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declarationMap": true, 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "fullTemplateTypeCheck": true, 18 | "strictInjectionParameters": true, 19 | "enableResourceInlining": true 20 | }, 21 | "exclude": [ 22 | "src/test.ts", 23 | "**/*.spec.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ngx-overflow-shadow/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "ngx", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "ngx", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
NgxOverflowShadow
3 |
4 |
5 | Displaying a shadow on the top/bottom side of the container as long as scrolling up/down is possible... 6 |
7 |
8 |
    9 |
  • Coffee 10 |
      11 |
    • Caffè Americano
    • 12 |
    • Café Latte (or Café au lait)
    • 13 |
    • Cappuccino
    • 14 |
    • Espresso
    • 15 |
    • Flat White
    • 16 |
    • Long Black
    • 17 |
    • Macchiato
    • 18 |
    • Mochaccino
    • 19 |
    • Irish Coffee
    • 20 |
    • Vienna
    • 21 |
    • Affogato
    • 22 |
    23 |
  • 24 |
  • Tea 25 |
      26 |
    • Barley Tea
    • 27 |
    • Black Tea
    • 28 |
    • Chaga Tea
    • 29 |
    • Chai Tea
    • 30 |
    • Chamomile Tea
    • 31 |
    • Chrysanthemum Tea
    • 32 |
    • Dandelion Tea
    • 33 |
    • Essiac Tea
    • 34 |
    • Green Tea
    • 35 |
    • Hibiscus Tea
    • 36 |
    • Matcha Tea
    • 37 |
    • Moringa Tea
    • 38 |
    39 |
  • 40 |
  • Milk 41 |
      42 |
    • Full cream
    • 43 |
    • Low fat
    • 44 |
    • Skim milk
    • 45 |
    • Lactose-free milk
    • 46 |
    • Rice and oat milk
    • 47 |
    • Soy milk
    • 48 |
    • Almond milk
    • 49 |
    • Flavoured milk
    • 50 |
    51 |
  • 52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /src/app/app.component.less: -------------------------------------------------------------------------------- 1 | .app { 2 | display: grid; 3 | justify-items: center; 4 | color: #565154; 5 | 6 | .header { 7 | margin: 40px 40px 80px 40px; 8 | font-size: 36px; 9 | font-weight: 600; 10 | text-decoration: underline; 11 | } 12 | 13 | .example { 14 | .description { 15 | margin-bottom: 10px; 16 | } 17 | 18 | .container { 19 | height: 300px; 20 | width: 600px; 21 | border: 1px solid #EAECEE; 22 | overflow: auto; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'OverflowShadowLibrary'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('OverflowShadowLibrary'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to OverflowShadowLibrary!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'os-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.less'] 7 | }) 8 | export class AppComponent { 9 | title = 'OverflowShadowLibrary'; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import {NgxOverflowShadowModule} from '../../projects/ngx-overflow-shadow/src/lib/ngx-overflow-shadow.module'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | imports: [ 12 | BrowserModule, 13 | NgxOverflowShadowModule 14 | ], 15 | providers: [], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule { } 19 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maormoshe/OverflowShadowLibrary/81ce0f3c8901393acd37a2a3a5f0a605f8ce6a8c/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maormoshe/OverflowShadowLibrary/81ce0f3c8901393acd37a2a3a5f0a605f8ce6a8c/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OverflowShadowLibrary 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags.ts'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js/dist/zone'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /src/styles.less: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), { 14 | teardown: { destroyAfterEach: false } 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "ES2022", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "useDefineForClassFields": false 22 | }, 23 | "angularCompilerOptions": { 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "os", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "os", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } --------------------------------------------------------------------------------