Compare commits
53 Commits
4.2.0-rc.2
...
4.2.0
Author | SHA1 | Date | |
---|---|---|---|
1c04b83ea3 | |||
429bc9d3cd | |||
41a765d715 | |||
f370fd36e0 | |||
a222c3e609 | |||
5a71df0cb3 | |||
6a46cabb10 | |||
74673545c0 | |||
75a311f250 | |||
391ed6334d | |||
0940e6d6ed | |||
4d2ee51bb0 | |||
680128bc09 | |||
5364b51979 | |||
67ef0f0c8f | |||
e9886d701d | |||
022835bab2 | |||
8be2e4c325 | |||
33ba3e31ed | |||
cba2b3c72d | |||
a4a2901294 | |||
bb46f54ad7 | |||
c9b930dd82 | |||
a39f7d63bb | |||
e317f7d51c | |||
3ddd28d37d | |||
fe6b39d585 | |||
d837bfc2d7 | |||
4759975be6 | |||
078a4b00a7 | |||
b00b80a45b | |||
269bbe0e7d | |||
bb2fc6b8da | |||
d4e196035c | |||
b8979c8701 | |||
bfdd3398f6 | |||
3a121a621f | |||
c06f4fc702 | |||
95f1ea2f12 | |||
784347f61f | |||
42176a7ac4 | |||
76b4b80a23 | |||
7c78282ce8 | |||
47c2a2e411 | |||
5faf520067 | |||
02d74cafba | |||
734f30d14c | |||
f2d810febc | |||
819514aeba | |||
b55adee982 | |||
4c32cb952f | |||
35f714e438 | |||
d5ce086089 |
6
.bazelrc
Normal file
6
.bazelrc
Normal file
@ -0,0 +1,6 @@
|
||||
# Disable sandboxing because it's too slow.
|
||||
# https://github.com/bazelbuild/bazel/issues/2424
|
||||
build --spawn_strategy=standalone
|
||||
|
||||
# Performance: avoid stat'ing input files
|
||||
build --watchfs
|
20
.circleci/config.yml
Normal file
20
.circleci/config.yml
Normal file
@ -0,0 +1,20 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/ng
|
||||
docker:
|
||||
- image: alexeagle/ngcontainer
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
- run: npm install
|
||||
- run: npm run postinstall
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
# Build twice, workaround for
|
||||
# https://github.com/bazelbuild/bazel/issues/3114
|
||||
- run: bazel build ... || bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
paths:
|
||||
- "node_modules"
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.DS_STORE
|
||||
|
||||
/dist/
|
||||
bazel-*
|
||||
node_modules
|
||||
bower_components
|
||||
|
||||
|
@ -97,15 +97,15 @@ groups:
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler/animations:
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/compiler/src/animation/*"
|
||||
- "packages/animation/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler/i18n:
|
||||
conditions:
|
||||
|
18
BUILD
Normal file
18
BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
exports_files(["tsconfig.json"])
|
||||
|
||||
# This rule belongs in node_modules/BUILD
|
||||
# It's here as a workaround for
|
||||
# https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940
|
||||
filegroup(
|
||||
name = "node_modules",
|
||||
srcs = glob([
|
||||
# Performance workaround: list individual files
|
||||
# This won't scale in the general case.
|
||||
# TODO(alexeagle): figure out what to do
|
||||
"node_modules/typescript/lib/**",
|
||||
"node_modules/zone.js/**/*.d.ts",
|
||||
"node_modules/rxjs/**/*.d.ts",
|
||||
"node_modules/@types/**/*.d.ts",
|
||||
]),
|
||||
)
|
25
CHANGELOG.md
25
CHANGELOG.md
@ -1,3 +1,28 @@
|
||||
<a name="4.2.0"></a>
|
||||
# [4.2.0](https://github.com/angular/angular/compare/4.2.0-rc.2...4.2.0) salubrious-stratagem (2017-06-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** ensure web-animations understands a numeric CSS perspective value ([819514a](https://github.com/angular/angular/commit/819514a)), closes [#14007](https://github.com/angular/angular/issues/14007)
|
||||
* **animations:** evaluate substitutions on option param values ([e9886d7](https://github.com/angular/angular/commit/e9886d7))
|
||||
* **forms:** fix min and max validator behavior on non-numbers ([a222c3e](https://github.com/angular/angular/commit/a222c3e))
|
||||
* **router:** opening links in new window ([4c32cb9](https://github.com/angular/angular/commit/4c32cb9))
|
||||
* **upgrade:** call setInterval outside the Angular zone ([269bbe0](https://github.com/angular/angular/commit/269bbe0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-cli:** introduce synchronous codegen API ([b00b80a](https://github.com/angular/angular/commit/b00b80a))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** do not create a closure each time a node is removed ([fe6b39d](https://github.com/angular/angular/commit/fe6b39d))
|
||||
* **animations:** only apply `:leave` flags if animations are set to run ([b55adee](https://github.com/angular/angular/commit/b55adee))
|
||||
|
||||
|
||||
|
||||
<a name="4.2.0-rc.2"></a>
|
||||
# [4.2.0-rc.2](https://github.com/angular/angular/compare/4.2.0-rc.1...4.2.0-rc.2) (2017-06-01)
|
||||
|
||||
|
12
WORKSPACE
Normal file
12
WORKSPACE
Normal file
@ -0,0 +1,12 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "io_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
tag = "0.0.3",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories", "yarn_install")
|
||||
|
||||
node_repositories()
|
||||
yarn_install(package_json = "//:package.json")
|
1
aio/content/examples/.gitignore
vendored
1
aio/content/examples/.gitignore
vendored
@ -45,6 +45,7 @@ dist/
|
||||
# aot
|
||||
**/*.ngfactory.ts
|
||||
**/*.ngsummary.json
|
||||
**/*.ngsummary.ts
|
||||
**/*.shim.ngstyle.ts
|
||||
**/*.metadata.json
|
||||
!aot/bs-config.json
|
||||
|
@ -803,6 +803,15 @@ The null hero's name is {{nullHero && nullHero.name}}
|
||||
<!-- #enddocregion safe-6 -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- #docregion non-null-assertion-1 -->
|
||||
<!--No hero, no text -->
|
||||
<div *ngIf="hero">
|
||||
The hero's name is {{hero!.name}}
|
||||
</div>
|
||||
<!-- #enddocregion non-null-assertion-1 -->
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- TODO: discuss this in the Style binding section -->
|
||||
|
@ -68,7 +68,7 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
</td>
|
||||
|
||||
<td>
|
||||
Marshmallow (6.0)
|
||||
Nougat (7.0)<br>Marshmallow (6.0)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
@ -19,26 +19,14 @@ unexpected definitions.
|
||||
|
||||
## Ahead-of-time (AOT) compilation
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
You can compile Angular applications at build time.
|
||||
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
|
||||
to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
||||
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Angular module
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Helps you organize an application into cohesive blocks of functionality.
|
||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
||||
|
||||
@ -48,23 +36,11 @@ called `AppModule` and resides in a file named `app.module.ts`.
|
||||
For details and examples, see the [Angular Modules (NgModule)](guide/ngmodule) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Annotation
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
In practice, a synonym for [Decoration](guide/glossary#decorator).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a attribute-directive}
|
||||
|
||||
|
||||
@ -73,10 +49,6 @@ In practice, a synonym for [Decoration](guide/glossary#decorator).
|
||||
|
||||
## Attribute directives
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A category of [directive](guide/glossary#directive) that can listen to and modify the behavior of
|
||||
other HTML elements, attributes, properties, and components. They are usually represented
|
||||
as HTML attributes, hence the name.
|
||||
@ -86,18 +58,10 @@ For example, you can use the `ngClass` directive to add and remove CSS class nam
|
||||
Learn about them in the [_Attribute Directives_](guide/attribute-directives) guide.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a B}
|
||||
|
||||
## Barrel
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
||||
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
||||
|
||||
@ -157,17 +121,8 @@ You can often achieve the same result using [Angular modules](guide/glossary#ang
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Binding
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Usually refers to [data binding](guide/glossary#data-binding) and the act of
|
||||
binding an HTML object property to a data object property.
|
||||
|
||||
@ -175,16 +130,8 @@ Sometimes refers to a [dependency-injection](guide/glossary#dependency-injection
|
||||
between a "token"—also referred to as a "key"—and a dependency [provider](guide/glossary#provider).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Bootstrap
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
||||
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
||||
which is the first component that is loaded for the application.
|
||||
@ -193,17 +140,10 @@ For more information, see the [Setup](guide/setup) page.
|
||||
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a C}
|
||||
|
||||
## camelCase
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
The practice of writing compound words or phrases such that each word or abbreviation begins with a capital letter
|
||||
_except the first letter, which is lowercase_.
|
||||
|
||||
@ -213,19 +153,11 @@ camelCase is also known as *lower camel case* to distinguish it from *upper came
|
||||
In Angular documentation, "camelCase" always means *lower camel case*.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a component}
|
||||
|
||||
|
||||
## Component
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
An Angular class responsible for exposing data to a [view](guide/glossary#view) and handling most of the view’s display and user-interaction logic.
|
||||
|
||||
The *component* is one of the most important building blocks in the Angular system.
|
||||
@ -240,17 +172,10 @@ Those familiar with "MVC" and "MVVM" patterns will recognize
|
||||
the component in the role of "controller" or "view model".
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a D}
|
||||
|
||||
## dash-case
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
||||
This form is also known as kebab-case.
|
||||
|
||||
@ -259,16 +184,8 @@ the root of filenames (such as `hero-list.component.ts`) are often
|
||||
spelled in dash-case.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Data binding
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Applications display data values to a user and respond to user
|
||||
actions (such as clicks, touches, and keystrokes).
|
||||
|
||||
@ -292,10 +209,6 @@ operations and supporting declaration syntax.
|
||||
* [Two-way data binding with ngModel](guide/template-syntax#ngModel).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a decorator}
|
||||
|
||||
|
||||
@ -304,10 +217,6 @@ operations and supporting declaration syntax.
|
||||
|
||||
## Decorator | decoration
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A *function* that adds metadata to a class, its members (properties, methods) and function arguments.
|
||||
|
||||
Decorators are an experimental (stage 2), JavaScript language [feature](https://github.com/wycats/javascript-decorators). TypeScript adds support for decorators.
|
||||
@ -340,17 +249,8 @@ Always include parentheses `()` when applying a decorator.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Dependency injection
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A design pattern and mechanism
|
||||
for creating and delivering parts of an application to other
|
||||
parts of an application that request them.
|
||||
@ -401,10 +301,6 @@ You can register your own providers.
|
||||
Read more in the [Dependency Injection](guide/dependency-injection) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a directive}
|
||||
|
||||
|
||||
@ -413,10 +309,6 @@ Read more in the [Dependency Injection](guide/dependency-injection) page.
|
||||
|
||||
## Directive
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
An Angular class responsible for creating, reshaping, and interacting with HTML elements
|
||||
in the browser DOM. The directive is Angular's most fundamental feature.
|
||||
|
||||
@ -447,17 +339,10 @@ shaping or reshaping HTML layout, typically by adding, removing, or manipulating
|
||||
elements and their children.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a E}
|
||||
|
||||
## ECMAScript
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
||||
|
||||
The latest approved version of JavaScript is
|
||||
@ -473,47 +358,21 @@ to ES5 JavaScript.
|
||||
Angular developers can write in ES5 directly.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## ES2015
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## ES5
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 5, the version of JavaScript run by most modern browsers.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## ES6
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a F}
|
||||
|
||||
|
||||
@ -526,25 +385,13 @@ Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||
|
||||
## Injector
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
|
||||
that can find a named dependency in its cache or create a dependency
|
||||
with a registered [provider](guide/glossary#provider).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Input
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A directive property that can be the *target* of a
|
||||
[property binding](guide/template-syntax#property-binding) (explained in detail in the [Template Syntax](guide/template-syntax) page).
|
||||
Data values flow *into* this property from the data source identified
|
||||
@ -553,16 +400,8 @@ in the template expression to the right of the equal sign.
|
||||
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Interpolation
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A form of [property data binding](guide/glossary#data-binding) in which a
|
||||
[template expression](guide/glossary#template-expression) between double-curly braces
|
||||
renders as text. That text may be concatenated with neighboring text
|
||||
@ -581,9 +420,6 @@ Read more about [interpolation](guide/template-syntax#interpolation) in the
|
||||
[Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a J}
|
||||
|
||||
{@a jit}
|
||||
@ -591,40 +427,22 @@ Read more about [interpolation](guide/template-syntax#interpolation) in the
|
||||
|
||||
## Just-in-time (JIT) compilation
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A bootstrapping method of compiling components and modules in the browser
|
||||
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
||||
Consider using the [ahead-of-time](guide/glossary#aot) mode for production apps.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a K}
|
||||
|
||||
## kebab-case
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
See [dash-case](guide/glossary#dash-case).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a L}
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
[Directives](guide/glossary#directive) and [components](guide/glossary#component) have a lifecycle
|
||||
managed by Angular as it creates, updates, and destroys them.
|
||||
|
||||
@ -648,21 +466,13 @@ Angular calls these hook methods in the following order:
|
||||
Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a M}
|
||||
|
||||
## Module
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
|
||||
|
||||
Angular has the following types of modules:
|
||||
|
||||
* [Angular modules](guide/glossary#angular-module).
|
||||
@ -699,20 +509,12 @@ class belongs to a feature module named `date_pipe` in the file `date_pipe.ts`.
|
||||
You rarely access Angular feature modules directly. You usually import them from an Angular [scoped package](guide/glossary#scoped-package) such as `@angular/core`.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a N}
|
||||
|
||||
{@a O}
|
||||
|
||||
## Observable
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
An array whose items arrive asynchronously over time.
|
||||
Observables help you manage asynchronous data, such as data coming from a backend service.
|
||||
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
|
||||
@ -721,14 +523,8 @@ To use observables, Angular uses a third-party library called Reactive Extension
|
||||
Observables are a proposed feature for ES2016, the next version of JavaScript.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
## Output
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A directive property that can be the *target* of event binding
|
||||
(read more in the [event binding](guide/template-syntax#event-binding)
|
||||
section of the [Template Syntax](guide/template-syntax) page).
|
||||
@ -738,17 +534,10 @@ in the template expression to the right of the equal sign.
|
||||
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a P}
|
||||
|
||||
## PascalCase
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
The practice of writing individual words, compound words, or phrases such that each word or abbreviation begins with a capital letter.
|
||||
Class names are typically spelled in PascalCase. For example, `Person` and `HeroDetailComponent`.
|
||||
|
||||
@ -756,16 +545,8 @@ This form is also known as *upper camel case* to distinguish it from *lower came
|
||||
In this documentation, "PascalCase" means *upper camel case* and "camelCase" means *lower camel case*.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Pipe
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
An Angular pipe is a function that transforms input values to output values for
|
||||
display in a [view](guide/glossary#view).
|
||||
Here's an example that uses the built-in `currency` pipe to display
|
||||
@ -783,35 +564,19 @@ You can also write your own custom pipes.
|
||||
Read more in the page on [pipes](guide/pipes).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Provider
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A _provider_ creates a new instance of a dependency for the
|
||||
[dependency injection](guide/glossary#dependency-injection) system.
|
||||
It relates a lookup token to code—sometimes called a "recipe"—that can create a dependency value.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a Q}
|
||||
|
||||
{@a R}
|
||||
|
||||
## Reactive forms
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A technique for building Angular forms through code in a component.
|
||||
The alternative technique is [template-driven forms](guide/glossary#template-driven-forms).
|
||||
|
||||
@ -825,16 +590,8 @@ When building reactive forms:
|
||||
Reactive forms are powerful, flexible, and a good choice for more complex data-entry form scenarios, such as dynamic generation of form controls.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Router
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Most applications consist of many screens or [views](guide/glossary#view).
|
||||
The user navigates among them by clicking links and buttons,
|
||||
and performing other similar actions that cause the application to
|
||||
@ -855,47 +612,24 @@ directives that users can click to navigate.
|
||||
For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Router module
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A separate [Angular module](guide/glossary#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
||||
|
||||
For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Routing component
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
An Angular [component](guide/glossary#component) with a `RouterOutlet` that displays views based on router navigations.
|
||||
|
||||
For more information, see the [Routing & Navigation](guide/router) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a S}
|
||||
|
||||
## Scoped package
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A way to group related *npm* packages.
|
||||
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
|
||||
|
||||
@ -912,17 +646,8 @@ is that the scoped package name begins with the Angular *scope name*, `@angular`
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Service
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
For data or logic that is not associated
|
||||
with a specific view or that you want to share across components, build services.
|
||||
|
||||
@ -938,27 +663,15 @@ Applications often require services such as a data service or a logging service.
|
||||
For more information, see the [Services](tutorial/toh-pt4) page of the [Tour of Heroes](tutorial) tutorial.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a snake-case}
|
||||
|
||||
|
||||
## snake_case
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
The practice of writing compound words or phrases such that an
|
||||
underscore (`_`) separates one word from the next. This form is also known as *underscore case*.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a structural-directive}
|
||||
|
||||
|
||||
@ -967,10 +680,6 @@ underscore (`_`) separates one word from the next. This form is also known as *u
|
||||
|
||||
## Structural directives
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A category of [directive](guide/glossary#directive) that can
|
||||
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
||||
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
||||
@ -978,32 +687,17 @@ The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive
|
||||
Read more in the [Structural Directives](guide/structural-directives) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a T}
|
||||
|
||||
## Template
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A chunk of HTML that Angular uses to render a [view](guide/glossary#view) with
|
||||
the support and guidance of an Angular [directive](guide/glossary#directive),
|
||||
most notably a [component](guide/glossary#component).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Template-driven forms
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A technique for building Angular forms using HTML forms and input elements in the view.
|
||||
The alternate technique is [Reactive Forms](guide/glossary#reactive-forms).
|
||||
|
||||
@ -1020,16 +714,8 @@ Read about how to build template-driven forms
|
||||
in the [Forms](guide/forms) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Template expression
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A TypeScript-like syntax that Angular evaluates within
|
||||
a [data binding](guide/glossary#data-binding).
|
||||
|
||||
@ -1038,30 +724,14 @@ in the [Template expressions](guide/template-syntax#template-expressions) sectio
|
||||
of the [Template Syntax](guide/template-syntax) page.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Transpile
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
The process of transforming code written in one form of JavaScript
|
||||
(such as TypeScript) into another form of JavaScript (such as [ES5](guide/glossary#es5)).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A version of JavaScript that supports most [ECMAScript 2015](guide/glossary#es2015)
|
||||
language features such as [decorators](guide/glossary#decorator).
|
||||
|
||||
@ -1076,20 +746,12 @@ you can use other JavaScript dialects such as [ES5](guide/glossary#es5).
|
||||
Read more about TypeScript at [typescriptlang.org](http://www.typescriptlang.org/).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a U}
|
||||
|
||||
{@a V}
|
||||
|
||||
## View
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A portion of the screen that displays information and responds
|
||||
to user actions such as clicks, mouse moves, and keystrokes.
|
||||
|
||||
@ -1103,10 +765,6 @@ dynamically as the user navigates through the application, typically
|
||||
under the control of a [router](guide/glossary#router).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{@a W}
|
||||
|
||||
|
||||
@ -1120,10 +778,6 @@ under the control of a [router](guide/glossary#router).
|
||||
|
||||
## Zone
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
A mechanism for encapsulating and intercepting
|
||||
a JavaScript application's asynchronous activity.
|
||||
|
||||
@ -1142,6 +796,3 @@ the information it displays via [data bindings](guide/glossary#data-binding).
|
||||
|
||||
Learn more about zones in this
|
||||
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -104,7 +104,7 @@ including:
|
||||
Other notable differences from JavaScript syntax include:
|
||||
|
||||
* no support for the bitwise operators `|` and `&`
|
||||
* new [template expression operators](guide/template-syntax#expression-operators), such as `|` and `?.`
|
||||
* new [template expression operators](guide/template-syntax#expression-operators), such as `|`, `?.` and `!`.
|
||||
|
||||
{@a expression-context}
|
||||
|
||||
@ -1931,6 +1931,27 @@ It works perfectly with long property paths such as `a?.b?.c?.d`.
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a non-null-assertion-operator}
|
||||
|
||||
### The non-null assertion operator ( <span class="syntax">!</span> )
|
||||
|
||||
The Angular **non-null assertion operator (`!`)** is a post-fix operator that asserts that the preceeding property path
|
||||
will never be null or undefined.
|
||||
|
||||
Unlike the [_safe navigation operator_](guide/template-syntax#safe-navigation-operator "Safe naviation operator (?.)")
|
||||
the **non-null assertion operator** does not guard against a null or undefined; rather, it informs the TypeScript type
|
||||
checker that there is something it might be unaware of that ensures that this property path is defined. This prevents
|
||||
TypeScript from reporting that the path is possibly null or undefined when strict null checking is enabled.
|
||||
|
||||
For example, if you use [*ngIf](guide/template-syntax#ngIf) to check if `hero` is defined, you can assert the uses of
|
||||
`hero` are defined in the body of the template.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="non-null-assertion-1" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The Angular **non-null assertion operator (`!`)** is like TypeScript's _non-null assertion operator (!)_
|
||||
introduced in [TypeScript 2.0](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html).
|
||||
|
||||
## Summary
|
||||
You've completed this survey of template syntax.
|
||||
Now it's time to put that knowledge to work on your own components and directives.
|
||||
|
@ -66,7 +66,7 @@ Anything you can import from `@angular` is a nested member of this `ng` object:
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="ng2import">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="ng2import">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="ng2import">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="ng2import">
|
||||
</code-pane>
|
||||
@ -104,7 +104,7 @@ Here is a `HeroComponent` as it might be defined and "exported" in each of the f
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="appexport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="appexport">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="appexport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="appexport">
|
||||
</code-pane>
|
||||
@ -121,7 +121,7 @@ In _ES5_ you use the shared namespace object to access "exported" entities from
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="appimport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="appimport">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="appimport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="appimport">
|
||||
</code-pane>
|
||||
@ -167,7 +167,7 @@ Use the constructor function pattern instead, adding methods to the prototype.
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="class">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="class">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="class">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="class">
|
||||
</code-pane>
|
||||
@ -193,7 +193,7 @@ See these variations side-by-side:
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="metadata">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="metadata">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="metadata">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="metadata">
|
||||
</code-pane>
|
||||
@ -213,7 +213,7 @@ The component, `HeroTitleComponent` in this case, then references the template f
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts" region="templateUrl">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6" region="templateUrl">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6" region="templateUrl">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6" region="templateUrl">
|
||||
</code-pane>
|
||||
@ -322,7 +322,7 @@ Just implement the methods and ignore interfaces when translating code samples f
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-lifecycle.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-lifecycle.component.es6">
|
||||
</code-pane>
|
||||
@ -354,7 +354,7 @@ combined in the metadata `inputs` and `outputs` _arrays_.
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/confirm.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/confirm.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/confirm.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/confirm.component.es6">
|
||||
</code-pane>
|
||||
@ -412,7 +412,7 @@ This format should be familiar to AngularJS developers.
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di.component.es6">
|
||||
</code-pane>
|
||||
@ -430,8 +430,8 @@ In _TypeScript_ and _ES6-with-decorators_, you precede the class constructor par
|
||||
by calling the `@Inject()` decorator with the injection token.
|
||||
In the following example, the token is the string `'heroName'`.
|
||||
|
||||
The other JavaScript dialects add a `parameters` array to the class contructor function.
|
||||
Each item constains a new instance of `Inject`:
|
||||
The other JavaScript dialects add a `parameters` array to the class constructor function.
|
||||
Each item constrains a new instance of `Inject`:
|
||||
|
||||
* _Plain ES6_—each item is a new instance of `Inject(token)` in a sub-array.
|
||||
* _ES5_—simply list the string tokens.
|
||||
@ -442,7 +442,7 @@ array as before. Create a new instance of `ng.core.Inject(token)` for each param
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di-inject.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di-inject.component.es6">
|
||||
</code-pane>
|
||||
@ -475,7 +475,7 @@ array as before. Use a nested array to define a parameter's complete injection s
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6">
|
||||
</code-pane>
|
||||
@ -522,7 +522,7 @@ The `host` value is an object whose properties are host property and listener b
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-host.component.es6">
|
||||
</code-pane>
|
||||
@ -545,7 +545,7 @@ These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host-meta.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
@ -578,7 +578,7 @@ The `queries` property value is a hash map.
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="view">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="view">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="view">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="view">
|
||||
</code-pane>
|
||||
@ -597,7 +597,7 @@ They can be added in the same way as [`@ViewChild`](api/core/ViewChild) and
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="content">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="content">
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="content">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="content">
|
||||
</code-pane>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 32 KiB |
Binary file not shown.
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 7.4 KiB |
34
aio/content/marketing/contribute.html
Normal file
34
aio/content/marketing/contribute.html
Normal file
@ -0,0 +1,34 @@
|
||||
<header class="marketing-banner">
|
||||
<h1 class="banner-headline no-toc no-anchor">Contribute to Angular</h1>
|
||||
</header>
|
||||
|
||||
<article class="contribute-container">
|
||||
<h2 class="no-anchor">Angular Projects</h2>
|
||||
|
||||
<p>We'd love for you to contribute to our source code and to make Angular projects even better.</p>
|
||||
|
||||
<div class="l-sub-section">
|
||||
<h3 class="no-anchor">Angular</h3>
|
||||
|
||||
Angular is a next generation mobile and desktop application development platform.
|
||||
|
||||
<a href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button>Contribute to Angular</a>
|
||||
</div>
|
||||
|
||||
<div class="l-sub-section">
|
||||
<h3 class="no-anchor">Angular Material</h3>
|
||||
|
||||
Our goal is to deliver a lean, lightweight set of Angular-based UI elements that implement the material design specification for use in Angular single-page applications (SPAs).
|
||||
|
||||
<a href="https://github.com/angular/material2/blob/master/CONTRIBUTING.md" class="button" md-button>Contribute to Angular Material</a>
|
||||
</div>
|
||||
|
||||
<div class="l-sub-section">
|
||||
<h3 class="no-anchor">AngularFire</h3>
|
||||
|
||||
AngularFire is the officially supported Angular binding for Firebase. Firebase is a full backend so you don't need servers to build your Angular app.
|
||||
|
||||
<a href="https://github.com/angular/angularfire2" class="button" md-button> Contribute to AngularFire</a>
|
||||
</div>
|
||||
|
||||
</article>
|
@ -1,37 +0,0 @@
|
||||
<h1 class="no-toc">Contribute to Angular</h1>
|
||||
|
||||
Help us build the framework of the future!
|
||||
|
||||
## Angular Projects
|
||||
|
||||
We'd love for you to contribute to our source code and to make Angular projects even better.
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Angular
|
||||
|
||||
Angular is a next generation mobile and desktop application development platform.
|
||||
|
||||
<a href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button>Contribute to Angular</a>
|
||||
|
||||
~~~
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### Angular Material
|
||||
|
||||
Our goal is to deliver a lean, lightweight set of Angular-based UI elements that implement the material design specification for use in Angular single-page applications (SPAs).
|
||||
|
||||
<a href="https://github.com/angular/material/blob/master/.github/CONTRIBUTING.md" class="button" md-button>Contribute to Angular Material</a>
|
||||
|
||||
~~~
|
||||
|
||||
~~~ {.l-sub-section}
|
||||
|
||||
### AngularFire
|
||||
|
||||
AngularFire is the officially supported Angular binding for Firebase. Firebase is a full backend so you don't need servers to build your Angular app.
|
||||
|
||||
<a href="https://github.com/firebase/angularFire" class="button" md-button> Contribute to AngularFire</a>
|
||||
|
||||
~~~
|
@ -12,10 +12,9 @@ Angular is a platform that makes it easy to build applications with the web. Ang
|
||||
|
||||
<div class="docs-card">
|
||||
<section>Get Going with Angular</section>
|
||||
<p>Get going on your own environment with the Quickstart and Tutorial</p>
|
||||
<p class="card-footer center" >
|
||||
<a href="guide/quickstart" title="Angular Quickstart">Quickstart</a>
|
||||
<a href="tutorial" title="Angular Tutorial">Tutorial</a>
|
||||
<p>Get going on your own environment with the Quickstart.</p>
|
||||
<p class="card-footer" >
|
||||
<a href="guide/quickstart" title="Angular Quickstart">Quickstart</a>
|
||||
</p>
|
||||
<!--<p class="card-footer"><a href="guide/quickstart">Quickstart</a></p>
|
||||
<p class="card-footer"><a href="guide/tutorial">Tutorial</a></p>-->
|
||||
|
@ -1,5 +1,5 @@
|
||||
<header class="marketing-banner">
|
||||
<div class="banner-headline no-toc">Events</div>
|
||||
<h1 class="banner-headline no-toc no-anchor">Events</h1>
|
||||
</header>
|
||||
|
||||
<article class="l-content ">
|
||||
|
@ -1,3 +1,7 @@
|
||||
<header class="marketing-banner">
|
||||
<h1 class="banner-headline no-toc no-anchor">Features & Benefits</h1>
|
||||
</header>
|
||||
|
||||
<article class="l-content ">
|
||||
<div class="flex-center">
|
||||
<div>
|
||||
@ -100,6 +104,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cta-bar"><a href="guide/quickstart" md-button="md-button" class="button button-large hero-cta">Get Started</a></div>
|
||||
<div class="cta-bar announcement-bar">
|
||||
<button class="button">
|
||||
<a href="guide/quickstart">Get Started</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
@ -35,7 +35,7 @@
|
||||
<img src="assets/images/logos/angular/angular-banner-logo-grey.png" width="64"/>
|
||||
<p>Angular v4.0 is out! Smaller, faster, no biggie...</p>
|
||||
<button class="button">
|
||||
<a href="http://angularjs.blogspot.com/2017/03/angular-400-now-available.html" target="_blank">Learn More</a><span class="material-icons">arrow_forward</span>
|
||||
<a href="http://angularjs.blogspot.com/2017/03/angular-400-now-available.html" target="_blank">Learn More</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,8 @@
|
||||
<h1 class="no-toc">Press Kit</h1>
|
||||
<div class="presskit-container l-space-neg-top-8">
|
||||
<header class="marketing-banner">
|
||||
<h1 class="banner-headline no-toc no-anchor">Press kit</h1>
|
||||
</header>
|
||||
|
||||
<article class="presskit-container">
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
@ -189,4 +192,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<header class="marketing-banner">
|
||||
<div class="banner-headline no-toc">Explore Angular Resources</div>
|
||||
<h1 class="banner-headline no-toc no-anchor">Explore Angular Resources</h1>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
|
@ -12,20 +12,17 @@
|
||||
<a class="nav-link home" href="/"><img src="{{ homeImageUrl }}" title="Home" alt="Home"></a>
|
||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (search)="doSearch($event)"></aio-search-box>
|
||||
<span class="fill-remaining-space"></span>
|
||||
</md-toolbar>
|
||||
<aio-search-results #searchResults *ngIf="showSearchResults" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
|
||||
<md-sidenav-container class="sidenav-container" [class.starting]="isStarting" role="main">
|
||||
|
||||
<md-sidenav [ngClass]="{'collapsed': !isSideBySide }" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
||||
<aio-nav-menu *ngIf="!isSideBySide" [nodes]="topMenuNarrowNodes" [currentNode]="currentNode"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNode" ></aio-nav-menu>
|
||||
<aio-nav-menu *ngIf="!isSideBySide" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes?.SideNav" ></aio-nav-menu>
|
||||
|
||||
<div class="doc-version" title="Angular docs version {{currentDocVersion?.title}}">
|
||||
<select (change)="onDocVersionChange($event.target.selectedIndex)">
|
||||
<option *ngFor="let version of docVersions" [value]="version.title">{{version.title}}</option>
|
||||
</select>
|
||||
<aio-select (change)="onDocVersionChange($event.index)" [options]="docVersions" [selected]="docVersions && docVersions[0]"></aio-select>
|
||||
</div>
|
||||
</md-sidenav>
|
||||
|
||||
|
@ -24,6 +24,7 @@ import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent, Option } from 'app/shared/select/select.component';
|
||||
import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service';
|
||||
import { TocComponent } from 'app/embedded/toc/toc.component';
|
||||
import { MdSidenav } from '@angular/material';
|
||||
@ -221,26 +222,28 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('SideNav version selector', () => {
|
||||
let selectElement: DebugElement;
|
||||
let selectComponent: SelectComponent;
|
||||
beforeEach(() => {
|
||||
component.onResize(sideBySideBreakPoint + 1); // side-by-side
|
||||
selectElement = fixture.debugElement.query(By.directive(SelectComponent));
|
||||
selectComponent = selectElement.componentInstance;
|
||||
});
|
||||
|
||||
it('should pick first (current) version by default', () => {
|
||||
const versionSelector = sidenav.querySelector('select');
|
||||
expect(versionSelector.value).toEqual(component.versionInfo.raw);
|
||||
expect(versionSelector.selectedIndex).toEqual(0);
|
||||
expect(selectComponent.selected.title).toEqual(component.versionInfo.raw);
|
||||
});
|
||||
|
||||
// Older docs versions have an href
|
||||
it('should navigate when change to a version with an href', () => {
|
||||
component.onDocVersionChange(1);
|
||||
selectElement.triggerEventHandler('change', { option: component.docVersions[1] as Option, index: 1});
|
||||
expect(locationService.go).toHaveBeenCalledWith(TestHttp.docVersions[0].url);
|
||||
});
|
||||
|
||||
// The current docs version should not have an href
|
||||
// This may change when we perfect our docs versioning approach
|
||||
it('should not navigate when change to a version without an href', () => {
|
||||
component.onDocVersionChange(0);
|
||||
selectElement.triggerEventHandler('change', { option: component.docVersions[0] as Option, index: 0});
|
||||
expect(locationService.go).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { Component, ElementRef, HostBinding, HostListener, OnInit,
|
||||
QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { MdSidenav } from '@angular/material';
|
||||
|
||||
import { CurrentNode, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
@ -25,7 +25,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
currentDocument: DocumentContents;
|
||||
currentDocVersion: NavigationNode;
|
||||
currentNode: CurrentNode;
|
||||
currentNodes: CurrentNodes;
|
||||
currentPath: string;
|
||||
docVersions: NavigationNode[];
|
||||
dtOn = false;
|
||||
@ -59,7 +59,6 @@ export class AppComponent implements OnInit {
|
||||
isSideBySide = false;
|
||||
private isFetchingTimeout: any;
|
||||
private isSideNavDoc = false;
|
||||
private previousNavView: string;
|
||||
|
||||
private sideBySideWidth = 992;
|
||||
sideNavNodes: NavigationNode[];
|
||||
@ -139,17 +138,17 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
this.navigationService.currentNode.subscribe(currentNode => {
|
||||
this.currentNode = currentNode;
|
||||
this.navigationService.currentNodes.subscribe(currentNodes => {
|
||||
this.currentNodes = currentNodes;
|
||||
|
||||
// Preserve current sidenav open state by default
|
||||
let openSideNav = this.sidenav.opened;
|
||||
const isSideNavDoc = !!currentNodes[sideNavView];
|
||||
|
||||
if (this.previousNavView !== currentNode.view) {
|
||||
this.previousNavView = currentNode.view;
|
||||
if (this.isSideNavDoc !== isSideNavDoc) {
|
||||
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
||||
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
||||
openSideNav = this.isSideNavDoc = currentNode.view === sideNavView;
|
||||
openSideNav = this.isSideNavDoc = isSideNavDoc;
|
||||
}
|
||||
// May be open or closed when wide; always closed when narrow
|
||||
this.sideNavToggle(this.isSideBySide ? openSideNav : false);
|
||||
@ -186,6 +185,9 @@ export class AppComponent implements OnInit {
|
||||
// Stop fetching timeout (which, when render is fast, means progress bar never shown)
|
||||
clearTimeout(this.isFetchingTimeout);
|
||||
|
||||
// Put page in a clean visual state
|
||||
this.scrollService.scrollToTop();
|
||||
|
||||
// Scroll 500ms after the doc-viewer has finished rendering the new doc
|
||||
// The delay is to allow time for async layout to complete
|
||||
setTimeout(() => {
|
||||
@ -253,9 +255,9 @@ export class AppComponent implements OnInit {
|
||||
const sideNavOpen = `sidenav-${this.sidenav.opened ? 'open' : 'closed'}`;
|
||||
const pageClass = `page-${this.pageId}`;
|
||||
const folderClass = `folder-${this.folderId}`;
|
||||
const viewClass = `view-${this.currentNode && this.currentNode.view}`;
|
||||
const viewClasses = Object.keys(this.currentNodes || {}).map(view => `view-${view}`).join(' ');
|
||||
|
||||
this.hostClasses = `${sideNavOpen} ${pageClass} ${folderClass} ${viewClass}`;
|
||||
this.hostClasses = `${sideNavOpen} ${pageClass} ${folderClass} ${viewClasses}`;
|
||||
}
|
||||
|
||||
// Dynamically change height of table of contents container
|
||||
|
@ -45,6 +45,8 @@ import { SearchResultsComponent } from './search/search-results/search-results.c
|
||||
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
|
||||
// These are the hardcoded inline svg sources to be used by the `<md-icon>` component
|
||||
export const svgIconProviders = [
|
||||
{
|
||||
@ -80,7 +82,8 @@ export const svgIconProviders = [
|
||||
MdSidenavModule,
|
||||
MdTabsModule,
|
||||
MdToolbarModule,
|
||||
SwUpdatesModule
|
||||
SwUpdatesModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
@ -1,26 +1,17 @@
|
||||
<div class="l-flex-wrap info-banner api-filter">
|
||||
|
||||
<div class="form-select-menu">
|
||||
<button class="form-select-button has-symbol" (click)="toggleTypeMenu()">
|
||||
<strong>Type:</strong><span class="symbol {{type.name}}"></span>{{type.title}}
|
||||
</button>
|
||||
<ul class="form-select-dropdown" *ngIf="showTypeMenu">
|
||||
<li *ngFor="let t of types" (click)="setType(t)" [class.selected]="t === type">
|
||||
<span class="symbol {{t.name}}"></span>{{t.title}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<aio-select (change)="setType($event.option)"
|
||||
[options]="types"
|
||||
[selected]="type"
|
||||
[showSymbol]="true"
|
||||
label="Type:">
|
||||
</aio-select>
|
||||
|
||||
<div class="form-select-menu">
|
||||
<button class="form-select-button" (click)="toggleStatusMenu()">
|
||||
<strong>Status:</strong>{{status.title}}
|
||||
</button>
|
||||
<ul class="form-select-dropdown" *ngIf="showStatusMenu">
|
||||
<li *ngFor="let s of statuses" (click)="setStatus(s)" [class.selected]="s === status">
|
||||
{{s.title}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<aio-select (change)="setStatus($event.option)"
|
||||
[options]="statuses"
|
||||
[selected]="status"
|
||||
label="Status:">
|
||||
</aio-select>
|
||||
|
||||
<div class="form-search">
|
||||
<input #filter placeholder="Filter" (input)="setQuery($event.target.value)">
|
||||
|
@ -4,6 +4,7 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { ApiListComponent } from './api-list.component';
|
||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
|
||||
describe('ApiListComponent', () => {
|
||||
let component: ApiListComponent;
|
||||
@ -12,6 +13,7 @@ describe('ApiListComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ SharedModule ],
|
||||
declarations: [ ApiListComponent ],
|
||||
providers: [
|
||||
{ provide: ApiService, useClass: TestApiService },
|
||||
@ -75,17 +77,17 @@ describe('ApiListComponent', () => {
|
||||
});
|
||||
|
||||
it('item.show should be true for items with selected status', () => {
|
||||
component.setStatus({name: 'stable', title: 'Stable'});
|
||||
component.setStatus({value: 'stable', title: 'Stable'});
|
||||
expectFilteredResult('status: stable', item => item.stability === 'stable');
|
||||
});
|
||||
|
||||
it('item.show should be true for items with "security-risk" status when selected', () => {
|
||||
component.setStatus({name: 'security-risk', title: 'Security Risk'});
|
||||
component.setStatus({value: 'security-risk', title: 'Security Risk'});
|
||||
expectFilteredResult('status: security-risk', item => item.securityRisk);
|
||||
});
|
||||
|
||||
it('item.show should be true for items of selected type', () => {
|
||||
component.setType({name: 'class', title: 'Class'});
|
||||
component.setType({value: 'class', title: 'Class'});
|
||||
expectFilteredResult('type: class', item => item.docType === 'class');
|
||||
});
|
||||
|
||||
@ -189,8 +191,8 @@ describe('ApiListComponent', () => {
|
||||
|
||||
it('should have query, status, and type', () => {
|
||||
component.setQuery('foo');
|
||||
component.setStatus({name: 'stable', title: 'Stable'});
|
||||
component.setType({name: 'class', title: 'Class'});
|
||||
component.setStatus({value: 'stable', title: 'Stable'});
|
||||
component.setType({value: 'class', title: 'Class'});
|
||||
|
||||
const search = locationService.setSearch.calls.mostRecent().args[1];
|
||||
expect(search.query).toBe('foo');
|
||||
|
@ -15,10 +15,7 @@ import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||
|
||||
interface MenuItem {
|
||||
name: string;
|
||||
title: string;
|
||||
}
|
||||
import { Option } from 'app/shared/select/select.component';
|
||||
|
||||
class SearchCriteria {
|
||||
query? = '';
|
||||
@ -40,29 +37,29 @@ export class ApiListComponent implements OnInit {
|
||||
private criteriaSubject = new ReplaySubject<SearchCriteria>(1);
|
||||
private searchCriteria = new SearchCriteria();
|
||||
|
||||
status: MenuItem;
|
||||
type: MenuItem;
|
||||
status: Option;
|
||||
type: Option;
|
||||
|
||||
// API types
|
||||
types: MenuItem[] = [
|
||||
{ name: 'all', title: 'All' },
|
||||
{ name: 'directive', title: 'Directive' },
|
||||
{ name: 'pipe', title: 'Pipe'},
|
||||
{ name: 'decorator', title: 'Decorator' },
|
||||
{ name: 'class', title: 'Class' },
|
||||
{ name: 'interface', title: 'Interface' },
|
||||
{ name: 'function', title: 'Function' },
|
||||
{ name: 'enum', title: 'Enum' },
|
||||
{ name: 'type-alias', title: 'Type Alias' },
|
||||
{ name: 'const', title: 'Const'}
|
||||
types: Option[] = [
|
||||
{ value: 'all', title: 'All' },
|
||||
{ value: 'directive', title: 'Directive' },
|
||||
{ value: 'pipe', title: 'Pipe'},
|
||||
{ value: 'decorator', title: 'Decorator' },
|
||||
{ value: 'class', title: 'Class' },
|
||||
{ value: 'interface', title: 'Interface' },
|
||||
{ value: 'function', title: 'Function' },
|
||||
{ value: 'enum', title: 'Enum' },
|
||||
{ value: 'type-alias', title: 'Type Alias' },
|
||||
{ value: 'const', title: 'Const'}
|
||||
];
|
||||
|
||||
statuses: MenuItem[] = [
|
||||
{ name: 'all', title: 'All' },
|
||||
{ name: 'stable', title: 'Stable' },
|
||||
{ name: 'deprecated', title: 'Deprecated' },
|
||||
{ name: 'experimental', title: 'Experimental' },
|
||||
{ name: 'security-risk', title: 'Security Risk' }
|
||||
statuses: Option[] = [
|
||||
{ value: 'all', title: 'All' },
|
||||
{ value: 'stable', title: 'Stable' },
|
||||
{ value: 'deprecated', title: 'Deprecated' },
|
||||
{ value: 'experimental', title: 'Experimental' },
|
||||
{ value: 'security-risk', title: 'Security Risk' }
|
||||
];
|
||||
|
||||
@ViewChild('filter') queryEl: ElementRef;
|
||||
@ -90,16 +87,16 @@ export class ApiListComponent implements OnInit {
|
||||
this.setSearchCriteria({query: (query || '').toLowerCase().trim() });
|
||||
}
|
||||
|
||||
setStatus(status: MenuItem) {
|
||||
setStatus(status: Option) {
|
||||
this.toggleStatusMenu();
|
||||
this.status = status;
|
||||
this.setSearchCriteria({status: status.name});
|
||||
this.setSearchCriteria({status: status.value});
|
||||
}
|
||||
|
||||
setType(type: MenuItem) {
|
||||
setType(type: Option) {
|
||||
this.toggleTypeMenu();
|
||||
this.type = type;
|
||||
this.setSearchCriteria({type: type.name});
|
||||
this.setSearchCriteria({type: type.value});
|
||||
}
|
||||
|
||||
toggleStatusMenu() {
|
||||
@ -150,13 +147,13 @@ export class ApiListComponent implements OnInit {
|
||||
// Hack: can't bind to query because input cursor always forced to end-of-line.
|
||||
this.queryEl.nativeElement.value = q;
|
||||
|
||||
this.status = this.statuses.find(x => x.name === status) || this.statuses[0];
|
||||
this.type = this.types.find(x => x.name === type) || this.types[0];
|
||||
this.status = this.statuses.find(x => x.value === status) || this.statuses[0];
|
||||
this.type = this.types.find(x => x.value === type) || this.types[0];
|
||||
|
||||
this.searchCriteria = {
|
||||
query: q,
|
||||
status: this.status.name,
|
||||
type: this.type.name
|
||||
status: this.status.value,
|
||||
type: this.type.value
|
||||
};
|
||||
|
||||
this.criteriaSubject.next(this.searchCriteria);
|
||||
|
@ -13,16 +13,16 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
|
||||
<div *ngIf="person.picture" class="contributor-image" [style.background-image]="'url('+pictureBase+person.picture+')'">
|
||||
<div class="contributor-info">
|
||||
<button>
|
||||
<a *ngIf="person.bio" aria-label="View Bio">View Bio</a>
|
||||
<button *ngIf="person.bio" >
|
||||
<a aria-label="View Bio">View Bio</a>
|
||||
</button>
|
||||
<button class="icon">
|
||||
<a *ngIf="person.twitter" href="https://twitter.com/{{person.twitter}}" target="_blank">
|
||||
<button *ngIf="person.twitter" class="icon">
|
||||
<a href="https://twitter.com/{{person.twitter}}" target="_blank">
|
||||
<span class="fa fa-twitter fa-2x"></span>
|
||||
</a>
|
||||
</button>
|
||||
<button class="icon">
|
||||
<a *ngIf="person.website" href="{{person.website}}" target="_blank">
|
||||
<button *ngIf="person.website" class="icon">
|
||||
<a href="{{person.website}}" target="_blank">
|
||||
<span class="fa fa-link fa-2x"></span>
|
||||
</a>
|
||||
</button>
|
||||
@ -31,16 +31,16 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
|
||||
<div *ngIf="!person.picture" class="contributor-image" [style.background-image]="'url('+pictureBase+noPicture+')'">
|
||||
<div class="contributor-info">
|
||||
<button>
|
||||
<a *ngIf="person.bio" aria-label="View Bio">View Bio</a>
|
||||
<button *ngIf="person.bio">
|
||||
<a aria-label="View Bio">View Bio</a>
|
||||
</button>
|
||||
<button class="icon">
|
||||
<a *ngIf="person.twitter" href="https://twitter.com/{{person.twitter}}" target="_blank">
|
||||
<button *ngIf="person.twitter" class="icon">
|
||||
<a href="https://twitter.com/{{person.twitter}}" target="_blank">
|
||||
<span class="fa fa-twitter fa-2x"></span>
|
||||
</a>
|
||||
</button>
|
||||
<button class="icon">
|
||||
<a *ngIf="person.website" href="{{person.website}}" target="_blank">
|
||||
<button *ngIf="person.website" class="icon">
|
||||
<a href="{{person.website}}" target="_blank">
|
||||
<span class="fa fa-link fa-2x"></span>
|
||||
</a>
|
||||
</button>
|
||||
|
@ -11,6 +11,7 @@ import { PrettyPrinter } from './code/pretty-printer.service';
|
||||
// Reusable components (used inside embedded components)
|
||||
import { MdIconModule, MdTabsModule } from '@angular/material';
|
||||
import { CodeComponent } from './code/code.component';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
|
||||
// Embedded Components
|
||||
import { ApiListComponent } from './api/api-list.component';
|
||||
@ -41,7 +42,8 @@ export class EmbeddedComponents {
|
||||
imports: [
|
||||
CommonModule,
|
||||
MdIconModule,
|
||||
MdTabsModule
|
||||
MdTabsModule,
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
embeddedComponents,
|
||||
|
@ -13,7 +13,7 @@
|
||||
<md-icon class="rotating-icon" svgIcon="keyboard_arrow_right" [class.collapsed]="isCollapsed"></md-icon>
|
||||
</button>
|
||||
|
||||
<ul class="toc-list">
|
||||
<ul class="toc-list" [class.embedded]="type !== 'Floating'">
|
||||
<ng-container *ngFor="let toc of tocList; let i = index">
|
||||
<li #tocItem title="{{toc.title}}" *ngIf="type === 'Floating' || toc.level !== 'h1'"
|
||||
class="{{toc.level}}" [class.secondary]="type === 'EmbeddedExpandable' && i >= primaryMax" [class.active]="i === activeIndex">
|
||||
|
@ -16,9 +16,13 @@ export class NavItemComponent implements OnChanges {
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['selectedNodes'] || changes['node']) {
|
||||
const ix = this.selectedNodes.indexOf(this.node);
|
||||
this.isSelected = ix !== -1;
|
||||
if (ix !== 0) { this.isExpanded = this.isSelected; }
|
||||
if (this.selectedNodes) {
|
||||
const ix = this.selectedNodes.indexOf(this.node);
|
||||
this.isSelected = ix !== -1;
|
||||
if (ix !== 0) { this.isExpanded = this.isSelected; }
|
||||
} else {
|
||||
this.isSelected = false;
|
||||
}
|
||||
}
|
||||
this.setClasses();
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { CurrentNode, NavigationNode } from 'app/navigation/navigation.service';
|
||||
@Component({
|
||||
selector: 'aio-nav-menu',
|
||||
template: `
|
||||
<aio-nav-item *ngFor="let node of filteredNodes" [node]="node" [selectedNodes]="currentNode.nodes">
|
||||
<aio-nav-item *ngFor="let node of filteredNodes" [node]="node" [selectedNodes]="currentNode?.nodes">
|
||||
</aio-nav-item>`
|
||||
})
|
||||
export class NavMenuComponent {
|
||||
|
@ -19,7 +19,7 @@ export interface NavigationViews {
|
||||
/**
|
||||
* Navigation information about a node at specific URL
|
||||
* url: the current URL
|
||||
* view: 'SideNav' | 'TopBar' | 'Footer'
|
||||
* view: 'SideNav' | 'TopBar' | 'Footer' | etc
|
||||
* nodes: the current node and its ancestor nodes within that view
|
||||
*/
|
||||
export interface CurrentNode {
|
||||
@ -28,6 +28,15 @@ export interface CurrentNode {
|
||||
nodes: NavigationNode[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of current nodes by view.
|
||||
* This is needed because some urls map to nodes in more than one view.
|
||||
* If a view does not contain a node that matches the current url then the value will be undefined.
|
||||
*/
|
||||
export interface CurrentNodes {
|
||||
[view: string]: CurrentNode;
|
||||
}
|
||||
|
||||
export interface VersionInfo {
|
||||
raw: string;
|
||||
major: number;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
|
||||
import { MockBackend } from '@angular/http/testing';
|
||||
import { CurrentNode, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -113,7 +113,7 @@ describe('NavigationService', () => {
|
||||
});
|
||||
|
||||
describe('currentNode', () => {
|
||||
let currentNode: CurrentNode;
|
||||
let currentNodes: CurrentNodes;
|
||||
let locationService: MockLocationService;
|
||||
|
||||
const topBarNodes: NavigationNode[] = [
|
||||
@ -138,80 +138,92 @@ describe('NavigationService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
locationService = injector.get(LocationService);
|
||||
navService.currentNode.subscribe(selected => currentNode = selected);
|
||||
navService.currentNodes.subscribe(selected => currentNodes = selected);
|
||||
backend.connectionsArray[0].mockRespond(createResponse(navJson));
|
||||
});
|
||||
|
||||
it('should list the side navigation node that matches the current location, and all its ancestors', () => {
|
||||
locationService.go('b');
|
||||
expect(currentNode).toEqual({
|
||||
url: 'b',
|
||||
view: 'SideNav',
|
||||
nodes: [
|
||||
sideNavNodes[0].children[0],
|
||||
sideNavNodes[0]
|
||||
]
|
||||
expect(currentNodes).toEqual({
|
||||
SideNav: {
|
||||
url: 'b',
|
||||
view: 'SideNav',
|
||||
nodes: [
|
||||
sideNavNodes[0].children[0],
|
||||
sideNavNodes[0]
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
locationService.go('d');
|
||||
expect(currentNode).toEqual({
|
||||
url: 'd',
|
||||
view: 'SideNav',
|
||||
nodes: [
|
||||
sideNavNodes[0].children[0].children[1],
|
||||
sideNavNodes[0].children[0],
|
||||
sideNavNodes[0]
|
||||
]
|
||||
expect(currentNodes).toEqual({
|
||||
SideNav: {
|
||||
url: 'd',
|
||||
view: 'SideNav',
|
||||
nodes: [
|
||||
sideNavNodes[0].children[0].children[1],
|
||||
sideNavNodes[0].children[0],
|
||||
sideNavNodes[0]
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
locationService.go('f');
|
||||
expect(currentNode).toEqual({
|
||||
url: 'f',
|
||||
view: 'SideNav',
|
||||
nodes: [ sideNavNodes[1] ]
|
||||
expect(currentNodes).toEqual({
|
||||
SideNav: {
|
||||
url: 'f',
|
||||
view: 'SideNav',
|
||||
nodes: [ sideNavNodes[1] ]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be a TopBar selected node if the current location is a top menu node', () => {
|
||||
locationService.go('features');
|
||||
expect(currentNode).toEqual({
|
||||
url: 'features',
|
||||
view: 'TopBar',
|
||||
nodes: [ topBarNodes[0] ]
|
||||
expect(currentNodes).toEqual({
|
||||
TopBar: {
|
||||
url: 'features',
|
||||
view: 'TopBar',
|
||||
nodes: [ topBarNodes[0] ]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be a plain object if no side navigation node matches the current location', () => {
|
||||
it('should be a plain object if no navigation node matches the current location', () => {
|
||||
locationService.go('g?search=moo#anchor-1');
|
||||
expect(currentNode).toEqual({
|
||||
url: 'g',
|
||||
view: '',
|
||||
nodes: []
|
||||
expect(currentNodes).toEqual({
|
||||
'': {
|
||||
url: 'g',
|
||||
view: '',
|
||||
nodes: []
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore trailing slashes, hashes, and search params on URLs in the navmap', () => {
|
||||
const cnode: CurrentNode = {
|
||||
url: 'c',
|
||||
view: 'SideNav',
|
||||
nodes: [
|
||||
sideNavNodes[0].children[0].children[0],
|
||||
sideNavNodes[0].children[0],
|
||||
sideNavNodes[0]
|
||||
]
|
||||
const cnode: CurrentNodes = {
|
||||
SideNav: {
|
||||
url: 'c',
|
||||
view: 'SideNav',
|
||||
nodes: [
|
||||
sideNavNodes[0].children[0].children[0],
|
||||
sideNavNodes[0].children[0],
|
||||
sideNavNodes[0]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
locationService.go('c');
|
||||
expect(currentNode).toEqual(cnode, 'location: c');
|
||||
expect(currentNodes).toEqual(cnode, 'location: c');
|
||||
|
||||
locationService.go('c#foo');
|
||||
expect(currentNode).toEqual(cnode, 'location: c#foo');
|
||||
expect(currentNodes).toEqual(cnode, 'location: c#foo');
|
||||
|
||||
locationService.go('c?foo=1');
|
||||
expect(currentNode).toEqual(cnode, 'location: c?foo=1');
|
||||
expect(currentNodes).toEqual(cnode, 'location: c?foo=1');
|
||||
|
||||
locationService.go('c#foo?bar=1&baz=2');
|
||||
expect(currentNode).toEqual(cnode, 'location: c#foo?bar=1&baz=2');
|
||||
expect(currentNodes).toEqual(cnode, 'location: c#foo?bar=1&baz=2');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -13,8 +13,8 @@ import { LocationService } from 'app/shared/location.service';
|
||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
|
||||
// Import and re-export the Navigation model types
|
||||
import { CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
||||
export { CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
||||
import { CurrentNodes, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
||||
export { CurrentNodes, CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
||||
|
||||
const navigationPath = CONTENT_URL_PREFIX + 'navigation.json';
|
||||
|
||||
@ -35,13 +35,13 @@ export class NavigationService {
|
||||
* node (if any) that matches the current URL location
|
||||
* including its navigation view and its ancestor nodes in that view
|
||||
*/
|
||||
currentNode: Observable<CurrentNode>;
|
||||
currentNodes: Observable<CurrentNodes>;
|
||||
|
||||
constructor(private http: Http, private location: LocationService, private logger: Logger) {
|
||||
const navigationInfo = this.fetchNavigationInfo();
|
||||
this.navigationViews = this.getNavigationViews(navigationInfo);
|
||||
|
||||
this.currentNode = this.getCurrentNode(this.navigationViews);
|
||||
this.currentNodes = this.getCurrentNodes(this.navigationViews);
|
||||
// The version information is packaged inside the navigation response to save us an extra request.
|
||||
this.versionInfo = this.getVersionInfo(navigationInfo);
|
||||
}
|
||||
@ -88,23 +88,23 @@ export class NavigationService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an observable of the current node (the one that matches the current URL)
|
||||
* Get an observable of the current nodes (the ones that match the current URL)
|
||||
* We use `publishReplay(1)` because otherwise subscribers will have to wait until the next
|
||||
* URL change before they receive an emission.
|
||||
* See above for discussion of using `connect`.
|
||||
*/
|
||||
private getCurrentNode(navigationViews: Observable<NavigationViews>): Observable<CurrentNode> {
|
||||
const currentNode = combineLatest(
|
||||
private getCurrentNodes(navigationViews: Observable<NavigationViews>): Observable<CurrentNodes> {
|
||||
const currentNodes = combineLatest(
|
||||
navigationViews.map(views => this.computeUrlToNavNodesMap(views)),
|
||||
this.location.currentPath,
|
||||
|
||||
(navMap, url) => {
|
||||
const urlKey = url.startsWith('api/') ? 'api' : url;
|
||||
return navMap[urlKey] || { view: '', url: urlKey, nodes: [] };
|
||||
return navMap[urlKey] || { '' : { view: '', url: urlKey, nodes: [] }};
|
||||
})
|
||||
.publishReplay(1);
|
||||
currentNode.connect();
|
||||
return currentNode;
|
||||
currentNodes.connect();
|
||||
return currentNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,7 +114,7 @@ export class NavigationService {
|
||||
* @param navigation - A collection of navigation nodes that are to be mapped
|
||||
*/
|
||||
private computeUrlToNavNodesMap(navigation: NavigationViews) {
|
||||
const navMap = new Map<string, CurrentNode>();
|
||||
const navMap = new Map<string, CurrentNodes>();
|
||||
Object.keys(navigation)
|
||||
.forEach(view => navigation[view]
|
||||
.forEach(node => this.walkNodes(view, navMap, node)));
|
||||
@ -138,7 +138,7 @@ export class NavigationService {
|
||||
* patching them and computing their ancestor nodes
|
||||
*/
|
||||
private walkNodes(
|
||||
view: string, navMap: Map<string, CurrentNode>,
|
||||
view: string, navMap: Map<string, CurrentNodes>,
|
||||
node: NavigationNode, ancestors: NavigationNode[] = []) {
|
||||
const nodes = [node, ...ancestors];
|
||||
const url = node.url;
|
||||
@ -147,7 +147,9 @@ export class NavigationService {
|
||||
// only map to this node if it has a url
|
||||
if (url) {
|
||||
// Strip off trailing slashes from nodes in the navMap - they are not relevant to matching
|
||||
navMap[url.replace(/\/$/, '')] = { url, view, nodes };
|
||||
const cleanedUrl = url.replace(/\/$/, '');
|
||||
const navMapItem = navMap[cleanedUrl] = navMap[cleanedUrl] || {};
|
||||
navMapItem[view] = { url, view, nodes };
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
<ng-template #searchResults>
|
||||
<h2 class="visually-hidden">Search Results</h2>
|
||||
<div class="search-area" *ngFor="let area of searchAreas">
|
||||
<h3>{{area.name}} ({{area.pages.length}})</h3>
|
||||
<h3>{{area.name}} ({{area.pages.length + area.priorityPages.length}})</h3>
|
||||
<ul class="priority-pages" >
|
||||
<li class="search-page" *ngFor="let page of area.priorityPages">
|
||||
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">
|
||||
@ -13,7 +13,6 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="more-items material-icons" *ngIf="area.priorityPages.length > 0">more_horiz</div>
|
||||
<ul>
|
||||
<li class="search-page" *ngFor="let page of area.pages">
|
||||
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">
|
||||
|
@ -33,6 +33,11 @@ describe('SearchResultsComponent', () => {
|
||||
return take === undefined ? results : results.slice(0, take);
|
||||
}
|
||||
|
||||
function compareTitle(l: {title: string}, r: {title: string}) {
|
||||
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SearchResultsComponent ],
|
||||
@ -54,13 +59,13 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
searchResults.next({ query: '', results: results});
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'api', pages: [
|
||||
{ name: 'api', priorityPages: [
|
||||
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
|
||||
], priorityPages: [] },
|
||||
{ name: 'guide', pages: [
|
||||
], pages: [] },
|
||||
{ name: 'guide', priorityPages: [
|
||||
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
||||
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
||||
], priorityPages: [] }
|
||||
], pages: [] }
|
||||
]);
|
||||
});
|
||||
|
||||
@ -70,48 +75,35 @@ describe('SearchResultsComponent', () => {
|
||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||
]});
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'tutorial', pages: [
|
||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||
{ name: 'tutorial', priorityPages: [
|
||||
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
||||
], priorityPages: [] }
|
||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||
], pages: [] }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort by title within sorted area', () => {
|
||||
const results = getTestResults(5);
|
||||
searchResults.next({ query: '', results: results });
|
||||
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'api', pages: [
|
||||
{ path: 'api/c', title: 'API C', type: '', keywords: '', titleWords: '' },
|
||||
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' },
|
||||
], priorityPages: [] },
|
||||
{ name: 'guide', pages: [
|
||||
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
||||
{ path: 'guide/a/c', title: 'Guide A - C', type: '', keywords: '', titleWords: '' },
|
||||
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
||||
], priorityPages: [] }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should put first 5 area results into priorityPages when more than 10 pages', () => {
|
||||
it('should put first 5 results for each area into priorityPages', () => {
|
||||
const results = getTestResults();
|
||||
const sorted = results.slice().sort((l, r) => l.title > r.title ? 1 : -1);
|
||||
const expected = [
|
||||
{
|
||||
name: 'api',
|
||||
pages: sorted.filter(p => p.path.startsWith('api')),
|
||||
priorityPages: []
|
||||
},
|
||||
{
|
||||
name: 'guide',
|
||||
pages: sorted.filter(p => p.path.startsWith('guide')),
|
||||
priorityPages: results.filter(p => p.path.startsWith('guide')).slice(0, 5)
|
||||
}
|
||||
];
|
||||
|
||||
searchResults.next({ query: '', results: results });
|
||||
expect(component.searchAreas).toEqual(expected);
|
||||
expect(component.searchAreas[0].priorityPages).toEqual(results.filter(p => p.path.startsWith('api')).slice(0, 5));
|
||||
expect(component.searchAreas[1].priorityPages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(0, 5));
|
||||
});
|
||||
|
||||
it('should put the nonPriorityPages into the pages array, sorted by title', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
expect(component.searchAreas[0].pages).toEqual([]);
|
||||
expect(component.searchAreas[1].pages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(5).sort(compareTitle));
|
||||
});
|
||||
|
||||
it('should put a total count in the header of each area of search results', () => {
|
||||
const results = getTestResults();
|
||||
searchResults.next({ query: '', results: results });
|
||||
fixture.detectChanges();
|
||||
const headers = fixture.debugElement.queryAll(By.css('h3'));
|
||||
expect(headers.length).toEqual(2);
|
||||
expect(headers[0].nativeElement.textContent).toContain('(2)');
|
||||
expect(headers[1].nativeElement.textContent).toContain('(13)');
|
||||
});
|
||||
|
||||
it('should put search results with no containing folder into the default area (other)', () => {
|
||||
@ -121,9 +113,9 @@ describe('SearchResultsComponent', () => {
|
||||
|
||||
searchResults.next({ query: '', results: results });
|
||||
expect(component.searchAreas).toEqual([
|
||||
{ name: 'other', pages: [
|
||||
{ name: 'other', priorityPages: [
|
||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||
], priorityPages: [] }
|
||||
], pages: [] }
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -60,8 +60,10 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
const keys = Object.keys(searchAreaMap).sort((l, r) => l > r ? 1 : -1);
|
||||
return keys.map(name => {
|
||||
let pages = searchAreaMap[name];
|
||||
const priorityPages = pages.length > 10 ? searchAreaMap[name].slice(0, 5) : [];
|
||||
let pages: SearchResult[] = searchAreaMap[name];
|
||||
|
||||
// Extract the top 5 most relevant results as priorityPages
|
||||
const priorityPages = pages.splice(0, 5);
|
||||
pages = pages.sort(compareResults);
|
||||
return { name, pages, priorityPages };
|
||||
});
|
||||
|
@ -300,6 +300,7 @@ describe('ScrollSpyService', () => {
|
||||
});
|
||||
|
||||
describe('window resize events', () => {
|
||||
const RESIZE_EVENT_DELAY = 300;
|
||||
let onResizeSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -317,7 +318,7 @@ describe('ScrollSpyService', () => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
|
||||
tick(300);
|
||||
tick(RESIZE_EVENT_DELAY);
|
||||
expect(onResizeSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
@ -327,58 +328,59 @@ describe('ScrollSpyService', () => {
|
||||
onResizeSpy.calls.reset();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(300);
|
||||
tick(RESIZE_EVENT_DELAY);
|
||||
expect(onResizeSpy).toHaveBeenCalled();
|
||||
|
||||
info1.unspy();
|
||||
onResizeSpy.calls.reset();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(300);
|
||||
tick(RESIZE_EVENT_DELAY);
|
||||
expect(onResizeSpy).toHaveBeenCalled();
|
||||
|
||||
info2.unspy();
|
||||
onResizeSpy.calls.reset();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(300);
|
||||
tick(RESIZE_EVENT_DELAY);
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should only fire every 300ms', fakeAsync(() => {
|
||||
it(`should only fire every ${RESIZE_EVENT_DELAY}ms`, fakeAsync(() => {
|
||||
scrollSpyService.spyOn([]);
|
||||
onResizeSpy.calls.reset();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(100);
|
||||
tick(RESIZE_EVENT_DELAY - 2);
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onResizeSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
onResizeSpy.calls.reset();
|
||||
tick(150);
|
||||
tick(RESIZE_EVENT_DELAY / 2);
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(100);
|
||||
tick(RESIZE_EVENT_DELAY - 2);
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onResizeSpy).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('window scroll events', () => {
|
||||
const SCROLL_EVENT_DELAY = 10;
|
||||
let onScrollSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -395,7 +397,7 @@ describe('ScrollSpyService', () => {
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||
|
||||
tick(300);
|
||||
tick(SCROLL_EVENT_DELAY);
|
||||
expect(onScrollSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
@ -404,52 +406,52 @@ describe('ScrollSpyService', () => {
|
||||
const info2 = scrollSpyService.spyOn([]);
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(300);
|
||||
tick(SCROLL_EVENT_DELAY);
|
||||
expect(onScrollSpy).toHaveBeenCalled();
|
||||
|
||||
info1.unspy();
|
||||
onScrollSpy.calls.reset();
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(300);
|
||||
tick(SCROLL_EVENT_DELAY);
|
||||
expect(onScrollSpy).toHaveBeenCalled();
|
||||
|
||||
info2.unspy();
|
||||
onScrollSpy.calls.reset();
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(300);
|
||||
tick(SCROLL_EVENT_DELAY);
|
||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should only fire every 300ms', fakeAsync(() => {
|
||||
it(`should only fire every ${SCROLL_EVENT_DELAY}ms`, fakeAsync(() => {
|
||||
scrollSpyService.spyOn([]);
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(100);
|
||||
tick(SCROLL_EVENT_DELAY - 2);
|
||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onScrollSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
onScrollSpy.calls.reset();
|
||||
tick(150);
|
||||
tick(SCROLL_EVENT_DELAY / 2);
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(100);
|
||||
tick(SCROLL_EVENT_DELAY - 2);
|
||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('scroll'));
|
||||
tick(100);
|
||||
tick(1);
|
||||
expect(onScrollSpy).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
});
|
||||
|
@ -124,7 +124,7 @@ export class ScrollSpyService {
|
||||
private spiedElementGroups: ScrollSpiedElementGroup[] = [];
|
||||
private onStopListening = new Subject();
|
||||
private resizeEvents = Observable.fromEvent(window, 'resize').auditTime(300).takeUntil(this.onStopListening);
|
||||
private scrollEvents = Observable.fromEvent(window, 'scroll').auditTime(300).takeUntil(this.onStopListening);
|
||||
private scrollEvents = Observable.fromEvent(window, 'scroll').auditTime(10).takeUntil(this.onStopListening);
|
||||
private lastContentHeight: number;
|
||||
private lastMaxScrollTop: number;
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { DOCUMENT } from '@angular/platform-browser';
|
||||
import { ScrollService, topMargin } from './scroll.service';
|
||||
|
||||
describe('ScrollService', () => {
|
||||
const topOfPageElem = {} as Element;
|
||||
let injector: ReflectiveInjector;
|
||||
let document: MockDocument;
|
||||
let location: MockPlatformLocation;
|
||||
@ -16,7 +17,8 @@ describe('ScrollService', () => {
|
||||
|
||||
class MockDocument {
|
||||
body = new MockElement();
|
||||
getElementById = jasmine.createSpy('Document getElementById');
|
||||
getElementById = jasmine.createSpy('Document getElementById').and.returnValue(topOfPageElem);
|
||||
querySelector = jasmine.createSpy('Document querySelector');
|
||||
}
|
||||
|
||||
class MockElement {
|
||||
@ -38,6 +40,69 @@ describe('ScrollService', () => {
|
||||
scrollService = injector.get(ScrollService);
|
||||
});
|
||||
|
||||
describe('#topOffset', () => {
|
||||
it('should query for the top-bar by CSS selector', () => {
|
||||
expect(document.querySelector).not.toHaveBeenCalled();
|
||||
|
||||
expect(scrollService.topOffset).toBe(topMargin);
|
||||
expect(document.querySelector).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be calculated based on the top-bar\'s height + margin', () => {
|
||||
(document.querySelector as jasmine.Spy).and.returnValue({clientHeight: 50});
|
||||
expect(scrollService.topOffset).toBe(50 + topMargin);
|
||||
});
|
||||
|
||||
it('should only query for the top-bar once', () => {
|
||||
expect(scrollService.topOffset).toBe(topMargin);
|
||||
(document.querySelector as jasmine.Spy).calls.reset();
|
||||
|
||||
expect(scrollService.topOffset).toBe(topMargin);
|
||||
expect(document.querySelector).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should retrieve the top-bar\'s height again after resize', () => {
|
||||
let clientHeight = 50;
|
||||
(document.querySelector as jasmine.Spy).and.callFake(() => ({clientHeight}));
|
||||
|
||||
expect(scrollService.topOffset).toBe(50 + topMargin);
|
||||
expect(document.querySelector).toHaveBeenCalled();
|
||||
|
||||
(document.querySelector as jasmine.Spy).calls.reset();
|
||||
clientHeight = 100;
|
||||
|
||||
expect(scrollService.topOffset).toBe(50 + topMargin);
|
||||
expect(document.querySelector).not.toHaveBeenCalled();
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
expect(scrollService.topOffset).toBe(100 + topMargin);
|
||||
expect(document.querySelector).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#topOfPageElement', () => {
|
||||
it('should query for the top-of-page element by ID', () => {
|
||||
expect(document.getElementById).not.toHaveBeenCalled();
|
||||
|
||||
expect(scrollService.topOfPageElement).toBe(topOfPageElem);
|
||||
expect(document.getElementById).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should only query for the top-of-page element once', () => {
|
||||
expect(scrollService.topOfPageElement).toBe(topOfPageElem);
|
||||
(document.getElementById as jasmine.Spy).calls.reset();
|
||||
|
||||
expect(scrollService.topOfPageElement).toBe(topOfPageElem);
|
||||
expect(document.getElementById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return `<body>` if unable to find the top-of-page element', () => {
|
||||
(document.getElementById as jasmine.Spy).and.returnValue(null);
|
||||
expect(scrollService.topOfPageElement).toBe(document.body as any);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#scroll', () => {
|
||||
it('should scroll to the top if there is no hash', () => {
|
||||
location.hash = '';
|
||||
@ -73,10 +138,28 @@ describe('ScrollService', () => {
|
||||
|
||||
describe('#scrollToElement', () => {
|
||||
it('should scroll to element', () => {
|
||||
const element = <Element><any> new MockElement();
|
||||
const element: Element = new MockElement() as any;
|
||||
scrollService.scrollToElement(element);
|
||||
expect(element.scrollIntoView).toHaveBeenCalled();
|
||||
expect(window.scrollBy).toHaveBeenCalledWith(0, -topMargin);
|
||||
expect(window.scrollBy).toHaveBeenCalledWith(0, -scrollService.topOffset);
|
||||
});
|
||||
|
||||
it('should scroll all the way to the top if close enough', () => {
|
||||
const element: Element = new MockElement() as any;
|
||||
|
||||
(window as any).pageYOffset = 25;
|
||||
scrollService.scrollToElement(element);
|
||||
|
||||
expect(element.scrollIntoView).toHaveBeenCalled();
|
||||
expect(window.scrollBy).toHaveBeenCalledWith(0, -scrollService.topOffset);
|
||||
(window.scrollBy as jasmine.Spy).calls.reset();
|
||||
|
||||
(window as any).pageYOffset = 15;
|
||||
scrollService.scrollToElement(element);
|
||||
|
||||
expect(element.scrollIntoView).toHaveBeenCalled();
|
||||
expect(window.scrollBy).toHaveBeenCalledWith(0, -scrollService.topOffset);
|
||||
expect(window.scrollBy).toHaveBeenCalledWith(0, -15);
|
||||
});
|
||||
|
||||
it('should do nothing if no element', () => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
import {fromEvent} from 'rxjs/observable/fromEvent';
|
||||
|
||||
export const topMargin = 16;
|
||||
/**
|
||||
@ -9,20 +10,20 @@ export const topMargin = 16;
|
||||
@Injectable()
|
||||
export class ScrollService {
|
||||
|
||||
private _topOffset: number;
|
||||
private _topOffset: number | null;
|
||||
private _topOfPageElement: Element;
|
||||
|
||||
// Offset from the top of the document to bottom of any static elements
|
||||
// at the top (e.g. toolbar) + some margin
|
||||
get topOffset() {
|
||||
if (!this._topOffset) {
|
||||
// Since the toolbar is not static, we don't need to account for its height.
|
||||
this._topOffset = topMargin;
|
||||
const toolbar = this.document.querySelector('md-toolbar.app-toolbar');
|
||||
this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
|
||||
}
|
||||
return this._topOffset;
|
||||
}
|
||||
|
||||
private get topOfPageElement() {
|
||||
get topOfPageElement() {
|
||||
if (!this._topOfPageElement) {
|
||||
this._topOfPageElement = this.document.getElementById('top-of-page') || this.document.body;
|
||||
}
|
||||
@ -31,7 +32,10 @@ export class ScrollService {
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private document: any,
|
||||
private location: PlatformLocation) { }
|
||||
private location: PlatformLocation) {
|
||||
// On resize, the toolbar might change height, so "invalidate" the top offset.
|
||||
fromEvent(window, 'resize').subscribe(() => this._topOffset = null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the element with id extracted from the current location hash fragment.
|
||||
@ -53,7 +57,14 @@ export class ScrollService {
|
||||
scrollToElement(element: Element) {
|
||||
if (element) {
|
||||
element.scrollIntoView();
|
||||
if (window && window.scrollBy) { window.scrollBy(0, -this.topOffset); }
|
||||
if (window && window.scrollBy) {
|
||||
window.scrollBy(0, -this.topOffset);
|
||||
if (window.pageYOffset < 20) {
|
||||
// If we are very close to the top (<20px), then scroll all the way up.
|
||||
// (This can happen if `element` is at the top of the page, but has a small top-margin.)
|
||||
window.scrollBy(0, -window.pageYOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
16
aio/src/app/shared/select/select.component.html
Normal file
16
aio/src/app/shared/select/select.component.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="form-select-menu">
|
||||
<button class="form-select-button" (click)="toggleOptions()">
|
||||
<strong>{{label}}</strong><span *ngIf="showSymbol" class="symbol {{selected?.value}}"></span>{{selected?.title}}
|
||||
</button>
|
||||
<ul class="form-select-dropdown" *ngIf="showOptions">
|
||||
<li *ngFor="let option of options; index as i"
|
||||
[class.selected]="option === selected"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
(click)="select(option, i)"
|
||||
(keydown.enter)="select(option, i)"
|
||||
(keydown.space)="select(option, i); $event.preventDefault()">
|
||||
<span *ngIf="showSymbol" class="symbol {{option.value}}"></span>{{option.title}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
165
aio/src/app/shared/select/select.component.spec.ts
Normal file
165
aio/src/app/shared/select/select.component.spec.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SelectComponent, Option } from './select.component';
|
||||
|
||||
const options = [
|
||||
{ title: 'Option A', value: 'option-a' },
|
||||
{ title: 'Option B', value: 'option-b' }
|
||||
];
|
||||
|
||||
let component: SelectComponent;
|
||||
let host: HostComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let element: DebugElement;
|
||||
|
||||
describe('SelectComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SelectComponent, HostComponent ],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
host = fixture.componentInstance;
|
||||
element = fixture.debugElement.query(By.directive(SelectComponent));
|
||||
component = element.componentInstance;
|
||||
});
|
||||
|
||||
describe('(initially)', () => {
|
||||
it('should show the button and no options', () => {
|
||||
expect(getButton()).toBeDefined();
|
||||
expect(getOptionContainer()).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('button', () => {
|
||||
it('should display the label if provided', () => {
|
||||
expect(getButton().textContent.trim()).toEqual('');
|
||||
host.label = 'Label:';
|
||||
fixture.detectChanges();
|
||||
expect(getButton().textContent.trim()).toEqual('Label:');
|
||||
});
|
||||
|
||||
it('should contain a symbol `<span>` if hasSymbol is true', () => {
|
||||
expect(getButton().querySelector('span')).toEqual(null);
|
||||
host.showSymbol = true;
|
||||
fixture.detectChanges();
|
||||
const span = getButton().querySelector('span');
|
||||
expect(span).not.toEqual(null);
|
||||
expect(span.className).toContain('symbol');
|
||||
});
|
||||
|
||||
it('should display the selected option, if there is one', () => {
|
||||
host.showSymbol = true;
|
||||
host.selected = options[0];
|
||||
fixture.detectChanges();
|
||||
expect(getButton().textContent).toContain(options[0].title);
|
||||
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||
});
|
||||
|
||||
it('should toggle the visibility of the options list when clicked', () => {
|
||||
host.options = options;
|
||||
getButton().click();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionContainer()).not.toEqual(null);
|
||||
getButton().click();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionContainer()).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('options list', () => {
|
||||
beforeEach(() => {
|
||||
host.options = options;
|
||||
host.showSymbol = true;
|
||||
getButton().click(); // ensure the the options are visible
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the corresponding title of each option', () => {
|
||||
getOptions().forEach((li, index) => {
|
||||
expect(li.textContent).toContain(options[index].title);
|
||||
});
|
||||
});
|
||||
|
||||
it('should select the option that is clicked', () => {
|
||||
getOptions()[0].click();
|
||||
fixture.detectChanges();
|
||||
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
|
||||
expect(getButton().textContent).toContain(options[0].title);
|
||||
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||
});
|
||||
|
||||
it('should select the current option when enter is pressed', () => {
|
||||
const e = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: 'Enter'});
|
||||
getOptions()[0].dispatchEvent(e);
|
||||
fixture.detectChanges();
|
||||
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
|
||||
expect(getButton().textContent).toContain(options[0].title);
|
||||
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||
});
|
||||
|
||||
it('should select the current option when space is pressed', () => {
|
||||
const e = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: ' '});
|
||||
getOptions()[0].dispatchEvent(e);
|
||||
fixture.detectChanges();
|
||||
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
|
||||
expect(getButton().textContent).toContain(options[0].title);
|
||||
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||
});
|
||||
|
||||
it('should hide when an option is clicked', () => {
|
||||
getOptions()[0].click();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionContainer()).toEqual(null);
|
||||
});
|
||||
|
||||
it('should hide when there is a click that is not on the option list', () => {
|
||||
fixture.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
expect(getOptionContainer()).toEqual(null);
|
||||
});
|
||||
|
||||
it('should hide if the escape button is pressed', () => {
|
||||
const e = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Escape' });
|
||||
document.dispatchEvent(e);
|
||||
fixture.detectChanges();
|
||||
expect(getOptionContainer()).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<aio-select (change)="onChange($event)"
|
||||
[options]="options"
|
||||
[selected]="selected"
|
||||
[label]="label"
|
||||
[showSymbol]="showSymbol">
|
||||
</aio-select>`
|
||||
})
|
||||
class HostComponent {
|
||||
onChange = jasmine.createSpy('onChange');
|
||||
options: Option[];
|
||||
selected: Option;
|
||||
label: string;
|
||||
showSymbol: boolean;
|
||||
}
|
||||
|
||||
function getButton(): HTMLButtonElement {
|
||||
return element.query(By.css('button')).nativeElement;
|
||||
}
|
||||
|
||||
function getOptionContainer(): HTMLUListElement {
|
||||
const de = element.query(By.css('ul'));
|
||||
return de && de.nativeElement;
|
||||
}
|
||||
|
||||
function getOptions(): HTMLLIElement[] {
|
||||
return element.queryAll(By.css('li')).map(de => de.nativeElement);
|
||||
}
|
62
aio/src/app/shared/select/select.component.ts
Normal file
62
aio/src/app/shared/select/select.component.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Component, ElementRef, EventEmitter, HostListener, Input, Output, OnInit } from '@angular/core';
|
||||
|
||||
export interface Option {
|
||||
title: string;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'aio-select',
|
||||
templateUrl: 'select.component.html'
|
||||
})
|
||||
export class SelectComponent implements OnInit {
|
||||
@Input()
|
||||
selected: Option;
|
||||
|
||||
@Input()
|
||||
options: Option[];
|
||||
|
||||
@Output()
|
||||
change = new EventEmitter<{option: Option, index: number}>();
|
||||
|
||||
@Input()
|
||||
showSymbol = false;
|
||||
|
||||
@Input()
|
||||
label: string;
|
||||
|
||||
showOptions = false;
|
||||
|
||||
constructor(private hostElement: ElementRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.label = this.label || '';
|
||||
}
|
||||
|
||||
toggleOptions() {
|
||||
this.showOptions = !this.showOptions;
|
||||
}
|
||||
|
||||
hideOptions() {
|
||||
this.showOptions = false;
|
||||
}
|
||||
|
||||
select(option: Option, index: number) {
|
||||
this.selected = option;
|
||||
this.change.emit({option, index});
|
||||
this.hideOptions();
|
||||
}
|
||||
|
||||
@HostListener('document:click', ['$event.target'])
|
||||
onClick(eventTarget: HTMLElement) {
|
||||
// Hide the options if we clicked outside the component
|
||||
if (!this.hostElement.nativeElement.contains(eventTarget)) {
|
||||
this.hideOptions();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.escape')
|
||||
onKeyDown() {
|
||||
this.hideOptions();
|
||||
}
|
||||
}
|
16
aio/src/app/shared/shared.module.ts
Normal file
16
aio/src/app/shared/shared.module.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SelectComponent } from './select/select.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
exports: [
|
||||
SelectComponent
|
||||
],
|
||||
declarations: [
|
||||
SelectComponent
|
||||
]
|
||||
})
|
||||
export class SharedModule {}
|
@ -8,7 +8,7 @@ body {
|
||||
}
|
||||
|
||||
h1 {
|
||||
display:inline-block;
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
margin: 8px 0px;
|
||||
|
@ -7,11 +7,7 @@ aio-shell.page-docs {
|
||||
|
||||
.sidenav-content {
|
||||
min-height: 100vh;
|
||||
padding: 6rem 3rem 1rem;
|
||||
}
|
||||
|
||||
.fill-remaining-space {
|
||||
flex: 1 1 auto;
|
||||
padding: 80px 3rem 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
@ -57,7 +57,7 @@ section#intro {
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,16 +146,10 @@ section#intro {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
.material-icons {
|
||||
display: inline-block;
|
||||
color: $white;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
opacity: 0.7;
|
||||
@ -247,7 +241,8 @@ section#intro {
|
||||
|
||||
button.hero-cta {
|
||||
border-radius: 48px;
|
||||
display: block;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.26);
|
||||
cursor: pointer;
|
||||
|
||||
@ -263,7 +258,7 @@ button.hero-cta {
|
||||
}
|
||||
|
||||
aio-shell {
|
||||
&.page-resources, &.page-events {
|
||||
&.page-resources, &.page-events, &.page-features, &.page-presskit, &.page-contribute {
|
||||
section {
|
||||
padding: 0rem 0rem 3rem;
|
||||
}
|
||||
@ -275,12 +270,12 @@ aio-shell {
|
||||
}
|
||||
}
|
||||
|
||||
&.page-home, &.page-resources, &.page-events {
|
||||
&.page-home, &.page-resources, &.page-events, &.page-contribute {
|
||||
article {
|
||||
padding: 32px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
padding: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -302,16 +297,6 @@ aio-shell {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
md-toolbar {
|
||||
padding-left: 24px;
|
||||
|
||||
button.hamburger {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta-bar .hero-cta {
|
||||
@ -319,6 +304,11 @@ aio-shell {
|
||||
}
|
||||
}
|
||||
|
||||
.cta-bar.announcement-bar {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.text-headline {
|
||||
font-size: 20px;
|
||||
margin-top: 10px;
|
||||
@ -455,6 +445,9 @@ header.bckground-sky.l-relative {
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
color: white;
|
||||
margin: 0;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
font-size: 18px;
|
||||
@ -466,3 +459,6 @@ header.bckground-sky.l-relative {
|
||||
}
|
||||
}
|
||||
}
|
||||
.page-features .marketing-banner {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
@ -25,15 +25,17 @@ aio-nav-menu {
|
||||
|
||||
md-sidenav.mat-sidenav.sidenav {
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 80px 0px 0px;
|
||||
padding: 0;
|
||||
min-width: 260px;
|
||||
background-color: $offwhite;
|
||||
box-shadow: 6px 0 6px rgba(0,0,0,0.10);
|
||||
height: calc(100vh - 64px);
|
||||
|
||||
&.collapsed {
|
||||
padding-top: 64px;
|
||||
top: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +104,7 @@ button.vertical-menu-item {
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: $blue-grey-700;
|
||||
color: $darkgray;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
|
@ -1,17 +1,3 @@
|
||||
.fill-remaining-space {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
aio-top-menu a.nav-link {
|
||||
margin: 0;
|
||||
padding: 24px 16px;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link.home img {
|
||||
position: relative;
|
||||
margin-top: -21px;
|
||||
@ -20,22 +6,6 @@ aio-top-menu a.nav-link {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.nav-link {
|
||||
font-size: 80%;
|
||||
margin-right: 8px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.nav-link {
|
||||
font-size: 80%;
|
||||
margin-right: 8px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
aio-top-menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -65,60 +35,102 @@ aio-top-menu {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aio-shell.page-home md-toolbar.app-toolbar.mat-toolbar {
|
||||
background-color: transparent;
|
||||
}
|
||||
a.nav-link {
|
||||
margin: 0;
|
||||
padding: 24px 16px;
|
||||
cursor: pointer;
|
||||
|
||||
md-toolbar.mat-toolbar {
|
||||
position: absolute;
|
||||
perspective: 2000px;
|
||||
top: 0px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
padding: 0 16px 0 0;
|
||||
|
||||
.mat-toolbar-row {
|
||||
@media (max-width: 600px) {
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: rgba($white, 0.15);
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aio-shell.sidenav-open {
|
||||
md-toolbar.mat-toolbar md-icon {
|
||||
color: $mediumgray;
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR HAMBURGER MENU
|
||||
aio-shell.page-home md-toolbar.app-toolbar.mat-toolbar {
|
||||
background-color: transparent;
|
||||
transition: background-color .2s linear .3s;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
background-color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||
md-toolbar.mat-toolbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
padding: 0 16px 0 0;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||
|
||||
md-icon {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||
aio-shell.page-home md-toolbar.mat-toolbar,
|
||||
aio-shell.page-features md-toolbar.mat-toolbar,
|
||||
aio-shell.page-events md-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources md-toolbar.mat-toolbar {
|
||||
// FIXED TOPNAV TOOLBAR FOR SMALL MOBILE
|
||||
@media (min-width: 481px) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
padding-left: 24px;
|
||||
|
||||
button.hamburger {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REMOVE BOX SHADOW ON CERTAIN MARKETING PAGES
|
||||
aio-shell.page-home md-toolbar.mat-toolbar,
|
||||
aio-shell.page-events md-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources md-toolbar.mat-toolbar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
// SEARCH BOX
|
||||
aio-search-box.search-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 150px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
aio-search-box input {
|
||||
color: $darkgray;
|
||||
border: none;
|
||||
border-radius: 100px;
|
||||
background-color: $offwhite;
|
||||
padding: 5px 16px;
|
||||
margin-left: 8px;
|
||||
width: 150px;
|
||||
height: 40%;
|
||||
input {
|
||||
color: $darkgray;
|
||||
border: none;
|
||||
border-radius: 100px;
|
||||
background-color: $offwhite;
|
||||
padding: 5px 16px;
|
||||
margin-left: 8px;
|
||||
width: 180px;
|
||||
max-width: 240px;
|
||||
height: 50%;
|
||||
|
||||
@include bp(big) {
|
||||
transition: width 0.4s ease-in-out;
|
||||
&:focus {
|
||||
width: 50%;
|
||||
max-width: 240px;
|
||||
@include bp(big) {
|
||||
transition: width 0.4s ease-in-out;
|
||||
|
||||
&:focus {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
margin: 24px 0px;
|
||||
font-size: 14px;
|
||||
color: $darkgray;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.is-critical {
|
||||
border-left: 10px solid $brightred;
|
||||
background-color: rgba($brightred, 0.05);
|
||||
|
@ -37,7 +37,7 @@ aio-api-list > div {
|
||||
margin: 16px auto;
|
||||
}
|
||||
|
||||
> div {
|
||||
.form-select-menu, .form-search {
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
@ -115,82 +115,6 @@ $tablet-breakpoint: 800px;
|
||||
}
|
||||
}
|
||||
|
||||
/* SELECT MENU */
|
||||
|
||||
$form-select-width: 200px;
|
||||
|
||||
.form-select-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-select-button {
|
||||
background: $white;
|
||||
box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid $white;
|
||||
color: $blue-grey-600;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
outline: none;
|
||||
padding: 0 16px;
|
||||
text-align: left;
|
||||
width: $form-select-width - 16px;
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.has-symbol {
|
||||
.symbol {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-select-dropdown {
|
||||
background: $white;
|
||||
box-shadow: 0 16px 16px rgba($black, 0.24), 0 0 16px rgba($black, 0.12);
|
||||
border-radius: 4px;
|
||||
left: -8px;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 8px 0;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
width: 200px;
|
||||
z-index: $layer-2;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
padding: 0 16px 0 40px;
|
||||
position: relative;
|
||||
transition: all .2s;
|
||||
|
||||
&:hover {
|
||||
background: $blue-grey-50;
|
||||
color: $blue-500;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $blue-grey-100;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
left: 16px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
z-index: $layer-5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* API SYMBOLS */
|
||||
|
||||
/* SYMBOL CLASS */
|
||||
@ -224,8 +148,12 @@ $form-select-width: 200px;
|
||||
/* API FILTER MENU */
|
||||
|
||||
.api-filter {
|
||||
.form-select-menu {
|
||||
float: left;
|
||||
aio-select {
|
||||
width: 200px;
|
||||
|
||||
.symbol {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-search {
|
||||
@ -237,21 +165,22 @@ $form-select-width: 200px;
|
||||
|
||||
.docs-content .api-list {
|
||||
list-style: none;
|
||||
margin: 0 0 48px 0;
|
||||
margin: 0 0 48px -8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
margin: 0;
|
||||
margin: 0 0 0 -8px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
margin: 0 0 16px 0;
|
||||
margin: 8px;
|
||||
line-height: 14px;
|
||||
padding: 0;
|
||||
float: left;
|
||||
width: 33%;
|
||||
overflow: hidden;
|
||||
min-width: 220px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -356,11 +285,6 @@ p {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
// Override highlight.js
|
||||
.kwd {
|
||||
color: #1E88E5 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
@ -69,40 +69,26 @@ code-tabs mat-tab-body-content .fadeIn {
|
||||
aio-code pre {
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
margin: 16px;
|
||||
margin: 16px 32px;
|
||||
white-space: pre-wrap;
|
||||
align-items: center;
|
||||
|
||||
code span, code ol li {
|
||||
code span {
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
code ol {
|
||||
margin: 0;
|
||||
font-family: $main-font;
|
||||
color: $lightgray;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
font-family: $code-font;
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.code-missing {
|
||||
color: $darkred;
|
||||
}
|
||||
|
||||
.prettyprint {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 8px;
|
||||
top: -8px;
|
||||
right: -32px;
|
||||
|
||||
color: $blue-grey-200;
|
||||
background-color: transparent;
|
||||
@ -111,11 +97,6 @@ code ol {
|
||||
&:hover {
|
||||
color: $mediumgray;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lang-sh .copy-button, .lang-bash .copy-button {
|
||||
@ -130,102 +111,6 @@ code ol {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* SCREEN COLORS */
|
||||
|
||||
.pnk,
|
||||
.blk {
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.pnk {
|
||||
background: $blue-grey-50;
|
||||
color: $blue-grey-900;
|
||||
}
|
||||
.blk {
|
||||
background: $blue-grey-900;
|
||||
}
|
||||
.otl {
|
||||
outline: 1px solid rgba($blue-grey-700, .56);
|
||||
}
|
||||
.kwd {
|
||||
color: $darkgray;
|
||||
}
|
||||
.typ,
|
||||
.tag {
|
||||
color: $darkred;
|
||||
}
|
||||
.str,
|
||||
.atv {
|
||||
color: $blue;
|
||||
}
|
||||
.atn {
|
||||
color: $blue;
|
||||
}
|
||||
.com {
|
||||
color: $mediumgray;
|
||||
}
|
||||
.lit {
|
||||
color: $blue;
|
||||
}
|
||||
.pun {
|
||||
color: $blue-grey-700;
|
||||
}
|
||||
.pln {
|
||||
color: $green-800;
|
||||
}
|
||||
.dec {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
/* SHELL / TERMINAL CODE BLOCKS */
|
||||
|
||||
code-example.code-shell, code-example[language=sh], code-example[language=bash] {
|
||||
& .pnk, .blk,.pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec {
|
||||
color: $codegreen;
|
||||
}
|
||||
}
|
||||
|
||||
/* PRINT COLORS */
|
||||
|
||||
|
||||
@media print {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
ol {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.kwd {
|
||||
color: $darkgray;
|
||||
}
|
||||
.typ,
|
||||
.tag {
|
||||
color: $darkred;
|
||||
}
|
||||
.str,
|
||||
.atv {
|
||||
color: $blue;
|
||||
}
|
||||
.atn {
|
||||
color: $blue;
|
||||
}
|
||||
.com {
|
||||
color: $mediumgray;
|
||||
}
|
||||
.lit {
|
||||
color: $blue;
|
||||
}
|
||||
.pun {
|
||||
color: $blue-grey-700;
|
||||
}
|
||||
.pln {
|
||||
color: $green-800;
|
||||
}
|
||||
.dec {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
// REMOVE RIPPLE EFFECT FROM MATERIAL TABS
|
||||
code-tabs md-tab-group *.mat-ripple-element, code-tabs md-tab-group *.mat-tab-body-active, code-tabs md-tab-group *.mat-tab-body-content, code-tabs md-tab-group *.mat-tab-body-content {
|
||||
@ -240,3 +125,63 @@ code-tabs md-tab-group *.mat-ripple-element, code-tabs md-tab-group *.mat-tab-bo
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/* PRETTY PRINTING STYLES for prettify.js. */
|
||||
|
||||
.prettyprint {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums {
|
||||
margin: 0;
|
||||
font-family: $main-font;
|
||||
color: #B3B6B7;
|
||||
li {
|
||||
margin: 0;
|
||||
font-family: $code-font;
|
||||
font-size: 90%;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* The following class|color styles are derived from https://github.com/google/code-prettify/blob/master/src/prettify.css*/
|
||||
|
||||
/* SPAN elements with the classes below are added by prettyprint. */
|
||||
.pln { color: #000 } /* plain text */
|
||||
|
||||
@media screen {
|
||||
.str { color: #800 } /* string content */
|
||||
.kwd { color: #00f } /* a keyword */
|
||||
.com { color: #060 } /* a comment */
|
||||
.typ { color: red } /* a type name */
|
||||
.lit { color: #08c } /* a literal value */
|
||||
/* punctuation, lisp open bracket, lisp close bracket */
|
||||
.pun, .opn, .clo { color: #660 }
|
||||
.tag { color: #008 } /* a markup tag name */
|
||||
.atn { color: #606 } /* a markup attribute name */
|
||||
.atv { color: #800 } /* a markup attribute value */
|
||||
.dec, .var { color: #606 } /* a declaration; a variable name */
|
||||
.fun { color: red } /* a function name */
|
||||
}
|
||||
|
||||
/* Use higher contrast and text-weight for printable form. */
|
||||
@media print, projection {
|
||||
.str { color: #060 }
|
||||
.kwd { color: #006; font-weight: bold }
|
||||
.com { color: #600; font-style: italic }
|
||||
.typ { color: #404; font-weight: bold }
|
||||
.lit { color: #044 }
|
||||
.pun, .opn, .clo { color: #440 }
|
||||
.tag { color: #006; font-weight: bold }
|
||||
.atn { color: #404 }
|
||||
.atv { color: #060 }
|
||||
}
|
||||
|
||||
/* SHELL / TERMINAL CODE BLOCKS */
|
||||
|
||||
code-example.code-shell, code-example[language=sh], code-example[language=bash] {
|
||||
& .pnk, .blk,.pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec {
|
||||
color: $codegreen;
|
||||
}
|
||||
}
|
||||
|
14
aio/src/styles/2-modules/_contribute.scss
Normal file
14
aio/src/styles/2-modules/_contribute.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.contribute-container {
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.l-sub-section {
|
||||
width: 90%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -59,8 +59,8 @@ aio-contributor {
|
||||
|
||||
.contributor-info {
|
||||
background: rgba($darkgray, 0.5);
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
height: 168px;
|
||||
width: 168px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
@ -94,7 +94,7 @@ aio-contributor {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $blue;
|
||||
color: $lightgray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,6 +112,10 @@ aio-contributor {
|
||||
transform-style:preserve-3d;
|
||||
transition:transform ease 500ms;
|
||||
|
||||
h3 {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.card-front, .card-back {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -159,8 +163,8 @@ aio-contributor {
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
height: 168px;
|
||||
width: 168px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
margin: 8px auto;
|
||||
|
@ -1,6 +1,10 @@
|
||||
.sidenav-content {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
|
||||
&.no-anchor .header-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mat-icon, .material-icons {
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
|
@ -10,6 +10,7 @@
|
||||
@import 'callout';
|
||||
@import 'card';
|
||||
@import 'code';
|
||||
@import 'contribute';
|
||||
@import 'contributor';
|
||||
@import 'edit-page-cta';
|
||||
@import 'features';
|
||||
@ -26,3 +27,4 @@
|
||||
@import 'search-results';
|
||||
@import 'subsection';
|
||||
@import 'toc';
|
||||
@import 'select-menu';
|
||||
|
@ -1,4 +1,6 @@
|
||||
.presskit-container {
|
||||
padding: 0 32px 32px 32px;
|
||||
|
||||
h2 {
|
||||
color: #37474F;
|
||||
}
|
||||
@ -71,4 +73,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.presskit-row:first-child {
|
||||
margin-top: 0;
|
||||
|
||||
@media(max-width: 599px) {
|
||||
margin-top: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,7 @@
|
||||
height: 2px;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
z-index: 5;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
top: 56px;
|
||||
}
|
||||
z-index: 11;
|
||||
}
|
@ -55,7 +55,7 @@ aio-search-results {
|
||||
font-size: 14px;
|
||||
color: $lightgray;
|
||||
text-decoration: none;
|
||||
font-weight: 300;
|
||||
font-weight: normal;
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
@ -68,11 +68,11 @@ aio-search-results {
|
||||
}
|
||||
}
|
||||
|
||||
.more-items {
|
||||
content: 'more_horiz';
|
||||
font-size: 20px;
|
||||
color: $mediumgray;
|
||||
padding: 0;
|
||||
.priority-pages {
|
||||
padding: 0.5rem 0;
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
@include bp(tiny) {
|
||||
|
72
aio/src/styles/2-modules/_select-menu.scss
Normal file
72
aio/src/styles/2-modules/_select-menu.scss
Normal file
@ -0,0 +1,72 @@
|
||||
/* SELECT MENU */
|
||||
|
||||
.form-select-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-select-button {
|
||||
background: $white;
|
||||
box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid $white;
|
||||
color: $blue-grey-600;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
outline: none;
|
||||
padding: 0 16px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 1px solid $blue-400;
|
||||
box-shadow: 0 2px 2px rgba($blue-400, 0.24), 0 0 2px rgba($blue-400, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
.form-select-dropdown {
|
||||
background: $white;
|
||||
box-shadow: 0 16px 16px rgba($black, 0.24), 0 0 16px rgba($black, 0.12);
|
||||
border-radius: 4px;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: $layer-2;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
padding: 0 16px 0 40px;
|
||||
position: relative;
|
||||
transition: all .2s;
|
||||
|
||||
&:hover {
|
||||
background: $blue-grey-50;
|
||||
color: $blue-500;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $blue-grey-100;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
left: 16px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
z-index: $layer-5;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@
|
||||
margin-bottom: 8px;
|
||||
display: table;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
h3 {
|
||||
margin: 8px 0 0;
|
||||
|
@ -109,59 +109,84 @@ aio-toc {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul.toc-list li {
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 3px 0 3px 12px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
&.h1:after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: 40%;
|
||||
margin: 7px 0 4px 0;
|
||||
background: #DBDBDB;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
&.h1, &.h2, &.h3 {
|
||||
border-left: 1px solid $blue;
|
||||
}
|
||||
|
||||
&.h3 {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: inherit;
|
||||
color: lighten($darkgray, 10);
|
||||
display:table-cell;
|
||||
overflow: visible;
|
||||
li {
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
display: table-cell;
|
||||
line-height: 16px;
|
||||
padding: 3px 0 3px 12px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
&.h1:after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: 40%;
|
||||
margin: 7px 0 4px 0;
|
||||
background: #DBDBDB;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
&.h3 {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: inherit;
|
||||
color: lighten($darkgray, 10);
|
||||
display:table-cell;
|
||||
overflow: visible;
|
||||
font-size: 12px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
&:hover a {
|
||||
color: $accentblue;
|
||||
}
|
||||
|
||||
&.active {
|
||||
a {
|
||||
color: $blue;
|
||||
font-weight: 500;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
left: -3px;
|
||||
top: 12px;
|
||||
background: $blue;
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
a {
|
||||
color: $blue;
|
||||
font-weight: 500;
|
||||
&:not(.embedded) li {
|
||||
&:before {
|
||||
border-left: 1px solid $lightgray;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
&:first-child:before {
|
||||
top: 13px;
|
||||
}
|
||||
|
||||
&:last-child:before {
|
||||
bottom: calc(100% - 14px);
|
||||
}
|
||||
|
||||
&:not(.active):hover a:before {
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
left: -4px;
|
||||
left: -3px;
|
||||
top: 12px;
|
||||
background: $blue;
|
||||
background: $lightgray;
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
@ -22,7 +22,7 @@ $offwhite: #FAFAFA;
|
||||
$backgroundgray: #F1F1F1;
|
||||
$lightgray: #DBDBDB;
|
||||
$mist: #ECEFF1;
|
||||
$mediumgray: #7E7E7E;
|
||||
$mediumgray: #6e6e6e;
|
||||
$darkgray: #333;
|
||||
$black: #0A1014;
|
||||
$orange: #FF9800;
|
||||
|
@ -1,11 +1,11 @@
|
||||
{% macro renderMember(member) %}{% if not member.internal -%}
|
||||
<a class="code-anchor" href="#{$ member.name $}">{$ member.name $}</a> {$ params.paramList(member.parameters) | indent(4, false) | trim() $}{$ params.returnType(member.returnType) $}
|
||||
<a class="code-anchor" href="#{$ member.name $}">{$ member.name $}</a>{$ params.paramList(member.parameters) | indent(4, false) | trim() $}{$ params.returnType(member.returnType) $}
|
||||
{%- endif %}{% endmacro -%}
|
||||
|
||||
<section class="class-overview">
|
||||
<h2>Overview</h2>
|
||||
<code-example language="ts" hideCopy="true">
|
||||
{$ doc.docType $} {$ doc.name $} {$ doc.heritage $} {
|
||||
{$ doc.docType $} {$ doc.name $}{$ doc.heritage $} {
|
||||
{%- if doc.statics.length %}{% for member in doc.statics %}
|
||||
static {$ renderMember(member) $}{% endfor %}{% endif %}
|
||||
{%- if doc.constructorDoc %}
|
||||
|
@ -1,10 +1,10 @@
|
||||
<section class="interface-overview">
|
||||
<h2>Interface Overview</h2>
|
||||
<code-example language="ts" hideCopy="true">
|
||||
interface {$ doc.name $} {$ doc.heritage $} { {% if doc.newMember %}
|
||||
<a class="code-anchor" href="#{$ doc.newMember.name $}">{$ doc.newMember.name | indent(6, false) | trim $}</a> {$ params.paramList(doc.newMember.parameters) | indent(8, false) | trim $}{$ params.returnType(doc.newMember.returnType) $}{% endif %}{% if doc.callMember %}
|
||||
<a class="code-anchor" href="#{$ doc.callMember.name $}">{$ doc.callMember.name | indent(6, false) | trim $}</a> {$ params.paramList(doc.callMember.parameters) | indent(8, false) | trim $}{$ params.returnType(doc.callMember.returnType) $}{% endif %}{% if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
|
||||
<a class="code-anchor" href="#{$ member.name $}">{$ member.name | indent(6, false) | trim $}</a> {$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}{% endif %}{% endfor %}{% endif %}
|
||||
interface {$ doc.name $}{$ doc.heritage $} { {% if doc.newMember %}
|
||||
<a class="code-anchor" href="#{$ doc.newMember.name $}">{$ doc.newMember.name | indent(6, false) | trim $}</a>{$ params.paramList(doc.newMember.parameters) | indent(8, false) | trim $}{$ params.returnType(doc.newMember.returnType) $}{% endif %}{% if doc.callMember %}
|
||||
<a class="code-anchor" href="#{$ doc.callMember.name $}">{$ doc.callMember.name | indent(6, false) | trim $}</a>{$ params.paramList(doc.callMember.parameters) | indent(8, false) | trim $}{$ params.returnType(doc.callMember.returnType) $}{% endif %}{% if doc.members.length %}{% for member in doc.members %}{% if not member.internal %}
|
||||
<a class="code-anchor" href="#{$ member.name $}">{$ member.name | indent(6, false) | trim $}</a>{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}{% endif %}{% endfor %}{% endif %}
|
||||
}
|
||||
</code-example>
|
||||
</section>
|
@ -1,19 +1,29 @@
|
||||
{% if doc.members.length %}
|
||||
{% if doc.members.length or doc.newMember or doc.callMember %}
|
||||
<section class="member-members">
|
||||
<h2>Members</h2>
|
||||
{% if doc.newMember %}
|
||||
<div class="new-member">
|
||||
<a id="{$ doc.newMember.name $}"></a>
|
||||
<code-example hideCopy="true">{$ doc.newMember.name $}{$ params.paramList(doc.newMember.parameters) | trim $}{$ params.returnType(doc.newMember.returnType) $}</code-example>
|
||||
{% if not doc.newMember.notYetDocumented %}{$ doc.newMember.description | marked $}{% endif %}
|
||||
</div>
|
||||
{% if doc.members.length or doc.callMember %}<hr>{% endif %}
|
||||
{% endif %}
|
||||
{% if doc.callMember %}
|
||||
<div class="call-member">
|
||||
<a id="{$ doc.callMember.name $}"></a>
|
||||
<code-example hideCopy="true">{$ doc.callMember.name $}{$ params.paramList(doc.callMember.parameters) | trim $}{$ params.returnType(doc.callMember.returnType) $}</code-example>
|
||||
{% if not doc.callMember.notYetDocumented %}{$ doc.callMember.description | marked $}{% endif %}
|
||||
</div>
|
||||
{% if doc.members.length %}<hr>{% endif %}
|
||||
{% endif %}
|
||||
{% for member in doc.members %}{% if not member.internal %}
|
||||
<div class="instance-member">
|
||||
<a id="{$ member.name $}"></a>
|
||||
<code-example hideCopy="true">{$ member.name $}{$ params.paramList(member.parameters) | trim $}{$ params.returnType(member.returnType) $}</code-example>
|
||||
{%- if not member.notYetDocumented %}
|
||||
{$ member.description | marked $}
|
||||
{% endif %}
|
||||
{% if not member.notYetDocumented %}{$ member.description | marked $}{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not loop.last %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
|
||||
{% if not loop.last %}<hr>{% endif %}
|
||||
{% endif %}{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
@ -4,6 +4,5 @@
|
||||
{% block details %}
|
||||
{% include "includes/interface-overview.html" %}
|
||||
{% include "includes/description.html" %}
|
||||
<!-- TODO include callMember and newMember -->
|
||||
{% include "includes/members.html" %}
|
||||
{% endblock %}
|
||||
|
@ -8,5 +8,5 @@
|
||||
|
||||
|
||||
{% macro returnType(returnType) -%}
|
||||
{%- if returnType %} : {$ returnType | escape $}{% endif -%}
|
||||
{%- if returnType %}: {$ returnType | escape $}{% endif -%}
|
||||
{%- endmacro -%}
|
||||
|
@ -4660,8 +4660,8 @@ ng-pwa-tools@^0.0.10:
|
||||
ts-node "^3.0.2"
|
||||
|
||||
ngo-loader@alxhub/ngo:
|
||||
version "0.0.5"
|
||||
resolved "https://codeload.github.com/alxhub/ngo/tar.gz/2c0d731289bbbfeef558f3aaee0596f1a7d229ab"
|
||||
version "0.0.6"
|
||||
resolved "https://codeload.github.com/alxhub/ngo/tar.gz/4b589d8ffb2b6278c387fc492bf17fd42227508f"
|
||||
dependencies:
|
||||
typescript "2.1"
|
||||
|
||||
@ -5607,8 +5607,8 @@ punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
|
||||
purify@igorminar/purify:
|
||||
version "0.0.19"
|
||||
resolved "https://codeload.github.com/igorminar/purify/tar.gz/cdf9c2959ffae1f02ed93e5031afe5a79e9d4d4c"
|
||||
version "0.0.23"
|
||||
resolved "https://codeload.github.com/igorminar/purify/tar.gz/98a5c8f39e3dc2954bf87062b5d91c5e4576a26e"
|
||||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
|
12
circle.yml
12
circle.yml
@ -1,12 +0,0 @@
|
||||
machine:
|
||||
node:
|
||||
version: 6.9.5
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm@3.10.7
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.21.3
|
||||
|
||||
test:
|
||||
override:
|
||||
- gulp lint
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.2.0-rc.2",
|
||||
"version": "4.2.0",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -10,7 +10,7 @@
|
||||
"engines": {
|
||||
"node": ">=6.9.5 <7.0.0",
|
||||
"npm": ">=3.10.7 <4.0.0",
|
||||
"yarn": ">=0.21.3 <0.22.0"
|
||||
"yarn": ">=0.21.3 <1.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -199,34 +199,35 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
|
||||
visitSequence(ast: SequenceAst, context: AnimationTimelineContext) {
|
||||
const subContextCount = context.subContextCount;
|
||||
let ctx = context;
|
||||
const options = ast.options;
|
||||
|
||||
if (options && (options.params || options.delay)) {
|
||||
context.createSubContext(options);
|
||||
context.transformIntoNewTimeline();
|
||||
ctx = context.createSubContext(options);
|
||||
ctx.transformIntoNewTimeline();
|
||||
|
||||
if (options.delay != null) {
|
||||
if (context.previousNode instanceof StyleAst) {
|
||||
context.currentTimeline.snapshotCurrentStyles();
|
||||
context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
|
||||
if (ctx.previousNode instanceof StyleAst) {
|
||||
ctx.currentTimeline.snapshotCurrentStyles();
|
||||
ctx.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
|
||||
}
|
||||
|
||||
const delay = resolveTimingValue(options.delay);
|
||||
context.delayNextStep(delay);
|
||||
ctx.delayNextStep(delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.steps.length) {
|
||||
ast.steps.forEach(s => s.visit(this, context));
|
||||
ast.steps.forEach(s => s.visit(this, ctx));
|
||||
|
||||
// this is here just incase the inner steps only contain or end with a style() call
|
||||
context.currentTimeline.applyStylesToKeyframe();
|
||||
ctx.currentTimeline.applyStylesToKeyframe();
|
||||
|
||||
// this means that some animation function within the sequence
|
||||
// ended up creating a sub timeline (which means the current
|
||||
// timeline cannot overlap with the contents of the sequence)
|
||||
if (context.subContextCount > subContextCount) {
|
||||
context.transformIntoNewTimeline();
|
||||
if (ctx.subContextCount > subContextCount) {
|
||||
ctx.transformIntoNewTimeline();
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,7 +476,7 @@ export class AnimationTimelineContext {
|
||||
|
||||
Object.keys(newParams).forEach(name => {
|
||||
if (!skipIfExists || !paramsToUpdate.hasOwnProperty(name)) {
|
||||
paramsToUpdate[name] = newParams[name];
|
||||
paramsToUpdate[name] = interpolateParams(newParams[name], paramsToUpdate, this.errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer {
|
||||
}
|
||||
|
||||
const DIMENSIONAL_PROP_MAP = makeBooleanMap(
|
||||
'width,height,minWidth,minHeight,maxWidth,maxHeight,left,top,bottom,right,fontSize,outlineWidth,outlineOffset,paddingTop,paddingLeft,paddingBottom,paddingRight,marginTop,marginLeft,marginBottom,marginRight,borderRadius,borderWidth,borderTopWidth,borderLeftWidth,borderRightWidth,borderBottomWidth,textIndent'
|
||||
'width,height,minWidth,minHeight,maxWidth,maxHeight,left,top,bottom,right,fontSize,outlineWidth,outlineOffset,paddingTop,paddingLeft,paddingBottom,paddingRight,marginTop,marginLeft,marginBottom,marginRight,borderRadius,borderWidth,borderTopWidth,borderLeftWidth,borderRightWidth,borderBottomWidth,textIndent,perspective'
|
||||
.split(','));
|
||||
|
||||
function makeBooleanMap(keys: string[]): {[key: string]: boolean} {
|
||||
|
@ -13,12 +13,24 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru
|
||||
import {AnimationTrigger} from '../dsl/animation_trigger';
|
||||
import {ElementInstructionMap} from '../dsl/element_instruction_map';
|
||||
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, LEAVE_SELECTOR, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util';
|
||||
|
||||
import {AnimationDriver} from './animation_driver';
|
||||
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
|
||||
|
||||
const EMPTY_PLAYER_ARRAY: AnimationPlayer[] = [];
|
||||
const NULL_REMOVAL_STATE: ElementAnimationState = {
|
||||
namespaceId: '',
|
||||
setForRemoval: null,
|
||||
hasAnimation: false,
|
||||
removedBeforeQueried: false
|
||||
};
|
||||
const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = {
|
||||
namespaceId: '',
|
||||
setForRemoval: null,
|
||||
hasAnimation: false,
|
||||
removedBeforeQueried: true
|
||||
};
|
||||
|
||||
interface TriggerListener {
|
||||
name: string;
|
||||
@ -36,6 +48,15 @@ export interface QueueInstruction {
|
||||
isFallbackTransition: boolean;
|
||||
}
|
||||
|
||||
export const REMOVAL_FLAG = '__ng_removed';
|
||||
|
||||
export interface ElementAnimationState {
|
||||
setForRemoval: any;
|
||||
hasAnimation: boolean;
|
||||
namespaceId: string;
|
||||
removedBeforeQueried: boolean;
|
||||
}
|
||||
|
||||
export class StateValue {
|
||||
public value: string;
|
||||
public options: AnimationOptions;
|
||||
@ -244,7 +265,7 @@ export class AnimationTransitionNamespace {
|
||||
});
|
||||
}
|
||||
|
||||
private _onElementDestroy(element: any) {
|
||||
clearElementCache(element: any) {
|
||||
this._engine.statesByElement.delete(element);
|
||||
this._elementListeners.delete(element);
|
||||
const elementPlayers = this._engine.playersByElement.get(element);
|
||||
@ -266,14 +287,13 @@ export class AnimationTransitionNamespace {
|
||||
|
||||
this.removeNode(elm, context, true);
|
||||
} else {
|
||||
this._onElementDestroy(elm);
|
||||
this.clearElementCache(elm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeNode(element: any, context: any, doNotRecurse?: boolean): void {
|
||||
const engine = this._engine;
|
||||
engine.markElementAsRemoved(element);
|
||||
|
||||
if (!doNotRecurse && element.childElementCount) {
|
||||
this._destroyInnerNodes(element, context, true);
|
||||
@ -294,12 +314,8 @@ export class AnimationTransitionNamespace {
|
||||
});
|
||||
|
||||
if (players.length) {
|
||||
optimizeGroupPlayer(players).onDone(() => {
|
||||
engine.destroyInnerAnimations(element);
|
||||
this._onElementDestroy(element);
|
||||
engine._onRemovalComplete(element, context);
|
||||
});
|
||||
|
||||
engine.markElementAsRemoved(this.id, element, true, context);
|
||||
optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -364,15 +380,11 @@ export class AnimationTransitionNamespace {
|
||||
// whether or not a parent has an animation we need to delay the deferral of the leave
|
||||
// operation until we have more information (which we do after flush() has been called)
|
||||
if (containsPotentialParentTransition) {
|
||||
engine.queuedRemovals.set(element, () => {
|
||||
engine.destroyInnerAnimations(element);
|
||||
this._onElementDestroy(element);
|
||||
engine._onRemovalComplete(element, context);
|
||||
});
|
||||
engine.markElementAsRemoved(this.id, element, false, context);
|
||||
} else {
|
||||
// we do this after the flush has occurred such
|
||||
// that the callbacks can be fired
|
||||
engine.afterFlush(() => this._onElementDestroy(element));
|
||||
engine.afterFlush(() => this.clearElementCache(element));
|
||||
engine.destroyInnerAnimations(element);
|
||||
engine._onRemovalComplete(element, context);
|
||||
}
|
||||
@ -446,7 +458,6 @@ export interface QueuedTransition {
|
||||
|
||||
export class TransitionAnimationEngine {
|
||||
public players: TransitionAnimationPlayer[] = [];
|
||||
public queuedRemovals = new Map<any, () => any>();
|
||||
public newHostElements = new Map<any, AnimationTransitionNamespace>();
|
||||
public playersByElement = new Map<any, TransitionAnimationPlayer[]>();
|
||||
public playersByQueriedElement = new Map<any, TransitionAnimationPlayer[]>();
|
||||
@ -460,7 +471,8 @@ export class TransitionAnimationEngine {
|
||||
private _whenQuietFns: (() => any)[] = [];
|
||||
|
||||
public namespacesByHostElement = new Map<any, AnimationTransitionNamespace>();
|
||||
public collectedElements: any[] = [];
|
||||
public collectedEnterElements: any[] = [];
|
||||
public collectedLeaveElements: any[] = [];
|
||||
|
||||
// this method is designed to be overridden by the code that uses this engine
|
||||
public onRemovalComplete = (element: any, context: any) => {};
|
||||
@ -496,7 +508,7 @@ export class TransitionAnimationEngine {
|
||||
// animation renderer type. If this happens then we can still have
|
||||
// access to this item when we query for :enter nodes. If the parent
|
||||
// is a renderer then the set data-structure will normalize the entry
|
||||
this.updateElementEpoch(hostElement);
|
||||
this.collectEnterElement(hostElement);
|
||||
}
|
||||
return this._namespaceLookup[namespaceId] = ns;
|
||||
}
|
||||
@ -571,9 +583,9 @@ export class TransitionAnimationEngine {
|
||||
|
||||
// special case for when an element is removed and reinserted (move operation)
|
||||
// when this occurs we do not want to use the element for deletion later
|
||||
if (this.queuedRemovals.has(element)) {
|
||||
this.markElementAsRemoved(element, true);
|
||||
this.queuedRemovals.delete(element);
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
if (details && details.setForRemoval) {
|
||||
details.setForRemoval = false;
|
||||
}
|
||||
|
||||
// in the event that the namespaceId is blank then the caller
|
||||
@ -585,33 +597,33 @@ export class TransitionAnimationEngine {
|
||||
|
||||
// only *directives and host elements are inserted before
|
||||
if (insertBefore) {
|
||||
this.updateElementEpoch(element);
|
||||
this.collectEnterElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
updateElementEpoch(element: any, isRemoval?: boolean) { this.collectedElements.push(element); }
|
||||
|
||||
markElementAsRemoved(element: any, unmark?: boolean) {
|
||||
if (unmark) {
|
||||
removeClass(element, LEAVE_CLASSNAME);
|
||||
} else {
|
||||
addClass(element, LEAVE_CLASSNAME);
|
||||
this.afterFlush(() => removeClass(element, LEAVE_CLASSNAME));
|
||||
}
|
||||
}
|
||||
collectEnterElement(element: any) { this.collectedEnterElements.push(element); }
|
||||
|
||||
removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void {
|
||||
if (namespaceId) {
|
||||
const ns = this._fetchNamespace(namespaceId);
|
||||
if (!isElementNode(element) || !ns) {
|
||||
this._onRemovalComplete(element, context);
|
||||
} else {
|
||||
ns.removeNode(element, context, doNotRecurse);
|
||||
}
|
||||
} else {
|
||||
this.markElementAsRemoved(element);
|
||||
this.queuedRemovals.set(element, () => this._onRemovalComplete(element, context));
|
||||
if (!isElementNode(element)) {
|
||||
this._onRemovalComplete(element, context);
|
||||
return;
|
||||
}
|
||||
|
||||
const ns = namespaceId ? this._fetchNamespace(namespaceId) : null;
|
||||
if (ns) {
|
||||
ns.removeNode(element, context, doNotRecurse);
|
||||
} else {
|
||||
this.markElementAsRemoved(namespaceId, element, false, context);
|
||||
}
|
||||
}
|
||||
|
||||
markElementAsRemoved(namespaceId: string, element: any, hasAnimation?: boolean, context?: any) {
|
||||
this.collectedLeaveElements.push(element);
|
||||
element[REMOVAL_FLAG] = {
|
||||
namespaceId,
|
||||
setForRemoval: context, hasAnimation,
|
||||
removedBeforeQueried: false
|
||||
};
|
||||
}
|
||||
|
||||
listen(
|
||||
@ -661,6 +673,22 @@ export class TransitionAnimationEngine {
|
||||
});
|
||||
}
|
||||
|
||||
processLeaveNode(element: any) {
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
if (details && details.setForRemoval) {
|
||||
// this will prevent it from removing it twice
|
||||
element[REMOVAL_FLAG] = NULL_REMOVAL_STATE;
|
||||
if (details.namespaceId) {
|
||||
this.destroyInnerAnimations(element);
|
||||
const ns = this._fetchNamespace(details.namespaceId);
|
||||
if (ns) {
|
||||
ns.clearElementCache(element);
|
||||
}
|
||||
}
|
||||
this._onRemovalComplete(element, details.setForRemoval);
|
||||
}
|
||||
}
|
||||
|
||||
flush(microtaskId: number = -1) {
|
||||
let players: AnimationPlayer[] = [];
|
||||
if (this.newHostElements.size) {
|
||||
@ -668,15 +696,19 @@ export class TransitionAnimationEngine {
|
||||
this.newHostElements.clear();
|
||||
}
|
||||
|
||||
if (this._namespaceList.length && (this.totalQueuedPlayers || this.queuedRemovals.size)) {
|
||||
if (this._namespaceList.length &&
|
||||
(this.totalQueuedPlayers || this.collectedLeaveElements.length)) {
|
||||
players = this._flushAnimations(microtaskId);
|
||||
} else {
|
||||
this.queuedRemovals.forEach(fn => fn());
|
||||
for (let i = 0; i < this.collectedLeaveElements.length; i++) {
|
||||
const element = this.collectedLeaveElements[i];
|
||||
this.processLeaveNode(element);
|
||||
}
|
||||
}
|
||||
|
||||
this.totalQueuedPlayers = 0;
|
||||
this.collectedElements = [];
|
||||
this.queuedRemovals.clear();
|
||||
this.collectedEnterElements.length = 0;
|
||||
this.collectedLeaveElements.length = 0;
|
||||
this._flushFns.forEach(fn => fn());
|
||||
this._flushFns = [];
|
||||
|
||||
@ -708,10 +740,22 @@ export class TransitionAnimationEngine {
|
||||
// the :enter queries match the elements (since the timeline queries
|
||||
// are fired during instruction building).
|
||||
const bodyNode = getBodyNode();
|
||||
const allEnterNodes: any[] = this.collectedElements;
|
||||
const allEnterNodes: any[] = this.collectedEnterElements;
|
||||
const enterNodes: any[] =
|
||||
allEnterNodes.length ? collectEnterElements(this.driver, allEnterNodes) : [];
|
||||
|
||||
const leaveNodes: any[] = [];
|
||||
for (let i = 0; i < this.collectedLeaveElements.length; i++) {
|
||||
const element = this.collectedLeaveElements[i];
|
||||
if (isElementNode(element)) {
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
if (details && details.setForRemoval) {
|
||||
addClass(element, LEAVE_CLASSNAME);
|
||||
leaveNodes.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = this._namespaceList.length - 1; i >= 0; i--) {
|
||||
const ns = this._namespaceList[i];
|
||||
ns.drainQueuedTransitions(microtaskId).forEach(entry => {
|
||||
@ -794,10 +838,6 @@ export class TransitionAnimationEngine {
|
||||
|
||||
allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy()));
|
||||
|
||||
const leaveNodes: any[] = bodyNode && allPostStyleElements.size ?
|
||||
this.driver.query(bodyNode, LEAVE_SELECTOR, true) :
|
||||
[];
|
||||
|
||||
// PRE STAGE: fill the ! styles
|
||||
const preStylesMap = allPreStyleElements.size ?
|
||||
cloakAndComputeStyles(this.driver, enterNodes, allPreStyleElements, PRE_STYLE) :
|
||||
@ -867,14 +907,18 @@ export class TransitionAnimationEngine {
|
||||
// run through all of the queued removals and see if they
|
||||
// were picked up by a query. If not then perform the removal
|
||||
// operation right away unless a parent animation is ongoing.
|
||||
this.queuedRemovals.forEach((fn, element) => {
|
||||
for (let i = 0; i < leaveNodes.length; i++) {
|
||||
const element = leaveNodes[i];
|
||||
const players = queriedElements.get(element);
|
||||
if (players) {
|
||||
optimizeGroupPlayer(players).onDone(fn);
|
||||
removeNodesAfterAnimationDone(this, element, players);
|
||||
} else {
|
||||
fn();
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
if (details && !details.hasAnimation) {
|
||||
this.processLeaveNode(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rootPlayers.forEach(player => {
|
||||
this.players.push(player);
|
||||
@ -894,7 +938,8 @@ export class TransitionAnimationEngine {
|
||||
|
||||
elementContainsData(namespaceId: string, element: any) {
|
||||
let containsData = false;
|
||||
if (this.queuedRemovals.has(element)) containsData = true;
|
||||
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||
if (details && details.setForRemoval) containsData = true;
|
||||
if (this.playersByElement.has(element)) containsData = true;
|
||||
if (this.playersByQueriedElement.has(element)) containsData = true;
|
||||
if (this.statesByElement.has(element)) containsData = true;
|
||||
@ -985,7 +1030,8 @@ export class TransitionAnimationEngine {
|
||||
const element = timelineInstruction.element;
|
||||
|
||||
// FIXME (matsko): make sure to-be-removed animations are removed properly
|
||||
if (element['REMOVED']) return new NoopAnimationPlayer();
|
||||
const details = element[REMOVAL_FLAG];
|
||||
if (details && details.removedBeforeQueried) return new NoopAnimationPlayer();
|
||||
|
||||
const isQueriedElement = element !== rootElement;
|
||||
let previousPlayers: AnimationPlayer[] = EMPTY_PLAYER_ARRAY;
|
||||
@ -1235,7 +1281,7 @@ function cloakAndComputeStyles(
|
||||
// there is no easy way to detect this because a sub element could be removed
|
||||
// by a parent animation element being detached.
|
||||
if (!value || value.length == 0) {
|
||||
element['REMOVED'] = true;
|
||||
element[REMOVAL_FLAG] = NULL_REMOVED_QUERIED_STATE;
|
||||
}
|
||||
});
|
||||
valuesMap.set(element, styles);
|
||||
@ -1286,17 +1332,14 @@ function removeClass(element: any, className: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function setAttribute(element: any, attr: string, value: any) {
|
||||
if (element.setAttribute) {
|
||||
element.setAttribute(attr, value);
|
||||
} else {
|
||||
element[attr] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function getBodyNode(): any|null {
|
||||
if (typeof document != 'undefined') {
|
||||
return document.body;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function removeNodesAfterAnimationDone(
|
||||
engine: TransitionAnimationEngine, element: any, players: AnimationPlayer[]) {
|
||||
optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
|
||||
}
|
||||
|
@ -358,6 +358,56 @@ export function main() {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should substitute in values that are defined as parameters for inner areas of a sequence',
|
||||
() => {
|
||||
const steps = sequence(
|
||||
[
|
||||
sequence(
|
||||
[
|
||||
sequence(
|
||||
[
|
||||
style({height: '{{ x0 }}px'}),
|
||||
animate(1000, style({height: '{{ x2 }}px'})),
|
||||
],
|
||||
buildParams({x2: '{{ x1 }}3'})),
|
||||
],
|
||||
buildParams({x1: '{{ x0 }}2'})),
|
||||
],
|
||||
buildParams({x0: '1'}));
|
||||
|
||||
const players = invokeAnimationSequence(rootElement, steps);
|
||||
expect(players.length).toEqual(1);
|
||||
const [player] = players;
|
||||
expect(player.keyframes).toEqual([
|
||||
{offset: 0, height: '1px'}, {offset: 1, height: '123px'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should substitute in values that are defined as parameters for reusable animations',
|
||||
() => {
|
||||
const anim = animation([
|
||||
style({height: '{{ start }}'}),
|
||||
animate(1000, style({height: '{{ end }}'})),
|
||||
]);
|
||||
|
||||
const steps = sequence(
|
||||
[
|
||||
sequence(
|
||||
[
|
||||
useAnimation(anim, buildParams({start: '{{ a }}', end: '{{ b }}'})),
|
||||
],
|
||||
buildParams({a: '100px', b: '200px'})),
|
||||
],
|
||||
buildParams({a: '0px'}));
|
||||
|
||||
const players = invokeAnimationSequence(rootElement, steps);
|
||||
expect(players.length).toEqual(1);
|
||||
const [player] = players;
|
||||
expect(player.keyframes).toEqual([
|
||||
{offset: 0, height: '100px'}, {offset: 1, height: '200px'}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw an error when an input variable is not provided when invoked and is not a default value',
|
||||
() => {
|
||||
expect(() => {invokeAnimationSequence(rootElement, [style({color: '{{ color }}'})])})
|
||||
|
@ -59,6 +59,12 @@ export function main() {
|
||||
expect(normalize('borderWidth', 'inherit')).toEqual('inherit');
|
||||
expect(normalize('paddingTop', 'calc(500px + 200px)')).toEqual('calc(500px + 200px)');
|
||||
});
|
||||
|
||||
it('should allow `perspective` to be a numerical property', () => {
|
||||
expect(normalize('perspective', 10)).toEqual('10px');
|
||||
expect(normalize('perspective', '100pt')).toEqual('100pt');
|
||||
expect(normalize('perspective', 'none')).toEqual('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
13
packages/common/BUILD
Normal file
13
packages/common/BUILD
Normal file
@ -0,0 +1,13 @@
|
||||
package(default_visibility=["//visibility:public"])
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "common",
|
||||
srcs = glob(["**/*.ts"], exclude=[
|
||||
"test/**",
|
||||
"testing/**",
|
||||
]),
|
||||
module_name = "@angular/common",
|
||||
deps = ["//packages/core"],
|
||||
tsconfig = ":tsconfig-build.json",
|
||||
)
|
@ -1,35 +1,41 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true,
|
||||
"flatModuleOutFile": "index.js",
|
||||
"flatModuleId": "@angular/common"
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2015",
|
||||
"skipLibCheck": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
// don't auto-discover @types/node, it results in a ///<reference in the .d.ts output
|
||||
"types": [],
|
||||
|
||||
/**
|
||||
* The rest of the file is configuration that's overridden by bazel.
|
||||
* It can be removed after we fully migrate.
|
||||
*/
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"outDir": "../../dist/packages/common",
|
||||
"paths": {
|
||||
"@angular/core": ["../../dist/packages/core"]
|
||||
},
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"target": "es2015",
|
||||
"skipLibCheck": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
// don't auto-discover @types/node, it results in a ///<reference in the .d.ts output
|
||||
"types": []
|
||||
"inlineSources": true
|
||||
},
|
||||
"files": [
|
||||
"public_api.ts",
|
||||
"../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true,
|
||||
"flatModuleOutFile": "index.js",
|
||||
"flatModuleId": "@angular/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "4.2.0-rc.2",
|
||||
"@angular/tsc-wrapped": "4.2.0",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -40,17 +40,24 @@ export class CodeGenerator {
|
||||
return this.compiler
|
||||
.analyzeModulesAsync(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
|
||||
.then(analyzedModules => this.compiler.emitAllImpls(analyzedModules))
|
||||
.then(generatedModules => {
|
||||
return generatedModules.map(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source =
|
||||
generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE);
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
return emitPath;
|
||||
});
|
||||
});
|
||||
.then(analyzedModules => this.emit(analyzedModules));
|
||||
}
|
||||
|
||||
codegenSync(): string[] {
|
||||
const analyzed = this.compiler.analyzeModulesSync(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
|
||||
return this.emit(analyzed);
|
||||
}
|
||||
|
||||
private emit(analyzedModules: compiler.NgAnalyzedModules) {
|
||||
const generatedModules = this.compiler.emitAllImpls(analyzedModules);
|
||||
return generatedModules.map(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = generatedModule.source || compiler.toTypeScript(generatedModule, PREAMBLE);
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
return emitPath;
|
||||
});
|
||||
}
|
||||
|
||||
static create(
|
||||
|
@ -258,7 +258,9 @@ export class CompilerHost implements AotCompilerHost {
|
||||
return v3Metadata;
|
||||
}
|
||||
|
||||
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
|
||||
loadResource(filePath: string): Promise<string>|string {
|
||||
return this.context.readResource(filePath);
|
||||
}
|
||||
|
||||
loadSummary(filePath: string): string|null {
|
||||
if (this.context.fileExists(filePath)) {
|
||||
|
@ -41,7 +41,7 @@ export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResol
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
loadResource(path: string): Promise<string>;
|
||||
loadResource(path: string): Promise<string>|string;
|
||||
}
|
||||
|
||||
export class Extractor {
|
||||
|
@ -28,6 +28,7 @@ const ALL_HTML_TAGS =
|
||||
// Elements missing from Chrome (HtmlUnknownElement), to be manually added
|
||||
const MISSING_FROM_CHROME: {[el: string]: string[]} = {
|
||||
'data^[HTMLElement]': ['value'],
|
||||
'keygen^[HTMLElement]': ['!autofocus', 'challenge', '!disabled', 'form', 'keytype', 'name'],
|
||||
// TODO(vicb): Figure out why Chrome and WhatWG do not agree on the props
|
||||
// 'menu^[HTMLElement]': ['type', 'label'],
|
||||
'menuitem^[HTMLElement]':
|
||||
|
13
packages/core/BUILD
Normal file
13
packages/core/BUILD
Normal file
@ -0,0 +1,13 @@
|
||||
package(default_visibility=["//visibility:public"])
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "core",
|
||||
srcs = glob(["**/*.ts"], exclude=[
|
||||
"test/**",
|
||||
"testing/**",
|
||||
]),
|
||||
module_name = "@angular/core",
|
||||
deps = [],
|
||||
tsconfig = ":tsconfig-build.json",
|
||||
)
|
@ -16,6 +16,7 @@ import {NgModuleFactoryLoader} from './ng_module_factory_loader';
|
||||
const _SEPARATOR = '#';
|
||||
|
||||
const FACTORY_CLASS_SUFFIX = 'NgFactory';
|
||||
declare var System: any;
|
||||
|
||||
/**
|
||||
* Configuration for SystemJsNgModuleLoader.
|
||||
|
@ -6,6 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// Import zero symbols from zone.js. This causes the zone ambient type to be
|
||||
// added to the type-checker, without emitting any runtime module load statement
|
||||
import {} from 'zone.js';
|
||||
|
||||
// TODO(jteplitz602): Load WorkerGlobalScope from lib.webworker.d.ts file #3492
|
||||
declare var WorkerGlobalScope: any /** TODO #9100 */;
|
||||
// CommonJS / Node have global context exposed as "global" variable.
|
||||
|
@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// Import zero symbols from zone.js. This causes the zone ambient type to be
|
||||
// added to the type-checker, without emitting any runtime module load statement
|
||||
import {} from 'zone.js';
|
||||
import {EventEmitter} from '../event_emitter';
|
||||
|
||||
/**
|
||||
|
@ -1,34 +1,40 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true,
|
||||
"flatModuleOutFile": "index.js",
|
||||
"flatModuleId": "@angular/core"
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2015",
|
||||
"lib": ["es2015", "dom"],
|
||||
"skipLibCheck": true,
|
||||
// don't auto-discover @types/node, it results in a ///<reference in the .d.ts output
|
||||
"types": [],
|
||||
|
||||
/**
|
||||
* The rest of the file is configuration that's overridden by bazel.
|
||||
* It can be removed after we fully migrate.
|
||||
*/
|
||||
"baseUrl": ".",
|
||||
"experimentalDecorators": true,
|
||||
"outDir": "../../dist/packages/core",
|
||||
"paths": {
|
||||
"rxjs/*": ["../../node_modules/rxjs/*"]
|
||||
},
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"target": "es2015",
|
||||
"lib": ["es2015", "dom"],
|
||||
"skipLibCheck": true,
|
||||
// don't auto-discover @types/node, it results in a ///<reference in the .d.ts output
|
||||
"types": []
|
||||
"inlineSources": true
|
||||
},
|
||||
"files": [
|
||||
"public_api.ts",
|
||||
"../../node_modules/zone.js/dist/zone.js.d.ts",
|
||||
"../system.d.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true,
|
||||
"flatModuleOutFile": "index.js",
|
||||
"flatModuleId": "@angular/core"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -64,11 +64,13 @@ export class Validators {
|
||||
*/
|
||||
static min(min: number): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
if (isEmptyInputValue(control.value)) {
|
||||
if (isEmptyInputValue(control.value) || isEmptyInputValue(min)) {
|
||||
return null; // don't validate empty values to allow optional controls
|
||||
}
|
||||
const value = parseFloat(control.value);
|
||||
return isNaN(value) || value < min ? {'min': {'min': min, 'actual': control.value}} : null;
|
||||
// Controls with NaN values after parsing should be treated as not having a
|
||||
// minimum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-min
|
||||
return !isNaN(value) && value < min ? {'min': {'min': min, 'actual': control.value}} : null;
|
||||
};
|
||||
}
|
||||
|
||||
@ -77,11 +79,13 @@ export class Validators {
|
||||
*/
|
||||
static max(max: number): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
if (isEmptyInputValue(control.value)) {
|
||||
if (isEmptyInputValue(control.value) || isEmptyInputValue(max)) {
|
||||
return null; // don't validate empty values to allow optional controls
|
||||
}
|
||||
const value = parseFloat(control.value);
|
||||
return isNaN(value) || value > max ? {'max': {'max': max, 'actual': control.value}} : null;
|
||||
// Controls with NaN values after parsing should be treated as not having a
|
||||
// maximum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-max
|
||||
return !isNaN(value) && value > max ? {'max': {'max': max, 'actual': control.value}} : null;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -49,12 +49,15 @@ export function main() {
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.min(2)(new FormControl(undefined))).toBeNull(); });
|
||||
|
||||
it('should error on non numbers', () => {
|
||||
expect(Validators.min(2)(new FormControl('a'))).toEqual({'min': {'min': 2, 'actual': 'a'}});
|
||||
it('should return null if NaN after parsing',
|
||||
() => { expect(Validators.min(2)(new FormControl('a'))).toBeNull(); });
|
||||
|
||||
it('should return a validation error on small values', () => {
|
||||
expect(Validators.min(2)(new FormControl(1))).toEqual({'min': {'min': 2, 'actual': 1}});
|
||||
});
|
||||
|
||||
it('should error on small values', () => {
|
||||
expect(Validators.min(2)(new FormControl(1))).toEqual({'min': {'min': 2, 'actual': 1}});
|
||||
it('should return a validation error on small values converted from strings', () => {
|
||||
expect(Validators.min(2)(new FormControl('1'))).toEqual({'min': {'min': 2, 'actual': '1'}});
|
||||
});
|
||||
|
||||
it('should not error on big values',
|
||||
@ -65,6 +68,18 @@ export function main() {
|
||||
|
||||
it('should not error on equal values when value is string',
|
||||
() => { expect(Validators.min(2)(new FormControl('2'))).toBeNull(); });
|
||||
|
||||
it('should validate as expected when min value is a string', () => {
|
||||
expect(Validators.min('2' as any)(new FormControl(1))).toEqual({
|
||||
'min': {'min': '2', 'actual': 1}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if min value is undefined',
|
||||
() => { expect(Validators.min(undefined as any)(new FormControl(3))).toBeNull(); });
|
||||
|
||||
it('should return null if min value is null',
|
||||
() => { expect(Validators.min(null as any)(new FormControl(3))).toBeNull(); });
|
||||
});
|
||||
|
||||
describe('max', () => {
|
||||
@ -77,14 +92,15 @@ export function main() {
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.max(2)(new FormControl(undefined))).toBeNull(); });
|
||||
|
||||
it('should error on non numbers', () => {
|
||||
expect(Validators.max(2)(new FormControl('aaa'))).toEqual({
|
||||
'max': {'max': 2, 'actual': 'aaa'}
|
||||
});
|
||||
it('should return null if NaN after parsing',
|
||||
() => { expect(Validators.max(2)(new FormControl('aaa'))).toBeNull(); });
|
||||
|
||||
it('should return a validation error on big values', () => {
|
||||
expect(Validators.max(2)(new FormControl(3))).toEqual({'max': {'max': 2, 'actual': 3}});
|
||||
});
|
||||
|
||||
it('should error on big values', () => {
|
||||
expect(Validators.max(2)(new FormControl(3))).toEqual({'max': {'max': 2, 'actual': 3}});
|
||||
it('should return a validation error on big values converted from strings', () => {
|
||||
expect(Validators.max(2)(new FormControl('3'))).toEqual({'max': {'max': 2, 'actual': '3'}});
|
||||
});
|
||||
|
||||
it('should not error on small values',
|
||||
@ -95,6 +111,18 @@ export function main() {
|
||||
|
||||
it('should not error on equal values when value is string',
|
||||
() => { expect(Validators.max(2)(new FormControl('2'))).toBeNull(); });
|
||||
|
||||
it('should validate as expected when max value is a string', () => {
|
||||
expect(Validators.max('2' as any)(new FormControl(3))).toEqual({
|
||||
'max': {'max': '2', 'actual': 3}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if max value is undefined',
|
||||
() => { expect(Validators.max(undefined as any)(new FormControl(3))).toBeNull(); });
|
||||
|
||||
it('should return null if max value is null',
|
||||
() => { expect(Validators.max(null as any)(new FormControl(3))).toBeNull(); });
|
||||
});
|
||||
|
||||
|
||||
|
@ -363,7 +363,6 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
}
|
||||
getComputedStyle(element: any): any { return getComputedStyle(element); }
|
||||
// TODO(tbosch): move this into a separate environment class once we have it
|
||||
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
|
||||
supportsWebAnimation(): boolean {
|
||||
return typeof(<any>Element).prototype['animate'] === 'function';
|
||||
}
|
||||
@ -419,20 +418,3 @@ export function parseCookieValue(cookieStr: string, name: string): string|null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setValueOnPath(global: any, path: string, value: any) {
|
||||
const parts = path.split('.');
|
||||
let obj: any = global;
|
||||
while (parts.length > 1) {
|
||||
const name = parts.shift() !;
|
||||
if (obj.hasOwnProperty(name) && obj[name] != null) {
|
||||
obj = obj[name];
|
||||
} else {
|
||||
obj = obj[name] = {};
|
||||
}
|
||||
}
|
||||
if (obj === undefined || obj === null) {
|
||||
obj = {};
|
||||
}
|
||||
obj[parts.shift() !] = value;
|
||||
}
|
||||
|
@ -7,11 +7,10 @@
|
||||
*/
|
||||
|
||||
import {ComponentRef} from '@angular/core';
|
||||
import {getDOM} from '../../dom/dom_adapter';
|
||||
|
||||
import {exportNgVar} from '../../dom/util';
|
||||
import {AngularProfiler} from './common_tools';
|
||||
|
||||
const PROFILER_GLOBAL_NAME = 'ng.profiler';
|
||||
const PROFILER_GLOBAL_NAME = 'profiler';
|
||||
|
||||
/**
|
||||
* Enabled Angular debug tools that are accessible via your browser's
|
||||
@ -27,7 +26,7 @@ const PROFILER_GLOBAL_NAME = 'ng.profiler';
|
||||
* @experimental All debugging apis are currently experimental.
|
||||
*/
|
||||
export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
|
||||
getDOM().setGlobalVar(PROFILER_GLOBAL_NAME, new AngularProfiler(ref));
|
||||
exportNgVar(PROFILER_GLOBAL_NAME, new AngularProfiler(ref));
|
||||
return ref;
|
||||
}
|
||||
|
||||
@ -37,5 +36,5 @@ export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
|
||||
* @experimental All debugging apis are currently experimental.
|
||||
*/
|
||||
export function disableDebugTools(): void {
|
||||
getDOM().setGlobalVar(PROFILER_GLOBAL_NAME, null);
|
||||
exportNgVar(PROFILER_GLOBAL_NAME, null);
|
||||
}
|
||||
|
@ -7,15 +7,15 @@
|
||||
*/
|
||||
|
||||
import * as core from '@angular/core';
|
||||
import {getDOM} from '../dom_adapter';
|
||||
import {exportNgVar} from '../util';
|
||||
|
||||
const CORE_TOKENS = {
|
||||
'ApplicationRef': core.ApplicationRef,
|
||||
'NgZone': core.NgZone,
|
||||
};
|
||||
|
||||
const INSPECT_GLOBAL_NAME = 'ng.probe';
|
||||
const CORE_TOKENS_GLOBAL_NAME = 'ng.coreTokens';
|
||||
const INSPECT_GLOBAL_NAME = 'probe';
|
||||
const CORE_TOKENS_GLOBAL_NAME = 'coreTokens';
|
||||
|
||||
/**
|
||||
* Returns a {@link DebugElement} for the given native DOM element, or
|
||||
@ -36,9 +36,8 @@ export class NgProbeToken {
|
||||
|
||||
export function _createNgProbe(extraTokens: NgProbeToken[], coreTokens: core.NgProbeToken[]): any {
|
||||
const tokens = (extraTokens || []).concat(coreTokens || []);
|
||||
getDOM().setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
|
||||
getDOM().setGlobalVar(
|
||||
CORE_TOKENS_GLOBAL_NAME, {...CORE_TOKENS, ..._ngProbeTokensToMap(tokens || [])});
|
||||
exportNgVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
|
||||
exportNgVar(CORE_TOKENS_GLOBAL_NAME, {...CORE_TOKENS, ..._ngProbeTokensToMap(tokens || [])});
|
||||
return () => inspectNativeElement;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user