Repository: ng-web-apis/intersection-observer Branch: master Commit: b162711f4e3e Files: 70 Total size: 70.5 KB Directory structure: gitextract_w7p2l720/ ├── .editorconfig ├── .eslintrc.js ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .husky/ │ ├── commit-msg │ └── pre-commit ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── angular.json ├── commitlint.config.js ├── package.json ├── prettier.config.js ├── projects/ │ ├── demo/ │ │ ├── .gitignore │ │ ├── angular.json │ │ ├── karma.conf.js │ │ ├── package.json │ │ ├── server.ts │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.browser.module.ts │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.less │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.routes.ts │ │ │ │ └── app.server.module.ts │ │ │ ├── assets/ │ │ │ │ ├── browserconfig.xml │ │ │ │ └── site.webmanifest │ │ │ ├── index.html │ │ │ ├── main.browser.ts │ │ │ ├── main.server.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.css │ │ │ └── typings.d.ts │ │ ├── tsconfig.demo.json │ │ ├── tsconfig.json │ │ ├── tsconfig.server.json │ │ └── tsconfig.spec.json │ └── intersection-observer/ │ ├── LICENSE │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src/ │ │ ├── directives/ │ │ │ ├── intersection-observee.directive.ts │ │ │ ├── intersection-observer.directive.ts │ │ │ ├── intersection-root.directive.ts │ │ │ └── tests/ │ │ │ └── intersection-observee.spec.ts │ │ ├── module.ts │ │ ├── public-api.ts │ │ ├── services/ │ │ │ ├── intersection-observee.service.ts │ │ │ ├── intersection-observer.service.ts │ │ │ └── tests/ │ │ │ └── intersection-observer.service.spec.ts │ │ ├── test.ts │ │ ├── tokens/ │ │ │ ├── intersection-root-margin.ts │ │ │ ├── intersection-root.ts │ │ │ ├── intersection-threshold.ts │ │ │ ├── support.ts │ │ │ └── tests/ │ │ │ └── support.spec.ts │ │ └── utils/ │ │ ├── root-margin-factory.ts │ │ └── threshold-factory.ts │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── scripts/ │ ├── postbuild.js │ └── syncVersions.js ├── tsconfig.eslint.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # Editor configuration, see https://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: .eslintrc.js ================================================ /** * @type {import('eslint').Linter.Config} */ module.exports = { root: true, extends: [ // TODO: warning No cached ProjectGraph is available. The rule will be skipped. @nrwl/nx/enforce-module-boundaries // If you encounter this error as part of running standard `nx` commands then please open an issue on // https://github.com/nrwl/nx // './scripts/eslint/nx.js', '@tinkoff/eslint-config-angular', '@tinkoff/eslint-config-angular/html', '@tinkoff/eslint-config-angular/imports', '@tinkoff/eslint-config-angular/line-statements', '@tinkoff/eslint-config-angular/member-ordering', ], ignorePatterns: ['projects/**/test.ts', '*.js', '*.json', '*.less', '*.md'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module', project: [require.resolve('./tsconfig.eslint.json')], }, parser: '@typescript-eslint/parser', rules: { 'dot-notation': 'off', '@typescript-eslint/dot-notation': [ 'error', { allowPrivateClassPropertyAccess: true, allowProtectedClassPropertyAccess: true, allowIndexSignaturePropertyAccess: true, }, ], '@typescript-eslint/no-useless-constructor': 'off', 'no-prototype-builtins': 'off', '@typescript-eslint/no-unnecessary-type-constraint': 'error', '@typescript-eslint/prefer-includes': 'error', 'prefer-template': 'error', '@typescript-eslint/explicit-function-return-type': [ 'error', { allowExpressions: true, allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, allowConciseArrowFunctionExpressionsStartingWithVoid: true, }, ], '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/ban-types': 'error', '@typescript-eslint/no-for-in-array': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', }, }; ================================================ FILE: .github/CODEOWNERS ================================================ # ================================================================================== # ================================================================================== # @ng-web-apis/intersection-observer codeowners # ================================================================================== # ================================================================================== # # Configuration of code ownership and review approvals for the @ng-web-apis/intersection-observer repo. # # More info: https://help.github.com/articles/about-codeowners/ # * @waterplea @MarsiBarsi # will be requested for review when someone opens a pull request ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms open_collective: ng-web-apis issuehunt: ng-web-apis ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: 🐞 Bug report about: Create a report to help us improve title: '[BUG] ' labels: '' assignee: waterplea --- # 🐞 Bug report ### Description ### Reproduction http://www.stackblitz.com/... ### Expected behavior ### Versions - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Angular [e.g. 8] ### Additional context ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: 🚀 Feature request about: Suggest an idea for this project title: '[FEATURE]' labels: '' assignee: waterplea --- # 🚀 Feature request ### Is your feature request related to a problem? I'm always frustrated when... ### Describe the solution you'd like ### Describe alternatives you've considered ### Additional context ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## PR Checklist Please check if your PR fulfills the following requirements: - [ ] The commit message follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) - [ ] Tests for the changes have been added (for bug fixes / features) - [ ] Docs have been added / updated (for bug fixes / features) ## PR Type What kind of change does this PR introduce? - [ ] Bugfix - [ ] Feature - [ ] Refactoring (no functional changes, no api changes) - [ ] Other... Please describe: ## What is the current behavior? Issue Number: N/A ## What is the new behavior? ## Does this PR introduce a breaking change? - [ ] Yes - [ ] No ## Other information ================================================ FILE: .github/workflows/ci.yml ================================================ name: Web APIs CI on: push jobs: ci: # Setup part runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js uses: actions/setup-node@v1 with: node-version: '12.x' - name: Cache Node.js modules uses: actions/cache@v2 with: path: ~/.npm key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.OS }}-node- ${{ runner.OS }}- - name: Install dependencies run: npm ci # End of setup - run: | npm run build npm run test npm run lint - name: Coveralls uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: ./coverage/intersection-observer/lcov.info ================================================ FILE: .gitignore ================================================ # compiled schematics schematics/library-starter/*.js schematics/library-starter/*.js.map schematics/library-starter/*.d.ts # compiled output /dist /tmp /out-tsc # Only exists if Bazel was run /bazel-out # dependencies /node_modules # profiling files chrome-profiler-events.json speed-measure-plugin.json # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .history/* # misc /.sass-cache /connect.lock /coverage /libpeerconnection.log npm-debug.log yarn-error.log testem.log /typings # System Files .DS_Store Thumbs.db ================================================ FILE: .husky/commit-msg ================================================ #!/bin/sh # shellcheck disable=SC1090 . "$(dirname "$0")/_/husky.sh" npx commitlint --edit $1 ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh # shellcheck disable=SC1090 . "$(dirname "$0")/_/husky.sh" npx lint-staged npm run typecheck ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. ### [3.0.1](https://github.com/ng-web-apis/intersection-observer/compare/v3.0.0...v3.0.1) (2023-05-25) - add missing README ## [3.0.0](https://github.com/ng-web-apis/intersection-observer/compare/v2.1.0...v3.0.0) (2022-07-15) ### ⚠ BREAKING CHANGES - update to Angular 12 and Ivy distribution ([90e166b](https://github.com/ng-web-apis/intersection-observer/commit/90e166b7404f2e6edac8713dfbb56cd344e861f7)) ## [2.1.0](https://github.com/ng-web-apis/intersection-observer/compare/v2.0.1...v2.1.0) (2020-09-19) ### Features - **directive:** add `exportAs` ([8341df3](https://github.com/ng-web-apis/intersection-observer/commit/8341df3)) ### [2.0.1](https://github.com/ng-web-apis/intersection-observer/compare/v2.0.0...v2.0.1) (2020-08-26) ### Bug Fixes - **service:** fix exception in Internet Explorer ([5f28df4](https://github.com/ng-web-apis/intersection-observer/commit/5f28df4)) ## [2.0.0](https://github.com/ng-web-apis/intersection-observer/compare/v1.1.3...v2.0.0) (2020-05-15) ### Features - **observer:** Add new directives and service to create single observer and observe multiple elements, rename old directive ([c2de2ff](https://github.com/ng-web-apis/intersection-observer/commit/c2de2ff)) BREAKING CHANGE: New directives names and mechanics ### [1.1.3](https://github.com/ng-web-apis/intersection-observer/compare/v1.1.2...v1.1.3) (2020-04-24) - add default values for tokens and remove @Optional ### [1.1.2](https://github.com/ng-web-apis/intersection-observer/compare/v1.1.1...v1.1.2) (2020-04-06) ### Bug Fixes - **directive:** add attributes to constructor to make code completion work in IDE ([ec3c25a](https://github.com/ng-web-apis/intersection-observer/commit/ec3c25a)) ### [1.1.1](https://github.com/ng-web-apis/intersection-observer/compare/v1.1.0...v1.1.1) (2020-03-30) ### Bug Fixes - **service:** only observe when there are subscriptions ([2a8e267](https://github.com/ng-web-apis/intersection-observer/commit/2a8e267)) ## [1.1.0](https://github.com/ng-web-apis/intersection-observer/compare/v1.0.0...v1.1.0) (2020-03-26) ### Features - **service:** add `IntersectionObserverService` ([65e79aa](https://github.com/ng-web-apis/intersection-observer/commit/65e79aa)) ## 1.0.0 (2020-03-25) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project at ng.web.apis@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing > Thank you for considering contributing to our project. Your help if very welcome! When contributing, it's better to first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. All members of our community are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). Please make sure you are welcoming and friendly in all of our spaces. ## Getting started In order to make your contribution please make a fork of the repository. After you've pulled the code, follow these steps to kick start the development: 1. Run `npm ci` to install dependencies 2. Run `npm start` to launch demo project where you could test your changes 3. Use following commands to ensure code quality ``` npm run lint npm run build npm run test ``` ## Pull Request Process 1. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) in our commit messages, i.e. `feat(core): improve typing` 2. Update [README.md](README.md) to reflect changes related to public API and everything relevant 3. Make sure you cover all code changes with unit tests 4. When you are ready, create Pull Request of your fork into original repository ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Alexander Inkin 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 ================================================ ___ ___ **Attention!** This repository is archived and the library has been moved to [tinkoff/ng-web-apis](https://github.com/Tinkoff/ng-web-apis) monorepository ___ ___ # ![ng-web-apis logo](projects/demo/src/assets/logo.svg) Intersection Observer API for Angular > Part of [Web APIs for Angular](https://ng-web-apis.github.io/) [![npm version](https://img.shields.io/npm/v/@ng-web-apis/intersection-observer.svg)](https://npmjs.com/package/@ng-web-apis/intersection-observer) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@ng-web-apis/intersection-observer)](https://bundlephobia.com/result?p=@ng-web-apis/intersection-observer) [![.github/workflows/ci.yml](https://github.com/ng-web-apis/intersection-observer/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ng-web-apis/intersection-observer/actions/workflows/ci.yml) [![Coveralls github](https://img.shields.io/coveralls/github/ng-web-apis/intersection-observer)](https://coveralls.io/github/ng-web-apis/intersection-observer?branch=master) [![angular-open-source-starter](https://img.shields.io/badge/made%20with-angular--open--source--starter-d81676?logo=angular)](https://github.com/TinkoffCreditSystems/angular-open-source-starter) This is a library for declarative use of [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) with Angular. ## Install If you do not have [@ng-web-apis/common](https://github.com/ng-web-apis/common): ``` npm i @ng-web-apis/common ``` Now install the package: ``` npm i @ng-web-apis/intersection-observer ``` ## Usage 1. Import `IntersectionObserverModule` for directives to work 2. Create [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) with `waIntersectionObserver` directive 3. Observe elements with `waIntersectionObservee` directive 4. _Optional:_ provide root element with `waIntersectionRoot` directive and use `waIntersectionThreshold` and `waIntersectionRootMargin` attributes to configure [IntersectionObserver options](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver) > **NOTE:** Keep in mind these are used one time in constructor so you cannot use binding, only strings. Pass comma separated numbers to set an array of thresholds. ### Usage with Jest DOM environment provided by Jest does not emulate IntersectionObserver API and need to be mocked. You can add the following line to your `setup.ts`: ```ts // setup.ts import '@ng-web-apis/universal/mocks'; ``` to use mocks from [@ng-web-apis/universal](https://github.com/ng-web-apis/universal) package. ## Examples Observing multiple elements intersecting with viewport using single observer ```html
I'm being observed
I'm being observed
``` Observing elements intersecting with parent element, each having different configuration therefore using individual observers: ```html
I'm being observed
I'm being observed
``` ## Services Alternatively you can use `Observable`-based services: 1. `IntersectionObserveeService` can be used to observe elements under `waIntersectionObserver` directive in the DI tree 2. `IntersectionObserverService` can be used to observe single element independently. Provide tokens manually to configure it: ```typescript @Component({ selector: 'my-component', providers: [ IntersectionObserverService, { provide: INTERSECTION_THRESHOLD, useValue: 0.5, }, { provide: INTERSECTION_ROOT_MARGIN, useValue: '10px', }, ], }) export class MyComponent { constructor( @Inject(IntersectionObserverService) entries$: IntersectionObserverService, ) { entries$.subscribe(entries => { // Don't forget to unsubscribe console.log(entries); }); } } ``` > In this case provide `INTERSECTION_ROOT` up the DI tree if you > want to observe intersection with a particular parent element ## Browser support | [IE / Edge](http://godban.github.io/browsers-support-badges/) | [Firefox](http://godban.github.io/browsers-support-badges/) | [Chrome](http://godban.github.io/browsers-support-badges/) | [Safari](http://godban.github.io/browsers-support-badges/) | | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | 15+ | 55+ | 51+ | 12.2+ | > You can use [polyfill](https://www.npmjs.com/package/intersection-observer) to support older browsers ## Angular Universal If you want to use this package with SSR, you need to mock `IntersectionObserver` class on the server. You can use our Universal package for this, see [this example](https://github.com/ng-web-apis/universal#mocks). ## Demo You can [try online demo here](https://ng-web-apis.github.io/intersection-observer) ## See also Other [Web APIs for Angular](https://ng-web-apis.github.io/) by [@ng-web-apis](https://github.com/ng-web-apis) ## Open-source Do you also want to open-source something, but hate the collateral work? Check out this [Angular Open-source Library Starter](https://github.com/TinkoffCreditSystems/angular-open-source-starter) we’ve created for our projects. It got you covered on continuous integration, pre-commit checks, linting, versioning + changelog, code coverage and all that jazz. ================================================ FILE: angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "demo": { "projectType": "application", "schematics": {}, "root": "projects/demo", "sourceRoot": "projects/demo/src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "baseHref": "/intersection-observer/", "deployUrl": "/intersection-observer/", "outputPath": "dist/demo/browser", "index": "projects/demo/src/index.html", "main": "projects/demo/src/main.browser.ts", "polyfills": "projects/demo/src/polyfills.ts", "tsConfig": "tsconfig.json", "assets": [ { "glob": "**/*", "input": "projects/demo/src/assets/", "output": "./assets/" }, "projects/demo/src/favicon.ico" ], "styles": ["projects/demo/src/styles.css"], "showCircularDependencies": false, "vendorChunk": true, "extractLicenses": false, "buildOptimizer": false, "sourceMap": true, "optimization": false, "namedChunks": true, "scripts": [] }, "configurations": { "production": { "baseHref": "/intersection-observer/", "deployUrl": "/intersection-observer/", "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, "buildOptimizer": true, "statsJson": false, "progress": false, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" } ] }, "development": { "baseHref": "/", "deployUrl": "/" } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "demo:build" }, "configurations": { "production": { "browserTarget": "demo:build:production" } } }, "server": { "builder": "@angular-devkit/build-angular:server", "options": { "outputPath": "dist/demo/server", "main": "projects/demo/server.ts", "tsConfig": "projects/demo/tsconfig.server.json", "inlineStyleLanguage": "less" }, "configurations": { "production": { "outputHashing": "media", "fileReplacements": [ { "replace": "projects/demo/src/environments/environment.ts", "with": "projects/demo/src/environments/environment.prod.ts" } ] }, "development": { "optimization": false, "sourceMap": true, "extractLicenses": false } }, "defaultConfiguration": "production" }, "serve-ssr": { "builder": "@nguniversal/builders:ssr-dev-server", "configurations": { "development": { "browserTarget": "demo:build:development", "serverTarget": "demo:server:development" }, "production": { "browserTarget": "demo:build:production", "serverTarget": "demo:server:production" } }, "defaultConfiguration": "development" }, "prerender": { "builder": "@nguniversal/builders:prerender", "options": { "routes": ["/"] }, "configurations": { "production": { "browserTarget": "demo:build:production", "serverTarget": "demo:server:production" }, "development": { "browserTarget": "demo:build:development", "serverTarget": "demo:server:development" } }, "defaultConfiguration": "production" } } }, "intersection-observer": { "projectType": "library", "root": "projects/intersection-observer", "sourceRoot": "projects/intersection-observer/src", "architect": { "build": { "builder": "@angular-devkit/build-angular:ng-packagr", "options": { "tsConfig": "projects/intersection-observer/tsconfig.lib.json", "project": "projects/intersection-observer/ng-package.json" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "projects/intersection-observer/src/test.ts", "tsConfig": "projects/intersection-observer/tsconfig.spec.json", "karmaConfig": "projects/intersection-observer/karma.conf.js", "codeCoverage": true, "browsers": "ChromeHeadless" } } } } }, "defaultProject": "intersection-observer" } ================================================ FILE: commitlint.config.js ================================================ module.exports = {extends: ['@commitlint/config-conventional']}; ================================================ FILE: package.json ================================================ { "name": "@ng-web-apis/intersection-observer", "version": "3.0.1", "description": "A library for declarative use of Intersection Observer API with Angular", "keywords": [ "angular", "ng", "intersection", "observer" ], "homepage": "https://github.com/ng-web-apis/intersection-observer#README", "bugs": "https://github.com/ng-web-apis/intersection-observer/issues", "repository": "https://github.com/ng-web-apis/intersection-observer", "license": "MIT", "author": { "name": "Alexander Inkin", "email": "alexander@inkin.ru" }, "contributors": [ "Roman Sedov <79601794011@ya.ru>" ], "scripts": { "postinstall": "husky install", "ng": "ng", "start": "ng serve", "start:ssr": "ng run demo:serve-ssr", "serve:ssr": "node dist/demo/server/main.js", "build:ssr": "ng build && ng run demo:server", "prerender": "ng run demo:prerender", "build": "ng build", "postbuild": "node scripts/postbuild.js", "test": "ng test", "stylelint": "stylelint '**/*.{less,css}'", "lint": "eslint --cache --cache-location node_modules/.cache/eslint", "typecheck": "tsc --noEmit --skipLibCheck", "release": "standard-version", "release:patch": "npm run release -- --release-as patch", "release:minor": "npm run release -- --release-as minor", "release:major": "npm run release -- --release-as major", "publish": "npm run build && npm publish ./dist/intersection-observer" }, "lint-staged": { "*.{js,ts,html,md,less,json}": [ "npm run lint -- --fix", "prettier --write", "git add" ], "*.less": [ "stylelint --fix", "git add" ] }, "dependencies": { "@angular/common": "12.2.16", "@angular/compiler": "12.2.16", "@angular/core": "12.2.16", "@angular/forms": "12.2.16", "@angular/platform-browser": "12.2.16", "@angular/platform-browser-dynamic": "12.2.16", "@angular/platform-server": "12.2.16", "@angular/router": "12.2.16", "@ng-web-apis/common": "^2.0.0", "@ng-web-apis/universal": "^2.0.0", "@nguniversal/express-engine": "12.1.3", "core-js": "3.20.3", "intersection-observer": "^0.12.2", "rxjs": "7.5.2", "tslib": "2.3.1", "zone.js": "0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "12.2.16", "@angular-devkit/core": "12.2.16", "@angular/cli": "12.2.16", "@angular/compiler-cli": "12.2.16", "@angular/language-service": "12.2.16", "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", "@nguniversal/builders": "12.1.3", "@tinkoff/eslint-config": "1.36.1", "@tinkoff/eslint-config-angular": "1.36.1", "@tinkoff/prettier-config": "1.32.1", "@types/estree": "1.0.0", "@types/express": "4.17.13", "@types/jasmine": "3.10.3", "@types/jasminewd2": "2.0.10", "@types/node": "18.0.4", "coveralls": "3.1.1", "husky": "7.0.4", "jasmine-core": "4.0.0", "jasmine-spec-reporter": "7.0.0", "karma": "6.3.11", "karma-chrome-launcher": "3.1.0", "karma-coverage-istanbul-reporter": "3.0.3", "karma-jasmine": "4.0.1", "karma-jasmine-html-reporter": "1.7.0", "lint-staged": "12.2.1", "ng-packagr": "12.2.6", "prettier": "2.5.1", "standard-version": "9.3.2", "ts-node": "9.0.0", "tslint": "6.1.3", "typescript": "4.3.5" }, "engines": { "node": ">= 10", "npm": ">= 3" }, "standard-version": { "scripts": { "postbump": "node scripts/syncVersions.js && git add **/package.json" } } } ================================================ FILE: prettier.config.js ================================================ const base = require('@tinkoff/prettier-config/angular'); module.exports = { ...base, singleAttributePerLine: true, overrides: [ ...base.overrides, { files: ['*.js', '*.ts'], options: {printWidth: 90, parser: 'typescript'}, }, { files: ['*.html'], options: {printWidth: 120, parser: 'angular'}, }, ], }; ================================================ FILE: projects/demo/.gitignore ================================================ # compiled output /dist /tmp /out-tsc # Only exists if Bazel was run /bazel-out # dependencies /node_modules # profiling files chrome-profiler-events.json speed-measure-plugin.json # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .history/* # misc /.sass-cache /connect.lock /coverage /libpeerconnection.log npm-debug.log yarn-error.log testem.log /typings # System Files .DS_Store Thumbs.db ================================================ FILE: projects/demo/angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "demo": { "root": "", "sourceRoot": "src", "projectType": "application", "prefix": "app", "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/demo", "index": "src/index.html", "main": "src/main.browser.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.demo.json", "aot": false, "assets": [ { "glob": "**/*", "input": "projects/demo/src/assets/", "output": "./assets/" }, "src/favicon.ico" ], "styles": ["src/styles.css"], "scripts": [] }, "configurations": { "production": { "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "demo:build" }, "configurations": { "production": { "browserTarget": "demo:build:production" } } } } } }, "defaultProject": "demo" } ================================================ FILE: projects/demo/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function(config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma'), ], client: { clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../../coverage/demo'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true, }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['ChromeHeadless'], singleRun: true, customLaunchers: { ChromeHeadless: { base: 'Chrome', flags: [ '--no-sandbox', '--headless', '--disable-gpu', '--remote-debugging-port=9222', ], }, }, }); }; ================================================ FILE: projects/demo/package.json ================================================ { "name": "demo", "version": "3.0.1", "private": true, "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build" }, "dependencies": { "@angular/common": "12.2.16", "@angular/compiler": "12.2.16", "@angular/core": "12.2.16", "@angular/forms": "12.2.16", "@angular/platform-browser": "12.2.16", "@angular/platform-browser-dynamic": "12.2.16", "@angular/router": "12.2.16", "@ng-web-apis/common": "latest", "@ng-web-apis/intersection-observer": "latest", "core-js": "3.20.3", "intersection-observer": "^0.12.2", "rxjs": "7.5.2", "zone.js": "0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "12.2.16", "@angular-devkit/core": "12.2.16", "@angular/cli": "12.2.16", "@angular/compiler-cli": "12.2.16", "@angular/language-service": "12.2.16", "@types/node": "18.0.4", "ts-node": "9.0.0", "tslint": "6.1.3", "typescript": "4.3.5" } } ================================================ FILE: projects/demo/server.ts ================================================ import '@ng-web-apis/universal/mocks'; import 'zone.js/node'; import {APP_BASE_HREF} from '@angular/common'; import {provideLocation, provideUserAgent} from '@ng-web-apis/universal'; import {ngExpressEngine} from '@nguniversal/express-engine'; import * as express from 'express'; import {existsSync} from 'fs'; import {join} from 'path'; import {AppServerModule} from './src/main.server'; // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { const server = express(); const distFolder = join(process.cwd(), 'dist/demo/browser'); const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index'; // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine) server.engine( 'html', ngExpressEngine({ bootstrap: AppServerModule, }), ); server.set('view engine', 'html'); server.set('views', distFolder); // Example Express Rest API endpoints // server.get('/api/**', (req, res) => { }); // Serve static files from /browser server.get( '*.*', express.static(distFolder, { maxAge: '1y', }), ); // All regular routes use the Universal engine server.get('*', (req, res) => { res.render(indexHtml, { req, providers: [ {provide: APP_BASE_HREF, useValue: req.baseUrl}, provideLocation(req), provideUserAgent(req), ], }); }); return server; } function run(): void { const port = process.env.PORT || 4000; const server = app(); server.listen(port, () => { console.info(`Node Express server listening on http://localhost:${port}`); }); } // Webpack will replace 'require' with '__webpack_require__' // '__non_webpack_require__' is a proxy to Node 'require' // The below code is to ensure that the server is run only when not requiring the bundle. declare const __non_webpack_require__: NodeRequire; const mainModule = __non_webpack_require__.main; const moduleFilename = mainModule?.filename || ''; if (moduleFilename === __filename || moduleFilename.includes('iisnode')) { run(); } export * from './src/main.server'; ================================================ FILE: projects/demo/src/app/app.browser.module.ts ================================================ import { APP_BASE_HREF, CommonModule, LocationStrategy, PathLocationStrategy, } from '@angular/common'; import {NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {BrowserModule} from '@angular/platform-browser'; import {IntersectionObserverModule} from '@ng-web-apis/intersection-observer'; import {AppComponent} from './app.component'; import {AppRoutingModule} from './app.routes'; @NgModule({ bootstrap: [AppComponent], imports: [ CommonModule, FormsModule, BrowserModule.withServerTransition({appId: 'demo'}), AppRoutingModule, IntersectionObserverModule, ], declarations: [AppComponent], providers: [ { provide: LocationStrategy, useClass: PathLocationStrategy, }, { provide: APP_BASE_HREF, useValue: '', }, ], }) export class AppBrowserModule {} ================================================ FILE: projects/demo/src/app/app.component.html ================================================

I'm being observed

Your browser does not support Intersection Observer API ================================================ FILE: projects/demo/src/app/app.component.less ================================================ :host { perspective: 150vw; user-select: none; flex-direction: column; align-items: center; } .wrapper { position: relative; height: 200px; width: 80vw; overflow: auto; box-shadow: 0 12px 36px rgba(0, 0, 0, 0.2); &:before { content: ''; display: block; height: 900px; } } .element { position: absolute; display: flex; align-items: center; justify-content: center; margin: 0; top: 300px; left: 10vw; width: 60vw; height: 200px; transition: background 0.1s; &[data-ratio='0'] { background: #8591eb; } &[data-ratio='1'] { background: #85a0eb; } &[data-ratio='2'] { background: #84aeeb; } &[data-ratio='3'] { background: #83beeb; } &[data-ratio='4'] { background: #86d2eb; } &[data-ratio='5'] { background: #87ddeb; } &[data-ratio='6'] { background: #8ae5eb; } &[data-ratio='7'] { background: #8bebdf; } &[data-ratio='8'] { background: #83ebc8; } &[data-ratio='9'] { background: #6beb99; } &[data-ratio='10'] { background: #4ceb60; } } ================================================ FILE: projects/demo/src/app/app.component.ts ================================================ import {ChangeDetectionStrategy, Component, Inject} from '@angular/core'; import {INTERSECTION_OBSERVER_SUPPORT} from '@ng-web-apis/intersection-observer'; @Component({ selector: 'main', templateUrl: './app.component.html', styleUrls: ['./app.component.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { ratio = 0; constructor(@Inject(INTERSECTION_OBSERVER_SUPPORT) readonly support: boolean) {} onIntersection(intersections: IntersectionObserverEntry[]) { this.ratio = Math.round(intersections[0].intersectionRatio * 10); } } ================================================ FILE: projects/demo/src/app/app.routes.ts ================================================ import {NgModule} from '@angular/core'; import {RouterModule} from '@angular/router'; import {AppComponent} from './app.component'; export const appRoutes = [ { path: '**', component: AppComponent, }, ]; @NgModule({ imports: [RouterModule.forRoot(appRoutes)], exports: [RouterModule], }) export class AppRoutingModule {} ================================================ FILE: projects/demo/src/app/app.server.module.ts ================================================ import {NgModule} from '@angular/core'; import {ServerModule} from '@angular/platform-server'; import {AppBrowserModule} from './app.browser.module'; import {AppComponent} from './app.component'; @NgModule({ imports: [AppBrowserModule, ServerModule], bootstrap: [AppComponent], }) export class AppServerModule {} ================================================ FILE: projects/demo/src/assets/browserconfig.xml ================================================ #2d89ef ================================================ FILE: projects/demo/src/assets/site.webmanifest ================================================ { "name": "", "short_name": "", "icons": [ { "src": "/intersection-observer/assets/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/intersection-observer/assets/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" } ================================================ FILE: projects/demo/src/index.html ================================================ Intersection Observer API for Angular

Intersection Observer API for Angular

Part of Web APIs logo Web APIs for Angular
loading
================================================ FILE: projects/demo/src/main.browser.ts ================================================ import './polyfills'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppBrowserModule} from './app/app.browser.module'; platformBrowserDynamic() .bootstrapModule(AppBrowserModule) .then(ref => { const windowRef: any = window; // Ensure Angular destroys itself on hot reloads for Stackblitz if (windowRef['ngRef']) { windowRef['ngRef'].destroy(); } windowRef['ngRef'] = ref; }) .catch(err => console.error(err)); ================================================ FILE: projects/demo/src/main.server.ts ================================================ export {AppServerModule} from './app/app.server.module'; ================================================ FILE: projects/demo/src/polyfills.ts ================================================ import 'intersection-observer'; import 'zone.js'; ================================================ FILE: projects/demo/src/styles.css ================================================ /* Base demo styles */ body, html { display: flex; flex-direction: column; margin: 0; height: 100%; font-family: Roboto, 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif; color: #444; } header { display: flex; width: 100%; max-width: 800px; margin: 0 auto; padding: 40px 10px; box-sizing: border-box; border-bottom: 1px solid gainsboro; } main { flex: 1; display: flex; justify-content: center; padding: 40px 0; } footer { padding: 16px; font-size: 12px; border-top: 1px solid gainsboro; text-align: center; } a { color: #1976D2; text-decoration: none; } .logo { margin-right: 20px; } ================================================ FILE: projects/demo/src/typings.d.ts ================================================ declare module '*'; ================================================ FILE: projects/demo/tsconfig.demo.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "typeRoots": [], "paths": {} } } ================================================ FILE: projects/demo/tsconfig.json ================================================ { "extends": "../../tsconfig.json" } ================================================ FILE: projects/demo/tsconfig.server.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs" }, "angularCompilerOptions": { "entryModule": "src/app/app.server.module#AppServerModule" } } ================================================ FILE: projects/demo/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/spec", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: projects/intersection-observer/LICENSE ================================================ MIT License Copyright (c) 2020 Alexander Inkin 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: projects/intersection-observer/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function(config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma'), ], client: { clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../../coverage/intersection-observer'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true, }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['ChromeHeadless'], singleRun: true, customLaunchers: { ChromeHeadless: { base: 'Chrome', flags: [ '--no-sandbox', '--headless', '--disable-gpu', '--disable-web-security', '--remote-debugging-port=9222', ], }, }, }); }; ================================================ FILE: projects/intersection-observer/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/intersection-observer", "lib": { "entryFile": "src/public-api.ts" } } ================================================ FILE: projects/intersection-observer/package.json ================================================ { "name": "@ng-web-apis/intersection-observer", "version": "3.0.1", "description": "A library for declarative use of Intersection Observer API with Angular", "keywords": [ "angular", "ng", "intersection", "observer" ], "homepage": "https://github.com/ng-web-apis/intersection-observer#README", "bugs": "https://github.com/ng-web-apis/intersection-observer/issues", "repository": "https://github.com/ng-web-apis/intersection-observer", "license": "MIT", "author": { "name": "Alexander Inkin", "email": "alexander@inkin.ru" }, "contributors": [ "Roman Sedov <79601794011@ya.ru>" ], "peerDependencies": { "@angular/core": ">=12.0.0", "@ng-web-apis/common": ">=2.0.0" } } ================================================ FILE: projects/intersection-observer/src/directives/intersection-observee.directive.ts ================================================ import {Directive, Inject} from '@angular/core'; import {Observable} from 'rxjs'; import {IntersectionObserveeService} from '../services/intersection-observee.service'; @Directive({ selector: '[waIntersectionObservee]', outputs: ['waIntersectionObservee'], providers: [IntersectionObserveeService], }) export class IntersectionObserveeDirective { constructor( @Inject(IntersectionObserveeService) readonly waIntersectionObservee: Observable, ) {} } ================================================ FILE: projects/intersection-observer/src/directives/intersection-observer.directive.ts ================================================ import { Attribute, Directive, ElementRef, Inject, OnDestroy, Optional, } from '@angular/core'; import {INTERSECTION_ROOT} from '../tokens/intersection-root'; import {rootMarginFactory} from '../utils/root-margin-factory'; import {thresholdFactory} from '../utils/threshold-factory'; @Directive({ selector: '[waIntersectionObserver]', exportAs: 'IntersectionObserver', }) export class IntersectionObserverDirective extends IntersectionObserver implements OnDestroy { private readonly callbacks = new Map(); constructor( @Optional() @Inject(INTERSECTION_ROOT) root: ElementRef | null, @Attribute('waIntersectionRootMargin') rootMargin: string | null, @Attribute('waIntersectionThreshold') threshold: string | null, ) { super( entries => { this.callbacks.forEach((callback, element) => { const filtered = entries.filter(({target}) => target === element); return filtered.length && callback(filtered, this); }); }, { root: root && root.nativeElement, rootMargin: rootMarginFactory(rootMargin), threshold: thresholdFactory(threshold), }, ); } observe(target: Element, callback: IntersectionObserverCallback = () => {}) { super.observe(target); this.callbacks.set(target, callback); } unobserve(target: Element) { super.unobserve(target); this.callbacks.delete(target); } ngOnDestroy() { this.disconnect(); } } ================================================ FILE: projects/intersection-observer/src/directives/intersection-root.directive.ts ================================================ import {Directive, ElementRef} from '@angular/core'; import {INTERSECTION_ROOT} from '../tokens/intersection-root'; @Directive({ selector: '[waIntersectionRoot]', providers: [ { provide: INTERSECTION_ROOT, useExisting: ElementRef, }, ], }) export class IntersectionRootDirective {} ================================================ FILE: projects/intersection-observer/src/directives/tests/intersection-observee.spec.ts ================================================ import {Component, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {IntersectionObserverModule} from '../../module'; import {INTERSECTION_ROOT_MARGIN} from '../../tokens/intersection-root-margin'; import {INTERSECTION_THRESHOLD} from '../../tokens/intersection-threshold'; import {IntersectionObserverDirective} from '../intersection-observer.directive'; describe('IntersectionObserveeDirective', () => { @Component({ template: `
Hello
Height expander

I'm being observed

Default values

`, }) class TestComponent { @ViewChild('root', {read: IntersectionObserverDirective}) observer!: IntersectionObserverDirective; onIntersection = jasmine.createSpy('onIntersection'); observe = true; } let fixture: ComponentFixture; let testComponent: TestComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [IntersectionObserverModule], declarations: [TestComponent], }); fixture = TestBed.createComponent(TestComponent); testComponent = fixture.componentInstance; fixture.detectChanges(); testComponent.onIntersection.calls.reset(); }); it('Emits intersections', done => { document.querySelector('#observer_root')!.scrollTop = 350; fixture.detectChanges(); setTimeout(() => { expect(testComponent.onIntersection).toHaveBeenCalled(); document.querySelector('#observer_root')!.scrollTop = 0; fixture.detectChanges(); testComponent.observe = false; fixture.detectChanges(); done(); }, 100); }); it('Compatible with native method signature', () => { expect(() => testComponent.observer.observe(document.querySelector('#manual_observee')!), ).not.toThrow(); }); it('Default options', () => { // https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver expect(TestBed.get(INTERSECTION_ROOT_MARGIN)).toBe('0px 0px 0px 0px'); expect(TestBed.get(INTERSECTION_THRESHOLD)).toBe(0); }); }); ================================================ FILE: projects/intersection-observer/src/module.ts ================================================ import {NgModule} from '@angular/core'; import {IntersectionObserveeDirective} from './directives/intersection-observee.directive'; import {IntersectionObserverDirective} from './directives/intersection-observer.directive'; import {IntersectionRootDirective} from './directives/intersection-root.directive'; @NgModule({ declarations: [ IntersectionObserverDirective, IntersectionObserveeDirective, IntersectionRootDirective, ], exports: [ IntersectionObserverDirective, IntersectionObserveeDirective, IntersectionRootDirective, ], }) export class IntersectionObserverModule {} ================================================ FILE: projects/intersection-observer/src/public-api.ts ================================================ /** * Public API Surface of @ng-web-apis/intersection-observer */ /* Directives */ export * from './directives/intersection-observee.directive'; export * from './directives/intersection-observer.directive'; export * from './directives/intersection-root.directive'; /* Modules */ export * from './module'; /* Services */ export * from './services/intersection-observee.service'; export * from './services/intersection-observer.service'; /* Tokens */ export * from './tokens/intersection-root'; export * from './tokens/intersection-root-margin'; export * from './tokens/intersection-threshold'; export * from './tokens/support'; ================================================ FILE: projects/intersection-observer/src/services/intersection-observee.service.ts ================================================ import {ElementRef, Inject, Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {share} from 'rxjs/operators'; import {IntersectionObserverDirective} from '../directives/intersection-observer.directive'; @Injectable() export class IntersectionObserveeService extends Observable { constructor( @Inject(ElementRef) {nativeElement}: ElementRef, @Inject(IntersectionObserverDirective) observer: IntersectionObserverDirective, ) { super(subscriber => { observer.observe(nativeElement, entries => { subscriber.next(entries); }); return () => { observer.unobserve(nativeElement); }; }); return this.pipe(share()); } } ================================================ FILE: projects/intersection-observer/src/services/intersection-observer.service.ts ================================================ import {ElementRef, Inject, Injectable, Optional} from '@angular/core'; import {Observable} from 'rxjs'; import {share} from 'rxjs/operators'; import {INTERSECTION_ROOT} from '../tokens/intersection-root'; import {INTERSECTION_ROOT_MARGIN} from '../tokens/intersection-root-margin'; import {INTERSECTION_THRESHOLD} from '../tokens/intersection-threshold'; import {INTERSECTION_OBSERVER_SUPPORT} from '../tokens/support'; @Injectable() export class IntersectionObserverService extends Observable { constructor( @Inject(ElementRef) {nativeElement}: ElementRef, @Inject(INTERSECTION_OBSERVER_SUPPORT) support: boolean, @Inject(INTERSECTION_ROOT_MARGIN) rootMargin: string, @Inject(INTERSECTION_THRESHOLD) threshold: number | number[], @Optional() @Inject(INTERSECTION_ROOT) root: ElementRef | null, ) { super(subscriber => { if (!support) { subscriber.error('IntersectionObserver is not supported in your browser'); return; } const observer = new IntersectionObserver( entries => { subscriber.next(entries); }, { root: root && root.nativeElement, rootMargin, threshold, }, ); observer.observe(nativeElement); return () => { observer.disconnect(); }; }); return this.pipe(share()); } } ================================================ FILE: projects/intersection-observer/src/services/tests/intersection-observer.service.spec.ts ================================================ import {take} from 'rxjs/operators'; import {IntersectionObserverService} from '../intersection-observer.service'; describe('IntersectionObserverService', () => { it('works', done => { let called = false; const nativeElement = document.createElement('div'); const service = new IntersectionObserverService( { nativeElement, }, true, '0px 0px 0px 0px', 0, { nativeElement: document.body, }, ); service.pipe(take(1)).subscribe({ next: () => { called = true; }, }); document.body.appendChild(nativeElement); setTimeout(() => { expect(called).toBe(true); done(); }); }); it('throws when not supported', () => { let error = false; const service = new IntersectionObserverService( { nativeElement: document.createElement('DIV'), }, false, '0px 0px 0px 0px', 0, null, ); service.subscribe({ error: () => { error = true; }, }); expect(error).toBe(true); }); }); ================================================ FILE: projects/intersection-observer/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js'; import 'zone.js/testing'; import {getTestBed} from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting, } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting(), ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: projects/intersection-observer/src/tokens/intersection-root-margin.ts ================================================ import {InjectionToken} from '@angular/core'; export const INTERSECTION_ROOT_MARGIN_DEFAULT = '0px 0px 0px 0px'; export const INTERSECTION_ROOT_MARGIN = new InjectionToken( 'rootMargin for IntersectionObserver', { providedIn: 'root', factory: () => INTERSECTION_ROOT_MARGIN_DEFAULT, }, ); ================================================ FILE: projects/intersection-observer/src/tokens/intersection-root.ts ================================================ import {ElementRef, InjectionToken} from '@angular/core'; export const INTERSECTION_ROOT = new InjectionToken>( 'Root element for IntersectionObserver', ); ================================================ FILE: projects/intersection-observer/src/tokens/intersection-threshold.ts ================================================ import {InjectionToken} from '@angular/core'; export const INTERSECTION_THRESHOLD_DEFAULT = 0; export const INTERSECTION_THRESHOLD = new InjectionToken( 'threshold for IntersectionObserver', { providedIn: 'root', factory: () => INTERSECTION_THRESHOLD_DEFAULT, }, ); ================================================ FILE: projects/intersection-observer/src/tokens/support.ts ================================================ import {inject, InjectionToken} from '@angular/core'; import {WINDOW} from '@ng-web-apis/common'; export const INTERSECTION_OBSERVER_SUPPORT = new InjectionToken( 'Intersection Observer API support', { providedIn: 'root', factory: () => !!(inject(WINDOW) as any).IntersectionObserver, }, ); ================================================ FILE: projects/intersection-observer/src/tokens/tests/support.spec.ts ================================================ import {TestBed} from '@angular/core/testing'; import {INTERSECTION_OBSERVER_SUPPORT} from '../support'; describe('INTERSECTION_OBSERVER_SUPPORT', () => { it('true in modern browsers', () => { TestBed.configureTestingModule({}); expect(TestBed.get(INTERSECTION_OBSERVER_SUPPORT)).toBe(true); }); }); ================================================ FILE: projects/intersection-observer/src/utils/root-margin-factory.ts ================================================ import {INTERSECTION_ROOT_MARGIN_DEFAULT} from '../tokens/intersection-root-margin'; export function rootMarginFactory(rootMargin: string | null): string { return rootMargin || INTERSECTION_ROOT_MARGIN_DEFAULT; } ================================================ FILE: projects/intersection-observer/src/utils/threshold-factory.ts ================================================ import {INTERSECTION_THRESHOLD_DEFAULT} from '../tokens/intersection-threshold'; export function thresholdFactory(threshold: string | null): number | number[] { return threshold?.split(',').map(parseFloat) || INTERSECTION_THRESHOLD_DEFAULT; } ================================================ FILE: projects/intersection-observer/tsconfig.lib.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/lib", "target": "es2015", "declaration": true, "inlineSources": true, "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true, "enableIvy": true, "compilationMode": "partial" }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: projects/intersection-observer/tsconfig.spec.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../out-tsc/spec", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: scripts/postbuild.js ================================================ const fs = require('fs'); const DIST_LIB_PATH = 'dist/intersection-observer/'; const README_PATH = 'README.md'; const PATH_TO_README = DIST_LIB_PATH + README_PATH; copyExtraFiles(); function copyExtraFiles() { if (!fs.existsSync(README_PATH)) { throw new Error('Requested files do not exit'); } else { copyReadmeIntoDistFolder(README_PATH, PATH_TO_README); } } function copyReadmeIntoDistFolder(srcPath, toPath) { const fileBody = fs.readFileSync(srcPath).toString(); const withoutLogos = fileBody .replace('![ng-web-apis logo](projects/demo/src/assets/logo.svg) ', '') .replace(' ', ''); fs.writeFileSync(toPath, withoutLogos); } ================================================ FILE: scripts/syncVersions.js ================================================ const fs = require('fs'); const glob = require('glob'); const JSON_INDENTATION_LEVEL = 4; const {version} = require('../package.json'); // Sync libraries package.json versions with main package.json syncVersions('projects'); function syncVersions(root) { glob(root + '/**/package.json', (_, files) => { files.forEach(file => { const packageJson = JSON.parse(fs.readFileSync(file)); fs.writeFileSync( file, JSON.stringify( { ...packageJson, version, }, null, JSON_INDENTATION_LEVEL, ), ); }); }); } ================================================ FILE: tsconfig.eslint.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "rootDir": ".", "baseUrl": ".", "strict": false, "incremental": true }, "include": ["projects", "scripts"], "exclude": ["**/node_modules", "**/schematics/**", "**/.*/", "*.js"] } ================================================ FILE: tsconfig.json ================================================ { "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, "module": "esnext", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "strict": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "noUnusedParameters": true, "noUnusedLocals": true, "target": "es5", "typeRoots": ["node_modules/@types"], "lib": ["es2018", "dom"], "paths": { "@ng-web-apis/intersection-observer": [ "projects/intersection-observer/src/public-api" ] } } }