├── .babelrc ├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist └── lib │ ├── angular-iscroll.js │ ├── angular-iscroll.js.gz │ └── scss │ └── _iscroll.scss ├── package.json ├── postcss.config.js ├── src └── lib │ ├── _iscroll.scss │ └── angular-iscroll.js ├── webpack.config.mjs └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": "3.33" 8 | } 9 | ] 10 | ], 11 | "plugins": [ 12 | [ 13 | "angularjs-annotate", 14 | { 15 | "explicitOnly": true 16 | } 17 | ] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '15 8 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | dist/examples 22 | 23 | # Dependency directory 24 | # Commenting this out is preferred by some people, see 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 26 | node_modules 27 | bower_components 28 | 29 | # Users Environment Variables 30 | .lock-wscript 31 | 32 | # IDEA-based editor project directory 33 | .idea/ 34 | 35 | # Sass cache 36 | .sass-cache/ 37 | 38 | # Modern Yarn 39 | .yarn/* 40 | !.yarn/cache 41 | !.yarn/patches 42 | !.yarn/plugins 43 | !.yarn/releases 44 | !.yarn/sdks 45 | !.yarn/versions 46 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v3.6.0](https://github.com/mtr/angular-iscroll/tree/v3.6.0) (2018-01-20) 4 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.5.1...v3.6.0) 5 | 6 | **Implemented enhancements:** 7 | 8 | - Automated detection is too strict on Android devices. [\#41](https://github.com/mtr/angular-iscroll/issues/41) 9 | 10 | **Fixed bugs:** 11 | 12 | - Automated detection is too strict on Android devices. [\#41](https://github.com/mtr/angular-iscroll/issues/41) 13 | 14 | **Closed issues:** 15 | 16 | - FF and IE directive service and scope reference problems. [\#15](https://github.com/mtr/angular-iscroll/issues/15) 17 | 18 | ## [v3.5.1](https://github.com/mtr/angular-iscroll/tree/v3.5.1) (2017-06-13) 19 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.5.0...v3.5.1) 20 | 21 | ## [v3.5.0](https://github.com/mtr/angular-iscroll/tree/v3.5.0) (2017-06-13) 22 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.4.1...v3.5.0) 23 | 24 | ## [v3.4.1](https://github.com/mtr/angular-iscroll/tree/v3.4.1) (2017-06-13) 25 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.4.0...v3.4.1) 26 | 27 | **Closed issues:** 28 | 29 | - Why does it always appear "scroll-off" [\#35](https://github.com/mtr/angular-iscroll/issues/35) 30 | - Keep getting "TypeError: IScroll is not a function" [\#16](https://github.com/mtr/angular-iscroll/issues/16) 31 | 32 | ## [v3.4.0](https://github.com/mtr/angular-iscroll/tree/v3.4.0) (2016-09-02) 33 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.2.0...v3.4.0) 34 | 35 | **Implemented enhancements:** 36 | 37 | - Refine the need-iScroll auto-detection rules [\#28](https://github.com/mtr/angular-iscroll/issues/28) 38 | 39 | **Merged pull requests:** 40 | 41 | - Fix. Fixed issue with overriding instance config with default options. [\#34](https://github.com/mtr/angular-iscroll/pull/34) ([egor-manjula](https://github.com/egor-manjula)) 42 | 43 | ## [v3.2.0](https://github.com/mtr/angular-iscroll/tree/v3.2.0) (2016-01-27) 44 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.1.0...v3.2.0) 45 | 46 | **Merged pull requests:** 47 | 48 | - \#25 Add platform.js requirement to bower.json [\#27](https://github.com/mtr/angular-iscroll/pull/27) ([simison](https://github.com/simison)) 49 | 50 | ## [v3.1.0](https://github.com/mtr/angular-iscroll/tree/v3.1.0) (2016-01-22) 51 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v3.0.0...v3.1.0) 52 | 53 | ## [v3.0.0](https://github.com/mtr/angular-iscroll/tree/v3.0.0) (2016-01-22) 54 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v2.0.1...v3.0.0) 55 | 56 | **Implemented enhancements:** 57 | 58 | - Automatically detect the need for using iScroll. [\#25](https://github.com/mtr/angular-iscroll/issues/25) 59 | - Support being initially disabled. [\#24](https://github.com/mtr/angular-iscroll/issues/24) 60 | - Address jshint issues [\#23](https://github.com/mtr/angular-iscroll/issues/23) 61 | - Remove unmaintained example code because core-layout is well maintained [\#22](https://github.com/mtr/angular-iscroll/issues/22) 62 | - Add bitHound configuration. [\#21](https://github.com/mtr/angular-iscroll/issues/21) 63 | - Address npm dependency issues found in master branch \[Opened by bitHound\] [\#20](https://github.com/mtr/angular-iscroll/issues/20) 64 | 65 | **Fixed bugs:** 66 | 67 | - iScrollServiceProvider.configureDefaults\(\) does not perform a deep merge of options with defaultOptions. [\#26](https://github.com/mtr/angular-iscroll/issues/26) 68 | 69 | ## [v2.0.1](https://github.com/mtr/angular-iscroll/tree/v2.0.1) (2015-12-17) 70 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v2.0.0...v2.0.1) 71 | 72 | ## [v2.0.0](https://github.com/mtr/angular-iscroll/tree/v2.0.0) (2015-12-16) 73 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.3.3...v2.0.0) 74 | 75 | **Merged pull requests:** 76 | 77 | - Add option to skip digest cycle when refresh option is turned on [\#19](https://github.com/mtr/angular-iscroll/pull/19) ([DinkoMiletic](https://github.com/DinkoMiletic)) 78 | - Fixing bug where the argument passed to configureDefaults\(\) clobbers the built-in defaults [\#17](https://github.com/mtr/angular-iscroll/pull/17) ([mikechamberlain](https://github.com/mikechamberlain)) 79 | 80 | ## [v1.3.3](https://github.com/mtr/angular-iscroll/tree/v1.3.3) (2015-06-07) 81 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.3.2...v1.3.3) 82 | 83 | **Implemented enhancements:** 84 | 85 | - Create simple configuration interface for defining event handlers. [\#10](https://github.com/mtr/angular-iscroll/issues/10) 86 | 87 | **Merged pull requests:** 88 | 89 | - Adds bower.json and bower install instructions [\#12](https://github.com/mtr/angular-iscroll/pull/12) ([brechtpm](https://github.com/brechtpm)) 90 | 91 | ## [v1.3.2](https://github.com/mtr/angular-iscroll/tree/v1.3.2) (2015-05-23) 92 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.3.1...v1.3.2) 93 | 94 | ## [v1.3.1](https://github.com/mtr/angular-iscroll/tree/v1.3.1) (2015-05-23) 95 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.3.0...v1.3.1) 96 | 97 | ## [v1.3.0](https://github.com/mtr/angular-iscroll/tree/v1.3.0) (2015-05-23) 98 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.2.5...v1.3.0) 99 | 100 | ## [v1.2.5](https://github.com/mtr/angular-iscroll/tree/v1.2.5) (2015-05-23) 101 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.2.4...v1.2.5) 102 | 103 | **Merged pull requests:** 104 | 105 | - Add a Bitdeli Badge to README [\#9](https://github.com/mtr/angular-iscroll/pull/9) ([bitdeli-chef](https://github.com/bitdeli-chef)) 106 | 107 | ## [v1.2.4](https://github.com/mtr/angular-iscroll/tree/v1.2.4) (2015-03-25) 108 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.2.3...v1.2.4) 109 | 110 | **Fixed bugs:** 111 | 112 | - Interval is not being closed after destroying instance of angular-iscroll [\#6](https://github.com/mtr/angular-iscroll/issues/6) 113 | 114 | **Merged pull requests:** 115 | 116 | - Cancelling intervals when destroying instance [\#7](https://github.com/mtr/angular-iscroll/pull/7) ([wkwiatek](https://github.com/wkwiatek)) 117 | 118 | ## [v1.2.3](https://github.com/mtr/angular-iscroll/tree/v1.2.3) (2015-03-05) 119 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.2.2...v1.2.3) 120 | 121 | ## [v1.2.2](https://github.com/mtr/angular-iscroll/tree/v1.2.2) (2015-02-25) 122 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.2.1...v1.2.2) 123 | 124 | **Implemented enhancements:** 125 | 126 | - Documentation [\#4](https://github.com/mtr/angular-iscroll/issues/4) 127 | 128 | ## [v1.2.1](https://github.com/mtr/angular-iscroll/tree/v1.2.1) (2015-02-24) 129 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.2.0...v1.2.1) 130 | 131 | ## [v1.2.0](https://github.com/mtr/angular-iscroll/tree/v1.2.0) (2015-02-15) 132 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.1.2...v1.2.0) 133 | 134 | **Implemented enhancements:** 135 | 136 | - Support configuring the default options during myApp.config\(\) [\#3](https://github.com/mtr/angular-iscroll/issues/3) 137 | 138 | ## [v1.1.2](https://github.com/mtr/angular-iscroll/tree/v1.1.2) (2015-02-07) 139 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.1.1...v1.1.2) 140 | 141 | **Fixed bugs:** 142 | 143 | - Declare Sass variables as defaults, so they can easily be overridden. [\#2](https://github.com/mtr/angular-iscroll/issues/2) 144 | 145 | ## [v1.1.1](https://github.com/mtr/angular-iscroll/tree/v1.1.1) (2015-02-06) 146 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.1.0...v1.1.1) 147 | 148 | ## [v1.1.0](https://github.com/mtr/angular-iscroll/tree/v1.1.0) (2015-02-06) 149 | [Full Changelog](https://github.com/mtr/angular-iscroll/compare/v1.0.0...v1.1.0) 150 | 151 | **Implemented enhancements:** 152 | 153 | - Add support for periodic instance.refresh\(\) calls. [\#1](https://github.com/mtr/angular-iscroll/issues/1) 154 | 155 | ## [v1.0.0](https://github.com/mtr/angular-iscroll/tree/v1.0.0) (2015-02-05) 156 | 157 | 158 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Martin Thorsen Ranang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-iscroll 2 | AngularJS module that enables iScroll 5 functionality, wrapping it in an easy-to-use directive 3 | 4 | ## Install 5 | 6 | Install the [angular-iscroll NPM package](https://www.npmjs.com/package/angular-iscroll) 7 | ```bash 8 | npm install --save angular-iscroll 9 | ``` 10 | 11 | Install through Yarn 12 | ```bash 13 | yarn add angular-iscroll 14 | ``` 15 | 16 | Or, to check out a development version, start by cloning the repository, by 17 | ```bash 18 | git clone git@github.com:mtr/angular-iscroll.git 19 | ``` 20 | If you don't use `yarn` you may run `npm run-script [command]` instead of `yarn [command]`. So, to install the necessary dependencies: 21 | ```bash 22 | cd angular-iscroll/ 23 | yarn install 24 | yarn build # or `npm run-script build` 25 | ``` 26 | 27 | After that, you should have a `dist` directory with a subdirectory named `lib`: 28 | ``` 29 | dist/ 30 | └── lib 31 | ├── angular-iscroll.js 32 | ├── angular-iscroll.js.gz 33 | └── scss 34 | └── _iscroll.scss 35 | ``` 36 | 37 | ### Build 38 | 39 | To rebuild the library, run 40 | ```bash 41 | yarn build # or `yarn watch` 42 | ``` 43 | 44 | 45 | ## Demo 46 | You may have a look at [core-layout](http://mtr.github.io/core-layout/examples/) ([GitHub repo](https://github.com/mtr/core-layout/)), an Angular demo app that shows how you can use the `iscroll` directive in a responsive-design web-app with support for both drawers (slide-out menus) and modals. For example, the demo shows how to handle DOM content generated dynamically through [ngRepeat](https://docs.angularjs.org/api/ng/directive/ngRepeat). 47 | 48 | ## Usage 49 | 50 | In the following, `IScroll` (with capital 'I' and 'S') refers to instances 51 | of the [iScroll Javascript library](http://iscrolljs.com/) that this package provides an AngularJS wrapper for. 52 | 53 | The main usage pattern for `angular-iscroll` is to define a dependency on the `angular-iscroll` module in your AngularJS app. For example: 54 | ```js 55 | angular.module('myApp', ['angular-iscroll']); 56 | ``` 57 | or, in a Browserify-based code base: 58 | ```js 59 | angular.module('myApp', [require('angular-iscroll').name]); 60 | ``` 61 | 62 | The `angular-iscroll` module includes both a directive, `iscroll`, and a service, `iScrollService`, which gives you access to and control over a shared, global state of whether to enable, disable, or refresh the `IScroll` instances for each `iscroll` directive instance. 63 | 64 | Next, to use the directive, you should set up your HTML template like 65 | ```html 66 | … 67 | 72 |
73 |
74 |
75 |
76 | … 77 | ``` 78 | Let me explain the essential parts of that HTML example. First of all, the `iscroll` directive is an attribute of an element belonging to the `iscroll-wrapper` class, which wraps an element of the `iscroll-scroller` class. Those two classes are defined in the [SASS](http://sass-lang.com/) file [dist/lib/scss/_iscroll.scss](dist/lib/scss/_iscroll.scss), but they don't have any meaning unless they occur inside an `iscroll-on` class; and that's where the shared, global state from iScrollService comes in. The controller, `MyAppController`, in the above example exposes the state variable shared by iScrollService in its scope 79 | ```js 80 | function MyAppController(iScrollService) { 81 | var vm = this; // Use 'controller as' syntax 82 | 83 | vm.iScrollState = iScrollService.state; 84 | } 85 | ``` 86 | thereby providing a way to globally change the meaning of the `iscroll-wrapper` + `iscroll-scroller` combination. Please note: To get more info about the "controller as" syntax, you might enjoy [John Papa's AngularJS Style Guide](https://github.com/johnpapa/angularjs-styleguide#controlleras-with-vm). 87 | 88 | Furthermore, the global iScroll state exposed by the service should be changed through the service's `enable([signalOnly])`, `disable([signalOnly])`, and `toggle([signalOnly])` methods, where each method will change the state accordingly, and then emit a corresponding signal from `$rootScope` that gets picked up and handled by the available `angular-iscroll` directive instances. If the `signalOnly` flag is `true`, then the state is not changed by the service method, but the signal is sent nonetheless. If the directives receive an `iscroll:disabled` signal, they will destroy any existing `IScroll` instances, and if they receive an `iscroll:enabled` signal, they will create a new `IScroll` instances per directive instance if it doesn't already exist. 89 | 90 | It should also be noted that during instantiation, in the directive's post-link phase, the `iscroll` directive will check the `iScrollService`'s `useIScroll` state to decide whether or not it will create an actual `IScroll` instance. Consequently, if you would like to create an AngularJS solution that uses iScroll only on, for example, iOS devices, you should determine the current browser type early, probably inside the app controller's [configuration block](https://docs.angularjs.org/guide/module#module-loading-dependencies), and set the service's `useIscroll` state accordingly. Please note that `angular-iscroll` does not contain any code to detect which browser or platform it is currently running on, which is a separate, complex task better solved by specialized libraries, like [platform.js](https://github.com/bestiejs/platform.js). 91 | 92 | 93 | ### Manual Interaction with Each Directive's IScroll Instance 94 | If you want access to a scope's `IScroll` instance, you can supply an optional 95 | `iscroll-instance` attribute when applying the `iscroll` directive, like 96 | ```html 97 | … 98 |
99 |
100 |
101 |
102 | … 103 | ``` 104 | That way, the scope's `instance` variable will hold a reference to the actual 105 | `IScroll` instance, so you can access the IScroll instance's own API, for 106 | example to define [custom events](http://iscrolljs.com/#custom-events) or 107 | access its [scroller info](http://iscrolljs.com/#scroller-info). 108 | 109 | 110 | ### Configuration 111 | I've designed this module so that it should be easy to configure. First of all, you can supply per-instance options, both for `IScroll` and the directive itself, when you apply the directive. For example 112 | ```html 113 |
114 | ``` 115 | would pass along the options `{mouseWheel: true, momentum: true}` to `IScroll`, while the directive-specific configuration parameter, `{refreshInterval: 500}`, is only interpreted by the directive. Any config option not recognized as a directive-specific option, will be forwarded to `IScroll`. 116 | 117 | There are lots of configuration options for IScroll itself; those are best [documented by iScroll](http://iscrolljs.com/#configuring). 118 | 119 | #### Directive Options 120 | The directive provides two configuration options: 121 | 122 | - `asyncRefreshDelay` (default `0`): defines the delay, in ms, before the directive asynchronously performs an IScroll.refresh(). If `false`, then no async refresh is performed. This can come in handy when you need to wait for the DOM to be rendered before `IScroll` can know the size of its scrolling area. 123 | - `refreshInterval` (default `false`): a delay, in ms, between each periodic iScroll.refresh(). If `false`, then no periodic refresh is performed. This functionality can be handy in complex applications, where it might be difficult to decide when `iScrollService.refresh()` should be called, and a periodic call to `IScroll.refresh()`, for example every 500 ms, might provide a smooth user experience. To avoid scroll stuttering caused by calls to refresh during an ongoing scroll operation, the `angular-iscroll` directive prevents `refresh()` calls if IScroll is currently performing a scroll operation. 124 | - `invokeApply` (default `false`, since version _2.0.0_): whether or not to invoke AngularJS' `$apply()` (and thereby `$digest()`) cycle on every refresh, as determined by `asyncRefreshDelay` or `refreshInterval`. When `false`, it will not invoke model dirty checking on every call to `IScroll.refresh()`. This can result in huge performance gain if `refreshInterval` is set to a low value (for example 500 ms). Example usage: 125 | 126 | iScrollServiceProvider.configureDefaults({ 127 | iscroll: { 128 | invokeApply:false 129 | } 130 | }); 131 | 132 | To test it, you can paste this code to your app `run` block: 133 | 134 | /** 135 | * This code measures `$digest()` performance 136 | * by logging digest times to the console. 137 | */ 138 | var $oldDigest = $rootScope.$digest; 139 | var $newDigest = function() { 140 | console.time("$digest"); 141 | $oldDigest.apply($rootScope); 142 | console.timeEnd("$digest"); 143 | }; 144 | $rootScope.$digest = $newDigest; 145 | 146 | With `invokeApply = true` and `refreshInterval = 500` you'll see that digest is run every 500ms. 147 | With `invokeApply = false` and `refreshInterval = 500` you'll see that digest is not invoked by `angular-iscroll`. 148 | 149 | Thanks to [DinkoMiletic](https://github.com/DinkoMiletic) for implementing this optimization. 150 | 151 | 152 | #### Globally Configuring the Directive's Default Options 153 | 154 | The `iscroll` directive gets its default configuration from the `iScrollService`. To provide a way to easily, globally configure the defaults for all `iscroll` instances, the module defines an `iScrollServiceProvider` which can be injected into the app controller's configuration block which is guaranteed to run before the controller is used anywhere. For example: 155 | ```js 156 | /* @ngInject */ 157 | function _config(iScrollServiceProvider) { 158 | // Supply a default configuration object, eg: 159 | iScrollServiceProvider.configureDefaults({ 160 | iScroll: { 161 | // Passed through to the iScroll library 162 | scrollbars: true, 163 | fadeScrollbars: true 164 | }, 165 | directive: { 166 | // Interpreted by the directive 167 | refreshInterval: 500 168 | } 169 | }); 170 | } 171 | 172 | angular 173 | .module('myApp', ['angular-iscroll']) 174 | .config(_config); 175 | ``` 176 | The configuration you provide this way will serve as the updated global default for all `iscroll` directive instances. 177 | 178 | Please note that the above example relies on [ng-annotate](https://www.npmjs.com/package/ng-annotate) for adding AngularJS dependency-injection annotations during builds, as indicated by the `/* @ngInject */` comment. 179 | 180 | ## Support ## 181 | Thanks to a generous “free for Open Source” sponsorship from [BrowserStack](https://www.browserstack.com) I've been able to test [core-layout](http://mtr.github.io/core-layout/examples/), and thereby [angular-iscroll](https://github.com/mtr/angular-iscroll/), with a plethora of devices and browsers. The following browsers and devices has been tested successfully: 182 | 183 | * Desktop 184 | * Chrome 16–48 on OS X 10.10 185 | * Chrome 48 on Ubuntu 14.04 186 | * Firefox 6.0–43.0 on OS X 10.10 187 | * Firefox 43.0.4 on Ubuntu 14.04 188 | * Internet Explorer 9, 10, and 11 on Windows 7 189 | * Internet Explorer Edge 12 on Windows 10 190 | * Opera 12.12–34 on OS X 10.10 191 | * Safari 5.1.7 on Windows 10 192 | * Safari 8.0.8 on OS X 10.10.5 193 | * Yandex 14.12 on OS X 10.10 194 | * Mobile 195 | * Android Browser 4.0 with Android 4.0.4 on Samsung Galaxy Note 10.1 196 | * Android Browser 4.0 with Android 4.1.2 on Samsung Galaxy S3 197 | * Chrome Mobile 45 with Android 5.0.2 on Samsung Galaxy S6 198 | * Chrome Mobile 45 with Android 4.4 on Google Nexus 5 199 | * Safari 4.0.5 with iOS 4.0.1 on iPhone 4 200 | * Safari 5.0.2 with iOS 4.3.2 on iPad 2 201 | * Safari 5.1 with iOS 5.0 on iPad 2 202 | * Safari 5.1 with iOS 5.1 on iPhone 4S 203 | * Safari 6.0 with iOS 6.0 on iPhone 5 204 | * Safari 7.0 with iOS 7.0.4 on iPad Air 205 | * Safari 8.0 with iOS 8.1.1 on iPad Air 2 206 | * Safari 9.0 with iOS 9.0.2 on iPhone 6S 207 | * IE Mobile 11.0 with Windows Phone 8.1 on Nokia Lumia 520, 925, and 930 208 | 209 | ### Incompatible Browsers ### 210 | During testing, the [core-layout](http://mtr.github.io/core-layout/examples/) demo broke in the following browsers: 211 | 212 | * Firefox 3.6, 4, and 5 on OS X 10.10 213 | * Internet Explorer 8 on Windows 7 (fails during jQuery version 2.2.0 initialization): 214 | ``` 215 | // Use the handy event callback 216 | document.addEventListener( "DOMContentLoaded", completed ); 217 | ``` 218 | Error message: `Object doesn't support this property or method`. 219 | * Safari 3 with iOS 3 on iPhone 3GS 220 | 221 | This does not necessarily mean that `angular-iscroll` itself breaks in the same browsers, but the demo code did. 222 | -------------------------------------------------------------------------------- /dist/lib/angular-iscroll.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see angular-iscroll.js.LICENSE.txt */ 2 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("angular")):"function"==typeof define&&define.amd?define(["angular"],e):"object"==typeof exports?exports.angularIscroll=e(require("angular")):t.angularIscroll=e(t.angular)}(self,(function(t){return function(){var e={495:function(t,e,i){var s;!function(o,n,r){var a=o.requestAnimationFrame||o.webkitRequestAnimationFrame||o.mozRequestAnimationFrame||o.oRequestAnimationFrame||o.msRequestAnimationFrame||function(t){o.setTimeout(t,1e3/60)},l=function(){var t={},e=n.createElement("div").style,i=function(){for(var t=["t","webkitT","MozT","msT","OT"],i=0,s=t.length;i0&&(a=o?o/2.5*(c/8):0,l=(h=r.abs(t)+a)/c),{destination:r.round(a),duration:l}};var a=s("transform");return t.extend(t,{hasTransform:!1!==a,hasPerspective:s("perspective")in e,hasTouch:"ontouchstart"in o,hasPointer:!(!o.PointerEvent&&!o.MSPointerEvent),hasTransition:s("transition")in e}),t.isBadAndroid=function(){var t=o.navigator.appVersion;if(/Android/.test(t)&&!/Chrome\/\d/.test(t)){var e=t.match(/Safari\/(\d+.\d)/);return!(e&&"object"==typeof e&&e.length>=2)||parseFloat(e[1])<535.19}return!1}(),t.extend(t.style={},{transform:a,transitionTimingFunction:s("transitionTimingFunction"),transitionDuration:s("transitionDuration"),transitionDelay:s("transitionDelay"),transformOrigin:s("transformOrigin")}),t.hasClass=function(t,e){return new RegExp("(^|\\s)"+e+"(\\s|$)").test(t.className)},t.addClass=function(e,i){if(!t.hasClass(e,i)){var s=e.className.split(" ");s.push(i),e.className=s.join(" ")}},t.removeClass=function(e,i){if(t.hasClass(e,i)){var s=new RegExp("(^|\\s)"+i+"(\\s|$)","g");e.className=e.className.replace(s," ")}},t.offset=function(t){for(var e=-t.offsetLeft,i=-t.offsetTop;t=t.offsetParent;)e-=t.offsetLeft,i-=t.offsetTop;return{left:e,top:i}},t.preventDefaultException=function(t,e){for(var i in e)if(e[i].test(t[i]))return!0;return!1},t.extend(t.eventType={},{touchstart:1,touchmove:1,touchend:1,mousedown:2,mousemove:2,mouseup:2,pointerdown:3,pointermove:3,pointerup:3,MSPointerDown:3,MSPointerMove:3,MSPointerUp:3}),t.extend(t.ease={},{quadratic:{style:"cubic-bezier(0.25, 0.46, 0.45, 0.94)",fn:function(t){return t*(2-t)}},circular:{style:"cubic-bezier(0.1, 0.57, 0.1, 1)",fn:function(t){return r.sqrt(1- --t*t)}},back:{style:"cubic-bezier(0.175, 0.885, 0.32, 1.275)",fn:function(t){return(t-=1)*t*(5*t+4)+1}},bounce:{style:"",fn:function(t){return(t/=1)<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}},elastic:{style:"",fn:function(t){return 0===t?0:1==t?1:.4*r.pow(2,-10*t)*r.sin((t-.055)*(2*r.PI)/.22)+1}}}),t.tap=function(t,e){var i=n.createEvent("Event");i.initEvent(e,!0,!0),i.pageX=t.pageX,i.pageY=t.pageY,t.target.dispatchEvent(i)},t.click=function(t){var e,i=t.target;/(SELECT|INPUT|TEXTAREA)/i.test(i.tagName)||((e=n.createEvent("MouseEvents")).initMouseEvent("click",!0,!0,t.view,1,i.screenX,i.screenY,i.clientX,i.clientY,t.ctrlKey,t.altKey,t.shiftKey,t.metaKey,0,null),e._constructed=!0,i.dispatchEvent(e))},t}();function h(t,e){for(var i in this.wrapper="string"==typeof t?n.querySelector(t):t,this.scroller=this.wrapper.children[0],this.scrollerStyle=this.scroller.style,this.options={resizeScrollbars:!0,mouseWheelSpeed:20,snapThreshold:.334,disablePointer:!l.hasPointer,disableTouch:l.hasPointer||!l.hasTouch,disableMouse:l.hasPointer||l.hasTouch,startX:0,startY:0,scrollY:!0,directionLockThreshold:5,momentum:!0,bounce:!0,bounceTime:600,bounceEasing:"",preventDefault:!0,preventDefaultException:{tagName:/^(INPUT|TEXTAREA|BUTTON|SELECT)$/},HWCompositing:!0,useTransition:!0,useTransform:!0,bindToWrapper:void 0===o.onmousedown},e)this.options[i]=e[i];this.translateZ=this.options.HWCompositing&&l.hasPerspective?" translateZ(0)":"",this.options.useTransition=l.hasTransition&&this.options.useTransition,this.options.useTransform=l.hasTransform&&this.options.useTransform,this.options.eventPassthrough=!0===this.options.eventPassthrough?"vertical":this.options.eventPassthrough,this.options.preventDefault=!this.options.eventPassthrough&&this.options.preventDefault,this.options.scrollY="vertical"!=this.options.eventPassthrough&&this.options.scrollY,this.options.scrollX="horizontal"!=this.options.eventPassthrough&&this.options.scrollX,this.options.freeScroll=this.options.freeScroll&&!this.options.eventPassthrough,this.options.directionLockThreshold=this.options.eventPassthrough?0:this.options.directionLockThreshold,this.options.bounceEasing="string"==typeof this.options.bounceEasing?l.ease[this.options.bounceEasing]||l.ease.circular:this.options.bounceEasing,this.options.resizePolling=void 0===this.options.resizePolling?60:this.options.resizePolling,!0===this.options.tap&&(this.options.tap="tap"),"scale"==this.options.shrinkScrollbars&&(this.options.useTransition=!1),this.options.invertWheelDirection=this.options.invertWheelDirection?-1:1,this.x=0,this.y=0,this.directionX=0,this.directionY=0,this._events={},this._init(),this.refresh(),this.scrollTo(this.options.startX,this.options.startY),this.enable()}function c(t,e,i){var s=n.createElement("div"),o=n.createElement("div");return!0===i&&(s.style.cssText="position:absolute;z-index:9999",o.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px"),o.className="iScrollIndicator","h"==t?(!0===i&&(s.style.cssText+=";height:7px;left:2px;right:2px;bottom:0",o.style.height="100%"),s.className="iScrollHorizontalScrollbar"):(!0===i&&(s.style.cssText+=";width:7px;bottom:2px;top:2px;right:1px",o.style.width="100%"),s.className="iScrollVerticalScrollbar"),s.style.cssText+=";overflow:hidden",e||(s.style.pointerEvents="none"),s.appendChild(o),s}function p(t,e){for(var i in this.wrapper="string"==typeof e.el?n.querySelector(e.el):e.el,this.wrapperStyle=this.wrapper.style,this.indicator=this.wrapper.children[0],this.indicatorStyle=this.indicator.style,this.scroller=t,this.options={listenX:!0,listenY:!0,interactive:!1,resize:!0,defaultScrollbars:!1,shrink:!1,fade:!1,speedRatioX:0,speedRatioY:0},e)this.options[i]=e[i];if(this.sizeRatioX=1,this.sizeRatioY=1,this.maxPosX=0,this.maxPosY=0,this.options.interactive&&(this.options.disableTouch||(l.addEvent(this.indicator,"touchstart",this),l.addEvent(o,"touchend",this)),this.options.disablePointer||(l.addEvent(this.indicator,l.prefixPointerEvent("pointerdown"),this),l.addEvent(o,l.prefixPointerEvent("pointerup"),this)),this.options.disableMouse||(l.addEvent(this.indicator,"mousedown",this),l.addEvent(o,"mouseup",this))),this.options.fade){this.wrapperStyle[l.style.transform]=this.scroller.translateZ;var s=l.style.transitionDuration;this.wrapperStyle[s]=l.isBadAndroid?"0.0001ms":"0ms";var r=this;l.isBadAndroid&&a((function(){"0.0001ms"===r.wrapperStyle[s]&&(r.wrapperStyle[s]="0s")})),this.wrapperStyle.opacity="0"}}h.prototype={version:"5.2.0",_init:function(){this._initEvents(),(this.options.scrollbars||this.options.indicators)&&this._initIndicators(),this.options.mouseWheel&&this._initWheel(),this.options.snap&&this._initSnap(),this.options.keyBindings&&this._initKeys()},destroy:function(){this._initEvents(!0),clearTimeout(this.resizeTimeout),this.resizeTimeout=null,this._execEvent("destroy")},_transitionEnd:function(t){t.target==this.scroller&&this.isInTransition&&(this._transitionTime(),this.resetPosition(this.options.bounceTime)||(this.isInTransition=!1,this._execEvent("scrollEnd")))},_start:function(t){if((1==l.eventType[t.type]||0===(t.which?t.button:t.button<2?0:4==t.button?1:2))&&this.enabled&&(!this.initiated||l.eventType[t.type]===this.initiated)){!this.options.preventDefault||l.isBadAndroid||l.preventDefaultException(t.target,this.options.preventDefaultException)||t.preventDefault();var e,i=t.touches?t.touches[0]:t;this.initiated=l.eventType[t.type],this.moved=!1,this.distX=0,this.distY=0,this.directionX=0,this.directionY=0,this.directionLocked=0,this.startTime=l.getTime(),this.options.useTransition&&this.isInTransition?(this._transitionTime(),this.isInTransition=!1,e=this.getComputedPosition(),this._translate(r.round(e.x),r.round(e.y)),this._execEvent("scrollEnd")):!this.options.useTransition&&this.isAnimating&&(this.isAnimating=!1,this._execEvent("scrollEnd")),this.startX=this.x,this.startY=this.y,this.absStartX=this.x,this.absStartY=this.y,this.pointX=i.pageX,this.pointY=i.pageY,this._execEvent("beforeScrollStart")}},_move:function(t){if(this.enabled&&l.eventType[t.type]===this.initiated){this.options.preventDefault&&t.preventDefault();var e,i,s,o,n=t.touches?t.touches[0]:t,a=n.pageX-this.pointX,h=n.pageY-this.pointY,c=l.getTime();if(this.pointX=n.pageX,this.pointY=n.pageY,this.distX+=a,this.distY+=h,s=r.abs(this.distX),o=r.abs(this.distY),!(c-this.endTime>300&&s<10&&o<10)){if(this.directionLocked||this.options.freeScroll||(s>o+this.options.directionLockThreshold?this.directionLocked="h":o>=s+this.options.directionLockThreshold?this.directionLocked="v":this.directionLocked="n"),"h"==this.directionLocked){if("vertical"==this.options.eventPassthrough)t.preventDefault();else if("horizontal"==this.options.eventPassthrough)return void(this.initiated=!1);h=0}else if("v"==this.directionLocked){if("horizontal"==this.options.eventPassthrough)t.preventDefault();else if("vertical"==this.options.eventPassthrough)return void(this.initiated=!1);a=0}a=this.hasHorizontalScroll?a:0,h=this.hasVerticalScroll?h:0,e=this.x+a,i=this.y+h,(e>0||e0?0:this.maxScrollX),(i>0||i0?0:this.maxScrollY),this.directionX=a>0?-1:a<0?1:0,this.directionY=h>0?-1:h<0?1:0,this.moved||this._execEvent("scrollStart"),this.moved=!0,this._translate(e,i),c-this.startTime>300&&(this.startTime=c,this.startX=this.x,this.startY=this.y)}}},_end:function(t){if(this.enabled&&l.eventType[t.type]===this.initiated){this.options.preventDefault&&!l.preventDefaultException(t.target,this.options.preventDefaultException)&&t.preventDefault(),t.changedTouches&&t.changedTouches[0];var e,i,s=l.getTime()-this.startTime,o=r.round(this.x),n=r.round(this.y),a=r.abs(o-this.startX),h=r.abs(n-this.startY),c=0,p="";if(this.isInTransition=0,this.initiated=0,this.endTime=l.getTime(),!this.resetPosition(this.options.bounceTime)){if(this.scrollTo(o,n),!this.moved)return this.options.tap&&l.tap(t,this.options.tap),this.options.click&&l.click(t),void this._execEvent("scrollCancel");if(this._events.flick&&s<200&&a<100&&h<100)this._execEvent("flick");else{if(this.options.momentum&&s<300&&(e=this.hasHorizontalScroll?l.momentum(this.x,this.startX,s,this.maxScrollX,this.options.bounce?this.wrapperWidth:0,this.options.deceleration):{destination:o,duration:0},i=this.hasVerticalScroll?l.momentum(this.y,this.startY,s,this.maxScrollY,this.options.bounce?this.wrapperHeight:0,this.options.deceleration):{destination:n,duration:0},o=e.destination,n=i.destination,c=r.max(e.duration,i.duration),this.isInTransition=1),this.options.snap){var d=this._nearestSnap(o,n);this.currentPage=d,c=this.options.snapSpeed||r.max(r.max(r.min(r.abs(o-d.x),1e3),r.min(r.abs(n-d.y),1e3)),300),o=d.x,n=d.y,this.directionX=0,this.directionY=0,p=this.options.bounceEasing}if(o!=this.x||n!=this.y)return(o>0||o0||n0?e=0:this.x0?i=0:this.y-1&&this._events[t].splice(i,1)}},_execEvent:function(t){if(this._events[t]){var e=0,i=this._events[t].length;if(i)for(;e0;var o=this.options.useTransition&&s.style;!i||o?(o&&(this._transitionTimingFunction(s.style),this._transitionTime(i)),this._translate(t,e)):this._animate(t,e,i,s.fn)},scrollToElement:function(t,e,i,s,o){if(t=t.nodeType?t:this.scroller.querySelector(t)){var n=l.offset(t);n.left-=this.wrapperOffset.left,n.top-=this.wrapperOffset.top,!0===i&&(i=r.round(t.offsetWidth/2-this.wrapper.offsetWidth/2)),!0===s&&(s=r.round(t.offsetHeight/2-this.wrapper.offsetHeight/2)),n.left-=i||0,n.top-=s||0,n.left=n.left>0?0:n.left0?0:n.top0?s--:e<0&&s++,i>0?o--:i<0&&o++,void this.goToPage(s,o);s=this.x+r.round(this.hasHorizontalScroll?e:0),o=this.y+r.round(this.hasVerticalScroll?i:0),this.directionX=e>0?-1:e<0?1:0,this.directionY=i>0?-1:i<0?1:0,s>0?s=0:s0?o=0:o-this.scrollerWidth;){for(this.pages[a]=[],t=0,o=0;o>-this.scrollerHeight;)this.pages[a][t]={x:r.max(h,this.maxScrollX),y:r.max(o,this.maxScrollY),width:c,height:p,cx:h-i,cy:o-s},o-=p,t++;h-=c,a++}else for(t=(n=this.options.snap).length,e=-1;athis.maxScrollX&&l++;this.goToPage(this.currentPage.pageX||0,this.currentPage.pageY||0,0),this.options.snapThreshold%1==0?(this.snapThresholdX=this.options.snapThreshold,this.snapThresholdY=this.options.snapThreshold):(this.snapThresholdX=r.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width*this.options.snapThreshold),this.snapThresholdY=r.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height*this.options.snapThreshold))}})),this.on("flick",(function(){var t=this.options.snapSpeed||r.max(r.max(r.min(r.abs(this.x-this.startX),1e3),r.min(r.abs(this.y-this.startY),1e3)),300);this.goToPage(this.currentPage.pageX+this.directionX,this.currentPage.pageY+this.directionY,t)}))},_nearestSnap:function(t,e){if(!this.pages.length)return{x:0,y:0,pageX:0,pageY:0};var i=0,s=this.pages.length,o=0;if(r.abs(t-this.absStartX)0?t=0:t0?e=0:e=this.pages[i][0].cx){t=this.pages[i][0].x;break}for(s=this.pages[i].length;o=this.pages[0][o].cy){e=this.pages[0][o].y;break}return i==this.currentPage.pageX&&((i+=this.directionX)<0?i=0:i>=this.pages.length&&(i=this.pages.length-1),t=this.pages[i][0].x),o==this.currentPage.pageY&&((o+=this.directionY)<0?o=0:o>=this.pages[0].length&&(o=this.pages[0].length-1),e=this.pages[0][o].y),{x:t,y:e,pageX:i,pageY:o}},goToPage:function(t,e,i,s){s=s||this.options.bounceEasing,t>=this.pages.length?t=this.pages.length-1:t<0&&(t=0),e>=this.pages[t].length?e=this.pages[t].length-1:e<0&&(e=0);var o=this.pages[t][e].x,n=this.pages[t][e].y;i=void 0===i?this.options.snapSpeed||r.max(r.max(r.min(r.abs(o-this.x),1e3),r.min(r.abs(n-this.y),1e3)),300):i,this.currentPage={x:o,y:n,pageX:t,pageY:e},this.scrollTo(o,n,i,s)},next:function(t,e){var i=this.currentPage.pageX,s=this.currentPage.pageY;++i>=this.pages.length&&this.hasVerticalScroll&&(i=0,s++),this.goToPage(i,s,t,e)},prev:function(t,e){var i=this.currentPage.pageX,s=this.currentPage.pageY;--i<0&&this.hasVerticalScroll&&(i=0,s--),this.goToPage(i,s,t,e)},_initKeys:function(t){var e,i={pageUp:33,pageDown:34,end:35,home:36,left:37,up:38,right:39,down:40};if("object"==typeof this.options.keyBindings)for(e in this.options.keyBindings)"string"==typeof this.options.keyBindings[e]&&(this.options.keyBindings[e]=this.options.keyBindings[e].toUpperCase().charCodeAt(0));else this.options.keyBindings={};for(e in i)this.options.keyBindings[e]=this.options.keyBindings[e]||i[e];l.addEvent(o,"keydown",this),this.on("destroy",(function(){l.removeEvent(o,"keydown",this)}))},_key:function(t){if(this.enabled){var e,i=this.options.snap,s=i?this.currentPage.pageX:this.x,o=i?this.currentPage.pageY:this.y,n=l.getTime(),a=this.keyTime||0;switch(this.options.useTransition&&this.isInTransition&&(e=this.getComputedPosition(),this._translate(r.round(e.x),r.round(e.y)),this.isInTransition=!1),this.keyAcceleration=n-a<200?r.min(this.keyAcceleration+.25,50):0,t.keyCode){case this.options.keyBindings.pageUp:this.hasHorizontalScroll&&!this.hasVerticalScroll?s+=i?1:this.wrapperWidth:o+=i?1:this.wrapperHeight;break;case this.options.keyBindings.pageDown:this.hasHorizontalScroll&&!this.hasVerticalScroll?s-=i?1:this.wrapperWidth:o-=i?1:this.wrapperHeight;break;case this.options.keyBindings.end:s=i?this.pages.length-1:this.maxScrollX,o=i?this.pages[0].length-1:this.maxScrollY;break;case this.options.keyBindings.home:s=0,o=0;break;case this.options.keyBindings.left:s+=i?-1:5+this.keyAcceleration>>0;break;case this.options.keyBindings.up:o+=i?1:5+this.keyAcceleration>>0;break;case this.options.keyBindings.right:s-=i?-1:5+this.keyAcceleration>>0;break;case this.options.keyBindings.down:o-=i?1:5+this.keyAcceleration>>0;break;default:return}i?this.goToPage(s,o):(s>0?(s=0,this.keyAcceleration=0):s0?(o=0,this.keyAcceleration=0):o=c)return o.isAnimating=!1,o._translate(t,e),void(o.resetPosition(o.options.bounceTime)||o._execEvent("scrollEnd"));f=s(m=(m-h)/i),d=(t-n)*f+n,u=(e-r)*f+r,o._translate(d,u),o.isAnimating&&a(p)}()},handleEvent:function(t){switch(t.type){case"touchstart":case"pointerdown":case"MSPointerDown":case"mousedown":this._start(t);break;case"touchmove":case"pointermove":case"MSPointerMove":case"mousemove":this._move(t);break;case"touchend":case"pointerup":case"MSPointerUp":case"mouseup":case"touchcancel":case"pointercancel":case"MSPointerCancel":case"mousecancel":this._end(t);break;case"orientationchange":case"resize":this._resize();break;case"transitionend":case"webkitTransitionEnd":case"oTransitionEnd":case"MSTransitionEnd":this._transitionEnd(t);break;case"wheel":case"DOMMouseScroll":case"mousewheel":this._wheel(t);break;case"keydown":this._key(t);break;case"click":this.enabled&&!t._constructed&&(t.preventDefault(),t.stopPropagation())}}},p.prototype={handleEvent:function(t){switch(t.type){case"touchstart":case"pointerdown":case"MSPointerDown":case"mousedown":this._start(t);break;case"touchmove":case"pointermove":case"MSPointerMove":case"mousemove":this._move(t);break;case"touchend":case"pointerup":case"MSPointerUp":case"mouseup":case"touchcancel":case"pointercancel":case"MSPointerCancel":case"mousecancel":this._end(t)}},destroy:function(){this.options.fadeScrollbars&&(clearTimeout(this.fadeTimeout),this.fadeTimeout=null),this.options.interactive&&(l.removeEvent(this.indicator,"touchstart",this),l.removeEvent(this.indicator,l.prefixPointerEvent("pointerdown"),this),l.removeEvent(this.indicator,"mousedown",this),l.removeEvent(o,"touchmove",this),l.removeEvent(o,l.prefixPointerEvent("pointermove"),this),l.removeEvent(o,"mousemove",this),l.removeEvent(o,"touchend",this),l.removeEvent(o,l.prefixPointerEvent("pointerup"),this),l.removeEvent(o,"mouseup",this)),this.options.defaultScrollbars&&this.wrapper.parentNode.removeChild(this.wrapper)},_start:function(t){var e=t.touches?t.touches[0]:t;t.preventDefault(),t.stopPropagation(),this.transitionTime(),this.initiated=!0,this.moved=!1,this.lastPointX=e.pageX,this.lastPointY=e.pageY,this.startTime=l.getTime(),this.options.disableTouch||l.addEvent(o,"touchmove",this),this.options.disablePointer||l.addEvent(o,l.prefixPointerEvent("pointermove"),this),this.options.disableMouse||l.addEvent(o,"mousemove",this),this.scroller._execEvent("beforeScrollStart")},_move:function(t){var e,i,s,o,n=t.touches?t.touches[0]:t;l.getTime(),this.moved||this.scroller._execEvent("scrollStart"),this.moved=!0,e=n.pageX-this.lastPointX,this.lastPointX=n.pageX,i=n.pageY-this.lastPointY,this.lastPointY=n.pageY,s=this.x+e,o=this.y+i,this._pos(s,o),t.preventDefault(),t.stopPropagation()},_end:function(t){if(this.initiated){if(this.initiated=!1,t.preventDefault(),t.stopPropagation(),l.removeEvent(o,"touchmove",this),l.removeEvent(o,l.prefixPointerEvent("pointermove"),this),l.removeEvent(o,"mousemove",this),this.scroller.options.snap){var e=this.scroller._nearestSnap(this.scroller.x,this.scroller.y),i=this.options.snapSpeed||r.max(r.max(r.min(r.abs(this.scroller.x-e.x),1e3),r.min(r.abs(this.scroller.y-e.y),1e3)),300);this.scroller.x==e.x&&this.scroller.y==e.y||(this.scroller.directionX=0,this.scroller.directionY=0,this.scroller.currentPage=e,this.scroller.scrollTo(e.x,e.y,i,this.scroller.options.bounceEasing))}this.moved&&this.scroller._execEvent("scrollEnd")}},transitionTime:function(t){t=t||0;var e=l.style.transitionDuration;if(this.indicatorStyle[e]=t+"ms",!t&&l.isBadAndroid){this.indicatorStyle[e]="0.0001ms";var i=this;a((function(){"0.0001ms"===i.indicatorStyle[e]&&(i.indicatorStyle[e]="0s")}))}},transitionTimingFunction:function(t){this.indicatorStyle[l.style.transitionTimingFunction]=t},refresh:function(){this.transitionTime(),this.options.listenX&&!this.options.listenY?this.indicatorStyle.display=this.scroller.hasHorizontalScroll?"block":"none":this.options.listenY&&!this.options.listenX?this.indicatorStyle.display=this.scroller.hasVerticalScroll?"block":"none":this.indicatorStyle.display=this.scroller.hasHorizontalScroll||this.scroller.hasVerticalScroll?"block":"none",this.scroller.hasHorizontalScroll&&this.scroller.hasVerticalScroll?(l.addClass(this.wrapper,"iScrollBothScrollbars"),l.removeClass(this.wrapper,"iScrollLoneScrollbar"),this.options.defaultScrollbars&&this.options.customStyle&&(this.options.listenX?this.wrapper.style.right="8px":this.wrapper.style.bottom="8px")):(l.removeClass(this.wrapper,"iScrollBothScrollbars"),l.addClass(this.wrapper,"iScrollLoneScrollbar"),this.options.defaultScrollbars&&this.options.customStyle&&(this.options.listenX?this.wrapper.style.right="2px":this.wrapper.style.bottom="2px")),this.wrapper.offsetHeight,this.options.listenX&&(this.wrapperWidth=this.wrapper.clientWidth,this.options.resize?(this.indicatorWidth=r.max(r.round(this.wrapperWidth*this.wrapperWidth/(this.scroller.scrollerWidth||this.wrapperWidth||1)),8),this.indicatorStyle.width=this.indicatorWidth+"px"):this.indicatorWidth=this.indicator.clientWidth,this.maxPosX=this.wrapperWidth-this.indicatorWidth,"clip"==this.options.shrink?(this.minBoundaryX=8-this.indicatorWidth,this.maxBoundaryX=this.wrapperWidth-8):(this.minBoundaryX=0,this.maxBoundaryX=this.maxPosX),this.sizeRatioX=this.options.speedRatioX||this.scroller.maxScrollX&&this.maxPosX/this.scroller.maxScrollX),this.options.listenY&&(this.wrapperHeight=this.wrapper.clientHeight,this.options.resize?(this.indicatorHeight=r.max(r.round(this.wrapperHeight*this.wrapperHeight/(this.scroller.scrollerHeight||this.wrapperHeight||1)),8),this.indicatorStyle.height=this.indicatorHeight+"px"):this.indicatorHeight=this.indicator.clientHeight,this.maxPosY=this.wrapperHeight-this.indicatorHeight,"clip"==this.options.shrink?(this.minBoundaryY=8-this.indicatorHeight,this.maxBoundaryY=this.wrapperHeight-8):(this.minBoundaryY=0,this.maxBoundaryY=this.maxPosY),this.maxPosY=this.wrapperHeight-this.indicatorHeight,this.sizeRatioY=this.options.speedRatioY||this.scroller.maxScrollY&&this.maxPosY/this.scroller.maxScrollY),this.updatePosition()},updatePosition:function(){var t=this.options.listenX&&r.round(this.sizeRatioX*this.scroller.x)||0,e=this.options.listenY&&r.round(this.sizeRatioY*this.scroller.y)||0;this.options.ignoreBoundaries||(tthis.maxBoundaryX?"scale"==this.options.shrink?(this.width=r.max(this.indicatorWidth-(t-this.maxPosX),8),this.indicatorStyle.width=this.width+"px",t=this.maxPosX+this.indicatorWidth-this.width):t=this.maxBoundaryX:"scale"==this.options.shrink&&this.width!=this.indicatorWidth&&(this.width=this.indicatorWidth,this.indicatorStyle.width=this.width+"px"),ethis.maxBoundaryY?"scale"==this.options.shrink?(this.height=r.max(this.indicatorHeight-3*(e-this.maxPosY),8),this.indicatorStyle.height=this.height+"px",e=this.maxPosY+this.indicatorHeight-this.height):e=this.maxBoundaryY:"scale"==this.options.shrink&&this.height!=this.indicatorHeight&&(this.height=this.indicatorHeight,this.indicatorStyle.height=this.height+"px")),this.x=t,this.y=e,this.scroller.options.useTransform?this.indicatorStyle[l.style.transform]="translate("+t+"px,"+e+"px)"+this.scroller.translateZ:(this.indicatorStyle.left=t+"px",this.indicatorStyle.top=e+"px")},_pos:function(t,e){t<0?t=0:t>this.maxPosX&&(t=this.maxPosX),e<0?e=0:e>this.maxPosY&&(e=this.maxPosY),t=this.options.listenX?r.round(t/this.sizeRatioX):this.scroller.x,e=this.options.listenY?r.round(e/this.sizeRatioY):this.scroller.y,this.scroller.scrollTo(t,e)},fade:function(t,e){if(!e||this.visible){clearTimeout(this.fadeTimeout),this.fadeTimeout=null;var i=t?250:500,s=t?0:300;t=t?"1":"0",this.wrapperStyle[l.style.transitionDuration]=i+"ms",this.fadeTimeout=setTimeout(function(t){this.wrapperStyle.opacity=t,this.visible=+t}.bind(this,t),s)}}},h.utils=l,t.exports?t.exports=h:void 0===(s=function(){return h}.call(e,i,e,t))||(t.exports=s)}(window,document,Math)},795:function(t,e,i){var s;t=i.nmd(t),function(){"use strict";var o={function:!0,object:!0},n=o[typeof window]&&window||this,r=o[typeof e]&&e,a=o.object&&t&&!t.nodeType&&t,l=r&&a&&"object"==typeof i.g&&i.g;!l||l.global!==l&&l.window!==l&&l.self!==l||(n=l);var h=Math.pow(2,53)-1,c=/\bOpera/,p=Object.prototype,d=p.hasOwnProperty,u=p.toString;function f(t){return(t=String(t)).charAt(0).toUpperCase()+t.slice(1)}function m(t){return t=x(t),/^(?:webOS|i(?:OS|P))/.test(t)?t:f(t)}function b(t,e){for(var i in t)d.call(t,i)&&e(t[i],i,t)}function g(t){return null==t?f(t):u.call(t).slice(8,-1)}function v(t){return String(t).replace(/([ -])(?!$)/g,"$1?")}function S(t,e){var i=null;return function(t,e){var i=-1,s=t?t.length:0;if("number"==typeof s&&s>-1&&s<=h)for(;++i3?"WebKit":/\bOpera\b/.test(I)&&(/\bOPR\b/.test(e)?"Blink":"Presto"))||/\b(?:Midori|Nook|Safari)\b/i.test(e)&&!/^(?:Trident|EdgeHTML)$/.test(H)&&"WebKit"||!H&&/\bMSIE\b/i.test(e)&&("Mac OS"==F?"Tasman":"Trident")||"WebKit"==H&&/\bPlayStation\b(?! Vita\b)/i.test(I)&&"NetFront")&&(H=[a]),"IE"==I&&(a=(/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(e)||0)[1])?(I+=" Mobile",F="Windows Phone "+(/\+$/.test(a)?a:a+".x"),B.unshift("desktop mode")):/\bWPDesktop\b/i.test(e)?(I="IE Mobile",F="Windows Phone 8.x",B.unshift("desktop mode"),D||(D=(/\brv:([\d.]+)/.exec(e)||0)[1])):"IE"!=I&&"Trident"==H&&(a=/\brv:([\d.]+)/.exec(e))&&(I&&B.push("identifying as "+I+(D?" "+D:"")),I="IE",D=a[1]),z){if(p="global",d=null!=(h=i)?typeof h[p]:"number",/^(?:boolean|number|string|undefined)$/.test(d)||"object"==d&&!h[p])g(a=i.runtime)==w?(I="Adobe AIR",F=a.flash.system.Capabilities.os):g(a=i.phantom)==P?(I="PhantomJS",D=(a=a.version||null)&&a.major+"."+a.minor+"."+a.patch):"number"==typeof W.documentMode&&(a=/\bTrident\/(\d+)/i.exec(e))?(D=[D,W.documentMode],(a=+a[1]+4)!=D[1]&&(B.push("IE "+D[1]+" mode"),H&&(H[1]=""),D[1]=a),D="IE"==I?String(D[1].toFixed(1)):D[0]):"number"==typeof W.documentMode&&/^(?:Chrome|Firefox)\b/.test(I)&&(B.push("masking as "+I+" "+D),I="IE",D="11.0",H=["Trident"],F="Windows");else if(k&&(C=(a=k.lang.System).getProperty("os.arch"),F=F||a.getProperty("os.name")+" "+a.getProperty("os.version")),X){try{D=i.require("ringo/engine").version.join("."),I="RingoJS"}catch(t){(a=i.system)&&a.global.system==i.system&&(I="Narwhal",F||(F=a[0].os||null))}I||(I="Rhino")}else"object"==typeof i.process&&!i.process.browser&&(a=i.process)&&("object"==typeof a.versions&&("string"==typeof a.versions.electron?(B.push("Node "+a.versions.node),I="Electron",D=a.versions.electron):"string"==typeof a.versions.nw&&(B.push("Chromium "+D,"Node "+a.versions.node),I="NW.js",D=a.versions.nw)),I||(I="Node.js",C=a.arch,F=a.platform,D=(D=/[\d.]+/.exec(a.version))?D[0]:null));F=F&&m(F)}if(D&&(a=/(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(D)||/(?:alpha|beta)(?: ?\d)?/i.exec(e+";"+(z&&o.appMinorVersion))||/\bMinefield\b/i.test(e)&&"a")&&(A=/b/i.test(a)?"beta":"alpha",D=D.replace(RegExp(a+"\\+?$"),"")+("beta"==A?M:Y)+(/\d+\+?/.exec(a)||"")),"Fennec"==I||"Firefox"==I&&/\b(?:Android|Firefox OS|KaiOS)\b/.test(F))I="Firefox Mobile";else if("Maxthon"==I&&D)D=D.replace(/\.[\d.]+/,".x");else if(/\bXbox\b/i.test(R))"Xbox 360"==R&&(F=null),"Xbox 360"==R&&/\bIEMobile\b/.test(e)&&B.unshift("mobile mode");else if(!/^(?:Chrome|IE|Opera)$/.test(I)&&(!I||R||/Browser|Mobi/.test(I))||"Windows CE"!=F&&!/Mobi/i.test(e))if("IE"==I&&z)try{null===i.external&&B.unshift("platform preview")}catch(t){B.unshift("embedded")}else(/\bBlackBerry\b/.test(R)||/\bBB10\b/.test(e))&&(a=(RegExp(R.replace(/ +/g," *")+"/([.\\d]+)","i").exec(e)||0)[1]||D)?(F=((a=[a,/BB10/.test(e)])[1]?(R=null,$="BlackBerry"):"Device Software")+" "+a[0],D=null):this!=b&&"Wii"!=R&&(z&&O||/Opera/.test(I)&&/\b(?:MSIE|Firefox)\b/i.test(e)||"Firefox"==I&&/\bOS X (?:\d+\.){2,}/.test(F)||"IE"==I&&(F&&!/^Win/.test(F)&&D>5.5||/\bWindows XP\b/.test(F)&&D>8||8==D&&!/\bTrident\b/.test(e)))&&!c.test(a=t.call(b,e.replace(c,"")+";"))&&a.name&&(a="ing as "+a.name+((a=a.version)?" "+a:""),c.test(I)?(/\bIE\b/.test(a)&&"Mac OS"==F&&(F=null),a="identify"+a):(a="mask"+a,I=_?m(_.replace(/([a-z])([A-Z])/g,"$1 $2")):"Opera",/\bIE\b/.test(a)&&(F=null),z||(D=null)),H=["Presto"],B.push(a));else I+=" Mobile";(a=(/\bAppleWebKit\/([\d.]+\+?)/i.exec(e)||0)[1])&&(a=[parseFloat(a.replace(/\.(\d)$/,".0$1")),a],"Safari"==I&&"+"==a[1].slice(-1)?(I="WebKit Nightly",A="alpha",D=a[1].slice(0,-1)):D!=a[1]&&D!=(a[2]=(/\bSafari\/([\d.]+\+?)/i.exec(e)||0)[1])||(D=null),a[1]=(/\b(?:Headless)?Chrome\/([\d.]+)/i.exec(e)||0)[1],537.36==a[0]&&537.36==a[2]&&parseFloat(a[1])>=28&&"WebKit"==H&&(H=["Blink"]),z&&(f||a[1])?(H&&(H[1]="like Chrome"),a=a[1]||((a=a[0])<530?1:a<532?2:a<532.05?3:a<533?4:a<534.03?5:a<534.07?6:a<534.1?7:a<534.13?8:a<534.16?9:a<534.24?10:a<534.3?11:a<535.01?12:a<535.02?"13+":a<535.07?15:a<535.11?16:a<535.19?17:a<536.05?18:a<536.1?19:a<537.01?20:a<537.11?"21+":a<537.13?23:a<537.18?24:a<537.24?25:a<537.36?26:"Blink"!=H?"27":"28")):(H&&(H[1]="like Safari"),a=(a=a[0])<400?1:a<500?2:a<526?3:a<533?4:a<534?"4+":a<535?5:a<537?6:a<538?7:a<601?8:a<602?9:a<604?10:a<606?11:a<608?12:"12"),H&&(H[1]+=" "+(a+="number"==typeof a?".x":/[.+]/.test(a)?"":"+")),"Safari"==I&&(!D||parseInt(D)>45)?D=a:"Chrome"==I&&/\bHeadlessChrome/i.test(e)&&B.unshift("headless")),"Opera"==I&&(a=/\bzbov|zvav$/.exec(F))?(I+=" ",B.unshift("desktop mode"),"zvav"==a?(I+="Mini",D=null):I+="Mobile",F=F.replace(RegExp(" *"+a+"$"),"")):"Safari"==I&&/\bChrome\b/.exec(H&&H[1])?(B.unshift("desktop mode"),I="Chrome Mobile",D=null,/\bOS X\b/.test(F)?($="Apple",F="iOS 4.3+"):F=null):/\bSRWare Iron\b/.test(I)&&!D&&(D=L("Chrome")),D&&0==D.indexOf(a=/[\d.]+$/.exec(F))&&e.indexOf("/"+a+"-")>-1&&(F=x(F.replace(a,""))),F&&-1!=F.indexOf(I)&&!RegExp(I+" OS").test(F)&&(F=F.replace(RegExp(" *"+v(I)+" *"),"")),H&&!/\b(?:Avant|Nook)\b/.test(I)&&(/Browser|Lunascape|Maxthon/.test(I)||"Safari"!=I&&/^iOS/.test(F)&&/\bSafari\b/.test(H[1])||/^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Samsung Internet|Sleipnir|SRWare Iron|Vivaldi|Web)/.test(I)&&H[1])&&(a=H[H.length-1])&&B.push(a),B.length&&(B=["("+B.join("; ")+")"]),$&&R&&R.indexOf($)<0&&B.push("on "+$),R&&B.push((/^on /.test(B[B.length-1])?"":"on ")+R),F&&(a=/ ([\d.+]+)$/.exec(F),l=a&&"/"==F.charAt(F.length-a[0].length-1),F={architecture:32,family:a&&!l?F.replace(a[0],""):F,version:a?a[1]:null,toString:function(){var t=this.version;return this.family+(t&&!l?" "+t:"")+(64==this.architecture?" 64-bit":"")}}),(a=/\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(C))&&!/\bi686\b/i.test(C)?(F&&(F.architecture=64,F.family=F.family.replace(RegExp(" *"+a),"")),I&&(/\bWOW64\b/i.test(e)||z&&/\w(?:86|32)$/.test(o.cpuClass||o.platform)&&!/\bWin64; x64\b/i.test(e))&&B.unshift("32-bit")):F&&/^OS X/.test(F.family)&&"Chrome"==I&&parseFloat(D)>=39&&(F.architecture=64),e||(e=null);var j={};return j.description=e,j.layout=H&&H[0],j.manufacturer=$,j.name=I,j.prerelease=A,j.product=R,j.ua=e,j.version=I&&D,j.os=F||{architecture:null,family:null,version:null,toString:function(){return"null"}},j.parse=t,j.toString=function(){return this.description||""},j.version&&B.unshift(D),j.name&&B.unshift(I),F&&I&&(F!=String(F).split(" ")[0]||F!=I.split(" ")[0]&&!R)&&B.push(R?"("+F+")":"on "+F),B.length&&(j.description=B.join(" ")),j}();n.platform=y,void 0===(s=function(){return y}.call(e,i,e,t))||(t.exports=s)}.call(this)},703:function(e){"use strict";e.exports=t}},i={};function s(t){var o=i[t];if(void 0!==o)return o.exports;var n=i[t]={id:t,loaded:!1,exports:{}};return e[t].call(n.exports,n,n.exports,s),n.loaded=!0,n.exports}s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,{a:e}),e},s.d=function(t,e){for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},s.nmd=function(t){return t.paths=[],t.children||(t.children=[]),t},function(){var t;s.g.importScripts&&(t=s.g.location+"");var e=s.g.document;if(!t&&e&&(e.currentScript&&(t=e.currentScript.src),!t)){var i=e.getElementsByTagName("script");if(i.length)for(var o=i.length-1;o>-1&&!t;)t=i[o--].src}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),s.p=t}();var o={};return function(){"use strict";s.r(o),s.d(o,{default:function(){return f}});var t=s(703),e=s.n(t),i=s(495),n=s.n(i),r=s(795),a=s.n(r);s.p,u.$inject=["$rootScope","$timeout","$interval","iScrollSignals","iScrollService"];const l={on:"iscroll-on",off:"iscroll-off"},h={},c=e().isDefined(a())&&function(t){if("Opera Mini"===t.name)return!1;if("IE Mobile"===t.name)return p(t.version,"11.0")>=0;switch(t.os.family){case"Android":return function(t){return"Chrome Mobile"===t.name}(t)||function(t){return p(t.os.version,"4.0.4")>=0}(t);case"iOS":return p(t.version,"5.1")>=0;default:return!0}}(a());function p(t,e,i){const s=i&&i.lexicographical,o=i&&i.zeroExtend;let n=t.split("."),r=e.split(".");function a(t){return(s?/^\d+[A-Za-z]*$/:/^\d+$/).test(t)}if(!n.every(a)||!r.every(a))return NaN;if(o){for(;n.lengthr[t]?1:-1}return n.length!==r.length?-1:0}function d(t){t()}function u(t,i,s,o,r){function a(t,e){i((function(){t.refresh()}),e.directive.asyncRefreshDelay,e.directive.invokeApply)}return{restrict:"A",link:function(i,c,p){const u={iScroll:e().extend({},r.defaults.iScroll,i.iscroll||{}),directive:e().extend({},r.defaults.directive)};function f(){c.hasClass(l.on)||function(i,r,c,p){const u=[t.$on(o.disabled,g),t.$on(o.refresh,v),i.$on("$destroy",g)],f=new(n())(r[0],p.iScroll);let m=!0,b=null;function g(){null!==b&&s.cancel(b),e().isDefined(i.iscrollInstance)&&delete i.iscrollInstance,f.destroy(),r.removeClass(l.on).addClass(l.off),r.children(".iscroll-scroller").attr("style",null),e().forEach(u,d)}function v(){m&&(m=!1,a(f,p),m=!0)}e().forEach(h,(function(t,i){e().isDefined(p.directive[i])&&f.on(t,p.directive[i])})),r.removeClass(l.off).addClass(l.on),e().isDefined(c.iscrollInstance)&&(i.iscrollInstance=f),!1!==p.directive.asyncRefreshDelay&&a(f,p),f.on("scrollStart",(function(){m=!1})),f.on("scrollEnd",(function(){m=!0})),!1!==p.directive.refreshInterval&&(b=s(v,p.directive.refreshInterval,0,p.directive.invokeApply))}(i,c,p,u)}e().forEach(u.iScroll,(function(t,e){(r.defaults.directive.hasOwnProperty(e)||h.hasOwnProperty(e))&&(u.directive[e]=t,delete u.iScroll[e])}));const m=[t.$on(o.enabled,f)];r.state.useIScroll?f():c.removeClass(l.on).addClass(l.off),i.$on("$destroy",(function(){e().forEach(m,d)}))},scope:{iscroll:"=",iscrollInstance:"="}}}e().forEach(["beforeScrollStart","scrollCancel","scrollStart","scroll","scrollEnd","flick","zoomStart","zoomEnd"],(function(t){var e;this["on"+(e=t,e.substring(0,1).toLocaleUpperCase()+e.substring(1))]=t}),h);var f=e().module("angular-iscroll",[]).directive("iscroll",u).provider("iScrollService",(function(){i.$inject=["$rootScope","iScrollSignals"];const t={iScroll:{momentum:!0,mouseWheel:!0},directive:{initiallyEnabled:!c,asyncRefreshDelay:0,refreshInterval:!1,invokeApply:!1}};function i(e,i){const s={useIScroll:t.directive.initiallyEnabled,autoDetectedUseNativeScroll:c};function o(t){t||(s.useIScroll=!1),e.$emit(i.disabled)}function n(t){t||(s.useIScroll=!0),e.$emit(i.enabled)}return{defaults:t,state:s,versionCompare:p,platform:a(),enable:n,disable:o,toggle:function(t){s.useIScroll?o(t):n(t)},refresh:function(t){e.$emit(i.refresh,t)}}}e().forEach(h,(function(t,e){this[e]=void 0}),t.directive),this.useNativeScroll=c,this.platform=a(),this.configureDefaults=function(i){e().isDefined(i.directive)&&e().extend(t.directive,i.directive),e().isDefined(i.iScroll)&&e().extend(t.iScroll,i.iScroll)},this.getDefaults=function(){return t},this.$get=i})).constant("iScrollSignals",{disabled:"iscroll:disabled",enabled:"iscroll:enabled",refresh:"iscroll:refresh"}).name}(),o}()})); -------------------------------------------------------------------------------- /dist/lib/angular-iscroll.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtr/angular-iscroll/2498b30f9af2e92cc7e85355d90320a492e582b8/dist/lib/angular-iscroll.js.gz -------------------------------------------------------------------------------- /dist/lib/scss/_iscroll.scss: -------------------------------------------------------------------------------- 1 | $iscroll-wrapper-background-color: #777 !default; 2 | $iscroll-scroller-background-color: #fff !default; 3 | 4 | .iscroll-on { 5 | .iscroll-wrapper { 6 | position: relative; 7 | width: 100%; 8 | height: 100%; 9 | overflow: hidden; 10 | background-color: $iscroll-wrapper-background-color; 11 | } 12 | .iscroll-scroller { 13 | position: absolute; 14 | width: 100%; 15 | min-height: 100%; 16 | background-color: $iscroll-scroller-background-color; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-iscroll", 3 | "version": "4.2.0", 4 | "description": "AngularJS module that enables iScroll 5 functionality, wrapping it in an easy-to-use directive.", 5 | "main": "dist/lib/angular-iscroll.js", 6 | "scripts": { 7 | "build": "NODE_ENV=production webpack --config-name=library", 8 | "build-dev": "NODE_ENV=development webpack --config-name=library", 9 | "watch": "NODE_ENV=development webpack --config-name=library --watch", 10 | "update-browserlist-db": "npx update-browserslist-db@latest", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/mtr/angular-iscroll.git" 16 | }, 17 | "keywords": [ 18 | "iscroll", 19 | "angular", 20 | "directive" 21 | ], 22 | "author": { 23 | "name": "Martin Thorsen Ranang", 24 | "email": "mtr@ranang.org" 25 | }, 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/mtr/angular-iscroll/issues" 29 | }, 30 | "homepage": "https://github.com/mtr/angular-iscroll", 31 | "browserslist": "> 0.5%, last 3 versions, Firefox ESR, not dead", 32 | "dependencies": { 33 | "iscroll": "^5.2.0", 34 | "platform": "^1.3.6" 35 | }, 36 | "peerDependencies": { 37 | "angular": ">=1.8" 38 | }, 39 | "resolutions": { 40 | "loader-utils": "^2.0.4", 41 | "minimatch": "^3.0.8" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.23.5", 45 | "@babel/preset-env": "^7.23.5", 46 | "@uirouter/angularjs": "^1.1.0", 47 | "babel-loader": "^9.1.3", 48 | "babel-plugin-angularjs-annotate": "^0.10.0", 49 | "bootstrap-sass": "^3.4.3", 50 | "clean-webpack-plugin": "^4.0.0", 51 | "compression-webpack-plugin": "^10.0.0", 52 | "css-loader": "^6.8.1", 53 | "cssnano": "^6.0.1", 54 | "dateformat": "^5.0.3", 55 | "file-loader": "^6.2.0", 56 | "html-loader": "^4.2.0", 57 | "jquery": "^3.7.1", 58 | "jshint": "^2.13.6", 59 | "mini-css-extract-plugin": "^2.7.6", 60 | "postcss-loader": "^7.3.3", 61 | "postcss-preset-env": "^9.3.0", 62 | "sass": "^1.69.5", 63 | "sass-loader": "^13.3.2", 64 | "url-loader": "^4.1.1", 65 | "webpack": "^5.89.0", 66 | "webpack-cli": "^5.1.4" 67 | }, 68 | "packageManager": "yarn@4.0.2" 69 | } 70 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = ({file, options, env}) => ({ 2 | plugins: { 3 | 'postcss-preset-env': options.presetEnv, 4 | 'autoprefixer': options.autoprefixer, 5 | 'cssnano': options.cssnano 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /src/lib/_iscroll.scss: -------------------------------------------------------------------------------- 1 | $iscroll-wrapper-background-color: #777 !default; 2 | $iscroll-scroller-background-color: #fff !default; 3 | 4 | .iscroll-on { 5 | .iscroll-wrapper { 6 | position: relative; 7 | width: 100%; 8 | height: 100%; 9 | overflow: hidden; 10 | background-color: $iscroll-wrapper-background-color; 11 | } 12 | .iscroll-scroller { 13 | position: absolute; 14 | width: 100%; 15 | min-height: 100%; 16 | background-color: $iscroll-scroller-background-color; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/angular-iscroll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import angular from 'angular' 4 | import IScroll from 'iscroll' 5 | import platform from 'platform' 6 | 7 | import './_iscroll.scss' 8 | 9 | const signals = { 10 | disabled: 'iscroll:disabled', 11 | enabled: 'iscroll:enabled', 12 | refresh: 'iscroll:refresh' 13 | }, 14 | classes = { 15 | on: 'iscroll-on', 16 | off: 'iscroll-off' 17 | }, 18 | iScrollEvents = [ 19 | 'beforeScrollStart', 20 | 'scrollCancel', 21 | 'scrollStart', 22 | 'scroll', 23 | 'scrollEnd', 24 | 'flick', 25 | 'zoomStart', 26 | 'zoomEnd' 27 | ], 28 | iScrollEventHandlerMap = {}, 29 | useNativeScroll = angular.isDefined(platform) && _useNativeScroll(platform); 30 | 31 | /** 32 | * Compares two software version numbers (e.g. "1.7.1" or "1.2b"). 33 | * 34 | * This function was born in http://stackoverflow.com/a/6832721. 35 | * 36 | * @param {string} v1 The first version to be compared. 37 | * @param {string} v2 The second version to be compared. 38 | * @param {object} [options] Optional flags that affect comparison behavior: 39 | *
    40 | *
  • 41 | * lexicographical: true compares each part of the version strings lexicographically instead of 42 | * naturally; this allows suffixes such as "b" or "dev" but will cause "1.10" to be considered smaller than 43 | * "1.2". 44 | *
  • 45 | *
  • 46 | * zeroExtend: true changes the result if one version string has less parts than the other. In 47 | * this case the shorter string will be padded with "zero" parts instead of being considered smaller. 48 | *
  • 49 | *
50 | * @returns {number|NaN} 51 | *
    52 | *
  • 0 if the versions are equal
  • 53 | *
  • a negative integer iff v1 < v2
  • 54 | *
  • a positive integer iff v1 > v2
  • 55 | *
  • NaN if either version string is in the wrong format
  • 56 | *
57 | * 58 | * @copyright by Jon Papaioannou (["john", "papaioannou"].join(".") + "@gmail.com") 59 | * @license This function is in the public domain. Do what you want with it, no strings attached. 60 | */ 61 | function versionCompare(v1, v2, options) { 62 | const lexicographical = options && options.lexicographical, 63 | zeroExtend = options && options.zeroExtend; 64 | let v1parts = v1.split('.'), 65 | v2parts = v2.split('.'); 66 | 67 | function isValidPart(x) { 68 | return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); 69 | } 70 | 71 | if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { 72 | return NaN; 73 | } 74 | 75 | if (zeroExtend) { 76 | while (v1parts.length < v2parts.length) v1parts.push('0'); 77 | while (v2parts.length < v1parts.length) v2parts.push('0'); 78 | } 79 | 80 | if (!lexicographical) { 81 | v1parts = v1parts.map(Number); 82 | v2parts = v2parts.map(Number); 83 | } 84 | 85 | for (let i = 0; i < v1parts.length; ++i) { 86 | if (v2parts.length === i) { 87 | return 1; 88 | } 89 | 90 | if (v1parts[i] === v2parts[i]) { 91 | /*continue; // Unnecessary as last statement in a loop. */ 92 | } else if (v1parts[i] > v2parts[i]) { 93 | return 1; 94 | } else { 95 | return -1; 96 | } 97 | } 98 | 99 | if (v1parts.length !== v2parts.length) { 100 | return -1; 101 | } 102 | 103 | return 0; 104 | } 105 | 106 | function _isChromeMobile(platform) { 107 | return platform.name === 'Chrome Mobile'; 108 | } 109 | 110 | function _isRecentOS(platform) { 111 | return versionCompare(platform.os.version, '4.0.4') >= 0; 112 | } 113 | 114 | function _useNativeScroll(platform) { 115 | if (platform.name === 'Opera Mini') { 116 | return false; 117 | } 118 | 119 | if (platform.name === 'IE Mobile') { 120 | return versionCompare(platform.version, '11.0') >= 0 121 | } 122 | 123 | switch (platform.os.family) { 124 | case 'Android': 125 | // In Chrome we trust, but also younger Android releases. 126 | return _isChromeMobile(platform) || _isRecentOS(platform); 127 | case 'iOS': 128 | // Buggy handling in older iOS versions. 129 | return versionCompare(platform.version, '5.1') >= 0; 130 | default: 131 | // Assuming desktop or other browser. 132 | return true; 133 | } 134 | } 135 | 136 | /** 137 | * Add handler name to event name mapping. 138 | * 139 | * Please note that the 'scroll' event is only available when using 140 | * iscroll-probe.js (for example, through angular-iscroll-probe). 141 | * 142 | * For example, the handler for the 'scrollEnd' event can be configured 143 | * by supplying the onScrollEnd option. 144 | **/ 145 | angular.forEach(iScrollEvents, function _addPair(event) { 146 | this['on' + _capitalizeFirst(event)] = event; 147 | }, iScrollEventHandlerMap); 148 | 149 | function _capitalizeFirst(str) { 150 | return str.substring(0, 1).toLocaleUpperCase() + str.substring(1); 151 | } 152 | 153 | function iScrollServiceProvider() { 154 | const defaultOptions = { 155 | iScroll: { 156 | /** 157 | * The different options for iScroll are explained in 158 | * detail at http://iscrolljs.com/#configuring 159 | **/ 160 | momentum: true, 161 | mouseWheel: true 162 | }, 163 | directive: { 164 | /** 165 | * Whether or not to initially enable the use of iScroll. 166 | * 167 | * The `useNativeScroll` flag is automatically determined 168 | * by running _useNativeScroll(); 169 | **/ 170 | initiallyEnabled: !useNativeScroll, 171 | /** 172 | * Delay, in ms, before we asynchronously perform an 173 | * iScroll.refresh(). If false, then no async refresh is 174 | * performed. 175 | **/ 176 | asyncRefreshDelay: 0, 177 | /** 178 | * Delay, in ms, between each iScroll.refresh(). If false, 179 | * then no periodic refresh is performed. 180 | **/ 181 | refreshInterval: false, 182 | /** 183 | * If `false`, skip `$digest()` cycle on iScroll.refresh(). 184 | */ 185 | invokeApply: false 186 | /** 187 | * Event handler options are added below. 188 | **/ 189 | } 190 | }; 191 | 192 | angular.forEach(iScrollEventHandlerMap, function _default(event, handler) { 193 | this[handler] = undefined; 194 | }, defaultOptions.directive); 195 | 196 | function _configureDefaults(options) { 197 | /** 198 | * Since angular.extend is not performing a "deep" merge, we'll 199 | * do it in two steps. 200 | **/ 201 | if (angular.isDefined(options.directive)) { 202 | angular.extend(defaultOptions.directive, options.directive); 203 | } 204 | if (angular.isDefined(options.iScroll)) { 205 | angular.extend(defaultOptions.iScroll, options.iScroll); 206 | } 207 | } 208 | 209 | // Export the auto-determined value of `useNativeScroll`. 210 | this.useNativeScroll = useNativeScroll; 211 | this.platform = platform; 212 | 213 | this.configureDefaults = _configureDefaults; 214 | 215 | function _getDefaults() { 216 | return defaultOptions; 217 | } 218 | 219 | //noinspection JSUnusedGlobalSymbols 220 | this.getDefaults = _getDefaults; 221 | 222 | /* @ngInject */ 223 | function iScrollService($rootScope, iScrollSignals) { 224 | const _state = { 225 | useIScroll: defaultOptions.directive.initiallyEnabled, 226 | autoDetectedUseNativeScroll: useNativeScroll 227 | }; 228 | 229 | function _disable(signalOnly) { 230 | if (!signalOnly) { 231 | _state.useIScroll = false; 232 | } 233 | //$log.debug('emit(iScrollSignals.disabled)', 234 | // iScrollSignals.disabled); 235 | $rootScope.$emit(iScrollSignals.disabled); 236 | } 237 | 238 | function _enable(signalOnly) { 239 | if (!signalOnly) { 240 | _state.useIScroll = true; 241 | } 242 | //$log.debug('emit(iScrollSignals.enabled)', 243 | // iScrollSignals.enabled); 244 | $rootScope.$emit(iScrollSignals.enabled); 245 | } 246 | 247 | function _toggle(signalOnly) { 248 | (_state.useIScroll) ? 249 | _disable(signalOnly) : _enable(signalOnly); 250 | } 251 | 252 | function _refresh(name) { 253 | // The name parameter is not really used for now. 254 | $rootScope.$emit(iScrollSignals.refresh, name); 255 | } 256 | 257 | return { 258 | defaults: defaultOptions, 259 | state: _state, 260 | versionCompare: versionCompare, 261 | platform: platform, 262 | enable: _enable, 263 | disable: _disable, 264 | toggle: _toggle, 265 | refresh: _refresh 266 | }; 267 | } 268 | 269 | //noinspection JSUnusedGlobalSymbols 270 | this.$get = iScrollService; 271 | } 272 | 273 | function _call(functor) { 274 | functor(); 275 | } 276 | 277 | /* @ngInject */ 278 | function iscroll($rootScope, $timeout, $interval, iScrollSignals, 279 | iScrollService) { 280 | function asyncRefresh(instance, options) { 281 | $timeout(function _refreshAfterInitialRender() { 282 | instance.refresh(); 283 | }, options.directive.asyncRefreshDelay, options.directive.invokeApply); 284 | } 285 | 286 | function _createInstance(scope, element, attrs, options) { 287 | const deregistrators = [ 288 | $rootScope.$on(iScrollSignals.disabled, _destroyInstance), 289 | $rootScope.$on(iScrollSignals.refresh, _refreshInstance), 290 | scope.$on('$destroy', _destroyInstance) 291 | ]; 292 | const instance = new IScroll(element[0], options.iScroll); 293 | let refreshEnabled = true, 294 | refreshInterval = null; 295 | 296 | angular.forEach(iScrollEventHandlerMap, 297 | function _addHandler(event, option) { 298 | if (angular.isDefined(options.directive[option])) { 299 | instance.on(event, options.directive[option]); 300 | } 301 | }); 302 | 303 | element.removeClass(classes.off).addClass(classes.on); 304 | 305 | if (angular.isDefined(attrs.iscrollInstance)) { 306 | scope.iscrollInstance = instance; 307 | } 308 | 309 | if (options.directive.asyncRefreshDelay !== false) { 310 | asyncRefresh(instance, options); 311 | } 312 | 313 | function _destroyInstance() { 314 | if (refreshInterval !== null) { 315 | $interval.cancel(refreshInterval); 316 | } 317 | 318 | if (angular.isDefined(scope.iscrollInstance)) { 319 | delete scope.iscrollInstance; 320 | } 321 | instance.destroy(); 322 | 323 | element.removeClass(classes.on).addClass(classes.off); 324 | // Remove element's CSS transition values: 325 | element.children('.iscroll-scroller').attr('style', null); 326 | 327 | angular.forEach(deregistrators, _call); 328 | //$log.debug('angular-iscroll: destroyInstance'); 329 | } 330 | 331 | function _refreshInstance() { 332 | if (refreshEnabled) { 333 | //noinspection JSUnusedAssignment 334 | refreshEnabled = false; 335 | asyncRefresh(instance, options); 336 | refreshEnabled = true; 337 | } 338 | } 339 | 340 | function _disableRefresh() { 341 | refreshEnabled = false; 342 | } 343 | 344 | function _enableRefresh() { 345 | refreshEnabled = true; 346 | } 347 | 348 | instance.on('scrollStart', _disableRefresh); 349 | instance.on('scrollEnd', _enableRefresh); 350 | 351 | if (options.directive.refreshInterval !== false) { 352 | refreshInterval = $interval(_refreshInstance, 353 | options.directive.refreshInterval, 0, options.directive.invokeApply); 354 | } 355 | 356 | return instance; 357 | } 358 | 359 | function _link(scope, element, attrs) { 360 | const options = { 361 | iScroll: angular.extend({}, iScrollService.defaults.iScroll, scope.iscroll || {}), 362 | directive: angular.extend({}, iScrollService.defaults.directive) 363 | }; 364 | 365 | angular.forEach(options.iScroll, 366 | function _extractOptions(value, key) { 367 | if (iScrollService.defaults.directive.hasOwnProperty(key) || 368 | iScrollEventHandlerMap.hasOwnProperty(key)) { 369 | options.directive[key] = value; 370 | delete options.iScroll[key]; 371 | } 372 | } 373 | ); 374 | 375 | function _init() { 376 | if (!element.hasClass(classes.on)) { 377 | _createInstance(scope, element, attrs, options); 378 | } 379 | } 380 | 381 | const enableHandlers = [$rootScope.$on(iScrollSignals.enabled, _init)]; 382 | 383 | function _removeEnableHandlers() { 384 | angular.forEach(enableHandlers, _call); 385 | } 386 | 387 | if (iScrollService.state.useIScroll) { 388 | _init(); 389 | } else { 390 | element.removeClass(classes.on).addClass(classes.off); 391 | } 392 | 393 | scope.$on('$destroy', _removeEnableHandlers); 394 | } 395 | 396 | return { 397 | restrict: 'A', 398 | link: _link, 399 | scope: { 400 | iscroll: '=', 401 | iscrollInstance: '=' 402 | } 403 | }; 404 | } 405 | 406 | export default angular.module('angular-iscroll', []) 407 | .directive('iscroll', iscroll) 408 | .provider('iScrollService', iScrollServiceProvider) 409 | .constant('iScrollSignals', signals) 410 | .name; 411 | 412 | -------------------------------------------------------------------------------- /webpack.config.mjs: -------------------------------------------------------------------------------- 1 | import {CleanWebpackPlugin} from 'clean-webpack-plugin'; 2 | import CompressionPlugin from 'compression-webpack-plugin'; 3 | import dateFormat from 'dateformat'; 4 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 5 | import path from 'path'; 6 | import {fileURLToPath} from 'url'; 7 | import webpack from 'webpack'; 8 | 9 | import _package from './package.json' assert {type: 'json'}; 10 | 11 | const __filename = fileURLToPath(import.meta.url); 12 | const __dirname = path.dirname(__filename); 13 | 14 | const now = new Date(), 15 | timestamp = dateFormat(now, 'isoDateTime'), 16 | year = dateFormat(now, 'yyyy'); 17 | 18 | export default { 19 | plugins: [ 20 | new CleanWebpackPlugin(), 21 | new webpack.BannerPlugin({ 22 | banner: `@license ${_package.name} v${_package.version}, ${timestamp} 23 | (c) ${year} ${_package.author.name} <${_package.author.email}> 24 | License: ${_package.license}` 25 | }) 26 | ].concat([ 27 | new MiniCssExtractPlugin({ 28 | filename: 'angular-iscroll.css', 29 | }), 30 | new CompressionPlugin({ 31 | test: /\.js(\?.*)?$/i, 32 | }) 33 | ]), 34 | name: 'library', 35 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', 36 | //mode: 'development', 37 | entry: './src/lib/angular-iscroll.js', 38 | output: { 39 | path: path.resolve(__dirname, 'dist/lib'), 40 | filename: 'angular-iscroll.js', 41 | library: 'angularIscroll', 42 | libraryTarget: 'umd' 43 | }, 44 | optimization: { 45 | usedExports: true, 46 | }, 47 | module: { 48 | rules: [ 49 | { 50 | test: /\.m?js$/, 51 | exclude: /node_modules/, 52 | use: [{ 53 | loader: 'babel-loader', 54 | options: { 55 | presets: ['@babel/preset-env'] 56 | } 57 | }] 58 | }, 59 | { 60 | test: /\.(sa|sc|c)ss$/, 61 | /* Exclude fonts while working with images, e.g. .svg can be both image or font. */ 62 | exclude: /node_modules/, 63 | use: [ 64 | { 65 | loader: 'file-loader', 66 | options: { 67 | name: '[name].[ext]', 68 | outputPath: 'scss/' 69 | } 70 | }, 71 | ] 72 | } 73 | ], 74 | }, 75 | externals: { 76 | lodash: { 77 | commonjs: 'lodash', 78 | commonjs2: 79 | 'lodash', 80 | amd: 81 | 'lodash', 82 | root: 83 | '_', 84 | }, 85 | angular: { 86 | commonjs: 'angular', 87 | commonjs2: 88 | 'angular', 89 | amd: 90 | 'angular', 91 | root: 92 | 'angular', 93 | }, 94 | }, 95 | }; 96 | --------------------------------------------------------------------------------