diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml new file mode 100644 index 000000000..bb5892876 --- /dev/null +++ b/.github/workflows/dart.yml @@ -0,0 +1,1074 @@ +# Created with package:mono_repo v5.0.2 +name: Dart CI +on: + push: + branches: + - main + - master + pull_request: + schedule: + - cron: "0 0 * * 0" +defaults: + run: + shell: bash +env: + PUB_ENVIRONMENT: bot.github + +jobs: + job_001: + name: mono_repo self validate + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:stable" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v2.3.4 + - name: mono_repo self validate + run: dart pub global activate mono_repo 5.0.2 + - name: mono_repo self validate + run: dart pub global run mono_repo generate --validate + job_002: + name: "analyze_and_format_core; Dart 2.14.2; PKGS: angular_components, angular_gallery, angular_gallery_section; `dart analyze .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:angular_components-angular_gallery-angular_gallery_section;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:angular_components-angular_gallery-angular_gallery_section + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2 + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: "2.14.2" + - id: checkout + uses: actions/checkout@v2.3.4 + - id: angular_components_pub_upgrade + name: angular_components; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_components + run: dart pub upgrade + - name: angular_components; dart analyze . + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: dart analyze . + - id: angular_gallery_pub_upgrade + name: angular_gallery; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_gallery + run: dart pub upgrade + - name: angular_gallery; dart analyze . + if: "always() && steps.angular_gallery_pub_upgrade.conclusion == 'success'" + working-directory: angular_gallery + run: dart analyze . + - id: angular_gallery_section_pub_upgrade + name: angular_gallery_section; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_gallery_section + run: dart pub upgrade + - name: angular_gallery_section; dart analyze . + if: "always() && steps.angular_gallery_section_pub_upgrade.conclusion == 'success'" + working-directory: angular_gallery_section + run: dart analyze . + if: + needs: + - job_001 + job_003: + name: "analyze_and_format_core; Dart dev; PKG: angular_components; `dart format --output=none --set-exit-if-changed .`, `dart analyze .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:angular_components;commands:format-analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:angular_components + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: angular_components_pub_upgrade + name: angular_components; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_components + run: dart pub upgrade + - name: "angular_components; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: "dart format --output=none --set-exit-if-changed ." + - name: angular_components; dart analyze . + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: dart analyze . + if: + needs: + - job_001 + job_004: + name: "analyze_and_format_core; Dart dev; PKGS: angular_gallery, angular_gallery_section; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:angular_gallery-angular_gallery_section;commands:format-analyze_1" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:angular_gallery-angular_gallery_section + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: angular_gallery_pub_upgrade + name: angular_gallery; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_gallery + run: dart pub upgrade + - name: "angular_gallery; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.angular_gallery_pub_upgrade.conclusion == 'success'" + working-directory: angular_gallery + run: "dart format --output=none --set-exit-if-changed ." + - name: "angular_gallery; dart analyze --fatal-infos ." + if: "always() && steps.angular_gallery_pub_upgrade.conclusion == 'success'" + working-directory: angular_gallery + run: dart analyze --fatal-infos . + - id: angular_gallery_section_pub_upgrade + name: angular_gallery_section; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_gallery_section + run: dart pub upgrade + - name: "angular_gallery_section; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.angular_gallery_section_pub_upgrade.conclusion == 'success'" + working-directory: angular_gallery_section + run: "dart format --output=none --set-exit-if-changed ." + - name: "angular_gallery_section; dart analyze --fatal-infos ." + if: "always() && steps.angular_gallery_section_pub_upgrade.conclusion == 'success'" + working-directory: angular_gallery_section + run: dart analyze --fatal-infos . + if: + needs: + - job_001 + job_005: + name: "analyze_and_format_examples_1; Dart 2.14.2; PKGS: examples/app_layout_example, examples/material_button_example, examples/material_card_example, examples/material_checkbox_example, examples/material_chips_example, examples/material_datepicker_example, examples/material_dialog_example, examples/material_expansionpanel_example, examples/material_icon_example, examples/material_list_example, examples/material_menu_example, examples/material_yes_no_buttons_example; `dart analyze .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:examples/app_layout_example-examples/material_button_example-examples/material_card_example-examples/material_checkbox_example-examples/material_chips_example-examples/material_datepicker_example-examples/material_dialog_example-examples/material_expansionpanel_example-examples/material_icon_example-examples/material_list_example-examples/material_menu_example-examples/material_yes_no_buttons_example;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:examples/app_layout_example-examples/material_button_example-examples/material_card_example-examples/material_checkbox_example-examples/material_chips_example-examples/material_datepicker_example-examples/material_dialog_example-examples/material_expansionpanel_example-examples/material_icon_example-examples/material_list_example-examples/material_menu_example-examples/material_yes_no_buttons_example + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2 + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: "2.14.2" + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_app_layout_example_pub_upgrade + name: examples/app_layout_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/app_layout_example + run: dart pub upgrade + - name: examples/app_layout_example; dart analyze . + if: "always() && steps.examples_app_layout_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/app_layout_example + run: dart analyze . + - id: examples_material_button_example_pub_upgrade + name: examples/material_button_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_button_example + run: dart pub upgrade + - name: examples/material_button_example; dart analyze . + if: "always() && steps.examples_material_button_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_button_example + run: dart analyze . + - id: examples_material_card_example_pub_upgrade + name: examples/material_card_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_card_example + run: dart pub upgrade + - name: examples/material_card_example; dart analyze . + if: "always() && steps.examples_material_card_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_card_example + run: dart analyze . + - id: examples_material_checkbox_example_pub_upgrade + name: examples/material_checkbox_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_checkbox_example + run: dart pub upgrade + - name: examples/material_checkbox_example; dart analyze . + if: "always() && steps.examples_material_checkbox_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_checkbox_example + run: dart analyze . + - id: examples_material_chips_example_pub_upgrade + name: examples/material_chips_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_chips_example + run: dart pub upgrade + - name: examples/material_chips_example; dart analyze . + if: "always() && steps.examples_material_chips_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_chips_example + run: dart analyze . + - id: examples_material_datepicker_example_pub_upgrade + name: examples/material_datepicker_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_datepicker_example + run: dart pub upgrade + - name: examples/material_datepicker_example; dart analyze . + if: "always() && steps.examples_material_datepicker_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_datepicker_example + run: dart analyze . + - id: examples_material_dialog_example_pub_upgrade + name: examples/material_dialog_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_dialog_example + run: dart pub upgrade + - name: examples/material_dialog_example; dart analyze . + if: "always() && steps.examples_material_dialog_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_dialog_example + run: dart analyze . + - id: examples_material_expansionpanel_example_pub_upgrade + name: examples/material_expansionpanel_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_expansionpanel_example + run: dart pub upgrade + - name: examples/material_expansionpanel_example; dart analyze . + if: "always() && steps.examples_material_expansionpanel_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_expansionpanel_example + run: dart analyze . + - id: examples_material_icon_example_pub_upgrade + name: examples/material_icon_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_icon_example + run: dart pub upgrade + - name: examples/material_icon_example; dart analyze . + if: "always() && steps.examples_material_icon_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_icon_example + run: dart analyze . + - id: examples_material_list_example_pub_upgrade + name: examples/material_list_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_list_example + run: dart pub upgrade + - name: examples/material_list_example; dart analyze . + if: "always() && steps.examples_material_list_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_list_example + run: dart analyze . + - id: examples_material_menu_example_pub_upgrade + name: examples/material_menu_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_menu_example + run: dart pub upgrade + - name: examples/material_menu_example; dart analyze . + if: "always() && steps.examples_material_menu_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_menu_example + run: dart analyze . + - id: examples_material_yes_no_buttons_example_pub_upgrade + name: examples/material_yes_no_buttons_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_yes_no_buttons_example + run: dart pub upgrade + - name: examples/material_yes_no_buttons_example; dart analyze . + if: "always() && steps.examples_material_yes_no_buttons_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_yes_no_buttons_example + run: dart analyze . + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + job_006: + name: "analyze_and_format_examples_1; Dart dev; PKGS: examples/app_layout_example, examples/material_button_example, examples/material_card_example, examples/material_checkbox_example, examples/material_chips_example, examples/material_datepicker_example, examples/material_dialog_example, examples/material_expansionpanel_example, examples/material_icon_example, examples/material_list_example, examples/material_yes_no_buttons_example; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/app_layout_example-examples/material_button_example-examples/material_card_example-examples/material_checkbox_example-examples/material_chips_example-examples/material_datepicker_example-examples/material_dialog_example-examples/material_expansionpanel_example-examples/material_icon_example-examples/material_list_example-examples/material_yes_no_buttons_example;commands:format-analyze_1" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/app_layout_example-examples/material_button_example-examples/material_card_example-examples/material_checkbox_example-examples/material_chips_example-examples/material_datepicker_example-examples/material_dialog_example-examples/material_expansionpanel_example-examples/material_icon_example-examples/material_list_example-examples/material_yes_no_buttons_example + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_app_layout_example_pub_upgrade + name: examples/app_layout_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/app_layout_example + run: dart pub upgrade + - name: "examples/app_layout_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_app_layout_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/app_layout_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/app_layout_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_app_layout_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/app_layout_example + run: dart analyze --fatal-infos . + - id: examples_material_button_example_pub_upgrade + name: examples/material_button_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_button_example + run: dart pub upgrade + - name: "examples/material_button_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_button_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_button_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_button_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_button_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_button_example + run: dart analyze --fatal-infos . + - id: examples_material_card_example_pub_upgrade + name: examples/material_card_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_card_example + run: dart pub upgrade + - name: "examples/material_card_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_card_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_card_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_card_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_card_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_card_example + run: dart analyze --fatal-infos . + - id: examples_material_checkbox_example_pub_upgrade + name: examples/material_checkbox_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_checkbox_example + run: dart pub upgrade + - name: "examples/material_checkbox_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_checkbox_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_checkbox_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_checkbox_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_checkbox_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_checkbox_example + run: dart analyze --fatal-infos . + - id: examples_material_chips_example_pub_upgrade + name: examples/material_chips_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_chips_example + run: dart pub upgrade + - name: "examples/material_chips_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_chips_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_chips_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_chips_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_chips_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_chips_example + run: dart analyze --fatal-infos . + - id: examples_material_datepicker_example_pub_upgrade + name: examples/material_datepicker_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_datepicker_example + run: dart pub upgrade + - name: "examples/material_datepicker_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_datepicker_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_datepicker_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_datepicker_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_datepicker_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_datepicker_example + run: dart analyze --fatal-infos . + - id: examples_material_dialog_example_pub_upgrade + name: examples/material_dialog_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_dialog_example + run: dart pub upgrade + - name: "examples/material_dialog_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_dialog_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_dialog_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_dialog_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_dialog_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_dialog_example + run: dart analyze --fatal-infos . + - id: examples_material_expansionpanel_example_pub_upgrade + name: examples/material_expansionpanel_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_expansionpanel_example + run: dart pub upgrade + - name: "examples/material_expansionpanel_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_expansionpanel_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_expansionpanel_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_expansionpanel_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_expansionpanel_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_expansionpanel_example + run: dart analyze --fatal-infos . + - id: examples_material_icon_example_pub_upgrade + name: examples/material_icon_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_icon_example + run: dart pub upgrade + - name: "examples/material_icon_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_icon_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_icon_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_icon_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_icon_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_icon_example + run: dart analyze --fatal-infos . + - id: examples_material_list_example_pub_upgrade + name: examples/material_list_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_list_example + run: dart pub upgrade + - name: "examples/material_list_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_list_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_list_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_list_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_list_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_list_example + run: dart analyze --fatal-infos . + - id: examples_material_yes_no_buttons_example_pub_upgrade + name: examples/material_yes_no_buttons_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_yes_no_buttons_example + run: dart pub upgrade + - name: "examples/material_yes_no_buttons_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_yes_no_buttons_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_yes_no_buttons_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_yes_no_buttons_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_yes_no_buttons_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_yes_no_buttons_example + run: dart analyze --fatal-infos . + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + job_007: + name: "analyze_and_format_examples_1; Dart dev; PKG: examples/material_input_example; `dart format --output=none --set-exit-if-changed .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_input_example;commands:format" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_input_example + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_material_input_example_pub_upgrade + name: examples/material_input_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_input_example + run: dart pub upgrade + - name: "examples/material_input_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_input_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_input_example + run: "dart format --output=none --set-exit-if-changed ." + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + job_008: + name: "analyze_and_format_examples_1; Dart dev; PKG: examples/material_menu_example; `dart format --output=none --set-exit-if-changed .`, `dart analyze .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_menu_example;commands:format-analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_menu_example + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_material_menu_example_pub_upgrade + name: examples/material_menu_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_menu_example + run: dart pub upgrade + - name: "examples/material_menu_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_menu_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_menu_example + run: "dart format --output=none --set-exit-if-changed ." + - name: examples/material_menu_example; dart analyze . + if: "always() && steps.examples_material_menu_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_menu_example + run: dart analyze . + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + job_009: + name: "analyze_and_format_examples_2; Dart 2.14.2; PKGS: examples/material_popup_example, examples/material_progress_example, examples/material_radio_example, examples/material_slider_example, examples/material_spinner_example, examples/material_stepper_example, examples/material_tab_example, examples/material_toggle_example, examples/material_tooltip_example, examples/scorecard_example, examples/simple_html_example; `dart analyze .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:examples/material_popup_example-examples/material_progress_example-examples/material_radio_example-examples/material_slider_example-examples/material_spinner_example-examples/material_stepper_example-examples/material_tab_example-examples/material_toggle_example-examples/material_tooltip_example-examples/scorecard_example-examples/simple_html_example;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:examples/material_popup_example-examples/material_progress_example-examples/material_radio_example-examples/material_slider_example-examples/material_spinner_example-examples/material_stepper_example-examples/material_tab_example-examples/material_toggle_example-examples/material_tooltip_example-examples/scorecard_example-examples/simple_html_example + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2 + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: "2.14.2" + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_material_popup_example_pub_upgrade + name: examples/material_popup_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_popup_example + run: dart pub upgrade + - name: examples/material_popup_example; dart analyze . + if: "always() && steps.examples_material_popup_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_popup_example + run: dart analyze . + - id: examples_material_progress_example_pub_upgrade + name: examples/material_progress_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_progress_example + run: dart pub upgrade + - name: examples/material_progress_example; dart analyze . + if: "always() && steps.examples_material_progress_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_progress_example + run: dart analyze . + - id: examples_material_radio_example_pub_upgrade + name: examples/material_radio_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_radio_example + run: dart pub upgrade + - name: examples/material_radio_example; dart analyze . + if: "always() && steps.examples_material_radio_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_radio_example + run: dart analyze . + - id: examples_material_slider_example_pub_upgrade + name: examples/material_slider_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_slider_example + run: dart pub upgrade + - name: examples/material_slider_example; dart analyze . + if: "always() && steps.examples_material_slider_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_slider_example + run: dart analyze . + - id: examples_material_spinner_example_pub_upgrade + name: examples/material_spinner_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_spinner_example + run: dart pub upgrade + - name: examples/material_spinner_example; dart analyze . + if: "always() && steps.examples_material_spinner_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_spinner_example + run: dart analyze . + - id: examples_material_stepper_example_pub_upgrade + name: examples/material_stepper_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_stepper_example + run: dart pub upgrade + - name: examples/material_stepper_example; dart analyze . + if: "always() && steps.examples_material_stepper_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_stepper_example + run: dart analyze . + - id: examples_material_tab_example_pub_upgrade + name: examples/material_tab_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_tab_example + run: dart pub upgrade + - name: examples/material_tab_example; dart analyze . + if: "always() && steps.examples_material_tab_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tab_example + run: dart analyze . + - id: examples_material_toggle_example_pub_upgrade + name: examples/material_toggle_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_toggle_example + run: dart pub upgrade + - name: examples/material_toggle_example; dart analyze . + if: "always() && steps.examples_material_toggle_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_toggle_example + run: dart analyze . + - id: examples_material_tooltip_example_pub_upgrade + name: examples/material_tooltip_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_tooltip_example + run: dart pub upgrade + - name: examples/material_tooltip_example; dart analyze . + if: "always() && steps.examples_material_tooltip_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tooltip_example + run: dart analyze . + - id: examples_scorecard_example_pub_upgrade + name: examples/scorecard_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/scorecard_example + run: dart pub upgrade + - name: examples/scorecard_example; dart analyze . + if: "always() && steps.examples_scorecard_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/scorecard_example + run: dart analyze . + - id: examples_simple_html_example_pub_upgrade + name: examples/simple_html_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/simple_html_example + run: dart pub upgrade + - name: examples/simple_html_example; dart analyze . + if: "always() && steps.examples_simple_html_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/simple_html_example + run: dart analyze . + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + job_010: + name: "analyze_and_format_examples_2; Dart dev; PKGS: examples/material_popup_example, examples/material_progress_example, examples/material_radio_example, examples/material_slider_example, examples/material_spinner_example, examples/material_stepper_example, examples/material_tab_example, examples/material_toggle_example, examples/material_tooltip_example, examples/scorecard_example, examples/simple_html_example; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_popup_example-examples/material_progress_example-examples/material_radio_example-examples/material_slider_example-examples/material_spinner_example-examples/material_stepper_example-examples/material_tab_example-examples/material_toggle_example-examples/material_tooltip_example-examples/scorecard_example-examples/simple_html_example;commands:format-analyze_1" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_popup_example-examples/material_progress_example-examples/material_radio_example-examples/material_slider_example-examples/material_spinner_example-examples/material_stepper_example-examples/material_tab_example-examples/material_toggle_example-examples/material_tooltip_example-examples/scorecard_example-examples/simple_html_example + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_material_popup_example_pub_upgrade + name: examples/material_popup_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_popup_example + run: dart pub upgrade + - name: "examples/material_popup_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_popup_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_popup_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_popup_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_popup_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_popup_example + run: dart analyze --fatal-infos . + - id: examples_material_progress_example_pub_upgrade + name: examples/material_progress_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_progress_example + run: dart pub upgrade + - name: "examples/material_progress_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_progress_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_progress_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_progress_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_progress_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_progress_example + run: dart analyze --fatal-infos . + - id: examples_material_radio_example_pub_upgrade + name: examples/material_radio_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_radio_example + run: dart pub upgrade + - name: "examples/material_radio_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_radio_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_radio_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_radio_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_radio_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_radio_example + run: dart analyze --fatal-infos . + - id: examples_material_slider_example_pub_upgrade + name: examples/material_slider_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_slider_example + run: dart pub upgrade + - name: "examples/material_slider_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_slider_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_slider_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_slider_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_slider_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_slider_example + run: dart analyze --fatal-infos . + - id: examples_material_spinner_example_pub_upgrade + name: examples/material_spinner_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_spinner_example + run: dart pub upgrade + - name: "examples/material_spinner_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_spinner_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_spinner_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_spinner_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_spinner_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_spinner_example + run: dart analyze --fatal-infos . + - id: examples_material_stepper_example_pub_upgrade + name: examples/material_stepper_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_stepper_example + run: dart pub upgrade + - name: "examples/material_stepper_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_stepper_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_stepper_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_stepper_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_stepper_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_stepper_example + run: dart analyze --fatal-infos . + - id: examples_material_tab_example_pub_upgrade + name: examples/material_tab_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_tab_example + run: dart pub upgrade + - name: "examples/material_tab_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_tab_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tab_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_tab_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_tab_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tab_example + run: dart analyze --fatal-infos . + - id: examples_material_toggle_example_pub_upgrade + name: examples/material_toggle_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_toggle_example + run: dart pub upgrade + - name: "examples/material_toggle_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_toggle_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_toggle_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_toggle_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_toggle_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_toggle_example + run: dart analyze --fatal-infos . + - id: examples_material_tooltip_example_pub_upgrade + name: examples/material_tooltip_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_tooltip_example + run: dart pub upgrade + - name: "examples/material_tooltip_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_tooltip_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tooltip_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/material_tooltip_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_material_tooltip_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tooltip_example + run: dart analyze --fatal-infos . + - id: examples_scorecard_example_pub_upgrade + name: examples/scorecard_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/scorecard_example + run: dart pub upgrade + - name: "examples/scorecard_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_scorecard_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/scorecard_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/scorecard_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_scorecard_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/scorecard_example + run: dart analyze --fatal-infos . + - id: examples_simple_html_example_pub_upgrade + name: examples/simple_html_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/simple_html_example + run: dart pub upgrade + - name: "examples/simple_html_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_simple_html_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/simple_html_example + run: "dart format --output=none --set-exit-if-changed ." + - name: "examples/simple_html_example; dart analyze --fatal-infos ." + if: "always() && steps.examples_simple_html_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/simple_html_example + run: dart analyze --fatal-infos . + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + job_011: + name: "analyze_and_format_examples_2; Dart dev; PKGS: examples/material_select_example, examples/material_tree_example; `dart format --output=none --set-exit-if-changed .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_select_example-examples/material_tree_example;commands:format" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/material_select_example-examples/material_tree_example + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_material_select_example_pub_upgrade + name: examples/material_select_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_select_example + run: dart pub upgrade + - name: "examples/material_select_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_select_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_select_example + run: "dart format --output=none --set-exit-if-changed ." + - id: examples_material_tree_example_pub_upgrade + name: examples/material_tree_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/material_tree_example + run: dart pub upgrade + - name: "examples/material_tree_example; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.examples_material_tree_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/material_tree_example + run: "dart format --output=none --set-exit-if-changed ." + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + job_012: + name: "unit_test; Dart 2.14.2; PKG: angular_components; `./tool/travis/install_protoc.sh`, `dart test --run-skipped`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:angular_components;commands:command_0-test" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:angular_components + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2 + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: "2.14.2" + - id: checkout + uses: actions/checkout@v2.3.4 + - id: angular_components_pub_upgrade + name: angular_components; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_components + run: dart pub upgrade + - name: angular_components; ./tool/travis/install_protoc.sh + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: ./tool/travis/install_protoc.sh + - name: "angular_components; dart test --run-skipped" + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: dart test --run-skipped + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + - job_009 + - job_010 + - job_011 + job_013: + name: "unit_test; Dart dev; PKG: angular_components; `./tool/travis/install_protoc.sh`, `dart test --run-skipped`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:angular_components;commands:command_0-test" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:angular_components + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: angular_components_pub_upgrade + name: angular_components; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: angular_components + run: dart pub upgrade + - name: angular_components; ./tool/travis/install_protoc.sh + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: ./tool/travis/install_protoc.sh + - name: "angular_components; dart test --run-skipped" + if: "always() && steps.angular_components_pub_upgrade.conclusion == 'success'" + working-directory: angular_components + run: dart test --run-skipped + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + - job_009 + - job_010 + - job_011 + job_014: + name: "build; Dart 2.14.2; PKG: examples/angular_components_example; `dart pub run build_runner build web`, `dart pub run build_runner build web --release`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:examples/angular_components_example;commands:command_1-command_2" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2;packages:examples/angular_components_example + os:ubuntu-latest;pub-cache-hosted;dart:2.14.2 + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: "2.14.2" + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_angular_components_example_pub_upgrade + name: examples/angular_components_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/angular_components_example + run: dart pub upgrade + - name: examples/angular_components_example; dart pub run build_runner build web + if: "always() && steps.examples_angular_components_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/angular_components_example + run: dart pub run build_runner build web + - name: "examples/angular_components_example; dart pub run build_runner build web --release" + if: "always() && steps.examples_angular_components_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/angular_components_example + run: dart pub run build_runner build web --release + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + - job_009 + - job_010 + - job_011 + - job_012 + - job_013 + job_015: + name: "build; Dart dev; PKG: examples/angular_components_example; `dart pub run build_runner build web`, `dart pub run build_runner build web --release`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.6 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/angular_components_example;commands:command_1-command_2" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:examples/angular_components_example + os:ubuntu-latest;pub-cache-hosted;dart:dev + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.2 + with: + sdk: dev + - id: checkout + uses: actions/checkout@v2.3.4 + - id: examples_angular_components_example_pub_upgrade + name: examples/angular_components_example; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: examples/angular_components_example + run: dart pub upgrade + - name: examples/angular_components_example; dart pub run build_runner build web + if: "always() && steps.examples_angular_components_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/angular_components_example + run: dart pub run build_runner build web + - name: "examples/angular_components_example; dart pub run build_runner build web --release" + if: "always() && steps.examples_angular_components_example_pub_upgrade.conclusion == 'success'" + working-directory: examples/angular_components_example + run: dart pub run build_runner build web --release + if: + needs: + - job_001 + - job_002 + - job_003 + - job_004 + - job_005 + - job_006 + - job_007 + - job_008 + - job_009 + - job_010 + - job_011 + - job_012 + - job_013 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4e6ab5607..000000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Created with package:mono_repo v1.2.1 -language: dart - -jobs: - include: - - stage: analyze_and_format - name: "SDK: stable - DIR: angular_components - TASKS: [dartfmt -n --set-exit-if-changed ., dartanalyzer --fatal-warnings .]" - script: ./tool/travis.sh dartfmt dartanalyzer - env: PKG="angular_components" - dart: stable - - stage: analyze_and_format - name: "SDK: dev - DIR: angular_components - TASKS: [dartfmt -n --set-exit-if-changed ., dartanalyzer --fatal-warnings .]" - script: ./tool/travis.sh dartfmt dartanalyzer - env: PKG="angular_components" - dart: dev - - stage: unit_test - name: "SDK: stable - DIR: angular_components - TASKS: [./tool/travis/install_protoc.sh, pub run test --run-skipped]" - script: ./tool/travis.sh command test - env: PKG="angular_components" - dart: stable - - stage: unit_test - name: "SDK: dev - DIR: angular_components - TASKS: [./tool/travis/install_protoc.sh, pub run test --run-skipped]" - script: ./tool/travis.sh command test - env: PKG="angular_components" - dart: dev - -stages: - - analyze_and_format - - unit_test - -# Only building master means that we don't run two builds for each pull request. -branches: - only: - - master - -cache: - directories: - - "$HOME/.pub-cache" - - angular_components/.dart_tool/build diff --git a/README.md b/README.md index 67e23a089..38eb3eb28 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ testing policies make these widgets an excellent fit for projects using [AngularDart]. [Material design]: https://material.io/guidelines -[AngularDart]: https://webdev.dartlang.org/angular +[AngularDart]: https://pub.dev/packages/angular [applications]: https://news.dartlang.org/2016/03/the-new-adwords-ui-uses-dart-we-asked.html -[AngularDart Components Gallery]: https://dart-lang.github.io/angular_components/ +[AngularDart Components Gallery]: https://angulardart.github.io/angular_components/ The following packages provide the components as well as code generation for the AngularDart Components gallery. -## [angular_components] [![Pub Package](https://img.shields.io/pub/v/angular_components.svg)](https://pub.dartlang.org/packages/angular_components) +## [angular_components] [![Pub Package](https://img.shields.io/pub/v/angular_components.svg)](https://pub.dev/packages/angular_components) The actual components package that is published to pub. @@ -36,13 +36,8 @@ Example packages for each of the components. Additionally, the [examples/angular_components_example] subdirectory contains the base package for building the AngularDart gallery. -[angular_components]: (https://github.com/dart-lang/angular_components/tree/master/angular_components) -[angular_gallery]: (https://github.com/dart-lang/angular_components/tree/master/angular_gallery) -[angular_gallery_section]: (https://github.com/dart-lang/angular_components/tree/master/angular_gallery_section) -[examples]: (https://github.com/dart-lang/angular_components/tree/master/examples) -[examples/angular_components_example]: (https://github.com/dart-lang/angular_components/tree/master/examples/angular_components_example) - -## Project Roadmap - -Our current work is summarized in the -[roadmap for this quarter](https://github.com/dart-lang/angular_components/issues/356). +[angular_components]: (https://github.com/angulardart/angular_components/tree/master/angular_components) +[angular_gallery]: (https://github.com/angulardart/angular_components/tree/master/angular_gallery) +[angular_gallery_section]: (https://github.com/angulardart/angular_components/tree/master/angular_gallery_section) +[examples]: (https://github.com/angulardart/angular_components/tree/master/examples) +[examples/angular_components_example]: (https://github.com/angulardart/angular_components/tree/master/examples/angular_components_example) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 000000000..664e065cb --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,14 @@ +analyzer: + errors: + # Allow importing .template.dart files without an [explicit] analyzer error. + #uri_has_not_been_generated: ignore + # Ignore our own deprecated symbols (too noisy). + deprecated_member_use_from_same_package: ignore + +linter: + rules: + - prefer_equal_for_default_values + - prefer_generic_function_type_aliases + - slash_for_doc_comments + - unnecessary_const + - unnecessary_new diff --git a/angular_components/CHANGELOG.md b/angular_components/CHANGELOG.md index c271de587..6416a551e 100644 --- a/angular_components/CHANGELOG.md +++ b/angular_components/CHANGELOG.md @@ -1,3 +1,522 @@ +## 1.0.3 + +* Roll observable dependency. + +## 1.0.2 + +* Update a number of outdated links in documentation. +* Reverted addition of `changeDetection: ChangeDetectionStrategy.OnPush` to + components where the functionality is broken. Note: this may result in + additional warnings during build. + +## 1.0.1 + +* Fix insecure link in `README.md`. +* Update dependency on `package:protobuf` to `^1.0.0`. + +## 1.0.0 + +* Stable release that supports the latest `package:angular` and + `package:angular_forms`. + +## 0.14.0-alpha+1 +### Component Updates + +#### Application Layout +* Add `canClose` input the temporary drawer to prevent the drawer from toggling. + +#### Dynamic Component +* Use type promotion instead of dynamic dispatch to update a `RendersValue` + instance. +* Change to "OnPush" change detection. + +#### Material Auto Suggest Input +* Stop event propagation when key nav events are handled. +* Update to work within components using `ChangeDetectionStrategy.OnPush`. + +#### Material Button +* Add Sass mixins to change the padding on the internal button content, adjust + vertical alignment, and reset `text-transform`. +* Remove Sass mixin `button-text-capitalize()`. +* Fix visibility in Microsoft Edge on Windows when using High Contrast mode. + +#### Material Datepicker +* Add Sass mixins for margins around and between the next and previous buttons. +* Increase the color ratio for the apply/cancel buttons. +* Allow picking times using specified increments. +* Handle `` key press when picker is visible. +* Removed deprecated `comparesToPreviousPeriod()` and + `comparesToSamePeriodLastYear()`. +* Delegate to `FocusItemDirective`. +* Fix the a11y role for the date range preset list. +* Improved accessibility for presets. + +#### Material Dialog +* Add Sass mixin for the footer margin. +* Always keep a transparent scroll stroke to prevent dialog from shifting. + +#### Material Expansion Panel +* Improve accessibility. +* Accommodate contents taller than the screen size. + +#### Material Input +* Make text size limits available to screen readers. +* Change attribute `inputRole` to and input `role`. +* Add a CSS class `.ltr` so input can set `direction: ltr` on the top section. +* Add Sass mixin to adjust the location of floating label. + +#### Material Menu +* Prevent screen readers from reading a non-collapsible label as "button". +* Apply the same background color on focused and active items. +* Update `icon` attribute to use a Dart boolean instead of a string `'true'`. +* Add Sass mixin for adding ellipsis to overflowing item text. +* Auto-activate FAB menu items when a screen reader triggers the fab. +* Only auto focus an active item when it exists. +* Protect against null pointer exception when closing. +* Move focus-trap outside of the menu-item-groups. +* Add a way to pass context to the `MenuItem` actions. + +#### Material Popup +* Add `ariaLabel` input. +* Encapsulate the popup class name. + +#### Material Select +* Fix selected item visibility in Microsoft Edge on Windows when using High + Contrast mode. +* Add Sass mixins for adding ellipsis to overflowing item text and a custom + outline. + +#### Material Slider +* Support two sided sliders. + +#### Material Stepper +* Add `yesText` input. +* Add input to keep inactive steps in DOM. +* Add partially complete state. +* Update icon and index colors for improved a11y. + +#### Material Tab +* Set tabbable tab to always be activeTab. +* Improve `allow-text-wrap` Sass mixin. +* Rename Sass mixin `allow-text-wrap` to `tab-text-wrap` and default + `$break-word` to `true`. +* Add Sass mixin to apply text transform. + +#### Material Tooltip +* Fix deferred content within tooltip. +* Add Sass mixin for paper tool tips with multiple sections. + +#### Material Tree +* Add input to toggle selection of non-leaf nodes. + +### Other Updates + +#### Miscellaneous +* Remove `mat-icon-image()` in favor of `inline-image()`. +* Add `subtract()` to Date model. +* Migrate event handlers with multiple statements to component methods. +* Fix violations of the `strict-raw-types` analysis option. +* Fix violations of `prefer_initializing_formals`, `prefer-collection-literals`, + and `prefer-final-fields` lints. +* Properly remove items when invoking `StickyContainerLayout.remove()`. +* Create mixins for error_panel. +* General code readability fixes. +* Deprecate outdated Sass mixins: `button-bar-layout()`, `mat-input-header()`, + `clear-button()`, `icon-background()`, `cursor-grab()`, and + `cursor-grabbing()`. +* Fix graphical issue with sticky elements with `height: 0`. +* Add option to disable `GestureListener` in `ScrollHost`. +* Rename `palette.dart` to `material_chart_colors.dart`. + +### Documentation +* Minor documentation fixes. + +## 0.14.0-alpha +### Component Updates + +#### Material Auto Suggest Input +* Adding generic type to `factoryRenderers` in items. +* Update blur event logic. +* Add `inputAutocomplete` input. +* Improve a11y with added aria controls. + +#### Material Checkbox +* Add Sass mixin to remove all margins. + +#### Material Date Picker +* Fix issue where given `dateFormat` was not used to decode the input value. +* Apply `FocusableMixin`. +* Modularize and deprecate `timeZoneAwareDatepickerProviders`. +* Prevent wordwrap for the range title. + +#### Material Dialog +* Add Sass mixin to justify footer content. + +#### Material Dropdown Select +* Pass `aria-describedby` through to the dropdown button. + +#### Material Expansion Panel +* Only auto focus a child, when the panel is expanded. +* Only fire events and DOM changes on on-target TransitionEnd events. +* Improve a11y by toggling content visibility when the panel expands or + collapses. +* Add `focusOnOpen` input. + +#### Material Input +* Add Sass mixin to change bottom section width and trailing text. +* Add `inputAriaControls` input. + +#### Material Menu +* Add `popupClass` and `buttonAriaLabelledBy` inputs. +* Add Sass mixin to configure the background color of a selected menu item. + +#### Material Popup +* Fix memory leak. + +#### Material Radio +* Ensure changes are picked up by Angular's change detection. +* Add Sass mixin to configure the content margin. +* Fix issue where programmatic changes to the value model were not shown. + +#### Material Select +* Adding generic type to `factoryRenderers` in items. +* Add Sass mixins to configure item colors. + +#### Material Stepper +* Fix an issue where when `activeStepIndex` is set to a value but the step state + is not updated accordingly. + +#### Material Tab +* Loop items and ignore up and down arrow key presses when focusing. + +#### Material Tooltip +* Add Sass mixin to configure the max width of a tooltip. +* Improve a11y with focus control. + +#### Material Tree +* Properly apply `nested-material-tree-item-style` Sass mixin to nested items. +* Introduce a `allowDeselectInHierarchy` configuration that allows clients to + specify if a user should be allowed to deselect an option that they have + already selected (by clicking on it again). + +#### Material Yes/No Buttons +* Make `EnterAcceptsDirective` use key press instead of key up to align with + button decorator. +* Added `aria-describedby`. + +#### Scorecard +* Only apply tabindex 0 to scorecards that are selectable. + +### Other Updates + +#### Selection Model +* Change `SelectionModel.isSingleSelect` from a field to an abstract getter. +* Add a missing `super.dispose()` call to `_StreamSelectionOptions` + +#### Miscellaneous +* Improve `OverlayService` singleton error message. +* Modularize ruler bindings. +* Fix a bug where scroll host would try to add events to a closed + `StreamController`. +* Update `FocusItemDirective` and `FocusListDirective` to work consistently + under `OnPush` components. +* Use GPU accelerated CSS translate rather than 2D translate in sticky + controller. +* Make the new trigger logic the default for popups. This ignores drag mouse up + calls to that users can more easily select text in popups. +* Mark `DeferredContentDirective` for change detection, after handling event + from deferred content aware parent. +* Fix a scroll host issue where scroll events were not ignored when the `Meta` + key was pressed. +* Cleanup uses of deprecated `getBool()`. +* Replace uses of `detectChanges()` with `runAfterChanges...`. +* Add generic type argument to `AcxImperativeViewUtils.insertComponent()`. +* Add the ability to ignore up and down keys (for moving focus around within + children) when using the focus list. +* Update all components to use `ref="noopener noreferrer"` for `target="_blank"` + links. + +### Documentation +* Minor documentation fixes. + +## 0.13.0 +### Component Updates + +#### Material Autosuggest Input +* Toggle multi-select items with `` instead of ``. +* Make the pointer for the clear icon consistent with other buttons when + disabled. + +#### Material Button +* Apply media query to `_button_hover` mixin call so that we will skip hover for + all touchscreens (only apply hover for media supporting hover). +* Add Sass mixins to customize icon colors and left align button text. +* Avoid double trigger of button with a space bar keypress. + +#### Material Checkbox +* Use both label and content as aria label. +* Add types to the outputs. + +#### Material Date Picker +* Make the calendar component invisible to screen readers. +* Add `selectDatePlaceHolderMsg`, `placeholderMsg`, and + `dropdownButtonAriaLabel` inputs. + +#### Material Date Range Picker +* Add `preferredPositions` input. +* Improve handling of pt-BR date range formatting to remove repetitive "de"s + when the endpoints fall into different years. +* Make comparison toggle label clickable. + +#### Material Dialog +* Add Sass mixin to customize font size. +* Use header as default dialog label. +* Mark current landmarks as `role="presentation"` to avoid unnecessary grouping. +* Add missing modal visible output. + +#### Material Expansion Panel +* Add missing modal visible output. +* Allow header to have an outline, and control it with keyboard only focus. +* Add aria-expanded to announce when it is opened/closed. +* Add Sass mixin to make save button raised and highlighted. +* Improve a11y. +* Add the ability to tag the content that you would like to focus when an + expansion panel opens. + +#### Material Icon +* Add Sass mixin to customize svg icon size. + +#### Material Input +* Update error message for negative percentage value to be "not negative" + instead of "positive", because zero is allowed. +* Ensure that `aria-disabled` is set for the input when the input is disabled. +* Use aria-describedby attribute in the to call out errors. +* Remove TAB focus from disabled multiline input. +* Add Sass mixin for multiline inputs to flex and scroll the text entry. +* Add Sass mixins for setting `flex-grow` property and the clear icon color. +* Stop hiding character counter from Aria. +* Add `role="alert"` to error text region. +* Add generic types to `MaterialInputDefaultValueAccessor` and + `MaterialNumberValueAccessor`. +* Create new `Int64` value accessor. +* Pass down the aria-label for leading and trailing glyphs. + +#### Material Menu +* Remove unnecessary escape key handling in MenuItemGroupsComponent. +* Add in aria-label support into the items and groups. +* Open dropdown when navigation keys are pressed. +* Add Sass mixin to customize the item icon size. +* Add aria-expanded to collapsible menu groups. +* Fix item focus bug when the fab menu is opened. +* Improve aria roles for `MenuItemGroup` items. +* Add keyboard accessibility functionality for active item handling. + +#### Material Progress +* Fix screen reader status messages. + +#### Material Popup +* Add `autoDismissBlockers` input to block click events in certain elements from + closing the popup. +* Support defining custom boundaries around window viewport. +* Ensure footer stays inside popup boundaries. +* Add Sass mixin to override overflow value. +* Fix memory leaks. + +#### Material Stepper +* Announce the current step via the screen reader. +* Provide custom aria label for steps. + +#### Material Select +* Fix issue where a keypress on selected item would re-open dropdown. +* Remove `tabIndex` from items and let the dropdown control focus instead. Focus + is now controlled by the dropdown itself. +* Support keyboard navigation. +* Add Sass mixins to customize: + * Background color of the selected item. + * Minimum height of the dropdown buttons. + * Color of the dropdown icon. + * Font size of items. +* Allow auto-focusing on the active item. +* Update Sass mixin `dropdown-icon-spacing` to accept all four margins. + +#### Material Tab +* Add Sass mixin to customize tab strip elevation. + +#### Material Toggle +* Add focus effect. + +#### Material Tooltip +* Remove `initAriaAttributes` for ink tooltips by default. +* Fix hiding tooltip for `MaterialTooltipTargetDirective` when focusing inside + of it. + +#### Material Tree +* Add `allowParentSingleSelection` input. +* Don't override state when `expandAll` hasn't been set. +* Add component generics and pass type through to nodes. + +#### Material Yes/No Buttons +* Add Sass mixin to make yes button raised and highlighted. +* Add Sass mixin to make no button highlighted. + +#### Scorecard +* Fix improper heading hierarchy. +* Fixed scrolling when the average size of the cards is bigger than the client. + +#### Simple HTML Component +* Add attribute `doNotVerifyUrlDestinations` to allow "normal" external URLs. + +### Other Updates + +#### Selection Model +* Allow `is NullSelectionModel` as a replacement for + `== const SelectionModel.empty()`, necessary for typed selection models which + can't use const. +* Expose null selection model type to allow type checks. +* Remove deprecated `SelectionModel()`. +* Add `isSingleSelect` field. + +#### Miscellaneous +* Allow `HasTabIndex` to not set a `TabIndex`. +* Fix a bug where sticky elements do not stack when sticky position is BOTTOM. +* Use named providers instead of the soft deprecated provide(...) and + Provider(...). +* Update MDC Web styles to v1.1.0 +* Tighten public APIs with `@visibleForTemplate`. +* Enable trigger logic that only considers `mouseup` events as part of + `triggersOutside()` if the corresponding `mousedown` event came from the same + element. +* Fix runtime cast errors. +* Remove use of `ChangeDetectionStrategy.Detached`. +* Non-tabbable `buttonDirectivesRemove` now have no tabIndex instead of -1. +* Many components migrated to `OnPush` change detection. + +## 0.12.0 + +### Component Updates + +#### Material Auto-Suggest Input +* Disable clear icon when the input is disabled. +* Hide an empty suggestion group. + +#### Material Button +* Add Sass mixin to change the color of a disabled button. + +#### Material Card +* Update elevation appearance to match spec. + +#### Material Chips +* Allow setting a custom aria label for the delete button. +* Fix issue that prevented removing chips while using JAWS screen reader. +* Add Sass mixins for font-weight and padding. + +#### Material Datepicker +* Remove `globalDateRangeBindings`. +* Allow setting a custom aria label to the dropdown button. +* Allow passing custom `DateFormat` from the `material-date-range-picker` to the + `date-range-input`. + +#### Material Dialog +* Allow setting a custom aria label and describe by. + +#### Material Expansion Panel +* Ensure height calculations are completed after the main content is destroyed. +* Fix keyboard controls to prevent focusing a hidden header button. +* Allow setting a custom aria label to the panel. +* Make the entire content of the panel deferred rather than just the buttons. +* Make the expansion button not be tabbable since the heading is tabbable. +* Prevent hidden buttons from being focused in the header. +* Improve panel resizing. +* Implement `focusableItem` so that it can work with a `focusList`. +* Move name ng-content above the input name and description. + +#### Material Icon +* Correctly stretch SVG icon. +* Remove aria label from the icon. + +#### Material Input +* Add Sass mixin for label text vertical-align. +* `'percent'` is an invalid type attribute, `'text'` instead. +* If the multi-line input is not currently in the DOM listen to DOM updates + until the line height can be read. +* Ensure only whitespace is considered an invalid number input. +* Allow specifying an aria described by id on the input. +* Add Sass mixin to center align text. +* Hide place holder on input field from screen reader. +* Fix focus behavior in disabled state. + +#### Material List +* Change the default roles to `list` and `listitem`. + +#### Material Menu +* Create standalone menu item affix components. +* Load standalone menu item affix components via `DynamicComponent` instead of + using `NgIf`s. +* Allow described by id to be specified for a dropdown button. +* Create `MenuItemMixin`. + +#### Material Month Picker +* Re-render highlights when view is reset. + +#### Material Popup +* Move the overlay focus placeholder elements inside of Material Popup. +* Enable `OnPush` change detection. + +#### Material Ripple +* Remove ripple elements when component is destroyed. + +#### Material Select +* Support custom aria handling for each list item in dropdown. +* Support `OnPush` change detection. +* Revert change that attempted to fix strange behavior when mixing keyboard and + mouse input because it broke some keyboard navigation. +* Allow setting a custom aria label and describe by. +* Add Sass mixin to customize dropdown item selected background color. + +#### Material Tab +* Add Sass mixin to make the tab contents `display: block`. + +#### Material Time Picker +* Fix regression where time cannot be set by user a programmatic change. + +#### Material Tooltip +* `initPopupAriaAttributes` is now passed through to all the tooltip variations. +* Restore any previously defined `aria-describedby` value, after popup closes. +* Add Sass mixin to set `word-break`. +* Fix nested tooltip targets preventing tooltips from staying open when hovered. + +#### Material Tree +* Add ability to specify a label renderer for dropdown button text. +* Add ability to style items in the tree dropdown. + +#### Material Yes/No Buttons +* Add optional ARIA label inputs. +* Add Sass mixin to remove the `margin-left`. +* Add autofocus functionality for use in confirmation dialogs. + +#### Scorecard +* Vertically align the change glyph to the middle. + +#### Simple HTML Component +* Allow 'class' attribute for all elements. + +### Miscellaneous +* Add home/end key modifiers to focus_list to focus the first or last value. +* Remove `$mat-gray` as an alias for `$mat-grey` in Sass mixins. +* Add `shouldFilterEmpty` parameter to `StringSelectionOptions` to return empty + filtered values when query is empty. +* Make the role of a button mutable, after initialization. +* Update MDC Web styles to v0.44.0. +* Add `HtmlDocument` in addition to `Document` as a provided `windowBinding`. +* Modularize clock bindings. +* Remove default values for optional parameters on `notifySelectionChange()`. +* Use `WheelEvent` instead of `MouseEvent` in scroll host. +* Fix previously uncaught violations of invalid override method parameter + default values. + +### Documentation +* Minor docs fixes. + ## 0.11.0 ### New Component @@ -328,8 +847,8 @@ ## 0.9.0 > Pub _transformers_ are no longer used. Instead, use the new -> [webdev](https://pub.dartlang.org/packages/webdev) CLI, or, for advanced -> users, the [build_runner](https://pub.dartlang.org/packages/build_runner) CLI. +> [webdev](https://pub.dev/packages/webdev) CLI, or, for advanced +> users, the [build_runner](https://pub.dev/packages/build_runner) CLI. > > Please see the Dart 2 [migration guide](https://webdev.dartlang.org/dart-2) for > more details. @@ -1023,7 +1542,7 @@ * Rename library to angular_components. > All previous versions were published as the -> [Pub Package](https://pub.dartlang.org/packages/angular2_components) named +> [Pub Package](https://pub.dev/packages/angular2_components) named > `angular2_components`. ## 0.4.1-beta diff --git a/angular_components/README.md b/angular_components/README.md index 04b852c7b..cb4f52e8d 100644 --- a/angular_components/README.md +++ b/angular_components/README.md @@ -1,5 +1,4 @@ -[Material design] components for [AngularDart]. Powering some of Google's most -sophisticated and mission-critical [applications]. +[Material design] components for [AngularDart]. The [AngularDart Components Gallery] contains live examples and documentation. @@ -9,50 +8,20 @@ testing policies make these widgets an excellent fit for projects using the Angular package. [Material design]: https://material.io/guidelines -[AngularDart]: https://webdev.dartlang.org/angular -[applications]: https://news.dartlang.org/2016/03/the-new-adwords-ui-uses-dart-we-asked.html -[AngularDart Components Gallery]: https://dart-lang.github.io/angular_components/ - -This is a continually growing set of widgets. Recent additions include: - -* Material Card styling -* Material Stacking Drawer -* Material Stepper - -At this time we are not taking pull requests, but please -[file an issue](https://github.com/dart-lang/angular_components/issues) -and we will work with you. - -**Officially supported browsers:** The last two versions of Chrome, Edge, -Firefox, and Safari. - -## Package `build_runner` support - -The pub transformer has been removed in favor of code generation through package -[build]. Please see the Dart 2 [migration guide]. - -[build]: https://pub.dartlang.org/packages/build -[migration guide]: https://webdev.dartlang.org/dart-2 +[AngularDart]: https://github.com/angulardart/angular +[AngularDart Components Gallery]: https://angulardart.github.io/angular_components/ ## Useful links -* **[AngularDart Components Gallery](https://dart-lang.github.io/angular_components/)** -* AngularDart Components resources: - * [About AngularDart Components](https://webdev.dartlang.org/components) - * [Google I/O 2017 codelab](https://codelabs.developers.google.com/codelabs/your-first-angulardart-web-app) - * [AngularDart Components launch codelab](https://webdev.dartlang.org/codelabs/angular_components) - * [API reference](https://webdev.dartlang.org/components/api) -* AngularDart resources: - * [AngularDart documentation](https://webdev.dartlang.org/angular/guide) - * [angular pub package](https://pub.dartlang.org/packages/angular) * [Material Design site](https://material.io) +* [Gitter chat room](https://gitter.im/angulardart/community) ## Required Fonts -Add the folowing font downloads to the head element of your page: +Add the following font downloads to the head element of your page: * __Roboto Font__ -([example](https://github.com/dart-lang/angular_components/blob/7f254c89cbbd512cc284a7e9d03bb687f9948bd9/angular_gallery/lib/builder/template/index.html.mustache#L9)) +([example](https://github.com/angulardart/angular_components/blob/7f254c89cbbd512cc284a7e9d03bb687f9948bd9/angular_gallery/lib/builder/template/index.html.mustache#L9)) ```html diff --git a/angular_components/lib/app_layout/README.md b/angular_components/lib/app_layout/README.md index c0280a30c..07f08e2a1 100644 --- a/angular_components/lib/app_layout/README.md +++ b/angular_components/lib/app_layout/README.md @@ -68,7 +68,7 @@ Here is an example: ``` -![Simple App Bar](/dart-lang/angular_components/master/angular_components/lib/app_layout/g3doc/simple_app_bar.png) +![Simple App Bar](/angulardart/angular_components/blob/master/angular_components/lib/app_layout/g3doc/simple_app_bar.png) ## Drawers @@ -106,7 +106,7 @@ done using the [reference variable](https://webdev.dartlang.org/angular/guide/template-syntax#!#ref-vars) syntax. The persistent drawer directive exports itself as `drawer` this allows it to be used easily by other actions. `toggle()` can be used to open/close the -drawer. The drawer supports the `deferredConent` directive allowing a developer +drawer. The drawer supports the `deferredContent` directive allowing a developer to add/remove content from the page when the drawer is not visible (closed.) Here is a complete example: @@ -135,7 +135,7 @@ Here is a complete example: ### Temporary drawers -Temporary drawers are drawers that live on top of the conent. They are provided +Temporary drawers are drawers that live on top of the content. They are provided by `MaterialTemporaryDrawerComponent` which has a similar look and feel to the other drawers. To use a temporary drawer add the `temporary` attribute to the `material-drawer` element, and add `MaterialTemporaryDrawerComponent` to the diff --git a/angular_components/lib/app_layout/_mixins.scss b/angular_components/lib/app_layout/_mixins.scss index 7b108b676..a024df115 100644 --- a/angular_components/lib/app_layout/_mixins.scss +++ b/angular_components/lib/app_layout/_mixins.scss @@ -8,7 +8,7 @@ /// /// Must be applied to the component itself, e.g.: /// -/// ``` +/// ```scss /// material-drawer { /// @include mat-drawer-width(512px); /// } @@ -78,7 +78,7 @@ /// /// Should be applied to the component itself, e.g.: /// -/// ``` +/// ```scss /// material-drawer[temporary] { /// @include mat-temporary-drawer-width(512px); /// } diff --git a/angular_components/lib/app_layout/material_persistent_drawer.dart b/angular_components/lib/app_layout/material_persistent_drawer.dart index 758ccda27..eb1b33abe 100644 --- a/angular_components/lib/app_layout/material_persistent_drawer.dart +++ b/angular_components/lib/app_layout/material_persistent_drawer.dart @@ -17,8 +17,7 @@ import 'material_drawer_base.dart'; selector: 'material-drawer[persistent]', exportAs: 'drawer', providers: [ - Provider(DeferredContentAware, - useExisting: MaterialPersistentDrawerDirective), + ExistingProvider(DeferredContentAware, MaterialPersistentDrawerDirective), ], visibility: Visibility.all, // Injected by child elements. ) diff --git a/angular_components/lib/app_layout/material_stackable_drawer.dart b/angular_components/lib/app_layout/material_stackable_drawer.dart index a434d9969..8b6c2c734 100644 --- a/angular_components/lib/app_layout/material_stackable_drawer.dart +++ b/angular_components/lib/app_layout/material_stackable_drawer.dart @@ -15,8 +15,7 @@ import 'package:angular_components/content/deferred_content_aware.dart'; @Component( selector: 'material-drawer[stackable]', providers: [ - Provider(DeferredContentAware, - useExisting: MaterialStackableDrawerComponent), + ExistingProvider(DeferredContentAware, MaterialStackableDrawerComponent), ], templateUrl: 'material_stackable_drawer.html', styleUrls: ['material_stackable_drawer.scss.css'], diff --git a/angular_components/lib/app_layout/material_temporary_drawer.dart b/angular_components/lib/app_layout/material_temporary_drawer.dart index 67b751f87..4ac5ee8b3 100644 --- a/angular_components/lib/app_layout/material_temporary_drawer.dart +++ b/angular_components/lib/app_layout/material_temporary_drawer.dart @@ -14,8 +14,7 @@ import 'material_drawer_base.dart'; selector: 'material-drawer[temporary]', exportAs: 'drawer', providers: [ - Provider(DeferredContentAware, - useExisting: MaterialTemporaryDrawerComponent), + ExistingProvider(DeferredContentAware, MaterialTemporaryDrawerComponent), ], templateUrl: 'material_temporary_drawer.html', styleUrls: ['material_temporary_drawer.scss.css'], @@ -35,5 +34,11 @@ class MaterialTemporaryDrawerComponent extends MaterialDrawerBase { // should also block keyboard selection outside of the drawer, while open. @HostListener('click') @override - void toggle() => super.toggle(); + void toggle() { + if (visible && !canClose) return; + super.toggle(); + } + + @Input() + bool canClose = true; } diff --git a/angular_components/lib/auto_dismiss/auto_dismiss.dart b/angular_components/lib/auto_dismiss/auto_dismiss.dart index 14392a5d9..5eb9c6800 100644 --- a/angular_components/lib/auto_dismiss/auto_dismiss.dart +++ b/angular_components/lib/auto_dismiss/auto_dismiss.dart @@ -64,5 +64,5 @@ class AutoDismissDirective { Stream get dismiss => _dismissEvents.where((_) => _autoDismissable && !_ignoreEvents); - _listenForEvents([_]) => _ignoreEvents = false; + void _listenForEvents([_]) => _ignoreEvents = false; } diff --git a/angular_components/lib/button_decorator/button_decorator.dart b/angular_components/lib/button_decorator/button_decorator.dart index 30220a021..7491a16f6 100644 --- a/angular_components/lib/button_decorator/button_decorator.dart +++ b/angular_components/lib/button_decorator/button_decorator.dart @@ -26,35 +26,32 @@ import 'package:angular_components/utils/browser/events/events.dart'; ) class ButtonDirective extends RootFocusable with HasTabIndex - implements OnInit, HasDisabled { + implements HasDisabled { /// Fired when the button is activated via click, tap, or key press. @Output() Stream get trigger => _trigger.stream; final _trigger = StreamController.broadcast(sync: true); - String _hostTabIndex; - String _role; - String _ariaRole; - - ButtonDirective(Element element, @Attribute('role') String role) - : _role = role, + String _hostTabIndex = '0'; + final String _nonTabbableIndex; + bool _shouldHandleSpaceKey; + + ButtonDirective(Element element, @Attribute('role') String role, + {bool addTabIndexWhenNonTabbable = false, bool handleSpacePresses = true}) + : this.role = (role ?? 'button'), + // Allow the subclass to define how the element should be made + // untabbable. + _nonTabbableIndex = addTabIndexWhenNonTabbable ? '-1' : null, + _shouldHandleSpaceKey = handleSpacePresses ?? true, super(element); /// Role of this component used for a11y. @Input() - set role(String value) { - assert(ariaRole == null, 'Role can only be set before initialization.'); - _role = value; - } + String role; @HostBinding('attr.role') - String get ariaRole => _ariaRole; - - @override - void ngOnInit() { - _ariaRole = _role ?? 'button'; - } + String get ariaRole => role; /// String value to be passed to aria-disabled. @HostBinding('attr.aria-disabled') @@ -69,7 +66,8 @@ class ButtonDirective extends RootFocusable @Input() bool tabbable = true; - String get hostTabIndex => tabbable && !disabled ? _hostTabIndex : '-1'; + String get hostTabIndex => + tabbable && !disabled ? _hostTabIndex : _nonTabbableIndex; /// The tab index of the component. /// @@ -90,6 +88,7 @@ class ButtonDirective extends RootFocusable @HostListener('keypress') void handleKeyPress(KeyboardEvent keyboardEvent) { if (disabled) return; + if (isSpaceKey(keyboardEvent) && !_shouldHandleSpaceKey) return; int keyCode = keyboardEvent.keyCode; if (keyCode == KeyCode.ENTER || isSpaceKey(keyboardEvent)) { _trigger.add(keyboardEvent); diff --git a/angular_components/lib/content/deferred_content.dart b/angular_components/lib/content/deferred_content.dart index 539a20b07..fbf8b0ba8 100644 --- a/angular_components/lib/content/deferred_content.dart +++ b/angular_components/lib/content/deferred_content.dart @@ -36,11 +36,24 @@ class DeferredContentDirective implements OnDestroy { @Input('deferredContent') bool preserveDimensions = false; + /// Even when the content is not-visible force it to be on the page. + /// + /// Only use this for common components which needs to give options to it's + /// content. + @Input() + set deferredContentForceContent(bool value) { + _forceContent = value; + _setVisible(); + } + // Keep around the current state. + bool _shown = false; bool _visible = false; + bool _forceContent = false; - void _setVisible(bool value) { - if (value == _visible) return; + void _setVisible() { + bool value = _visible || _forceContent; + if (value == _shown) return; if (value) { if (preserveDimensions) { // Remove the placeholder and add the deferred content. @@ -73,12 +86,20 @@ class DeferredContentDirective implements OnDestroy { } } } - _visible = value; + _shown = value; } DeferredContentDirective( - this._viewContainer, this._template, DeferredContentAware parent) { - _disposer.addStreamSubscription(parent.contentVisible.listen(_setVisible)); + this._viewContainer, + this._template, + DeferredContentAware parent, + ChangeDetectorRef changeDetector, + ) { + _disposer.addStreamSubscription(parent.contentVisible.listen((value) { + _visible = value; + _setVisible(); + changeDetector.markForCheck(); + })); } @override @@ -116,8 +137,15 @@ class CachingDeferredContentDirective implements OnDestroy { } CachingDeferredContentDirective( - this._viewContainer, this._template, DeferredContentAware parent) { - _disposer.addStreamSubscription(parent.contentVisible.listen(_setVisible)); + this._viewContainer, + this._template, + DeferredContentAware parent, + ChangeDetectorRef changeDetector, + ) { + _disposer.addStreamSubscription(parent.contentVisible.listen((value) { + _setVisible(value); + changeDetector.markForCheck(); + })); } @override diff --git a/angular_components/lib/css/_all_material.scss b/angular_components/lib/css/_all_material.scss index 99eed2f29..7e2e780e4 100644 --- a/angular_components/lib/css/_all_material.scss +++ b/angular_components/lib/css/_all_material.scss @@ -26,7 +26,7 @@ @import 'color_palette_material'; @import 'color_material'; -@import 'core'; +@import 'core_material'; @import 'elevation_material'; @import 'scrollbar_material'; @import 'transition_material'; diff --git a/angular_components/lib/css/_color_material.scss b/angular_components/lib/css/_color_material.scss index a07115168..85e4d372a 100644 --- a/angular_components/lib/css/_color_material.scss +++ b/angular_components/lib/css/_color_material.scss @@ -56,7 +56,7 @@ $mat-border-dark: rgba($mat-white, $mat-divider-opacity); $mat-border-dotted-light: $mat-lightest-transparent-black; // Text colors -// https://material.io/design/color/text-legibility.html +// https://material.io/design/color $mat-text-primary: $mat-transparent-black; $mat-text-secondary: $mat-light-transparent-black; $mat-text-hint: $mat-lighter-transparent-black; @@ -70,7 +70,7 @@ $mat-text-dark-disabled: $mat-lighter-transparent-white; $mat-text-dark-divider: $mat-border-dark; // Icon colors -// https://material.io/design/color/text-legibility.html#text-types +// https://material.io/guidelines/style/icons.html $mat-icon-focused: $mat-transparent-black; $mat-icon: $mat-light-transparent-black; $mat-icon-inactive: $mat-lighter-transparent-black; @@ -518,3 +518,9 @@ $mat-link-default: $mat-blue-700; $mat-link-visited: $mat-deep-purple-500; $mat-link-active: $mat-red-700; + +// Opacity Values +// + +$mat-ripple-focused-opacity: .24 + diff --git a/angular_components/lib/css/_core.scss b/angular_components/lib/css/_core_material.scss similarity index 83% rename from angular_components/lib/css/_core.scss rename to angular_components/lib/css/_core_material.scss index b29362ab8..ded1b1109 100644 --- a/angular_components/lib/css/_core.scss +++ b/angular_components/lib/css/_core_material.scss @@ -2,23 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. + // Grid $mat-grid: 8px; $mat-grid-type: 4px; -// Option that can be set before importing -$mat-option-inline-icons: false !default; - -// Creates a reference for the given file in either data or package url format, -// depending on the value of the global variable $mat-option-inline-icons. -@function mat-icon-image($file) { - @if $mat-option-inline-icons { - @return inline-image($file) - } @else { - @return url("package:#{$file}") - } -} - // Forces an element to take up the same space as its closest, positioned // ancestor or, if none exists, the root element. @mixin fit { @@ -43,22 +31,7 @@ $mat-option-inline-icons: false !default; } } -// Prefixed cursors -// http://caniuse.com/#feat=css3-cursors-newer -@mixin cursor-grab() { - cursor: move; // fall back to move if unavailable (IE) - cursor: -webkit-grab; - cursor: -moz-grab; - cursor: grab; -} - -@mixin cursor-grabbing() { - cursor: move; // fall back to move if unavailable (IE) - cursor: -webkit-grabbing; - cursor: -moz-grabbing; - cursor: grabbing; -} - +// DEPRECATED: use flex directly instead. @mixin button-bar-layout() { display: flex; flex-direction: row; @@ -66,6 +39,8 @@ $mat-option-inline-icons: false !default; } // Styles text that is used as a floating label over a material component. +// +// DEPRECATED: use the caption typography mixin instead. @mixin mat-input-header() { color: $mat-gray-600; // TODO(google): Migrate to extended mixin mat-font-caption @@ -73,6 +48,7 @@ $mat-option-inline-icons: false !default; font-weight: $mat-font-weight-regular; } +// DEPRECATED: use instead. @mixin clear-button() { background-color: transparent; border: 0; @@ -84,6 +60,10 @@ $mat-option-inline-icons: false !default; } // Note: this only supports square icons. +// +// WARINING: does not work in high contrast mode. +// +// DEPRECATED: use or instead. @mixin icon-background( $icon-url, $background-size: 6 * $mat-grid, $icon-size: 3 * $mat-grid) { @@ -91,7 +71,7 @@ $mat-option-inline-icons: false !default; flex-shrink: 0; flex-grow: 0; - background-image: mat-icon-image($icon-url); + background-image: inline-image($icon-url); background-position: center center; background-repeat: no-repeat; background-size: $icon-size $icon-size; @@ -100,8 +80,6 @@ $mat-option-inline-icons: false !default; width: $background-size; } -// TODO(google): remove icon-button and glyph-button in favor of a more -// standard way of creating glyph buttons (e.g. a glyph button component) // DEPRECATED: use instead. @mixin icon-button( $icon-url, $button-size: 6 * $mat-grid, $icon-size: 3 * $mat-grid) { @@ -154,6 +132,7 @@ $mat-option-inline-icons: false !default; // Similar to icon button, but to be used with a button containing an // `` element. +// // DEPRECATED: use instead. @mixin glyph-button($button-size: 6 * $mat-grid, $circle: false) { @@ -197,9 +176,26 @@ $mat-option-inline-icons: false !default; } } -// *** Deprecated -// The following functions are deprecated. +// DEPRECATED +// +// The following mixins are all deprecated. // The equivalent native CSS is available in all supported browsers. +// + +// Duplicate property for fall back if `grab` is unavailable (IE) +@mixin cursor-grab() { + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +@mixin cursor-grabbing() { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} @mixin box-sizing($type:border-box) { -moz-box-sizing: $type; // Firefox < 29 diff --git a/angular_components/lib/css/_scrollbar_material.scss b/angular_components/lib/css/_scrollbar_material.scss index ef1d3a5e1..51e915741 100644 --- a/angular_components/lib/css/_scrollbar_material.scss +++ b/angular_components/lib/css/_scrollbar_material.scss @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@import 'core'; +@import 'core_material'; @import 'color_material'; /// Common colors for scrollbars. diff --git a/angular_components/lib/css/_typography_material.scss b/angular_components/lib/css/_typography_material.scss index 48aedffc1..7c14d19e1 100644 --- a/angular_components/lib/css/_typography_material.scss +++ b/angular_components/lib/css/_typography_material.scss @@ -5,6 +5,8 @@ // Typography constants // https://material.io/design/typography +@import 'color_material'; + // Roboto font stack. $mat-font-family: Roboto, Noto, sans-serif; diff --git a/angular_components/lib/css/material/const/_functions.scss b/angular_components/lib/css/material/const/_functions.scss index cb6629c73..000207342 100644 --- a/angular_components/lib/css/material/const/_functions.scss +++ b/angular_components/lib/css/material/const/_functions.scss @@ -2,4 +2,4 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@import 'package:angular_components/css/core'; +@import 'package:angular_components/css/core_material'; diff --git a/angular_components/lib/css/material/const/_mixins.scss b/angular_components/lib/css/material/const/_mixins.scss index fdb00170c..b32bc40c4 100644 --- a/angular_components/lib/css/material/const/_mixins.scss +++ b/angular_components/lib/css/material/const/_mixins.scss @@ -2,5 +2,5 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@import 'package:angular_components/css/core'; +@import 'package:angular_components/css/core_material'; @import 'package:angular_components/css/scrollbar_material'; diff --git a/angular_components/lib/css/material/const/_settings.scss b/angular_components/lib/css/material/const/_settings.scss index 3f8a74487..ff0c33e2e 100644 --- a/angular_components/lib/css/material/const/_settings.scss +++ b/angular_components/lib/css/material/const/_settings.scss @@ -2,6 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -@import 'package:angular_components/css/core'; +@import 'package:angular_components/css/core_material'; @import 'package:angular_components/css/transition_material'; @import 'package:angular_components/css/color_material'; diff --git a/angular_components/lib/css/mdc_web/base/_mixins.scss b/angular_components/lib/css/mdc_web/base/_mixins.scss new file mode 100644 index 000000000..35c501386 --- /dev/null +++ b/angular_components/lib/css/mdc_web/base/_mixins.scss @@ -0,0 +1,33 @@ +// +// Copyright 2018 Google Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// This variable is not intended to be overridden externally; it uses !default to avoid being reset +// every time this file is imported. +$mdc-base-styles-emitted_: () !default; + +@mixin mdc-base-emit-once($key) { + @if not index($mdc-base-styles-emitted_, $key) { + $mdc-base-styles-emitted_: append($mdc-base-styles-emitted_, $key, comma) !global; + + @content; + } +} diff --git a/angular_components/lib/css/mdc_web/card/_mixins.scss b/angular_components/lib/css/mdc_web/card/_mixins.scss index 4b41e13d0..838168256 100644 --- a/angular_components/lib/css/mdc_web/card/_mixins.scss +++ b/angular_components/lib/css/mdc_web/card/_mixins.scss @@ -20,30 +20,303 @@ // THE SOFTWARE. // -@import "../theme/mixins"; +@import "../elevation/mixins"; +@import "../featuretargeting/functions"; +@import "../featuretargeting/mixins"; +@import "../ripple/mixins"; +@import "../rtl/mixins"; @import "../shape/mixins"; +@import "../theme/mixins"; @import "./variables"; // // Public // -@mixin mdc-card-fill-color($color) { - @include mdc-theme-prop(background-color, $color); +@mixin mdc-card-core-styles($query: mdc-feature-all()) { + @include mdc-card-without-ripple($query); + @include mdc-card-ripple($query); +} + +// This API is intended for use by frameworks that may want to separate the ripple-related styles from the other +// card styles. It is recommended that most users use `mdc-card-core-styles` instead. +@mixin mdc-card-without-ripple($query: mdc-feature-all()) { + // postcss-bem-linter: define card + + $feat-color: mdc-feature-create-target($query, color); + $feat-structure: mdc-feature-create-target($query, structure); + + .mdc-card { + @include mdc-card-shape-radius(medium, $query: $query); + @include mdc-card-fill-color(surface, $query); + @include mdc-elevation(1, $query: $query); + + @include mdc-feature-targets($feat-structure) { + @include mdc-card-container-layout_; + } + } + + .mdc-card--outlined { + @include mdc-elevation(0, $query: $query); + @include mdc-card-outline($mdc-card-outline-color, $query: $query); + } + + // + // Media + // + + .mdc-card__media { + @include mdc-feature-targets($feat-structure) { + position: relative; // Child element `__media-content` has `position: absolute` + box-sizing: border-box; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + } + + &::before { + @include mdc-feature-targets($feat-structure) { + display: block; + content: ""; + } + } + } + + .mdc-card__media:first-child { + @include mdc-feature-targets($feat-structure) { + border-top-left-radius: inherit; + border-top-right-radius: inherit; + } + } + + .mdc-card__media:last-child { + @include mdc-feature-targets($feat-structure) { + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + } + } + + .mdc-card__media--square { + @include mdc-card-media-aspect-ratio(1, 1, $query); + } + + .mdc-card__media--16-9 { + @include mdc-card-media-aspect-ratio(16, 9, $query); + } + + .mdc-card__media-content { + @include mdc-feature-targets($feat-structure) { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + box-sizing: border-box; + } + } + + // + // Primary action + // + + .mdc-card__primary-action { + @include mdc-feature-targets($feat-structure) { + @include mdc-card-container-layout_; + + position: relative; // Needed to prevent the ripple wash from overflowing the container in IE and Edge + outline: none; + color: inherit; + text-decoration: none; + cursor: pointer; + overflow: hidden; + } + } + + .mdc-card__primary-action:first-child { + @include mdc-feature-targets($feat-structure) { + border-top-left-radius: inherit; + border-top-right-radius: inherit; + } + } + + .mdc-card__primary-action:last-child { + @include mdc-feature-targets($feat-structure) { + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + } + } + + // + // Action row + // + + .mdc-card__actions { + @include mdc-feature-targets($feat-structure) { + @include mdc-card-actions-layout_; + + min-height: 52px; + padding: 8px; + } + } + + .mdc-card__actions--full-bleed { + @include mdc-feature-targets($feat-structure) { + padding: 0; + } + } + + .mdc-card__action-buttons, + .mdc-card__action-icons { + @include mdc-feature-targets($feat-structure) { + @include mdc-card-actions-layout_; + } + } + + .mdc-card__action-icons { + @include mdc-feature-targets($feat-color) { + @include mdc-theme-prop(color, $mdc-card-action-icon-color); + } + + @include mdc-feature-targets($feat-structure) { + flex-grow: 1; + justify-content: flex-end; + } + } + + .mdc-card__action-buttons + .mdc-card__action-icons { + @include mdc-feature-targets($feat-structure) { + @include mdc-rtl-reflexive-box(margin, left, 16px); + } + } + + // + // Action items + // + + .mdc-card__action { + @include mdc-feature-targets($feat-structure) { + @include mdc-card-actions-layout_(inline-flex); + + justify-content: center; + cursor: pointer; + user-select: none; + } + + &:focus { + @include mdc-feature-targets($feat-structure) { + outline: none; + } + } + } + + // + // Action buttons + // + + .mdc-card__action--button { + @include mdc-feature-targets($feat-structure) { + @include mdc-rtl-reflexive-box(margin, right, 8px); + + padding: 0 8px; + } + + &:last-child { + @include mdc-feature-targets($feat-structure) { + @include mdc-rtl-reflexive-box(margin, right, 0); + } + } + } + + .mdc-card__actions--full-bleed .mdc-card__action--button { + @include mdc-feature-targets($feat-structure) { + justify-content: space-between; + width: 100%; + height: auto; + max-height: none; + margin: 0; + padding: 8px 16px; + /* @noflip */ + text-align: left; + } + + @include mdc-rtl { + @include mdc-feature-targets($feat-structure) { + /* @noflip */ + text-align: right; + } + } + } + + // + // Action icons + // + + .mdc-card__action--icon { + @include mdc-feature-targets($feat-structure) { + // Icon buttons are taller than buttons, so we need to adjust their margins to prevent the action row from + // expanding. + margin: -6px 0; + + // Same padding as mdc-icon-button. + padding: 12px; + } + } + + .mdc-card__action--icon:not(:disabled) { + @include mdc-feature-targets($feat-color) { + @include mdc-theme-prop(color, $mdc-card-action-icon-color); + } + } + + // postcss-bem-linter: end +} + +// This API is intended for use by frameworks that may want to separate the ripple-related styles from the other +// card styles. It is recommended that most users use `mdc-card-core-styles` instead. +@mixin mdc-card-ripple($query: mdc-feature-all()) { + // @include mdc-ripple-common($query); + + .mdc-card__primary-action { + @include mdc-ripple-surface($query); + @include mdc-ripple-radius-bounded($query: $query); + @include mdc-states($query: $query); + } +} + +@mixin mdc-card-fill-color($color, $query: mdc-feature-all()) { + $feat-color: mdc-feature-create-target($query, color); + + @include mdc-feature-targets($feat-color) { + @include mdc-theme-prop(background-color, $color); + } } -@mixin mdc-card-outline($color, $thickness: $mdc-card-outline-width) { - border: $thickness solid mdc-theme-prop-value($color); +@mixin mdc-card-outline($color, $thickness: $mdc-card-outline-width, $query: mdc-feature-all()) { + $feat-color: mdc-feature-create-target($query, color); + $feat-structure: mdc-feature-create-target($query, structure); + + @include mdc-feature-targets($feat-structure) { + border-width: $thickness; + border-style: solid; + } + + @include mdc-feature-targets($feat-color) { + border-color: mdc-theme-prop-value($color); + } } -@mixin mdc-card-shape-radius($radius, $rtl-reflexive: false) { - @include mdc-shape-radius($radius, $rtl-reflexive); +@mixin mdc-card-shape-radius($radius, $rtl-reflexive: false, $query: mdc-feature-all()) { + @include mdc-shape-radius($radius, $rtl-reflexive, $query: $query); } -@mixin mdc-card-media-aspect-ratio($x, $y) { +@mixin mdc-card-media-aspect-ratio($x, $y, $query: mdc-feature-all()) { + $feat-structure: mdc-feature-create-target($query, structure); + &::before { - // This clever trick brought to you by: http://www.mademyday.de/css-height-equals-width-with-pure-css.html - margin-top: percentage($y / $x); + @include mdc-feature-targets($feat-structure) { + // This clever trick brought to you by: http://www.mademyday.de/css-height-equals-width-with-pure-css.html + margin-top: percentage($y / $x); + } } } @@ -63,10 +336,3 @@ align-items: center; box-sizing: border-box; } - -@mixin mdc-card-media-aspect-ratio-base_ { - &::before { - display: block; - content: ""; - } -} diff --git a/angular_components/lib/css/mdc_web/card/_variables.scss b/angular_components/lib/css/mdc_web/card/_variables.scss index 16fcb176b..877227759 100644 --- a/angular_components/lib/css/mdc_web/card/_variables.scss +++ b/angular_components/lib/css/mdc_web/card/_variables.scss @@ -21,6 +21,8 @@ // @import "../theme/mixins"; +@import "../theme/variables"; -$mdc-card-outline-color: mix(mdc-theme-prop-value(on-surface), mdc-theme-prop-value(surface), 12%); -$mdc-card-outline-width: 1px; +$mdc-card-action-icon-color: rgba(mdc-theme-prop-value(on-surface), mdc-theme-text-emphasis(medium)) !default; +$mdc-card-outline-color: mix(mdc-theme-prop-value(on-surface), mdc-theme-prop-value(surface), 12%) !default; +$mdc-card-outline-width: 1px !default; diff --git a/angular_components/lib/css/mdc_web/card/mdc-card.scss b/angular_components/lib/css/mdc_web/card/mdc-card.scss index 6d22f4ec2..153883b5a 100644 --- a/angular_components/lib/css/mdc_web/card/mdc-card.scss +++ b/angular_components/lib/css/mdc_web/card/mdc-card.scss @@ -20,185 +20,5 @@ // THE SOFTWARE. // -@import "../elevation/mixins"; -@import "../theme/mixins"; -@import "../ripple/mixins"; -@import "../rtl/mixins"; @import "./mixins"; -@import "./variables"; - -// postcss-bem-linter: define card - -.mdc-card { - @include mdc-card-fill-color(surface); - @include mdc-card-shape-radius(small); - @include mdc-elevation(2); - @include mdc-card-container-layout_; -} - -.mdc-card--outlined { - @include mdc-elevation(0); - @include mdc-card-outline($mdc-card-outline-color); -} - -// -// Media -// - -.mdc-card__media { - @include mdc-card-media-aspect-ratio-base_; - - position: relative; // Child element `__media-content` has `position: absolute` - box-sizing: border-box; - background-repeat: no-repeat; - background-position: center; - background-size: cover; -} - -.mdc-card__media:first-child { - border-top-left-radius: inherit; - border-top-right-radius: inherit; -} - -.mdc-card__media:last-child { - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; -} - -.mdc-card__media--square { - @include mdc-card-media-aspect-ratio(1, 1); -} - -.mdc-card__media--16-9 { - @include mdc-card-media-aspect-ratio(16, 9); -} - -.mdc-card__media-content { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - box-sizing: border-box; -} - -// -// Primary action -// - -.mdc-card__primary-action { - @include mdc-ripple-surface; - @include mdc-ripple-radius-bounded; - @include mdc-states; - @include mdc-card-container-layout_; - - position: relative; // Needed to prevent the ripple wash from overflowing the container in IE and Edge - outline: none; - color: inherit; - text-decoration: none; - cursor: pointer; - overflow: hidden; -} - -.mdc-card__primary-action:first-child { - border-top-left-radius: inherit; - border-top-right-radius: inherit; -} - -.mdc-card__primary-action:last-child { - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; -} - -// -// Action row -// - -.mdc-card__actions { - @include mdc-card-actions-layout_; - - min-height: 52px; - padding: 8px; -} - -.mdc-card__actions--full-bleed { - padding: 0; -} - -.mdc-card__action-buttons, -.mdc-card__action-icons { - @include mdc-card-actions-layout_; -} - -.mdc-card__action-icons { - @include mdc-theme-prop(color, text-icon-on-background); - - flex-grow: 1; - justify-content: flex-end; -} - -.mdc-card__action-buttons + .mdc-card__action-icons { - @include mdc-rtl-reflexive-box(margin, left, 16px); -} - -// -// Action items -// - -.mdc-card__action { - @include mdc-card-actions-layout_(inline-flex); - - justify-content: center; - cursor: pointer; - user-select: none; - - &:focus { - outline: none; - } -} - -// -// Action buttons -// - -.mdc-card__action--button { - @include mdc-rtl-reflexive-box(margin, right, 8px); - - padding: 0 8px; - - &:last-child { - @include mdc-rtl-reflexive-box(margin, right, 0); - } -} - -.mdc-card__actions--full-bleed .mdc-card__action--button { - justify-content: space-between; - width: 100%; - height: auto; - max-height: none; - margin: 0; - padding: 8px 16px; - text-align: left; - - @include mdc-rtl { - text-align: right; - } -} - -// -// Action icons -// - -.mdc-card__action--icon { - // Icon toggles are taller than buttons, so we need to adjust their margins to prevent the action row from expanding. - margin: -6px 0; - - // Same padding as mdc-icon-button. - padding: 12px; -} - -.mdc-card__action--icon:not(:disabled) { - @include mdc-theme-prop(color, text-icon-on-background); -} - -// postcss-bem-linter: end +@include mdc-card-core-styles; diff --git a/angular_components/lib/css/mdc_web/elevation/_mixins.scss b/angular_components/lib/css/mdc_web/elevation/_mixins.scss index 740863418..c365cfea7 100644 --- a/angular_components/lib/css/mdc_web/elevation/_mixins.scss +++ b/angular_components/lib/css/mdc_web/elevation/_mixins.scss @@ -20,14 +20,37 @@ // THE SOFTWARE. // +@import "../featuretargeting/functions"; +@import "../featuretargeting/mixins"; @import "../theme/variables"; @import "./variables"; +@mixin mdc-elevation-core-styles($query: mdc-feature-all()) { + $feat-animation: mdc-feature-create-target($query, animation); + $feat-structure: mdc-feature-create-target($query, structure); + + @for $z-value from 0 through 24 { + .mdc-elevation--z#{$z-value} { + @include mdc-elevation($z-value, $query: $query); + } + } + + .mdc-elevation-transition { + @include mdc-feature-targets($feat-animation) { + transition: mdc-elevation-transition-value(); + } + + @include mdc-feature-targets($feat-structure) { + will-change: $mdc-elevation-property; + } + } +} + // Applies the correct CSS rules to an element to give it the elevation specified by $z-value. // The $z-value must be between 0 and 24. // If $color has an alpha channel, it will be ignored and overridden. To increase the opacity of the shadow, use // $opacity-boost. -@mixin mdc-elevation($z-value, $color: $mdc-elevation-baseline-color, $opacity-boost: 0) { +@mixin mdc-elevation($z-value, $color: $mdc-elevation-baseline-color, $opacity-boost: 0, $query: mdc-feature-all()) { @if type-of($z-value) != number or not unitless($z-value) { @error "$z-value must be a unitless number, but received '#{$z-value}'"; } @@ -36,6 +59,8 @@ @error "$z-value must be between 0 and 24, but received '#{$z-value}'"; } + $feat-color: mdc-feature-create-target($query, color); + $color: mdc-theme-prop-value($color); $umbra-z-value: map-get($mdc-elevation-umbra-map, $z-value); @@ -46,10 +71,12 @@ $penumbra-color: rgba($color, $mdc-elevation-penumbra-opacity + $opacity-boost); $ambient-color: rgba($color, $mdc-elevation-ambient-opacity + $opacity-boost); - box-shadow: - #{"#{$umbra-z-value} #{$umbra-color}"}, - #{"#{$penumbra-z-value} #{$penumbra-color}"}, - #{$ambient-z-value} $ambient-color; + @include mdc-feature-targets($feat-color) { + box-shadow: + #{"#{$umbra-z-value} #{$umbra-color}"}, + #{"#{$penumbra-z-value} #{$penumbra-color}"}, + #{$ambient-z-value} $ambient-color; + } } // Returns a string that can be used as the value for a `transition` property for elevation. @@ -64,6 +91,7 @@ // ``` @function mdc-elevation-transition-value( $duration: $mdc-elevation-transition-duration, - $easing: $mdc-elevation-transition-timing-function) { + $easing: $mdc-elevation-transition-timing-function +) { @return #{$mdc-elevation-property} #{$duration} #{$easing}; } diff --git a/angular_components/lib/css/mdc_web/elevation/_variables.scss b/angular_components/lib/css/mdc_web/elevation/_variables.scss index eadfd85b2..a950bd195 100644 --- a/angular_components/lib/css/mdc_web/elevation/_variables.scss +++ b/angular_components/lib/css/mdc_web/elevation/_variables.scss @@ -22,10 +22,10 @@ @import "../animation/variables"; -$mdc-elevation-baseline-color: black; -$mdc-elevation-umbra-opacity: .2; -$mdc-elevation-penumbra-opacity: .14; -$mdc-elevation-ambient-opacity: .12; +$mdc-elevation-baseline-color: black !default; +$mdc-elevation-umbra-opacity: .2 !default; +$mdc-elevation-penumbra-opacity: .14 !default; +$mdc-elevation-ambient-opacity: .12 !default; $mdc-elevation-umbra-map: ( 0: "0px 0px 0px 0px", @@ -53,7 +53,7 @@ $mdc-elevation-umbra-map: ( 22: "0px 10px 14px -6px", 23: "0px 11px 14px -7px", 24: "0px 11px 15px -7px" -); +) !default; $mdc-elevation-penumbra-map: ( 0: "0px 0px 0px 0px", @@ -81,7 +81,7 @@ $mdc-elevation-penumbra-map: ( 22: "0px 22px 35px 3px", 23: "0px 23px 36px 3px", 24: "0px 24px 38px 3px" -); +) !default; $mdc-elevation-ambient-map: ( 0: "0px 0px 0px 0px", @@ -109,7 +109,7 @@ $mdc-elevation-ambient-map: ( 22: "0px 8px 42px 7px", 23: "0px 9px 44px 8px", 24: "0px 9px 46px 8px" -); +) !default; // The css property used for elevation. In most cases this should not be changed. It is exposed // as a variable for abstraction / easy use when needing to reference the property directly, for diff --git a/angular_components/lib/css/mdc_web/featuretargeting/_functions.scss b/angular_components/lib/css/mdc_web/featuretargeting/_functions.scss new file mode 100644 index 000000000..1aaa326ec --- /dev/null +++ b/angular_components/lib/css/mdc_web/featuretargeting/_functions.scss @@ -0,0 +1,193 @@ +// +// Copyright 2019 Google Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@import "./variables"; + +// ==Terminology== +// Feature: +// A simple string (e.g. `color`) representing a cross-cutting feature in +// Material. +// Feature query: +// A structure that represents a query for a feature or combination of features. This may be +// either a feature or a map containing `op` and `queries` fields. A single feature represents a +// simple query for just that feature. A map represents a complex query made up of an operator, +// `op`, applied to a list of sub-queries, `queries`. +// (e.g. `color`, `(op: any, queries: (color, typography))`). +// Feature target: +// A map that contains the feature being targeted as well as the current feature query. This is +// the structure that is intended to be passed to the `@mdc-feature-targets` mixin. +// (e.g. `(target: color, query: (op: any, queries: (color, typography))`). + +// +// Public +// + +// Creates a feature target from the given feature query and targeted feature. +@function mdc-feature-create-target($feature-query, $targeted-feature) { + $feature-target: (query: $feature-query, target: $targeted-feature); + $valid: mdc-feature-verify-target_($feature-target); + + @return $feature-target; +} + +// Parses a list of feature targets to produce a map containing the feature query and list of +// available features. +@function mdc-feature-parse-targets($feature-targets) { + $valid: mdc-feature-verify-target_($feature-targets...); + $available-features: (); + + @each $target in $feature-targets { + $available-features: append($available-features, map-get($target, target)); + } + + @return ( + available: $available-features, + query: map-get(nth($feature-targets, 1), query) + ); +} + +// Creates a feature query that is satisfied iff all of its sub-queries are satisfied. +@function mdc-feature-all($feature-queries...) { + $valid: mdc-feature-verify-query_($feature-queries...); + + @return ( + op: all, + queries: $feature-queries + ); +} + +// Creates a feature query that is satisfied iff any of its sub-queries are satisfied. +@function mdc-feature-any($feature-queries...) { + $valid: mdc-feature-verify-query_($feature-queries...); + + @return ( + op: any, + queries: $feature-queries + ); +} + +// Creates a feature query that is satisfied iff its sub-query is not satisfied. +@function mdc-feature-without($feature-query) { + $valid: mdc-feature-verify-query_($feature-query); + + @return ( + op: without, + // NOTE: we need to use `append`, just putting parens around a single value doesn't make it a list in Sass. + queries: append((), $feature-query) + ); +} + +// +// Package-internal +// + +// Verifies that the given feature targets are valid, throws an error otherwise. +@function mdc-feature-verify-target_($feature-targets...) { + @each $target in $feature-targets { + @if type-of($target) != map { + @error "Invalid feature target: '#{$target}'. Must be a map."; + } + + $targeted-feature: map-get($target, target); + $feature-query: map-get($target, query); + $valid: mdc-feature-verify-feature_($targeted-feature) and mdc-feature-verify-query_($feature-query); + } + + @return true; +} + +// Checks whether the given feature query is satisfied by the given list of available features. +@function mdc-feature-is-query-satisfied_($feature-query, $available-features) { + $valid: mdc-feature-verify-query_($feature-query); + $valid: mdc-feature-verify-feature_($available-features...); + + @if type-of($feature-query) == map { + $op: map-get($feature-query, op); + $sub-queries: map-get($feature-query, queries); + + @if $op == without { + @return not mdc-feature-is-query-satisfied_(nth($sub-queries, 1), $available-features); + } + + @if $op == any { + @each $sub-query in $sub-queries { + @if mdc-feature-is-query-satisfied_($sub-query, $available-features) { + @return true; + } + } + + @return false; + } + + @if $op == all { + @each $sub-query in $sub-queries { + @if not mdc-feature-is-query-satisfied_($sub-query, $available-features) { + @return false; + } + } + + @return true; + } + } + + @return mdc-feature-list-contains_($available-features, $feature-query); +} + +// +// Private +// + +// Verifies that the given feature(s) are valid, throws an error otherwise. +@function mdc-feature-verify-feature_($features...) { + @each $feature in $features { + @if not mdc-feature-list-contains_($mdc-feature-all-features, $feature) { + @error "Invalid feature: '#{$feature}'. Valid features are: #{$mdc-feature-all-features}."; + } + } + + @return true; +} + +// Verifies that the given feature queries are valid, throws an error otherwise. +@function mdc-feature-verify-query_($feature-queries...) { + @each $query in $feature-queries { + @if type-of($query) == map { + $op: map-get($query, op); + $sub-queries: map-get($query, queries); + $valid: mdc-feature-verify-query_($sub-queries...); + + @if not mdc-feature-list-contains_($mdc-feature-all-query-operators, $op) { + @error "Invalid feature query operator: '#{$op}'. " + + "Valid operators are: #{$mdc-feature-all-query-operators}"; + } + } @else { + $valid: mdc-feature-verify-feature_($query); + } + } + + @return true; +} + +// Checks whether the given list contains the given item. +@function mdc-feature-list-contains_($list, $item) { + @return index($list, $item) != null; +} diff --git a/angular_components/lib/css/mdc_web/featuretargeting/_mixins.scss b/angular_components/lib/css/mdc_web/featuretargeting/_mixins.scss new file mode 100644 index 000000000..dd761baca --- /dev/null +++ b/angular_components/lib/css/mdc_web/featuretargeting/_mixins.scss @@ -0,0 +1,44 @@ +// +// Copyright 2019 Google Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@import "./functions"; + +// Tracks whether the current context is inside a `mdc-feature-targets` mixin. +$mdc-feature-targets-context_: false; + +// Mixin that annotates the contained styles as applying to specific cross-cutting features +// indicated by the given list of feature targets. +@mixin mdc-feature-targets($feature-targets...) { + // Prevent accidental nesting of this mixin, which could lead to unexpected results. + @if $mdc-feature-targets-context_ { + @error "mdc-feature-targets must not be used inside of another mdc-feature-targets block"; + } + + $mdc-feature-targets-context_: true !global; + $parsed-targets: mdc-feature-parse-targets($feature-targets); + + @if mdc-feature-is-query-satisfied_(map-get($parsed-targets, query), map-get($parsed-targets, available)) { + @content; + } + + $mdc-feature-targets-context_: false !global; +} diff --git a/angular_components/lib/css/mdc_web/featuretargeting/_variables.scss b/angular_components/lib/css/mdc_web/featuretargeting/_variables.scss new file mode 100644 index 000000000..e8ad13e48 --- /dev/null +++ b/angular_components/lib/css/mdc_web/featuretargeting/_variables.scss @@ -0,0 +1,24 @@ +// +// Copyright 2019 Google Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +$mdc-feature-all-features: (structure, color, typography, animation); +$mdc-feature-all-query-operators: (any, all, without); diff --git a/angular_components/lib/css/mdc_web/ripple/_mixins.scss b/angular_components/lib/css/mdc_web/ripple/_mixins.scss index 6df132f18..262c41a7f 100644 --- a/angular_components/lib/css/mdc_web/ripple/_mixins.scss +++ b/angular_components/lib/css/mdc_web/ripple/_mixins.scss @@ -20,218 +20,461 @@ // THE SOFTWARE. // +@import "../animation/functions"; @import "../animation/variables"; +@import "../base/mixins"; +@import "../featuretargeting/functions"; +@import "../featuretargeting/mixins"; @import "../theme/mixins"; @import "./functions"; +@import "./keyframes"; @import "./variables"; -@mixin mdc-ripple-surface() { - --mdc-ripple-fg-size: 0; - --mdc-ripple-left: 0; - --mdc-ripple-top: 0; - --mdc-ripple-fg-scale: 1; - --mdc-ripple-fg-translate-end: 0; - --mdc-ripple-fg-translate-start: 0; +@mixin mdc-ripple-core-styles($query: mdc-feature-all()) { + // postcss-bem-linter: define ripple-surface - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - will-change: transform, opacity; + $feat-structure: mdc-feature-create-target($query, structure); - &::before, - &::after { - position: absolute; - border-radius: 50%; - opacity: 0; - pointer-events: none; - content: ""; + .mdc-ripple-surface { + @include mdc-ripple-surface($query: $query); + @include mdc-states($query: $query); + @include mdc-ripple-radius-bounded($query: $query); + + @include mdc-feature-targets($feat-structure) { + position: relative; + outline: none; + overflow: hidden; + } + + &[data-mdc-ripple-is-unbounded] { + @include mdc-ripple-radius-unbounded($query: $query); + + @include mdc-feature-targets($feat-structure) { + overflow: visible; + } + } + + &--primary { + @include mdc-states(primary, $query: $query); + } + + &--accent { + @include mdc-states(secondary, $query: $query); + } } - &::before { - // Also transition background-color to avoid unnatural color flashes when toggling activated/selected state - transition: - opacity $mdc-states-wash-duration linear, - background-color $mdc-states-wash-duration linear; - z-index: 1; // Ensure that the ripple wash for hover/focus states is displayed on top of positioned child elements + // postcss-bem-linter: end +} + +@mixin mdc-ripple-common($query: mdc-feature-all()) { + $feat-animation: mdc-feature-create-target($query, animation); + $feat-structure: mdc-feature-create-target($query, structure); + + // Ensure that styles needed by any component using MDC Ripple are emitted, but only once. + // (Every component using MDC Ripple imports these mixins, but doesn't necessarily import + // mdc-ripple.scss.) + @include mdc-feature-targets($feat-animation) { + @include mdc-base-emit-once("mdc-ripple/common/animation") { + @include mdc-ripple-keyframes_; + } } - // Common styles for upgraded surfaces (some of these depend on custom properties set via JS or other mixins) + @include mdc-feature-targets($feat-structure) { + @include mdc-base-emit-once("mdc-ripple/common/structure") { + // Styles used to detect buggy behavior of CSS custom properties in Edge. + // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11495448/ + // This is included in _mixins.scss rather than mdc-ripple.scss so that it will be + // present for other components which rely on ripple as well as mdc-ripple itself. + .mdc-ripple-surface--test-edge-var-bug { + --mdc-ripple-surface-test-edge-var: 1px solid #000; + + visibility: hidden; + + &::before { + border: var(--mdc-ripple-surface-test-edge-var); + } + } + } + } +} - &.mdc-ripple-upgraded::before { - transform: scale(var(--mdc-ripple-fg-scale, 1)); +@mixin mdc-ripple-surface($query: mdc-feature-all(), $ripple-target: "&") { + $feat-animation: mdc-feature-create-target($query, animation); + $feat-structure: mdc-feature-create-target($query, structure); + + @include mdc-feature-targets($feat-structure) { + --mdc-ripple-fg-size: 0; + --mdc-ripple-left: 0; + --mdc-ripple-top: 0; + --mdc-ripple-fg-scale: 1; + --mdc-ripple-fg-translate-end: 0; + --mdc-ripple-fg-translate-start: 0; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + will-change: transform, opacity; + } + + #{$ripple-target}::before, + #{$ripple-target}::after { + @include mdc-feature-targets($feat-structure) { + position: absolute; + border-radius: 50%; + opacity: 0; + pointer-events: none; + content: ""; + } } - &.mdc-ripple-upgraded::after { - top: 0; + #{$ripple-target}::before { + @include mdc-feature-targets($feat-animation) { + // Also transition background-color to avoid unnatural color flashes when toggling activated/selected state + transition: + opacity $mdc-states-wash-duration linear, + background-color $mdc-states-wash-duration linear; + } - /* @noflip */ - left: 0; - transform: scale(0); - transform-origin: center center; + @include mdc-feature-targets($feat-structure) { + z-index: 1; // Ensure that the ripple wash for hover/focus states is displayed on top of positioned child elements + } } - &.mdc-ripple-upgraded--unbounded::after { - top: var(--mdc-ripple-top, 0); + // Common styles for upgraded surfaces (some of these depend on custom properties set via JS or other mixins) + + &.mdc-ripple-upgraded { + #{$ripple-target}::before { + @include mdc-feature-targets($feat-structure) { + transform: scale(var(--mdc-ripple-fg-scale, 1)); + } + } - /* @noflip */ - left: var(--mdc-ripple-left, 0); + #{$ripple-target}::after { + @include mdc-feature-targets($feat-structure) { + top: 0; + /* @noflip */ + left: 0; + transform: scale(0); + transform-origin: center center; + } + } } - &.mdc-ripple-upgraded--foreground-activation::after { - animation: - $mdc-ripple-translate-duration mdc-ripple-fg-radius-in forwards, - $mdc-ripple-fade-in-duration mdc-ripple-fg-opacity-in forwards; + &.mdc-ripple-upgraded--unbounded { + #{$ripple-target}::after { + @include mdc-feature-targets($feat-structure) { + top: var(--mdc-ripple-top, 0); + /* @noflip */ + left: var(--mdc-ripple-left, 0); + } + } } - &.mdc-ripple-upgraded--foreground-deactivation::after { - animation: $mdc-ripple-fade-out-duration mdc-ripple-fg-opacity-out; - // Retain transform from mdc-ripple-fg-radius-in activation - transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1)); + &.mdc-ripple-upgraded--foreground-activation { + #{$ripple-target}::after { + @include mdc-feature-targets($feat-animation) { + animation: + mdc-ripple-fg-radius-in $mdc-ripple-translate-duration forwards, + mdc-ripple-fg-opacity-in $mdc-ripple-fade-in-duration forwards; + } + } + } + + &.mdc-ripple-upgraded--foreground-deactivation { + #{$ripple-target}::after { + @include mdc-feature-targets($feat-animation) { + animation: mdc-ripple-fg-opacity-out $mdc-ripple-fade-out-duration; + } + + @include mdc-feature-targets($feat-structure) { + // Retain transform from mdc-ripple-fg-radius-in activation + transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1)); + } + } } } -@mixin mdc-states-base-color($color) { - // Opacity styles are here (rather than in mdc-ripple-surface) to ensure that opacity is re-initialized for - // cases where this mixin is used to override another inherited use of itself, - // without needing to re-include mdc-ripple-surface. - &::before, - &::after { - @include mdc-theme-prop(background-color, $color, $edgeOptOut: true); +@mixin mdc-states-base-color( + $color, $query: mdc-feature-all(), $ripple-target: "&") { + $feat-color: mdc-feature-create-target($query, color); + + #{$ripple-target}::before, + #{$ripple-target}::after { + @include mdc-feature-targets($feat-color) { + @if alpha(mdc-theme-prop-value($color)) > 0 { + @include mdc-theme-prop(background-color, $color, $edgeOptOut: true); + } @else { + // If a color with 0 alpha is specified, don't render the ripple pseudo-elements at all. + // This avoids unnecessary transitions and overflow. + content: none; + } + } } } -@mixin mdc-states-hover-opacity($opacity) { +@mixin mdc-states-hover-opacity( + $opacity, $query: mdc-feature-all(), $ripple-target: "&") { + $feat-color: mdc-feature-create-target($query, color); + // Background wash styles, for both CSS-only and upgraded stateful surfaces - &:hover::before { - opacity: $opacity; + &:hover { + #{$ripple-target}::before { + // Opacity falls under color because the chosen opacity is color-dependent in typical usage + @include mdc-feature-targets($feat-color) { + opacity: $opacity; + } + } } } -@mixin mdc-states-focus-opacity($opacity, $has-nested-focusable-element: false) { +@mixin mdc-states-focus-opacity( + $opacity, + $has-nested-focusable-element: false, + $query: mdc-feature-all(), + $ripple-target: "&") { + // Focus overrides hover by reusing the ::before pseudo-element. // :focus-within generally works on non-MS browsers and matches when a *child* of the element has focus. // It is useful for cases where a component has a focusable element within the root node, e.g. text field, // but undesirable in general in case of nested stateful components. // We use a modifier class for JS-enabled surfaces to support all use cases in all browsers. - $cssOnlyFocusSelector: if( - $has-nested-focusable-element, - "&:not(.mdc-ripple-upgraded):focus::before, &:not(.mdc-ripple-upgraded):focus-within::before", - "&:not(.mdc-ripple-upgraded):focus::before" - ); + @if $has-nested-focusable-element { + // JS-enabled selectors. + &.mdc-ripple-upgraded--background-focused, + &.mdc-ripple-upgraded:focus-within, + // CSS-only selectors. + &:not(.mdc-ripple-upgraded):focus, + &:not(.mdc-ripple-upgraded):focus-within { + #{$ripple-target}::before { + @include mdc-states-focus-opacity-properties_( + $opacity: $opacity, $query: $query); + } + } + } @else { + // JS-enabled selectors. + &.mdc-ripple-upgraded--background-focused, + // CSS-only selectors. + &:not(.mdc-ripple-upgraded):focus { + #{$ripple-target}::before { + @include mdc-states-focus-opacity-properties_( + $opacity: $opacity, $query: $query); + } + } + } +} - #{$cssOnlyFocusSelector}, - &.mdc-ripple-upgraded--background-focused::before { - // Note that this duration is only effective on focus, not blur +@mixin mdc-states-focus-opacity-properties_($opacity, $query) { + $feat-animation: mdc-feature-create-target($query, animation); + // Opacity falls under color because the chosen opacity is color-dependent in typical usage + $feat-color: mdc-feature-create-target($query, color); + + // Note that this duration is only effective on focus, not blur + @include mdc-feature-targets($feat-animation) { transition-duration: 75ms; + } + + @include mdc-feature-targets($feat-color) { opacity: $opacity; } } -@mixin mdc-states-press-opacity($opacity) { +@mixin mdc-states-press-opacity($opacity, $query: mdc-feature-all(), $ripple-target: "&") { + $feat-animation: mdc-feature-create-target($query, animation); + $feat-color: mdc-feature-create-target($query, color); + // Styles for non-upgraded (CSS-only) stateful surfaces &:not(.mdc-ripple-upgraded) { // Apply press additively by using the ::after pseudo-element - &::after { - transition: opacity $mdc-ripple-fade-out-duration linear; + #{$ripple-target}::after { + @include mdc-feature-targets($feat-animation) { + transition: opacity $mdc-ripple-fade-out-duration linear; + } } - &:active::after { - transition-duration: $mdc-ripple-fade-in-duration; - opacity: $opacity; + &:active { + #{$ripple-target}::after { + @include mdc-feature-targets($feat-animation) { + transition-duration: $mdc-ripple-fade-in-duration; + } + + // Opacity falls under color because the chosen opacity is color-dependent in typical usage + @include mdc-feature-targets($feat-color) { + opacity: $opacity; + } + } } } &.mdc-ripple-upgraded { - --mdc-ripple-fg-opacity: #{$opacity}; + @include mdc-feature-targets($feat-color) { + --mdc-ripple-fg-opacity: #{$opacity}; + } } } // Simple mixin for base states which automatically selects opacity values based on whether the ink color is // light or dark. -@mixin mdc-states($color: mdc-theme-prop-value(on-surface), $has-nested-focusable-element: false) { - @include mdc-states-interactions_($color, $has-nested-focusable-element); +@mixin mdc-states( + $color: mdc-theme-prop-value(on-surface), + $has-nested-focusable-element: false, + $query: mdc-feature-all(), + $ripple-target: "&", +) { + @include mdc-states-interactions_( + $color: $color, + $has-nested-focusable-element: $has-nested-focusable-element, + $query: $query, + $ripple-target: $ripple-target); } // Simple mixin for activated states which automatically selects opacity values based on whether the ink color is // light or dark. -@mixin mdc-states-activated($color, $has-nested-focusable-element: false) { +@mixin mdc-states-activated( + $color, $has-nested-focusable-element: false, $query: mdc-feature-all(), $ripple-target: "&") { + $feat-color: mdc-feature-create-target($query, color); $activated-opacity: mdc-states-opacity($color, activated); &--activated { // Stylelint seems to think that '&' qualifies as a type selector here? // stylelint-disable-next-line selector-max-type - &::before { - opacity: $activated-opacity; + #{$ripple-target}::before { + // Opacity falls under color because the chosen opacity is color-dependent. + @include mdc-feature-targets($feat-color) { + opacity: $activated-opacity; + } } - @include mdc-states-interactions_($color, $has-nested-focusable-element, $activated-opacity); + @include mdc-states-interactions_( + $color: $color, + $has-nested-focusable-element: $has-nested-focusable-element, + $opacity-modifier: $activated-opacity, + $query: $query, + $ripple-target: $ripple-target); } } // Simple mixin for selected states which automatically selects opacity values based on whether the ink color is // light or dark. -@mixin mdc-states-selected($color, $has-nested-focusable-element: false) { +@mixin mdc-states-selected( + $color, + $has-nested-focusable-element: false, + $query: mdc-feature-all(), + $ripple-target: "&") { + $feat-color: mdc-feature-create-target($query, color); $selected-opacity: mdc-states-opacity($color, selected); &--selected { // stylelint-disable-next-line selector-max-type - &::before { - opacity: $selected-opacity; + #{$ripple-target}::before { + // Opacity falls under color because the chosen opacity is color-dependent. + @include mdc-feature-targets($feat-color) { + opacity: $selected-opacity; + } } - @include mdc-states-interactions_($color, $has-nested-focusable-element, $selected-opacity); + @include mdc-states-interactions_( + $color: $color, + $has-nested-focusable-element: $has-nested-focusable-element, + $opacity-modifier: $selected-opacity, + $query: $query, + $ripple-target: $ripple-target); } } -@mixin mdc-ripple-radius-bounded($radius: 100%) { - &::before, - &::after { - top: calc(50% - #{$radius}); - - /* @noflip */ - left: calc(50% - #{$radius}); - width: $radius * 2; - height: $radius * 2; +@mixin mdc-ripple-radius-bounded( + $radius: 100%, $query: mdc-feature-all(), $ripple-target: "&") { + $feat-struture: mdc-feature-create-target($query, structure); + + #{$ripple-target}::before, + #{$ripple-target}::after { + @include mdc-feature-targets($feat-struture) { + top: calc(50% - #{$radius}); + /* @noflip */ + left: calc(50% - #{$radius}); + width: $radius * 2; + height: $radius * 2; + } } - &.mdc-ripple-upgraded::after { - width: var(--mdc-ripple-fg-size, $radius); - height: var(--mdc-ripple-fg-size, $radius); + &.mdc-ripple-upgraded { + #{$ripple-target}::after { + @include mdc-feature-targets($feat-struture) { + width: var(--mdc-ripple-fg-size, $radius); + height: var(--mdc-ripple-fg-size, $radius); + } + } } } -@mixin mdc-ripple-radius-unbounded($radius: 100%) { - &::before, - &::after { - top: calc(50% - #{$radius / 2}); - - /* @noflip */ - left: calc(50% - #{$radius / 2}); - width: $radius; - height: $radius; +@mixin mdc-ripple-radius-unbounded( + $radius: 100%, $query: mdc-feature-all(), $ripple-target: "&") { + $feat-struture: mdc-feature-create-target($query, structure); + + #{$ripple-target}::before, + #{$ripple-target}::after { + @include mdc-feature-targets($feat-struture) { + top: calc(50% - #{$radius / 2}); + /* @noflip */ + left: calc(50% - #{$radius / 2}); + width: $radius; + height: $radius; + } } - &.mdc-ripple-upgraded::before, - &.mdc-ripple-upgraded::after { - top: var(--mdc-ripple-top, calc(50% - #{$radius / 2})); + &.mdc-ripple-upgraded { + #{$ripple-target}::before, + #{$ripple-target}::after { + @include mdc-feature-targets($feat-struture) { + top: var(--mdc-ripple-top, calc(50% - #{$radius / 2})); + /* @noflip */ + left: var(--mdc-ripple-left, calc(50% - #{$radius / 2})); + width: var(--mdc-ripple-fg-size, $radius); + height: var(--mdc-ripple-fg-size, $radius); + } + } - /* @noflip */ - left: var(--mdc-ripple-left, calc(50% - #{$radius / 2})); - width: var(--mdc-ripple-fg-size, $radius); - height: var(--mdc-ripple-fg-size, $radius); + #{$ripple-target}::after { + @include mdc-feature-targets($feat-struture) { + width: var(--mdc-ripple-fg-size, $radius); + height: var(--mdc-ripple-fg-size, $radius); + } + } } +} - &.mdc-ripple-upgraded::after { - width: var(--mdc-ripple-fg-size, $radius); - height: var(--mdc-ripple-fg-size, $radius); +@mixin mdc-states-interactions_( + $color, + $has-nested-focusable-element, + $opacity-modifier: 0, + $query: mdc-feature-all(), + $ripple-target: "&", +) { + @include mdc-ripple-target-selector($ripple-target) { + @include mdc-states-base-color($color, $query); } -} -@mixin mdc-states-interactions_($color, $has-nested-focusable-element, $opacity-modifier: 0) { - @include mdc-states-base-color($color); - @include mdc-states-hover-opacity(mdc-states-opacity($color, hover) + $opacity-modifier); + @include mdc-states-hover-opacity( + $opacity: mdc-states-opacity($color, hover) + $opacity-modifier, + $query: $query, + $ripple-target: $ripple-target); @include mdc-states-focus-opacity( - mdc-states-opacity($color, focus) + $opacity-modifier, - $has-nested-focusable-element + $opacity: mdc-states-opacity($color, focus) + $opacity-modifier, + $has-nested-focusable-element: $has-nested-focusable-element, + $query: $query, + $ripple-target: $ripple-target, ); - @include mdc-states-press-opacity(mdc-states-opacity($color, press) + $opacity-modifier); + @include mdc-states-press-opacity( + $opacity: mdc-states-opacity($color, press) + $opacity-modifier, + $query: $query, + $ripple-target: $ripple-target); +} + +// Wraps content in the `ripple-target` selector if it exists. +@mixin mdc-ripple-target-selector($ripple-target: "&") { + @if $ripple-target == "&" { + @content; + } @else { + #{$ripple-target} { + @content; + } + } } diff --git a/angular_components/lib/css/mdc_web/ripple/_variables.scss b/angular_components/lib/css/mdc_web/ripple/_variables.scss index edec310a6..5dc2a21af 100644 --- a/angular_components/lib/css/mdc_web/ripple/_variables.scss +++ b/angular_components/lib/css/mdc_web/ripple/_variables.scss @@ -20,15 +20,20 @@ // THE SOFTWARE. // -$mdc-ripple-fade-in-duration: 75ms; -$mdc-ripple-fade-out-duration: 150ms; -$mdc-ripple-translate-duration: 225ms; -$mdc-states-wash-duration: 15ms; +$mdc-ripple-fade-in-duration: 75ms !default; +$mdc-ripple-fade-out-duration: 150ms !default; +$mdc-ripple-translate-duration: 225ms !default; +$mdc-states-wash-duration: 15ms !default; + +// Notes on states: +// * focus takes precedence over hover (i.e. if an element is both focused and hovered, only focus value applies) +// * press state applies to a separate pseudo-element, so it has an additive effect on top of other states +// * selected/activated are applied additively to hover/focus via calculations at preprocessing time $mdc-ripple-dark-ink-opacities: ( hover: .04, focus: .12, - press: .16, + press: .12, selected: .08, activated: .12 ) !default; @@ -36,12 +41,12 @@ $mdc-ripple-dark-ink-opacities: ( $mdc-ripple-light-ink-opacities: ( hover: .08, focus: .24, - press: .32, + press: .24, selected: .16, activated: .24 ) !default; // Legacy -$mdc-ripple-pressed-dark-ink-opacity: .16; -$mdc-ripple-pressed-light-ink-opacity: .32; +$mdc-ripple-pressed-dark-ink-opacity: .16 !default; +$mdc-ripple-pressed-light-ink-opacity: .32 !default; diff --git a/angular_components/lib/css/mdc_web/rtl/_mixins.scss b/angular_components/lib/css/mdc_web/rtl/_mixins.scss index 65ae43cb5..6d1af73dc 100644 --- a/angular_components/lib/css/mdc_web/rtl/_mixins.scss +++ b/angular_components/lib/css/mdc_web/rtl/_mixins.scss @@ -258,14 +258,12 @@ ) { /* @noflip */ #{$left-property}: $left-value; - /* @noflip */ #{$right-property}: $right-value; @include mdc-rtl($root-selector) { /* @noflip */ #{$left-property}: $right-value; - /* @noflip */ #{$right-property}: $left-value; } diff --git a/angular_components/lib/css/mdc_web/shape/_functions.scss b/angular_components/lib/css/mdc_web/shape/_functions.scss index 4873f77b8..3736f45a9 100644 --- a/angular_components/lib/css/mdc_web/shape/_functions.scss +++ b/angular_components/lib/css/mdc_web/shape/_functions.scss @@ -57,6 +57,8 @@ // mdc-shape-resolve-percentage-radius(36px, 50%) => `18px` (i.e., 36px / 2) // @function mdc-shape-resolve-percentage-radius($component-height, $radius) { + $radius: mdc-shape-prop-value($radius); + @if type-of($radius) == "list" { $radius-value: (); @@ -70,41 +72,15 @@ } } -@function mdc-shape-resolve-percentage-for-corner_($component-height, $radius) { - @if type-of($radius) == "number" and unit($radius) == "%" { - // Converts the percentage to number without unit. Example: 50% => 50. - $percentage: $radius / ($radius * 0 + 1); - - @return $component-height * ($percentage / 100); - } @else { - @return $radius; - } -} - -// -// Strips unit from number. This is accomplished by dividing the value by itself to cancel out units, while resulting -// in a denominator of 1. -// -// Examples: -// -// 50% => 50 -// -@function mdc-shape-strip-unit_($number) { - @if type-of($number) == "number" and not unitless($number) { - @return $number / ($number * 0 + 1); - } - - @return $number; -} - // // Returns $radius value of shape category - `large`, `medium` or `small`. // Otherwise, it returns the $radius itself if valid. -// $radius can be a single value or list of up to 4. +// $radius can be a single value, or a list of up to 4 values. // // Examples: // // mdc-shape-prop-value(small) => 4px +// mdc-shape-prop-value(small small 0 0) => 4px 4px 0 0 // @function mdc-shape-prop-value($radius) { @if type-of($radius) == "list" { @@ -112,15 +88,27 @@ @error "Invalid radius: '#{$radius}' is more than 4 values"; } - $radius-value: (); + $radius-values: (); - @each $corner in $radius { - $radius-value: append($radius-value, mdc-shape-prop-corner-value_($corner)); + @for $i from 1 through length($radius) { + $corner: nth($radius, $i); + + @if map-has-key($mdc-shape-category-values, $corner) { + // If a category is encountered within a list of radii, apply the category's value for the corresponding corner + $radius-values: + append($radius-values, nth(mdc-shape-unpack-radius_(map-get($mdc-shape-category-values, $corner)), $i)); + } @else { + $radius-values: append($radius-values, mdc-shape-validate-radius-value_($corner)); + } } - @return $radius-value; + @return $radius-values; } @else { - @return mdc-shape-prop-corner-value_($radius); + @if map-has-key($mdc-shape-category-values, $radius) { + @return map-get($mdc-shape-category-values, $radius); + } @else { + @return mdc-shape-validate-radius-value_($radius); + } } } @@ -145,13 +133,7 @@ @error "Expected masked-corners of length 4 but got '#{length($masked-corners)}'."; } - @if length($radius) == 3 { - $radius: nth($radius, 1) nth($radius, 2) nth($radius, 3) nth($radius, 2); - } @else if length($radius) == 2 { - $radius: nth($radius, 1) nth($radius, 2) nth($radius, 1) nth($radius, 2); - } @else if length($radius) == 1 { - $radius: $radius $radius $radius $radius; - } + $radius: mdc-shape-unpack-radius_($radius); @return if(nth($masked-corners, 1) == 1, nth($radius, 1), 0) if(nth($masked-corners, 2) == 1, nth($radius, 2), 0) @@ -159,25 +141,50 @@ if(nth($masked-corners, 4) == 1, nth($radius, 4), 0); } -@function mdc-shape-prop-corner-value_($radius) { - @if map-has-key($mdc-shape-category-values, $radius) { - @return map-get($mdc-shape-category-values, $radius); - } @else if mdc-shape-is-valid-radius-value_($radius) { +// +// Unpacks shorthand values for border-radius (i.e. lists of 1-3 values). +// If a list of 4 values is given, it is returned as-is. +// +// Examples: +// +// 1. mdc-shape-unpack-radius_(4px) => 4px 4px 4px 4px +// 2. mdc-shape-unpack-radius_(4px 2px) => 4px 2px 4px 2px +// 3. mdc-shape-unpack-radius_(4px 2px 2px) => 4px 2px 2px 2px +// 2. mdc-shape-unpack-radius_(4px 2px 0 2px) => 4px 2px 0 2px +// +// TODO: This is private for purposes of getting it into a patch; make it public for a future minor/major release. +// +@function mdc-shape-unpack-radius_($radius) { + @if length($radius) == 4 { @return $radius; - } @else { - @error "Invalid radius: '#{$radius}' radius is not supported"; + } @else if length($radius) == 3 { + @return nth($radius, 1) nth($radius, 2) nth($radius, 3) nth($radius, 2); + } @else if length($radius) == 2 { + @return nth($radius, 1) nth($radius, 2) nth($radius, 1) nth($radius, 2); + } @else if length($radius) == 1 { + @return $radius $radius $radius $radius; } - @return map-get($mdc-shape-category-values, $radius); + @error "Invalid radius: '#{$radius}' is more than 4 values"; } -@function mdc-shape-is-valid-radius-value_($radius) { - $is-number: type-of($radius) == "number"; - $is-percentage: $is-number and unit($radius) == "%"; +@function mdc-shape-resolve-percentage-for-corner_($component-height, $radius) { + @if type-of($radius) == "number" and unit($radius) == "%" { + // Converts the percentage to number without unit. Example: 50% => 50. + $percentage: $radius / ($radius * 0 + 1); - @if $is-percentage { - @return false; + @return $component-height * ($percentage / 100); } @else { - @return $is-number or str_index($radius, "var(") or str_index($radius, "calc("); + @return $radius; } } + +@function mdc-shape-validate-radius-value_($radius) { + $is-number: type-of($radius) == "number"; + + @if not ($is-number or str_index($radius, "var(") or str_index($radius, "calc(")) { + @error "Invalid radius: #{$radius}"; + } + + @return $radius; +} diff --git a/angular_components/lib/css/mdc_web/shape/_mixins.scss b/angular_components/lib/css/mdc_web/shape/_mixins.scss index b439f74b8..27f6ac504 100644 --- a/angular_components/lib/css/mdc_web/shape/_mixins.scss +++ b/angular_components/lib/css/mdc_web/shape/_mixins.scss @@ -20,15 +20,29 @@ // THE SOFTWARE. // +@import "../featuretargeting/functions"; +@import "../featuretargeting/mixins"; @import "./variables"; @import "./functions"; -@mixin mdc-shape-radius($radius, $rtl-reflexive: false) { - border-radius: mdc-shape-prop-value($radius); +@mixin mdc-shape-radius($radius, $rtl-reflexive: false, $query: mdc-feature-all()) { + $feat-structure: mdc-feature-create-target($query, structure); - @if ($rtl-reflexive) { - @include mdc-rtl { - border-radius: mdc-shape-flip-radius($radius); + @include mdc-feature-targets($feat-structure) { + // Even if $rtl-reflexive is true, only emit RTL styles if we can't easily tell that the given radius is symmetrical + $needs-flip: $rtl-reflexive and length($radius) > 1; + + @if ($needs-flip) { + /* @noflip */ + } + + border-radius: mdc-shape-prop-value($radius); + + @if ($needs-flip) { + @include mdc-rtl { + /* @noflip */ + border-radius: mdc-shape-flip-radius(mdc-shape-prop-value($radius)); + } } } } diff --git a/angular_components/lib/css/mdc_web/shape/_variables.scss b/angular_components/lib/css/mdc_web/shape/_variables.scss index 6aa8cab71..948bd5b5a 100644 --- a/angular_components/lib/css/mdc_web/shape/_variables.scss +++ b/angular_components/lib/css/mdc_web/shape/_variables.scss @@ -30,4 +30,4 @@ $mdc-shape-category-values: ( small: $mdc-shape-small-component-radius, medium: $mdc-shape-medium-component-radius, large: $mdc-shape-large-component-radius, -); +) !default; diff --git a/angular_components/lib/css/mdc_web/theme/_functions.scss b/angular_components/lib/css/mdc_web/theme/_functions.scss index b2acd7a58..93c67eaf3 100644 --- a/angular_components/lib/css/mdc_web/theme/_functions.scss +++ b/angular_components/lib/css/mdc_web/theme/_functions.scss @@ -64,3 +64,18 @@ @function mdc-theme-contrast-tone($color) { @return if(mdc-theme-tone($color) == "dark", "light", "dark"); } + +@function mdc-theme-is-var-with-fallback_($style) { + @return type-of($style) == "map" and map-has-key($style, "varname") and map-has-key($style, "fallback"); +} + +@function mdc-theme-get-var-fallback_($style) { + @return map-get($style, "fallback"); +} + +@function mdc-theme-var_($style) { + $var: map-get($style, "varname"); + $fallback: mdc-theme-get-var-fallback_($style); + + @return var(#{$var}, $fallback); +} diff --git a/angular_components/lib/css/mdc_web/theme/_mixins.scss b/angular_components/lib/css/mdc_web/theme/_mixins.scss index f94d50f51..d84fa8f1c 100644 --- a/angular_components/lib/css/mdc_web/theme/_mixins.scss +++ b/angular_components/lib/css/mdc_web/theme/_mixins.scss @@ -20,7 +20,47 @@ // THE SOFTWARE. // +@import "../featuretargeting/functions"; +@import "../featuretargeting/mixins"; @import "./variables"; +@import "./functions"; + +@mixin mdc-theme-core-styles($query: mdc-feature-all()) { + $feat-color: mdc-feature-create-target($query, color); + + :root { + @include mdc-feature-targets($feat-color) { + @each $style in map-keys($mdc-theme-property-values) { + --mdc-theme-#{$style}: #{map-get($mdc-theme-property-values, $style)}; + } + } + } + + @each $style in map-keys($mdc-theme-property-values) { + @if $style != "background" and $style != "surface" { + .mdc-theme--#{$style} { + @include mdc-feature-targets($feat-color) { + @include mdc-theme-prop(color, $style, true); + } + } + } @else { + .mdc-theme--#{$style} { + @include mdc-feature-targets($feat-color) { + @include mdc-theme-prop(background-color, $style); + } + } + } + } + + // CSS rules for using primary and secondary (plus light/dark variants) as background colors. + @each $style in ("primary", "secondary") { + .mdc-theme--#{$style}-bg { + @include mdc-feature-targets($feat-color) { + @include mdc-theme-prop(background-color, $style, true); + } + } + } +} // Applies the correct theme color style to the specified property. // $property is typically color or background-color, but can be any CSS property that accepts color values. @@ -28,7 +68,17 @@ // $edgeOptOut controls whether to feature-detect around Edge to avoid emitting CSS variables for it, // intended for use in cases where interactions with pseudo-element styles cause problems due to Edge bugs. @mixin mdc-theme-prop($property, $style, $important: false, $edgeOptOut: false) { - @if mdc-theme-is-valid-theme-prop-value_($style) { + @if mdc-theme-is-var-with-fallback_($style) { + @if $important { + #{$property}: mdc-theme-get-var-fallback_($style) !important; + /* @alternate */ + #{$property}: mdc-theme-var_($style) !important; + } @else { + #{$property}: mdc-theme-get-var-fallback_($style); + /* @alternate */ + #{$property}: mdc-theme-var_($style); + } + } @else if mdc-theme-is-valid-theme-prop-value_($style) { @if $important { #{$property}: $style !important; } @else { @@ -47,14 +97,17 @@ @if $edgeOptOut { // stylelint-disable max-nesting-depth @at-root { - // @supports not (-ms-ime-align:auto) { + // IE 11 doesn't understand this syntax and ignores the entire block. + // Edge understands this syntax and skips the entire block to avoid a nasty :before/:after pseudo-element bug. + // All other browsers apply the styles within the block. +// @supports not (-ms-ime-align: auto) { // stylelint-disable scss/selector-no-redundant-nesting-selector & { /* @alternate */ #{$property}: var(--mdc-theme-#{$style}, $value) !important; } // stylelint-enable scss/selector-no-redundant-nesting-selector - // } +// } } // stylelint-enable max-nesting-depth } @else { @@ -67,14 +120,17 @@ @if $edgeOptOut { // stylelint-disable max-nesting-depth @at-root { - // @supports not (-ms-ime-align:auto) { + // IE 11 doesn't understand this syntax and ignores the entire block. + // Edge understands this syntax and skips the entire block to avoid a nasty :before/:after pseudo-element bug. + // All other browsers apply the styles within the block. +// @supports not (-ms-ime-align: auto) { // stylelint-disable scss/selector-no-redundant-nesting-selector & { /* @alternate */ #{$property}: var(--mdc-theme-#{$style}, $value); } // stylelint-enable scss/selector-no-redundant-nesting-selector - // } +// } } // stylelint-enable max-nesting-depth } @else { diff --git a/angular_components/lib/css/mdc_web/theme/_variables.scss b/angular_components/lib/css/mdc_web/theme/_variables.scss index e4aaf0b27..4455fb50e 100644 --- a/angular_components/lib/css/mdc_web/theme/_variables.scss +++ b/angular_components/lib/css/mdc_web/theme/_variables.scss @@ -69,7 +69,7 @@ $mdc-theme-text-emphasis: ( high: .87, medium: .6, disabled: .38, -); +) !default; @function mdc-theme-ink-color-for-fill_($text-style, $fill-color) { $contrast-tone: mdc-theme-contrast-tone($fill-color); @@ -82,47 +82,39 @@ $mdc-theme-text-emphasis: ( // $mdc-theme-property-values: ( - // Primary primary: $mdc-theme-primary, - // Secondary secondary: $mdc-theme-secondary, - // Background background: $mdc-theme-background, - // Surface surface: $mdc-theme-surface, - // Error error: $mdc-theme-error, on-primary: $mdc-theme-on-primary, on-secondary: $mdc-theme-on-secondary, on-surface: $mdc-theme-on-surface, on-error: $mdc-theme-on-error, - // Text-primary on "background" background text-primary-on-background: mdc-theme-ink-color-for-fill_(primary, $mdc-theme-background), text-secondary-on-background: mdc-theme-ink-color-for-fill_(secondary, $mdc-theme-background), text-hint-on-background: mdc-theme-ink-color-for-fill_(hint, $mdc-theme-background), text-disabled-on-background: mdc-theme-ink-color-for-fill_(disabled, $mdc-theme-background), text-icon-on-background: mdc-theme-ink-color-for-fill_(icon, $mdc-theme-background), - // Text-primary on "light" background text-primary-on-light: mdc-theme-ink-color-for-fill_(primary, light), text-secondary-on-light: mdc-theme-ink-color-for-fill_(secondary, light), text-hint-on-light: mdc-theme-ink-color-for-fill_(hint, light), text-disabled-on-light: mdc-theme-ink-color-for-fill_(disabled, light), text-icon-on-light: mdc-theme-ink-color-for-fill_(icon, light), - // Text-primary on "dark" background text-primary-on-dark: mdc-theme-ink-color-for-fill_(primary, dark), text-secondary-on-dark: mdc-theme-ink-color-for-fill_(secondary, dark), text-hint-on-dark: mdc-theme-ink-color-for-fill_(hint, dark), text-disabled-on-dark: mdc-theme-ink-color-for-fill_(disabled, dark), text-icon-on-dark: mdc-theme-ink-color-for-fill_(icon, dark) -); +) !default; // If `$style` is a color (a literal color value, `currentColor`, or a CSS custom property), it is returned verbatim. // Otherwise, `$style` is treated as a theme property name, and the corresponding value from @@ -137,6 +129,10 @@ $mdc-theme-property-values: ( // // NOTE: This function must be defined in _variables.scss instead of _functions.scss to avoid circular imports. @function mdc-theme-prop-value($style) { + @if mdc-theme-is-var-with-fallback_($style) { + @return mdc-theme-get-var-fallback_($style); + } + @if mdc-theme-is-valid-theme-prop-value_($style) { @return $style; } @@ -162,7 +158,11 @@ $mdc-theme-property-values: ( // NOTE: This function is depended upon by mdc-theme-prop-value (above) and thus must be defined in this file. @function mdc-theme-is-valid-theme-prop-value_($style) { - @return type-of($style) == "color" or $style == "currentColor" or str_slice($style, 1, 4) == "var("; + @return type-of($style) == "color" or + $style == "currentColor" or + str_slice($style, 1, 4) == "var(" or + $style == "inherit" or + $style == "transparent"; } @function mdc-theme-text-emphasis($emphasis) { diff --git a/angular_components/lib/dynamic_component/dynamic_component.dart b/angular_components/lib/dynamic_component/dynamic_component.dart index 20d42ca35..ad6f17b89 100644 --- a/angular_components/lib/dynamic_component/dynamic_component.dart +++ b/angular_components/lib/dynamic_component/dynamic_component.dart @@ -5,19 +5,21 @@ import 'dart:async'; import 'package:angular/angular.dart'; +import 'package:angular/experimental.dart' show changeDetectionLink; import 'package:angular_components/model/ui/has_renderer.dart'; /// Dynamically renders another component, setting the [value] field on the /// dynamic component if it implements [RendersValue] (and not if the component /// does not implement the interface). +@changeDetectionLink @Component( selector: 'dynamic-component', template: '''''', + changeDetection: ChangeDetectionStrategy.OnPush, ) class DynamicComponent implements OnDestroy, AfterChanges { final SlowComponentLoader _slowComponentLoader; final ComponentLoader _componentLoader; - final ChangeDetectorRef _changeDetectorRef; final _onLoadController = StreamController(); ViewContainerRef _viewContainerRef; @@ -45,8 +47,7 @@ class DynamicComponent implements OnDestroy, AfterChanges { @Output() Stream get onLoad => _onLoadController.stream; - DynamicComponent(this._slowComponentLoader, this._changeDetectorRef, - this._componentLoader); + DynamicComponent(this._slowComponentLoader, this._componentLoader); /// Returns the loaded dynamic component reference. ComponentRef get childComponent => _childComponent; @@ -135,12 +136,12 @@ class DynamicComponent implements OnDestroy, AfterChanges { } void _updateChildComponent() { - _changeDetectorRef.markForCheck(); - if (_childComponent != null) { - if (_childComponent.instance is RendersValue) { - _childComponent.instance.value = _value; - } + _childComponent.update((instance) { + if (instance is RendersValue) { + instance.value = _value; + } + }); } } } diff --git a/angular_components/lib/focus/focus.dart b/angular_components/lib/focus/focus.dart index beb1b4de8..9891a439c 100644 --- a/angular_components/lib/focus/focus.dart +++ b/angular_components/lib/focus/focus.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:html' show KeyCode, KeyboardEvent, Element, HtmlElement; import 'package:angular/angular.dart'; +import 'package:meta/meta.dart'; import 'package:angular_components/laminate/components/modal/modal.dart'; import 'package:angular_components/laminate/popup/popup.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; @@ -15,9 +16,9 @@ import 'focus_interface.dart'; export 'focus_interface.dart'; -/// An abstract class for components to extend if their programmatic focus +/// A class for components to extend if their programmatic focus /// should simply put focus on root element. -abstract class RootFocusable implements Focusable, Disposable { +class RootFocusable implements Focusable, Disposable { Element _root; RootFocusable(this._root); @@ -56,17 +57,13 @@ abstract class ProjectedFocus implements Focusable { if (delegate is Focusable) { _resolvedFocusable = delegate; } else { - _resolvedFocusable = _FocusableElement(delegate); + _resolvedFocusable = RootFocusable(delegate); } _resolvedFocusable.focus(); }); } } -class _FocusableElement extends RootFocusable { - _FocusableElement(HtmlElement element) : super(element); -} - /// A focusable component that can publish to the /// `focusmove` stream in order to move focus to another element in the list. abstract class FocusableItem implements Focusable { @@ -85,6 +82,15 @@ class FocusMoveEvent { /// The position, relative the item, of where to set focus. final int offset; + /// Home key was pressed. + final bool home; + + /// End key was pressed. + final bool end; + + /// Up or down arrow key was pressed. + final bool upDown; + /// Prevent Default action for occuring. When the `FocusMoveEvent` is created /// from a KeyboardEvent, this method delegates to the `preventDefault` method /// of the `KeyboardEvent`, allowing consumers of this event to control the @@ -95,18 +101,55 @@ class FocusMoveEvent { final Function _preventDefaultDelegate; - FocusMoveEvent(this.focusItem, this.offset, [this._preventDefaultDelegate]); + @visibleForTesting + FocusMoveEvent(this.focusItem, this.offset, [this._preventDefaultDelegate]) + : home = false, + end = false, + upDown = false; + + @visibleForTesting + FocusMoveEvent.homeKey(this.focusItem, [this._preventDefaultDelegate]) + : offset = 0, + home = true, + end = false, + upDown = false; + + @visibleForTesting + FocusMoveEvent.endKey(this.focusItem, [this._preventDefaultDelegate]) + : offset = 0, + home = false, + end = true, + upDown = false; + + @visibleForTesting + FocusMoveEvent.upDownKey(this.focusItem, this.offset, + [this._preventDefaultDelegate]) + : home = false, + end = false, + upDown = true; /// Builds a `FocusMoveEvent` instance from a keyboard event, iff the keycode - /// is a next or previous key (i.e. up/down/left/right). + /// is a next, previous, home or end key (i.e. up/down/left/right/home/end). factory FocusMoveEvent.fromKeyboardEvent( FocusableItem item, KeyboardEvent kbEvent) { int keyCode = kbEvent.keyCode; + final preventDefaultFn = () { + kbEvent.preventDefault(); + }; + if (_isHomeKey(keyCode)) { + return FocusMoveEvent.homeKey(item, preventDefaultFn); + } + if (_isEndKey(keyCode)) { + return FocusMoveEvent.endKey(item, preventDefaultFn); + } if (!_isNextKey(keyCode) && !_isPrevKey(keyCode)) return null; + int offset = _isNextKey(keyCode) ? 1 : -1; - return FocusMoveEvent(item, offset, () { - kbEvent.preventDefault(); - }); + if (keyCode == KeyCode.UP || keyCode == KeyCode.DOWN) { + return FocusMoveEvent.upDownKey(item, offset, preventDefaultFn); + } + + return FocusMoveEvent(item, offset, preventDefaultFn); } // TODO(google): account for RTL. @@ -114,6 +157,8 @@ class FocusMoveEvent { keyCode == KeyCode.RIGHT || keyCode == KeyCode.DOWN; static bool _isPrevKey(int keyCode) => keyCode == KeyCode.LEFT || keyCode == KeyCode.UP; + static bool _isHomeKey(int keyCode) => keyCode == KeyCode.HOME; + static bool _isEndKey(int keyCode) => keyCode == KeyCode.END; } /// The element will be focused as soon as directive is initialized. @@ -204,7 +249,7 @@ class AutoFocusDirective extends RootFocusable implements OnInit, OnDestroy { @Directive( selector: '[focusableElement]', exportAs: 'focusableElement', - providers: [Provider(Focusable, useExisting: FocusableDirective)]) + providers: [ExistingProvider(Focusable, FocusableDirective)]) class FocusableDirective extends RootFocusable { FocusableDirective(HtmlElement node) : super(node); } diff --git a/angular_components/lib/focus/focus_activable_item.dart b/angular_components/lib/focus/focus_activable_item.dart index aa3df5fa6..3043c0c8b 100644 --- a/angular_components/lib/focus/focus_activable_item.dart +++ b/angular_components/lib/focus/focus_activable_item.dart @@ -12,7 +12,7 @@ import 'package:angular_components/focus/focus.dart'; @Directive( selector: '[focusActivableItem]', providers: [ - Provider(FocusableActivateItem, useExisting: FocusActivableItemDirective) + ExistingProvider(FocusableActivateItem, FocusActivableItemDirective) ], ) class FocusActivableItemDirective extends RootFocusable diff --git a/angular_components/lib/focus/focus_item.dart b/angular_components/lib/focus/focus_item.dart index 8d511fea7..6eb1f4266 100644 --- a/angular_components/lib/focus/focus_item.dart +++ b/angular_components/lib/focus/focus_item.dart @@ -13,13 +13,16 @@ import 'package:angular_components/focus/focus.dart'; /// by way of keyboard interaction. @Directive( selector: '[focusItem]', - providers: [Provider(FocusableItem, useExisting: FocusItemDirective)], + providers: [ExistingProvider(FocusableItem, FocusItemDirective)], ) class FocusItemDirective extends RootFocusable implements FocusableItem { + final ChangeDetectorRef _changeDetectorRef; + @HostBinding('attr.role') final String role; - FocusItemDirective(HtmlElement element, @Attribute('role') String role) + FocusItemDirective(HtmlElement element, this._changeDetectorRef, + @Attribute('role') String role) : this.role = role ?? 'listitem', super(element); @@ -41,5 +44,6 @@ class FocusItemDirective extends RootFocusable implements FocusableItem { @override set tabbable(bool value) { tabIndex = value ? '0' : '-1'; + _changeDetectorRef.markForCheck(); } } diff --git a/angular_components/lib/focus/focus_list.dart b/angular_components/lib/focus/focus_list.dart index 563a3c9e3..1eac605bd 100644 --- a/angular_components/lib/focus/focus_list.dart +++ b/angular_components/lib/focus/focus_list.dart @@ -4,6 +4,7 @@ import 'package:angular/angular.dart'; import 'package:angular_components/focus/focus.dart'; +import 'package:angular_components/utils/angular/properties/properties.dart'; import 'package:angular_components/utils/disposer/disposer.dart'; /// Used in conjunction with [FocusItemDirective] or @@ -29,18 +30,28 @@ class FocusListDirective implements OnDestroy { @HostBinding('attr.role') final String role; + @HostBinding('attr.ignoreUpAndDown') + final bool ignoreUpAndDown; final _disposer = Disposer.multi(); final _children = []; int get _length => _children.length; - FocusListDirective(this._ngZone, @Attribute('role') String role) - : this.role = role ?? 'list'; + FocusListDirective(this._ngZone, @Attribute('role') String role, + @Attribute('ignoreUpAndDown') String ignoreUpAndDown) + : this.role = role ?? 'list', + this.ignoreUpAndDown = attributeToBool(ignoreUpAndDown); /// Whether focus movement loops from the end of the list to the beginning of /// the list. Default is `false`. @Input() bool loop = false; + /// Index of the element to focus on when the list appears. + /// + /// If null, focus will not be changed automatically. + @Input() + int autoFocusIndex; + @ContentChildren(FocusableItem) set listItems(List listItems) { _children.clear(); @@ -51,37 +62,53 @@ class FocusListDirective implements OnDestroy { }); // Since this is updating children that were already dirty-checked, // need to delay this change until next angular cycle. - _ngZone.onEventDone.first.then((_) { + _ngZone.runAfterChangesObserved(() { _children.forEach((c) { c.tabbable = false; }); if (_children.isNotEmpty) { - _children.first.tabbable = true; + if (autoFocusIndex != null) { + focus(autoFocusIndex); // This will also make the item tabbable. + } else { + _children.first.tabbable = true; + } } }); } void _moveFocus(FocusMoveEvent event) { - var i = _children.indexOf(event.focusItem); - if (i != -1) { - focus(i + event.offset); + if (event.home) { + focus(0); + } else if (event.end) { + focus(_length - 1); + } else if (!ignoreUpAndDown || !event.upDown) { + var i = _children.indexOf(event.focusItem); + if (i != -1) { + focus(i + event.offset); + } } event.preventDefault(); } void focus(int index) { if (_length == 0) return; - var newIndex; + int newIndex; if (loop) { newIndex = index % _length; } else { newIndex = index.clamp(0, _length - 1); } _children[newIndex].focus(); + setTabbable(newIndex); + } + + /// Makes the [index] tab focusable and makes all other tabs unfocusable. + void setTabbable(int index) { + if (index < 0 || index >= _length) return; _children.forEach((i) { i.tabbable = false; }); - _children[newIndex].tabbable = true; + _children[index].tabbable = true; } @override diff --git a/angular_components/lib/focus/keyboard_only_focus_indicator.dart b/angular_components/lib/focus/keyboard_only_focus_indicator.dart index 8758a2bae..a4f536271 100644 --- a/angular_components/lib/focus/keyboard_only_focus_indicator.dart +++ b/angular_components/lib/focus/keyboard_only_focus_indicator.dart @@ -6,8 +6,8 @@ import 'dart:html'; import 'package:angular/angular.dart'; import 'package:angular/meta.dart'; -import 'package:meta/meta.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; +import 'package:meta/meta.dart'; /// [KeyboardOnlyFocusIndicatorDirective] is a decorator that hides the outline /// on an element when the element is focused with a mouse, and shows the diff --git a/angular_components/lib/forms/error_renderer.dart b/angular_components/lib/forms/error_renderer.dart index c3ed5e711..788bd8b7d 100644 --- a/angular_components/lib/forms/error_renderer.dart +++ b/angular_components/lib/forms/error_renderer.dart @@ -4,9 +4,9 @@ /// A function which takes in an error map, and returns another modified errors /// map. -typedef Map ErrorFn(Map errors); +typedef ErrorFn = Map Function(Map errors); -/// Convience function for replacing multiple errors for Components using the +/// Convenience function for replacing multiple errors for Components using the /// errorRenderer pattern. ErrorFn replaceErrors(Map overrides) => (errors) => _replaceErrorsImpl(overrides, errors); diff --git a/angular_components/lib/framework_stabilizers/framework_stabilizers.dart b/angular_components/lib/framework_stabilizers/framework_stabilizers.dart index 5739ae5d6..3f21957f4 100644 --- a/angular_components/lib/framework_stabilizers/framework_stabilizers.dart +++ b/angular_components/lib/framework_stabilizers/framework_stabilizers.dart @@ -5,24 +5,24 @@ @JS() library angular_components.framework_stabilizers.framework_stabilizers; -import 'dart:js'; - import 'package:js/js.dart'; /// Function provided by a framework to register an [IsStableCallback] that is /// invoked by the framework when it reaches a stable state. -typedef void FrameworkStabilizer(IsStableCallback callback); +typedef FrameworkStabilizer = void Function(IsStableCallback callback); /// Function invoked by a framework when it has reached a stable state. The /// `didWork` parameter indicates, if the framework did any work between /// callback registration and callback invocation. -typedef void IsStableCallback(bool didWork, String name); +typedef IsStableCallback = void Function(bool didWork, String name); // frameworkStabilizers is a property of the window object. @JS('frameworkStabilizers') +// ignore: unused_element external List get _frameworkStabilizersJs; @JS('frameworkStabilizers') +// ignore: unused_element external set _frameworkStabilizersJs(List values); /// Provides a set of helper functions for frameworks to register and deregister @@ -33,10 +33,7 @@ class FrameworkStabilizers { static int _nextId = 0; static List get _frameworkStabilizers { - if (_frameworkStabilizersJs == null) { - _frameworkStabilizersJs = []; - } - return _frameworkStabilizersJs; + return _frameworkStabilizersJs ??= []; } /// Add a stabilize function for a framework. diff --git a/angular_components/lib/glyph/glyph.dart b/angular_components/lib/glyph/glyph.dart index c094b5563..59b790b64 100644 --- a/angular_components/lib/glyph/glyph.dart +++ b/angular_components/lib/glyph/glyph.dart @@ -32,7 +32,7 @@ const List _flippedIcons = [ /// This stylesheet should be included at the top of the page: /// /// ```html -/// /// ``` /// @@ -89,7 +89,7 @@ class GlyphComponent { _iconSet = value; } - bool _useMaterialIconsExtended = true; + final bool _useMaterialIconsExtended = true; bool get useMaterialIconsExtended => _useMaterialIconsExtended; diff --git a/angular_components/lib/laminate/components/modal/modal.dart b/angular_components/lib/laminate/components/modal/modal.dart index 935067dcb..8717ba967 100644 --- a/angular_components/lib/laminate/components/modal/modal.dart +++ b/angular_components/lib/laminate/components/modal/modal.dart @@ -23,7 +23,7 @@ import 'package:angular_components/utils/disposer/disposer.dart'; /// **NOTE**: Usage of this removes [Modal]'s built in LIFO stack. @Injectable() class GlobalModalStack { - final List _stack = List(); + final List _stack = []; /// Size of the stack. int get length => _stack.length; @@ -68,7 +68,7 @@ abstract class Modal { /// /// See [AsyncAction] for the API for deferring or cancelling the event.\ @Output('close') - Stream get onClose; + Stream> get onClose; /// Attempts to open the modal. /// @@ -79,7 +79,7 @@ abstract class Modal { /// /// See [AsyncAction] for the API for deferring or cancelling the event. @Output('open') - Stream get onOpen; + Stream> get onOpen; /// A stream of click events on the modal. /// @@ -145,8 +145,8 @@ abstract class Modal { @Component( selector: 'modal', providers: [ - Provider(DeferredContentAware, useExisting: ModalComponent), - Provider(Modal, useExisting: ModalComponent) + ExistingProvider(DeferredContentAware, ModalComponent), + ExistingProvider(Modal, ModalComponent), ], directives: [ModalControllerDirective], template: r''' @@ -154,6 +154,7 @@ abstract class Modal { ''', + changeDetection: ChangeDetectionStrategy.OnPush, // TODO(google): Change preserveWhitespace to false to improve codesize. preserveWhitespace: true, visibility: Visibility.all, // Injected by dialog, et al. @@ -166,12 +167,12 @@ class ModalComponent final DomService _domService; @override - Stream get onOpen => _onOpen.stream; - final _onOpen = StreamController.broadcast(sync: true); + Stream> get onOpen => _onOpen.stream; + final _onOpen = StreamController>.broadcast(sync: true); @override - Stream get onClose => _onClose.stream; - final _onClose = StreamController.broadcast(sync: true); + Stream> get onClose => _onClose.stream; + final _onClose = StreamController>.broadcast(sync: true); @override Stream get onVisibleChanged => _onVisibleChanged.stream; @@ -182,7 +183,7 @@ class ModalComponent bool _isDestroyed = false; bool _isHidden = false; bool _isVisible = false; - OverlayRef _resolvedOverlayRef; + final OverlayRef _resolvedOverlayRef; Element _lastFocusedElement; /// Whether to return focus to the last focused element before the modal @@ -196,9 +197,13 @@ class ModalComponent Future _pendingClose; ModalComponent(OverlayService overlayService, this._element, this._domService, - @Optional() @SkipSelf() this._parentModal, @Optional() this._stack) { - _createdOverlayRef( - overlayService.createOverlayRefSync(OverlayState.Dialog)); + @Optional() @SkipSelf() this._parentModal, @Optional() this._stack) + : _resolvedOverlayRef = + overlayService.createOverlayRefSync(OverlayState.Dialog) { + _disposer + ..addDisposable(_resolvedOverlayRef) + ..addStreamSubscription(_resolvedOverlayRef.onVisibleChanged + .listen(_onOverlayVisibleChanged)); } @Input() @@ -223,20 +228,6 @@ class ModalComponent _disposer.dispose(); } - void _createdOverlayRef(OverlayRef overlayRef) { - // If the modal is closed before the overlay is created we should dispose - // the ref to avoid leaks since `onDestroy` was already called. - if (_isDestroyed) { - overlayRef.dispose(); - } else { - _resolvedOverlayRef = overlayRef; - _disposer - ..addDisposable(_resolvedOverlayRef) - ..addStreamSubscription(_resolvedOverlayRef.onVisibleChanged - .listen(_onOverlayVisibleChanged)); - } - } - // A callback received when the overlay reports a visibility change. void _onOverlayVisibleChanged(bool isVisible) { _isVisible = isVisible; @@ -312,7 +303,7 @@ class ModalComponent @override Future open() { if (_pendingOpen == null) { - final controller = AsyncActionController(); + final controller = AsyncActionController(); controller.execute(_showModalOverlay); _pendingOpen = controller.action.onDone.then((completed) { _pendingOpen = null; @@ -326,7 +317,7 @@ class ModalComponent @override Future close() { if (_pendingClose == null) { - final controller = AsyncActionController(); + final controller = AsyncActionController(); controller.execute(_hideModalOverlay); _pendingClose = controller.action.onDone.then((completed) { _pendingClose = null; diff --git a/angular_components/lib/laminate/overlay/constants.dart b/angular_components/lib/laminate/overlay/constants.dart index 7ac953d24..17432635e 100644 --- a/angular_components/lib/laminate/overlay/constants.dart +++ b/angular_components/lib/laminate/overlay/constants.dart @@ -5,5 +5,3 @@ const overlayDefaultContainerId = 'default-acx-overlay-container'; const overlayContainerClassName = 'acx-overlay-container'; const overlayContainerNameAttribute = 'container-name'; -const overlayFocusablePlaceholderClassName = - 'acx-overlay-focusable-placeholder'; diff --git a/angular_components/lib/laminate/overlay/module.dart b/angular_components/lib/laminate/overlay/module.dart index bfb0f0cd3..9a1df52ed 100644 --- a/angular_components/lib/laminate/overlay/module.dart +++ b/angular_components/lib/laminate/overlay/module.dart @@ -12,6 +12,7 @@ import 'package:angular_components/src/laminate/overlay/render/overlay_dom_rende import 'package:angular_components/src/laminate/overlay/render/overlay_style_config.dart'; import 'package:angular_components/laminate/overlay/zindexer.dart'; import 'package:angular_components/laminate/ruler/dom_ruler.dart'; +import 'package:angular_components/model/math/box.dart'; import 'package:angular_components/utils/angular/imperative_view/imperative_view.dart'; import 'package:angular_components/utils/angular/managed_zone/angular_2.dart'; import 'package:angular_components/utils/browser/dom_service/angular_2.dart'; @@ -23,7 +24,8 @@ export 'package:angular_components/src/laminate/overlay/render/overlay_dom_rende overlayContainerParent, overlayContainerToken, overlayRepositionLoop, - overlaySyncDom; + overlaySyncDom, + overlayViewportBoundaries; /// Creates an overlay container inside the [parent] if one does not exist /// already. @@ -33,24 +35,11 @@ HtmlElement createAcxOverlayContainer(HtmlElement parent, {@required String id, @required String name, String className}) { var container = parent.querySelector('#$id'); if (container == null) { - // Add a hidden focusable element before overlay container to prevent screen - // reader from picking up content from a random element when users shift tab - // out of the first visible overlay. - parent.append(DivElement() - ..tabIndex = 0 - ..classes.add(overlayFocusablePlaceholderClassName)); - container = DivElement() ..id = id ..classes.add(overlayContainerClassName); if (className != null) container.classes.add(className); parent.append(container); - - // Add a hidden focusable element after overlay container to ensure there's - // a focusable element when users tab out of the last visible overlay. - parent.append(DivElement() - ..tabIndex = 0 - ..classes.add(overlayFocusablePlaceholderClassName)); } container.attributes[overlayContainerNameAttribute] = name; return container; @@ -107,6 +96,7 @@ const _overlayProviders = [ // Applications may experimentally make this true to increase performance. ValueProvider.forToken(overlaySyncDom, true), ValueProvider.forToken(overlayRepositionLoop, true), + ValueProvider.forToken(overlayViewportBoundaries, Box()), ClassProvider(OverlayDomRenderService), ClassProvider(OverlayStyleConfig), ClassProvider(OverlayService), diff --git a/angular_components/lib/laminate/portal/portal.dart b/angular_components/lib/laminate/portal/portal.dart index 00213078e..514ac06a7 100644 --- a/angular_components/lib/laminate/portal/portal.dart +++ b/angular_components/lib/laminate/portal/portal.dart @@ -18,7 +18,8 @@ abstract class Portal { /// Returns a future that completes with an instance of the portal's instance. /// /// Throws [StateError] if a portal is already attached. - Future attach(PortalHost host) { + Future | Map*/ > attach( + PortalHost host) { assert(host != null); if (isAttached) { throw StateError('Already attached to host!'); @@ -34,7 +35,7 @@ abstract class Portal { /// Detaches the portal if attached to a host. /// /// Returns a future that completes when detached. - Future detach() { + Future detach() { final currentHost = _attachedHost; assert(currentHost != null); _attachedHost = null; @@ -62,7 +63,7 @@ class ComponentPortal extends Portal { final ViewContainerRef origin; /// The factory to create the component. - final ComponentFactory componentFactory; + final ComponentFactory componentFactory; // TODO(google): Document and better explain when/when not to set origin. // TODO(google): Add optional `onInitialize` callback/future. @@ -106,7 +107,7 @@ class TemplatePortal extends Portal> { } @override - Future detach() { + Future detach() { _locals = const {}; return super.detach(); } @@ -125,12 +126,13 @@ abstract class PortalHost implements Disposable { /// /// When possible, prefer using [Portal.attach], as it returns a typed result /// instead of [dynamic]. - Future attach(Portal portal); + Future | Map*/ > attach( + Portal portal); /// Detaches any active portal. /// /// Returns a future that completes when the existing portal is detached. - Future detach(); + Future detach(); /// True if the host has a portal attached within. /// @@ -143,12 +145,13 @@ abstract class PortalHost implements Disposable { /// /// Implement [attachComponentPortal] and [attachTemplatePortal]. abstract class BasePortalHost implements PortalHost { - Portal _attachedPortal; + Portal _attachedPortal; DisposeFunction _detachPortal; bool _isDisposed = false; @override - Future attach(Portal portal) { + Future | Map*/ > attach( + Portal portal) { assert(portal != null); if (_isDisposed) { throw StateError('Already disposed.'); @@ -156,7 +159,7 @@ abstract class BasePortalHost implements PortalHost { if (hasAttached) { throw StateError('Already has attached portal!'); } - if (portal is ComponentPortal) { + if (portal is ComponentPortal) { _attachedPortal = portal; portal.setAttachedHost(this); return attachComponentPortal(portal); @@ -171,7 +174,8 @@ abstract class BasePortalHost implements PortalHost { } } - Future attachComponentPortal(ComponentPortal portal); + Future> attachComponentPortal( + ComponentPortal portal); Future> attachTemplatePortal(TemplatePortal portal); @@ -179,7 +183,7 @@ abstract class BasePortalHost implements PortalHost { static Map createLocalsMap(ViewRef viewRef) => {}; @override - Future detach() { + Future detach() { _attachedPortal.setAttachedHost(null); _attachedPortal = null; if (_detachPortal != null) { @@ -216,10 +220,12 @@ class DelegatingPortalHost implements PortalHost { bool get hasAttached => _delegateHost.hasAttached; @override - Future attach(Portal portal) => _delegateHost.attach(portal); + Future | Map*/ > attach( + Portal portal) => + _delegateHost.attach(portal); @override - Future detach() => _delegateHost.detach(); + Future detach() => _delegateHost.detach(); @override void dispose() { @@ -241,7 +247,8 @@ class PortalHostDirective extends BasePortalHost { PortalHostDirective(this._componentLoader, this._viewContainerRef); @override - Future attachComponentPortal(ComponentPortal portal) { + Future> attachComponentPortal( + ComponentPortal portal) { // By default, use the portal host as the origin. If [portal.origin] is set // however, then use that. var viewContainerRef = _viewContainerRef; @@ -264,7 +271,7 @@ class PortalHostDirective extends BasePortalHost { } @Input('portalHost') - set portal(Portal portal) { + set portal(Portal portal) { if (hasAttached) { detach().then((_) { if (portal != null) { @@ -289,7 +296,8 @@ class DomPortalHost extends BasePortalHost { DomPortalHost(this._hostElement, this._imperativeViewUtils); @override - Future attachComponentPortal(ComponentPortal portal) { + Future> attachComponentPortal( + ComponentPortal portal) { if (portal.origin == null) { throw StateError('A component hosted in a DomPortalHost must ' 'have an `origin` set, since the DOM element itself ' @@ -315,7 +323,7 @@ class DomPortalHost extends BasePortalHost { } } -typedef void OnTemplatePortalReady(TemplatePortal portal); +typedef OnTemplatePortalReady = void Function(TemplatePortal portal); /// An implementation of [TemplatePortal] as an Angular directive. /// diff --git a/angular_components/lib/laminate/ruler/dom_ruler.dart b/angular_components/lib/laminate/ruler/dom_ruler.dart index 95b488e0b..6b015c201 100644 --- a/angular_components/lib/laminate/ruler/dom_ruler.dart +++ b/angular_components/lib/laminate/ruler/dom_ruler.dart @@ -4,7 +4,6 @@ import 'dart:async'; import 'dart:html'; -import 'dart:math'; import 'package:angular/angular.dart'; import 'package:angular_components/src/laminate/ruler/ruler_interface.dart'; @@ -33,7 +32,7 @@ class DomRulerImpl extends RulerBase implements DomRuler { } @override - Stream get onLayoutChanged => _domService.onLayoutChanged; + Stream get onLayoutChanged => _domService.onLayoutChanged; @override Future onRead() => _domService.onRead(); diff --git a/angular_components/lib/laminate/ruler/module.dart b/angular_components/lib/laminate/ruler/module.dart index 55dcce3fa..e0eb0d7a5 100644 --- a/angular_components/lib/laminate/ruler/module.dart +++ b/angular_components/lib/laminate/ruler/module.dart @@ -10,10 +10,16 @@ import 'package:angular_components/utils/browser/dom_service/angular_2.dart'; import 'package:angular_components/utils/browser/window/module.dart'; /// Providers for using the ruler service. -const rulerBindings = [ - DomRuler, - domServiceBinding, - Provider(ManagedZone, useClass: Angular2ManagedZone), - NgRuler, - windowBindings +const rulerBindings = [_rulerProviders, domServiceBinding, windowBindings]; + +/// DI module for ruler service. +const rulerModule = Module(include: [ + domServiceModule, + windowModule, +], provide: _rulerProviders); + +const _rulerProviders = [ + ClassProvider(DomRuler), + ClassProvider(ManagedZone, useClass: Angular2ManagedZone), + ClassProvider(NgRuler), ]; diff --git a/angular_components/lib/material_button/_mixins.scss b/angular_components/lib/material_button/_mixins.scss index 6a314fe16..5d99f7df2 100644 --- a/angular_components/lib/material_button/_mixins.scss +++ b/angular_components/lib/material_button/_mixins.scss @@ -132,18 +132,22 @@ $button-disabled-background-dark: rgba(255, 255, 255, $mat-divider-opacity); } } - @include _button-hover { - content: ''; - display: block; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: currentColor; - opacity: $mat-divider-opacity; - border-radius: inherit; - pointer-events: none; + @media (hover: hover) { + @include _button-hover { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: currentColor; + // For windows high contrast. + outline: 2px solid transparent; + opacity: $mat-divider-opacity; + border-radius: inherit; + pointer-events: none; + } } &[raised] { @@ -225,7 +229,7 @@ $button-disabled-background-dark: rgba(255, 255, 255, $mat-divider-opacity); border-radius: $size / 2; ::ng-deep { - .content { + .content.content { height: $size; width: $size; } @@ -252,12 +256,24 @@ $button-disabled-background-dark: rgba(255, 255, 255, $mat-divider-opacity); } } +@mixin button-disabled-color($selector, $disabled-color) { + ::ng-deep #{$selector}[disabled] { + color: $disabled-color; + } +} + @mixin button-background-color($selector, $background-color) { ::ng-deep #{$selector}:not([disabled]) { background-color: $background-color; } } +/// Offsets the button content so it's left aligned, without removing the +/// padding. +@mixin button-left-aligned-content { + margin-left: -$button-horizontal-padding; +} + /// Applies letter-spacing to the text of . @mixin button-letter-spacing($selector, $letter-spacing) { ::ng-deep #{$selector} div { @@ -265,6 +281,28 @@ $button-disabled-background-dark: rgba(255, 255, 255, $mat-divider-opacity); } } +@mixin icon-button-hover-color($selector, $color) { + @media (hover: hover) { + ::ng-deep #{$selector}[icon]:not([disabled]):hover { + > .content > material-icon, + // TODO(google): Remove once glyph has been replaced with material-icon. + > .content > glyph { + color: $color; + } + } + } +} + +@mixin icon-button-focus-color($selector, $color) { + ::ng-deep #{$selector}[icon]:not([disabled]):focus { + > .content > material-icon, + // TODO(google): Remove once glyph has been replaced with material-icon. + > .content > glyph { + color: $color; + } + } +} + @mixin icon-button-color($selector, $color) { ::ng-deep #{$selector}[icon]:not([disabled]) { // Set the color here to be found by the ripple @@ -280,12 +318,43 @@ $button-disabled-background-dark: rgba(255, 255, 255, $mat-divider-opacity); } /// Sets padding for the icon button. +// @deprecated use button-padding instead. @mixin icon-button-padding($selector, $padding) { - #{$selector}[icon] ::ng-deep .content { + #{$selector}[icon] ::ng-deep .content.content { padding: $padding; } } +/// Sets the padding for a button. +/// +/// Should target the button specifically. Works for standard and icon buttons. +/// Example: +/// ```scss +/// material-button.padding { +/// @include button-padding($mat-grid); +/// } +/// ``` +@mixin button-padding($padding) { + ::ng-deep .content.content.content { + padding: $padding; + } +} + +/// Sets the vertical alignment for a button. +/// +/// Should target the button specifically. +/// Example: +/// ```scss +/// material-button.bottom { +/// @include vertical-align(bottom); +/// } +/// ``` +@mixin vertical-align($value) { + ::ng-deep .content.content { + vertical-align: $value; + } +} + /// Mixin to create a raised button taking an optional color of the button. /// /// Should be scopped to the specific button needed to make raised. @@ -345,3 +414,13 @@ $button-disabled-background-dark: rgba(255, 255, 255, $mat-divider-opacity); @include _button-compact; } } + +/// Resets the text-transform property of the button. +/// +/// This removes the uppercase transform default for the button and allows the +/// content itself to control capitalization. +@mixin reset-button-text-transform { + ::ng-deep .content { + text-transform: initial; + } +} diff --git a/angular_components/lib/material_button/material_button.dart b/angular_components/lib/material_button/material_button.dart index 8b7df3c48..524386151 100644 --- a/angular_components/lib/material_button/material_button.dart +++ b/angular_components/lib/material_button/material_button.dart @@ -78,8 +78,8 @@ import 'material_button_base.dart'; templateUrl: 'material_button.html', providers: [ AcxDarkTheme, - Provider(ButtonDirective, useExisting: MaterialButtonComponent), - Provider(HasDisabled, useExisting: MaterialButtonComponent), + ExistingProvider(ButtonDirective, MaterialButtonComponent), + ExistingProvider(HasDisabled, MaterialButtonComponent), ], styleUrls: ['material_button.scss.css'], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/angular_components/lib/material_button/material_button_base.dart b/angular_components/lib/material_button/material_button_base.dart index 6ed3c0664..3b67c7e25 100644 --- a/angular_components/lib/material_button/material_button_base.dart +++ b/angular_components/lib/material_button/material_button_base.dart @@ -37,7 +37,9 @@ class MaterialButtonBase extends ButtonDirective { int get zElevation => _isMouseDown || _focused ? mediumElevation : lowElevation; - MaterialButtonBase(HtmlElement element, String role) : super(element, role); + MaterialButtonBase(HtmlElement element, String role, + {bool handleSpacePresses = true}) + : super(element, role, handleSpacePresses: handleSpacePresses); // Set _focused in a microtask to avoid triggering changes during a change // detection cycle, which is illegal. This avoids 'AST has changed' errors. diff --git a/angular_components/lib/material_button/material_fab.dart b/angular_components/lib/material_button/material_fab.dart index 64e8977c2..e50e2fdf3 100644 --- a/angular_components/lib/material_button/material_fab.dart +++ b/angular_components/lib/material_button/material_fab.dart @@ -5,6 +5,7 @@ import 'dart:html'; import 'package:angular/angular.dart'; +import 'package:angular/meta.dart'; import 'package:angular_components/material_ripple/material_ripple.dart'; import 'material_button_base.dart'; @@ -56,9 +57,11 @@ import 'material_button_base.dart'; ) class MaterialFabComponent extends MaterialButtonBase { final ChangeDetectorRef _changeDetector; + MaterialFabComponent(HtmlElement element, this._changeDetector) : super(element, null); + @visibleForTemplate bool get isPressed => isMouseDown || focused; @override @@ -66,18 +69,23 @@ class MaterialFabComponent extends MaterialButtonBase { _changeDetector.markForCheck(); } + @visibleForTemplate @HostBinding('attr.disabled') String get hostDisabled => disabled ? '' : null; + @visibleForTemplate @HostBinding('attr.raised') String get hostRaised => raised ? '' : null; + @visibleForTemplate @HostBinding('class.is-focused') bool get hostClassIsFocused => visualFocus; + @visibleForTemplate @HostBinding('class.is-pressed') bool get hostClassIsPressed => isPressed; + @visibleForTemplate @HostBinding('attr.animated') static const String hostAnimated = 'true'; } diff --git a/angular_components/lib/material_checkbox/_mixins.scss b/angular_components/lib/material_checkbox/_mixins.scss index 66fd4b393..7af0d3002 100644 --- a/angular_components/lib/material_checkbox/_mixins.scss +++ b/angular_components/lib/material_checkbox/_mixins.scss @@ -25,6 +25,11 @@ margin-right: 0; } +/// Removes all margins of . +@mixin checkbox-no-margin { + margin: 0; +} + /// Hides the text portion of . @mixin checkbox-hide-label { ::ng-deep .content { diff --git a/angular_components/lib/material_checkbox/material_checkbox.dart b/angular_components/lib/material_checkbox/material_checkbox.dart index c254a7391..f1405f89b 100644 --- a/angular_components/lib/material_checkbox/material_checkbox.dart +++ b/angular_components/lib/material_checkbox/material_checkbox.dart @@ -6,14 +6,16 @@ import 'dart:async'; import 'dart:html'; import 'package:angular/angular.dart'; -import 'package:angular_forms/angular_forms.dart'; -import 'package:meta/meta.dart'; +import 'package:angular/meta.dart'; import 'package:angular_components/focus/focus.dart'; import 'package:angular_components/interfaces/has_disabled.dart'; import 'package:angular_components/material_icon/material_icon.dart'; import 'package:angular_components/material_ripple/material_ripple.dart'; import 'package:angular_components/model/ui/icon.dart'; import 'package:angular_components/utils/browser/events/events.dart'; +import 'package:angular_components/utils/id_generator/id_generator.dart'; +import 'package:angular_forms/angular_forms.dart'; +import 'package:meta/meta.dart'; const Icon uncheckedIcon = Icon('check_box_outline_blank'); const Icon checkedIcon = Icon('check_box'); @@ -41,9 +43,7 @@ const indeterminateAriaState = 'mixed'; /// @Component( selector: 'material-checkbox', - providers: [ - Provider(HasDisabled, useExisting: MaterialCheckboxComponent), - ], + providers: [ExistingProvider(HasDisabled, MaterialCheckboxComponent)], directives: [MaterialIconComponent, MaterialRippleComponent, NgIf], templateUrl: 'material_checkbox.html', styleUrls: ['material_checkbox.scss.css'], @@ -51,6 +51,7 @@ const indeterminateAriaState = 'mixed'; ) class MaterialCheckboxComponent implements ControlValueAccessor, HasDisabled, Focusable, OnDestroy { + @visibleForTemplate @HostBinding('class') static const hostClass = 'themeable'; @@ -99,21 +100,21 @@ class MaterialCheckboxComponent /// Fired when checkbox is checked or unchecked, but not when set /// indeterminate. Sends the state of [checked]. @Output('checkedChange') - Stream get onChecked => _onChecked.stream; - final _onChecked = StreamController.broadcast(); + Stream get onChecked => _onChecked.stream; + final _onChecked = StreamController.broadcast(); /// Fired when checkbox goes in and out of indeterminate state, but not when /// set to checked. /// /// Sends the state of [indeterminate]. @Output('indeterminateChange') - Stream get onIndeterminate => _onIndeterminate.stream; - final _onIndeterminate = StreamController.broadcast(); + Stream get onIndeterminate => _onIndeterminate.stream; + final _onIndeterminate = StreamController.broadcast(); /// Fired when checkbox state changes, sends [checkedStr], i.e. ARIA state. @Output('change') - Stream get onChange => _onChange.stream; - final _onChange = StreamController.broadcast(); + Stream get onChange => _onChange.stream; + final _onChange = StreamController.broadcast(); /// Determines the state to go into when [indeterminate] state is toggled. /// @@ -130,6 +131,7 @@ class MaterialCheckboxComponent // Current tab index. @HostBinding('attr.tabindex') + @visibleForTemplate String get tabIndex => disabled ? "-1" : _defaultTabIndex; /// Current state of the checkbox. This is user set-able state, via @@ -155,9 +157,11 @@ class MaterialCheckboxComponent var _isKeyboardEvent = false; /// Whether focus should be drawn. + @visibleForTemplate bool get showFocus => _focused && _isKeyboardEvent; /// ARIA-checked state, like icon, has 3 states. + @visibleForTemplate String get checkedStr => _checkedStr; String _checkedStr = uncheckedAriaState; @@ -196,11 +200,15 @@ class MaterialCheckboxComponent _checkedStr = indeterminate ? indeterminateAriaState - : _checked ? checkedAriaState : uncheckedAriaState; + : _checked + ? checkedAriaState + : uncheckedAriaState; _icon = _indeterminate ? indeterminateIcon - : _checked ? checkedIcon : uncheckedIcon; + : _checked + ? checkedIcon + : uncheckedIcon; if (emitEvent && _checked != prevChecked) { _onChecked.add(_checked); @@ -223,6 +231,7 @@ class MaterialCheckboxComponent } /// Current icon, depends on the state of [checked] and [indeterminate]. + @visibleForTemplate Icon get icon => _icon; Icon _icon = uncheckedIcon; @@ -232,6 +241,7 @@ class MaterialCheckboxComponent /// themeColor is applied to the checkbox even when the box is unchecked, /// which deviates from the standard material spec. Use mixin to set /// themeColor unless you want this behavior. + @visibleForTemplate @Input() String themeColor; @@ -239,13 +249,18 @@ class MaterialCheckboxComponent /// /// When the checkbox is unchecked, the ripple color does not follow theme /// color. + @visibleForTemplate String get rippleColor => checked ? themeColor : ''; /// Label for the checkbox, alternatively use content. - @HostBinding('attr.aria-label') @Input() String label; + /// Id for the checkbox label and content. + @visibleForTemplate + @HostBinding('attr.aria-labelledby') + final contentId = SequentialIdGenerator.fromUUID().nextId(); + /// Toggles checkbox via user action. /// /// When it is indeterminate, toggle can go to checked or unchecked, depending @@ -275,12 +290,14 @@ class MaterialCheckboxComponent // Capture keyup when we are the target of event. @HostListener('keyup') + @visibleForTemplate void handleKeyUp(KeyboardEvent event) { if (event.target != _root) return; _isKeyboardEvent = true; } @HostListener('click') + @visibleForTemplate void handleClick(MouseEvent mouseEvent) { if (disabled) return; _isKeyboardEvent = false; @@ -288,6 +305,7 @@ class MaterialCheckboxComponent } @HostListener('mousedown') + @visibleForTemplate void handleMouseDown(MouseEvent mouseEvent) { // This removes the text selection behavior of mousedown. if (readOnly) { @@ -296,6 +314,7 @@ class MaterialCheckboxComponent } @HostListener('keypress') + @visibleForTemplate void handleKeyPress(KeyboardEvent event) { if (disabled) return; if (event.target != _root) return; @@ -309,12 +328,14 @@ class MaterialCheckboxComponent // Triggered on focus. @HostListener('focus') + @visibleForTemplate void handleFocus(_) { _focused = true; } // Triggered on blur. @HostListener('blur') + @visibleForTemplate void handleBlur(Event event) { _focused = false; _onTouched?.call(); @@ -328,6 +349,5 @@ class MaterialCheckboxComponent /// Unimplemented for M1. Future focusDelegate; - set checkboxInputElement(_) {} void ngOnDestroy() {} } diff --git a/angular_components/lib/material_checkbox/material_checkbox.html b/angular_components/lib/material_checkbox/material_checkbox.html index 381d8128c..765084e27 100644 --- a/angular_components/lib/material_checkbox/material_checkbox.html +++ b/angular_components/lib/material_checkbox/material_checkbox.html @@ -16,7 +16,7 @@ class="ripple"> -
+
{{label}}
diff --git a/angular_components/lib/material_chips/_mixins.scss b/angular_components/lib/material_chips/_mixins.scss index 5c91d64d9..4a5e71246 100644 --- a/angular_components/lib/material_chips/_mixins.scss +++ b/angular_components/lib/material_chips/_mixins.scss @@ -93,6 +93,13 @@ $left-icon-width: $mat-grid * 4; } } +/// Use this mixin to change the font weight of each of the chips. +@mixin material-chip-font-weight($weight) { + ::ng-deep material-chip { + font-weight: $weight; + } +} + /// Use this mixin to change the font size of each of the chips. @mixin material-chip-font-size($size) { ::ng-deep material-chip { @@ -100,8 +107,15 @@ $left-icon-width: $mat-grid * 4; } } -$main-hover-bg-color: $mat-gray-400; -$main-selected-bg-color: $mat-gray-600; +/// Use this mixin to change the padding of each of the chips. +@mixin material-chip-padding($padding) { + ::ng-deep material-chip { + padding: $padding; + } +} + +$main-hover-bg-color: $mat-grey-400; +$main-selected-bg-color: $mat-grey-600; $emphasis-hover-bg-color: $mat-blue-700; $emphasis-selected-bg-color: $mat-blue-900; @@ -182,7 +196,7 @@ $emphasis-selected-bg-color: $mat-blue-900; } ::ng-deep .delete-icon:focus { - fill: $mat-gray-300; + fill: $mat-grey-300; } } } diff --git a/angular_components/lib/material_chips/material_chip.dart b/angular_components/lib/material_chips/material_chip.dart index f2aec9baf..2120862e0 100644 --- a/angular_components/lib/material_chips/material_chip.dart +++ b/angular_components/lib/material_chips/material_chip.dart @@ -24,7 +24,7 @@ import 'package:angular_components/utils/id_generator/id_generator.dart'; /// Chip components are rendered in a `material-chips` component. @Component( selector: 'material-chip', - providers: [Provider(HasRenderer, useExisting: MaterialChipComponent)], + providers: [ExistingProvider(HasRenderer, MaterialChipComponent)], templateUrl: 'material_chip.html', styleUrls: ['material_chip.scss.css'], directives: [ButtonDirective, NgIf], @@ -41,6 +41,10 @@ class MaterialChipComponent extends RootFocusable implements HasRenderer { desc: 'Label for a button which removes the item when clicked.', meaning: 'Label for a button which removes the item when clicked.'); + /// Aria label for delete button. + @Input() + String deleteButtonAriaMessage = chipDeleteButtonMessage; + /// A selection model to render as chips. /// /// This model should not be used for rendering, changes will not be diff --git a/angular_components/lib/material_chips/material_chip.html b/angular_components/lib/material_chips/material_chip.html index 1ab1436a5..c329a48e0 100644 --- a/angular_components/lib/material_chips/material_chip.html +++ b/angular_components/lib/material_chips/material_chip.html @@ -10,14 +10,16 @@ {{label}}
- - - + + + + diff --git a/angular_components/lib/material_chips/material_chip.scss b/angular_components/lib/material_chips/material_chip.scss index 0c690965c..19f233735 100644 --- a/angular_components/lib/material_chips/material_chip.scss +++ b/angular_components/lib/material_chips/material_chip.scss @@ -4,12 +4,10 @@ @import 'package:angular_components/css/material/material'; -$mat-option-inline-icons: true; - $chip-height: $mat-grid * 4; $chip-border-radius: $chip-height / 2; -$main-bg-color: $mat-gray-300; +$main-bg-color: $mat-grey-300; $emphasis-bg-color: $mat-blue-500; @@ -40,8 +38,8 @@ $delete-icon-padding: ($clickable-size - $delete-icon-size) / 2; .left-icon { // make it easy to use either or - color: $mat-gray-500; - fill: $mat-gray-500; + color: $mat-grey-500; + fill: $mat-grey-500; display: flex; align-items: center; @@ -52,6 +50,16 @@ $delete-icon-padding: ($clickable-size - $delete-icon-size) / 2; padding: $delete-icon-padding; } +.delete-button { + border: 0; + cursor: pointer; + outline: none; + + &:focus .delete-icon { + fill: $mat-white; + } +} + .delete-icon { display: flex; background-size: $delete-icon-size $delete-icon-size; @@ -65,12 +73,7 @@ $delete-icon-padding: ($clickable-size - $delete-icon-size) / 2; padding: $delete-icon-padding; width: $delete-icon-size; - fill: $mat-gray-500; - - &:focus { - fill: $mat-white; - outline: none; - } + fill: $mat-grey-500; } // emphasis colors @@ -85,9 +88,9 @@ $delete-icon-padding: ($clickable-size - $delete-icon-size) / 2; .delete-icon { fill: $mat-white; + } - &:focus { - fill: $mat-gray-300; - } + .delete-button:focus .delete-icon-svg { + fill: $mat-grey-300; } } diff --git a/angular_components/lib/material_chips/material_chips.dart b/angular_components/lib/material_chips/material_chips.dart index 3802ff846..a1af37b46 100644 --- a/angular_components/lib/material_chips/material_chips.dart +++ b/angular_components/lib/material_chips/material_chips.dart @@ -11,7 +11,7 @@ import 'package:angular_components/utils/disposer/disposer.dart'; /// A __chips__ collection widget, displaying a list of objects as Chips. @Component( selector: 'material-chips', - providers: [Provider(HasRenderer, useExisting: MaterialChipsComponent)], + providers: [ExistingProvider(HasRenderer, MaterialChipsComponent)], templateUrl: 'material_chips.html', styleUrls: ['material_chips.scss.css'], directives: [MaterialChipComponent, NgFor], @@ -57,13 +57,7 @@ class MaterialChipsComponent implements HasRenderer, OnDestroy { /// itself must change in order to take effect. @Input() @override - set itemRenderer(ItemRenderer value) { - _itemRenderer = value; - } - - ItemRenderer _itemRenderer = _defaultItemRenderer; - @override - ItemRenderer get itemRenderer => _itemRenderer; + ItemRenderer itemRenderer = _defaultItemRenderer; Iterable get selectedItems => selectionModel.selectedValues; diff --git a/angular_components/lib/material_datepicker/_mixins.scss b/angular_components/lib/material_datepicker/_mixins.scss index c6a0aa66b..339fd305b 100644 --- a/angular_components/lib/material_datepicker/_mixins.scss +++ b/angular_components/lib/material_datepicker/_mixins.scss @@ -263,3 +263,17 @@ padding-left: 0; } } + +// Set the margin for the next-prev-buttons container. +@mixin next-prev-buttons-margin($margin) { + ::ng-deep .next-prev-buttons { + margin: $margin; + } +} + +// Removes the space between the next and prev buttons. +@mixin remove-prev-button-margin { + ::ng-deep .next-prev-buttons .prev { + margin-right: 0; + } +} diff --git a/angular_components/lib/material_datepicker/comparison.dart b/angular_components/lib/material_datepicker/comparison.dart index 10f220532..670cd753f 100644 --- a/angular_components/lib/material_datepicker/comparison.dart +++ b/angular_components/lib/material_datepicker/comparison.dart @@ -37,14 +37,6 @@ class DatepickerComparison implements DateRangeComparison { bool get isComparisonEnabled => comparison != null; - @Deprecated('use comparesTo instead') - bool comparesToPreviousPeriod() => - comparesTo(ComparisonOption.previousPeriod); - - @Deprecated('use comparesTo instead') - bool comparesToSamePeriodLastYear() => - comparesTo(ComparisonOption.samePeriodLastYear); - /// Checks the comparison date range has same logic as given comparisonOption. bool comparesTo(ComparisonOption option) => comparison != null && diff --git a/angular_components/lib/material_datepicker/comparison_option.dart b/angular_components/lib/material_datepicker/comparison_option.dart index bb14f537c..0ad5492a6 100644 --- a/angular_components/lib/material_datepicker/comparison_option.dart +++ b/angular_components/lib/material_datepicker/comparison_option.dart @@ -8,7 +8,7 @@ import 'package:angular_components/material_datepicker/range.dart'; /// Converts current date range to comparison date range. /// /// Return null if this is a "custom". -typedef DatepickerDateRange ComparisonFn(DatepickerDateRange range); +typedef ComparisonFn = DatepickerDateRange Function(DatepickerDateRange range); /// The [ComparisonOption]s the component provides to the user by default. List defaultComparisonOptions = [ diff --git a/angular_components/lib/material_datepicker/date_input.dart b/angular_components/lib/material_datepicker/date_input.dart index 88e901a65..846a400bd 100644 --- a/angular_components/lib/material_datepicker/date_input.dart +++ b/angular_components/lib/material_datepicker/date_input.dart @@ -200,24 +200,27 @@ class DateInputDirective implements OnDestroy { return date; } + Date _parseDateUsingFormat(String input, DateFormat format) { + try { + return _clampDate(Date.parseLoose(input, format)); + } on FormatException { + return null; + } on ArgumentError { + // This could be a valid date format, but a date greater than + // DateTime's max allowed time + return null; + } + } + /// Parse the given string as a date using one of the listed formats. /// Returns the parsed date, or null if the string didn't match any format. Date _parseDateUsingFormatList(String input, List formatList) { - Date date; - formatList.any((format) { - try { - date = Date.parseLoose(input, format); - date = _clampDate(date); - return true; - } on FormatException { - return false; - } on ArgumentError { - // This could be a valid date format, but a date greater than - // DateTime's max allowed time - return false; - } - }); - return date; + for (final format in formatList) { + final d = _parseDateUsingFormat(input, format); + + if (d != null) return d; + } + return null; } /// A cache for _parseDate, to speed up change detection. @@ -250,16 +253,22 @@ class DateInputDirective implements OnDestroy { return invalidDateMsg; } } else { + // try parse with the given dateFormat + _lastParse = _parseDateUsingFormat(input, dateFormat); + // Non-empty input is invalid iff parsing fails if (isMonthInput) { - _lastParse = _parseDateUsingFormatList(input, _inputFormats) ?? - _parseDateUsingFormatList(input, _monthInputFormatsWithoutDay) ?? - _guessYear( - _parseDateUsingFormatList(input, _monthInputFormatsMonthOnly)); + _lastParse ??= _parseDateUsingFormatList(input, _inputFormats); + _lastParse ??= + _parseDateUsingFormatList(input, _monthInputFormatsWithoutDay); + _lastParse ??= _guessYear( + _parseDateUsingFormatList(input, _monthInputFormatsMonthOnly), + ); } else { - _lastParse = _parseDateUsingFormatList(input, _inputFormats) ?? - _guessYear( - _parseDateUsingFormatList(input, _dayInputFormatsWithoutYear)); + _lastParse ??= _parseDateUsingFormatList(input, _inputFormats); + _lastParse ??= _guessYear( + _parseDateUsingFormatList(input, _dayInputFormatsWithoutYear), + ); } if (_lastParse == null) { diff --git a/angular_components/lib/material_datepicker/date_range_editor.dart b/angular_components/lib/material_datepicker/date_range_editor.dart index 6ce45c4ae..6555e0d3c 100644 --- a/angular_components/lib/material_datepicker/date_range_editor.dart +++ b/angular_components/lib/material_datepicker/date_range_editor.dart @@ -7,12 +7,11 @@ import 'dart:html'; import 'dart:math'; import 'package:angular/angular.dart'; -import 'package:intl/intl.dart'; -import 'package:quiver/time.dart'; import 'package:angular_components/button_decorator/button_decorator.dart'; import 'package:angular_components/focus/focus.dart'; +import 'package:angular_components/focus/focus_item.dart'; +import 'package:angular_components/focus/focus_list.dart'; import 'package:angular_components/focus/keyboard_only_focus_indicator.dart'; -import 'package:angular_components/laminate/popup/popup.dart'; import 'package:angular_components/material_button/material_button.dart'; import 'package:angular_components/material_datepicker/calendar.dart'; import 'package:angular_components/material_datepicker/date_range_editor_host.dart'; @@ -23,10 +22,10 @@ import 'package:angular_components/material_datepicker/module.dart'; import 'package:angular_components/material_datepicker/next_prev_buttons.dart'; import 'package:angular_components/material_datepicker/preset.dart'; import 'package:angular_components/material_datepicker/range.dart'; -import 'package:angular_components/src/material_datepicker/comparison_range_editor.dart'; -import 'package:angular_components/src/material_datepicker/date_range_editor_model.dart'; import 'package:angular_components/material_icon/material_icon.dart'; import 'package:angular_components/material_input/material_input.dart'; +import 'package:angular_components/material_menu/common/menu_root.dart'; +import 'package:angular_components/material_menu/menu_item_groups.dart'; import 'package:angular_components/material_popup/material_popup.dart'; import 'package:angular_components/material_ripple/material_ripple.dart'; import 'package:angular_components/material_select/material_select.dart'; @@ -34,11 +33,18 @@ import 'package:angular_components/material_select/material_select_item.dart'; import 'package:angular_components/material_tooltip/material_tooltip.dart'; import 'package:angular_components/model/date/date.dart'; import 'package:angular_components/model/date/date_formatter.dart'; +import 'package:angular_components/model/menu/menu.dart'; +import 'package:angular_components/model/menu/selectable_menu.dart'; import 'package:angular_components/model/observable/observable.dart'; -import 'package:angular_components/utils/angular/managed_zone/interface.dart'; +import 'package:angular_components/model/selection/select.dart'; +import 'package:angular_components/model/selection/selection_model.dart'; +import 'package:angular_components/src/material_datepicker/comparison_range_editor.dart'; +import 'package:angular_components/src/material_datepicker/date_range_editor_model.dart'; import 'package:angular_components/utils/angular/scroll_host/angular_2.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; import 'package:angular_components/utils/showhide/showhide.dart'; +import 'package:intl/intl.dart'; +import 'package:quiver/time.dart'; export 'package:angular_components/src/material_datepicker/date_range_editor_model.dart'; @@ -60,6 +66,8 @@ export 'package:angular_components/src/material_datepicker/date_range_editor_mod ButtonDirective, ComparisonRangeEditorComponent, DateRangeInputComponent, + FocusItemDirective, + FocusListDirective, KeyboardOnlyFocusIndicatorDirective, MaterialButtonComponent, MaterialCalendarPickerComponent, @@ -71,6 +79,8 @@ export 'package:angular_components/src/material_datepicker/date_range_editor_mod MaterialSelectComponent, MaterialSelectItemComponent, MaterialTooltipDirective, + MenuItemGroupsComponent, + MenuRootDirective, NextPrevComponent, NgFor, NgIf, @@ -91,26 +101,28 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { /// This is useful for reducing visual noise during popup open/close /// animations. Defaults to true. @Input() - set allowHighlightUpdates(bool value) { - _allowHighlightUpdates = value; - } - - bool get allowHighlightUpdates => _allowHighlightUpdates; - - bool _allowHighlightUpdates = true; + bool allowHighlightUpdates = true; /// Whether or not this editor includes a section to edit a comparison date /// range. /// /// Defaults to `true`. @Input() - set supportsComparison(bool value) { - _supportsComparison = value; - } + bool supportsComparison = true; - bool get supportsComparison => _supportsComparison; + bool _useMenuForPresets = false; - bool _supportsComparison = true; + /// Whether to use menu-items-groups for presets for improved accessibility. + /// + /// Internal flag for safe transition. + bool get useMenuForPresets => _useMenuForPresets; + @Input() + set useMenuForPresets(bool value) { + _useMenuForPresets = value; + if (value && _presetsMenu == null) { + _updateValidPresets(); + } + } /// Checks if custom comparison is a valid option. bool get isCustomComparisonValid => model.isCustomComparisonValid; @@ -121,14 +133,8 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { /// Whether to enable compact calendar styles. @Input() - set compact(bool value) { - _compact = value; - } - @HostBinding('class.compact') - bool get compact => _compact; - - bool _compact = false; + bool compact = false; /// For date range selection, whether clicking to move the start date should /// also move the end date (preserving the length of the selected range). @@ -142,13 +148,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { /// /// Defaults to `true`. @Input() - set supportsDaysInputs(bool value) { - _supportsDaysInputs = value; - } - - bool get supportsDaysInputs => _supportsDaysInputs; - - bool _supportsDaysInputs = true; + bool supportsDaysInputs = true; /// A list of predefined date ranges which the user can choose from. These are /// subject to clamping by `minDate` and `maxDate`, and are excluded entirely @@ -177,15 +177,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { // The subset of _presets which fall within minDate/maxDate (at least partly). // Filtered version of presets, regenerated as needed. - Set _validPresets = Set(); - - /// If the editor's inside a popup, a handle on the popup. (This is needed - /// because we can't initialize the calendar's scroll position until after it - /// makes it into the DOM and has been laid out. Normally this is done - /// automatically, but when the calendar is inside a popup it needs to wait - /// until after the popup's been laid out.) - @Input() - PopupRef popupHandle; + Set _validPresets = {}; /// Dates earlier than `minDate` cannot be chosen. Defaults to Jan 1, 1000. @Input() @@ -209,9 +201,18 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { Date _maxDate = Date(9999, DateTime.december, 31); Date get maxDate => _maxDate; + /// The [DateFormat] used to format dates. + @Input() + DateFormat dateFormat; + + /// The [DateFormat] used to format dates when the input is active. + @Input() + DateFormat activeDateFormat; + final Element _elementRef; final DomService _domService; - final ManagedZone _managedZone; + final NgZone _ngZone; + MenuModel _presetsMenu; // This controls when the calendar is created. // By default, this is null, and the calendar will be created shortly @@ -291,7 +292,8 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { bool get shouldShowCustomDateRangeColumn => model.shouldShowCustomDateRangeColumn; - bool get shouldShowPredefinedList => model.shouldShowPredefinedList; + bool get shouldShowPredefinedList => + _presets.isNotEmpty && model.shouldShowPredefinedList; /// Whether or not this date range picker is basic. /// @@ -327,7 +329,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { DateRangeEditorComponent( this._elementRef, this._domService, - this._managedZone, + this._ngZone, @Optional() DateRangeEditorHost editorHost, @Optional() @Inject(datepickerClock) Clock clock, Clock legacyClock) { @@ -359,7 +361,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { // Give the browser a chance to do other work before creating the // calendar component (for a snappier UX) _domService.nextFrame.then((_) { - _managedZone.runInside(() { + _ngZone.run(() { if (_isCalendarCreated != null) return; _isCalendarCreated = true; }); @@ -384,25 +386,84 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { /// Event which fires when one of the ranges is selected. @Output() Stream get presetRangeSelected => _controller.stream; + // TODO(google): change to async. final _controller = StreamController.broadcast(sync: true); + static String _renderPreset(DatepickerPreset value) => value.title; + static String _renderAlternativePreset(DatepickerPreset value) => + value.shortTitle; + final _presetSelection = SingleSelectionModel(); + void _updateValidPresets() { - _validPresets = Set(); + _validPresets = {}; for (var preset in _presets) { - if (preset.range.clamp(min: minDate, max: maxDate) != null) { - _validPresets.add(preset); + bool isValid = preset.range.clamp(min: minDate, max: maxDate) != null; + if (isValid) _validPresets.add(preset); + if (preset.alternatives != null) { + for (var alternative in preset.alternatives) { + bool isValidAlternative = + alternative.range.clamp(min: minDate, max: maxDate) != null; + if (isValidAlternative) _validPresets.add(alternative); + } + } + if (model.value?.range?.unclamped() == preset.range) { + _presetSelection.select(preset); } + } + if (useMenuForPresets) _buildMenu(); + } + + void _buildMenu() { + final items = >[]; + for (var preset in _presets) { + MenuModel subMenu; if (preset.alternatives != null) { + final subitems = >[]; for (var alternative in preset.alternatives) { - if (alternative.range.clamp(min: minDate, max: maxDate) != null) { - _validPresets.add(alternative); - } + bool isValid = _validPresets.contains(alternative); + subitems.add(SelectableMenuItem( + cssClasses: ['preset-dropdown-item'], + value: alternative, + action: () { + // TODO(google): pass the event instead of null. + onAlternativePresetClicked(null, preset, alternative); + _presetSelection.select(alternative); + }, + itemRenderer: _renderAlternativePreset, + tooltip: isValid ? null : rangeDisabledTooltip, + selectableState: isValid + ? SelectableOption.Selectable + : SelectableOption.Disabled)); } + subMenu = MenuModel([ + MenuItemGroupWithSelection( + items: subitems, selectionModel: _presetSelection) + ]); } + bool isValid = _validPresets.contains(preset); + items.add(SelectableMenuItem( + value: preset, + action: () { + _presetSelection.select(preset); + // TODO(google): pass the event instead of null. + onRangeClicked(null, preset.range); + }, + itemRenderer: _renderPreset, + tooltip: isValid ? null : rangeDisabledTooltip, + selectableState: + isValid ? SelectableOption.Selectable : SelectableOption.Disabled, + subMenu: subMenu)); } + _presetsMenu = MenuModel([ + MenuItemGroupWithSelection(items: items, selectionModel: _presetSelection) + ]); } void onRangeClicked(UIEvent event, DatepickerDateRange range) { + if (_presetSelection.isNotEmpty && + _presetSelection.selectedValue.range != range) { + _presetSelection.clear(); + } model.selectRange(range.clamp(min: minDate, max: maxDate)); _controller.add(event); } @@ -413,6 +474,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { for (var i = 0; i < _presets.length; i++) { if (_presets[i] == parent) { _presets[i] = alternative; + if (useMenuForPresets) _updateValidPresets(); // to refresh the menu break; } } @@ -435,6 +497,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { /// the current selection void onCustomClicked() { var oldRange = model.value?.range; + _presetSelection.clear(); if (oldRange != null) { model.selectRange(DatepickerDateRange.custom(oldRange.start, oldRange.end) .clamp(min: minDate, max: maxDate)); @@ -497,7 +560,8 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { bool showMonthSelector = false; - bool isSelected(range) => model.value?.range?.unclamped() == range; + bool isSelected(DatepickerDateRange range) => + model.value?.range?.unclamped() == range; bool isValid(DatepickerPreset preset) => _validPresets.contains(preset); @@ -514,6 +578,8 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { String get customRangeDescription => formatRange(model.range.value); + MenuModel get presetsMenu => _presetsMenu; + static final navigateBeforeMsg = Intl.message('Previous date range', name: 'navigateBeforeMsg', meaning: 'Update the calendar display to show the previous time period.', @@ -555,7 +621,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { desc: 'Message that explains why a date range is invalid.'); } -typedef void NextPrevCallback(); +typedef NextPrevCallback = void Function(); class DateRangeEditorNextPrevModel implements Sequential { final NextPrevCallback onNext; diff --git a/angular_components/lib/material_datepicker/date_range_editor.html b/angular_components/lib/material_datepicker/date_range_editor.html index 2d6504fc4..099103a54 100644 --- a/angular_components/lib/material_datepicker/date_range_editor.html +++ b/angular_components/lib/material_datepicker/date_range_editor.html @@ -3,19 +3,22 @@ for details. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> - + *ngIf="shouldShowPredefinedList" acxScrollHost>
- + {{clearRangeMsg}}
- + [closeOnActivate]="false">
{{customRangeMsg}} @@ -28,47 +31,57 @@
- - {{preset.title}} - - + + + {{preset.title}} + + + + + +
{{daysToTodayMsg}} @@ -124,6 +139,8 @@ [maxDate]="model.maxDate" [isClearRangeSelected]="isClearRangeSelected" [rangeId]="model.comparisonId" + [dateFormat]="dateFormat" + [activeDateFormat]="activeDateFormat" [(state)]="model.calendar.value" [(range)]="model.comparison.value" [disabled]="!isCustomComparisonValid"> @@ -152,7 +169,6 @@ [showhide]="!showMonthSelector" *ngIf="isCalendarCreated" [(state)]="model.calendar.value" - [popupHandle]="popupHandle" [minDate]="minDate" [maxDate]="maxDate" [allowHighlightUpdates]="allowHighlightUpdates" diff --git a/angular_components/lib/material_datepicker/date_range_editor.scss b/angular_components/lib/material_datepicker/date_range_editor.scss index afbce9a67..1b41bd7c8 100644 --- a/angular_components/lib/material_datepicker/date_range_editor.scss +++ b/angular_components/lib/material_datepicker/date_range_editor.scss @@ -8,6 +8,7 @@ @import 'package:angular_components/material_datepicker/mixins'; @import 'package:angular_components/material_input/mixins'; @import 'package:angular_components/material_list/mixins'; +@import 'package:angular_components/material_select/size'; @include material-scrollbars('date-range-editor', $mat-blue); @@ -41,9 +42,8 @@ border-bottom: 0; } - material-select-item { - font-size: inherit; - } + @include material-select-item-font-size($font-size: inherit); + @include material-select-item-padding($padding: 0 $select-item-padding); .days-input { display: flex; diff --git a/angular_components/lib/material_datepicker/date_range_input.dart b/angular_components/lib/material_datepicker/date_range_input.dart index 8441a6ed0..5404ead81 100644 --- a/angular_components/lib/material_datepicker/date_range_input.dart +++ b/angular_components/lib/material_datepicker/date_range_input.dart @@ -56,7 +56,7 @@ import 'package:angular_components/model/observable/observable.dart'; directives: [DateInputDirective, materialInputDirectives], ) class DateRangeInputComponent implements OnInit, OnDestroy { - ChangeDetectorRef _changeDetector; + final ChangeDetectorRef _changeDetector; StreamSubscription _calendarStream; DateRangeInputComponent(this._changeDetector); @@ -65,12 +65,7 @@ class DateRangeInputComponent implements OnInit, OnDestroy { /// /// If true, the component disable both start and end input field. @Input() - set disabled(bool isDisabled) { - _disabled = isDisabled; - } - - bool get disabled => _disabled; - bool _disabled = false; + bool disabled = false; @override void ngOnInit() { @@ -87,20 +82,22 @@ class DateRangeInputComponent implements OnInit, OnDestroy { } void onStartFocused() { - if (_disabled) { + if (disabled) { return; } - if (state.currentSelection == rangeId && !state.previewAnchoredAtStart) + if (state.currentSelection == rangeId && !state.previewAnchoredAtStart) { return; + } _model.value = state.select(rangeId, previewAnchoredAtStart: false); } void onEndFocused() { - if (_disabled) { + if (disabled) { return; } - if (state.currentSelection == rangeId && state.previewAnchoredAtStart) + if (state.currentSelection == rangeId && state.previewAnchoredAtStart) { return; + } _model.value = state.select(rangeId, previewAnchoredAtStart: true); } @@ -157,7 +154,7 @@ class DateRangeInputComponent implements OnInit, OnDestroy { } CalendarState get state => _model.value; - ObservableReference _model = + final ObservableReference _model = ObservableReference(CalendarState.empty(), coalesce: true); /// Fired when the calendar state changes -- e.g. when the user starts diff --git a/angular_components/lib/material_datepicker/material_calendar_picker.dart b/angular_components/lib/material_datepicker/material_calendar_picker.dart index f892d4d5e..be9be0db7 100644 --- a/angular_components/lib/material_datepicker/material_calendar_picker.dart +++ b/angular_components/lib/material_datepicker/material_calendar_picker.dart @@ -74,7 +74,7 @@ int _dayOfWeek(int year, int month, int day) { /// automatically. /// @Component( - changeDetection: ChangeDetectionStrategy.Detached, + changeDetection: ChangeDetectionStrategy.OnPush, directives: [NgFor], selector: 'material-calendar-picker', styleUrls: ['material_calendar_picker.scss.css'], @@ -144,7 +144,7 @@ class MaterialCalendarPickerComponent final slotTemplate = DivElement() ..className = 'day-slot' ..appendText(''); - var slot; + DivElement slot; for (var i = 0; i < WEEK_ROWS_IN_MONTH * 7; i++) { slot = slotTemplate.clone(true); container.append(slot); @@ -162,7 +162,7 @@ class MaterialCalendarPickerComponent } CalendarState get state => _model.value; - ObservableReference _model = + final ObservableReference _model = ObservableReference(CalendarState.empty(), coalesce: true); /// Fired when the calendar state changes -- e.g. when the user starts @@ -290,7 +290,7 @@ class MaterialCalendarPickerComponent } _Month _monthAtOffset(int offset) { - var month; + _Month month; int total = 0; for (month = _minMonth.copy(); total < offset && month < _maxMonth; @@ -406,7 +406,7 @@ class MaterialCalendarPickerComponent void _renderVisible() { // Determine which months are visible (+/- overdraw). - var baseline; + _Month baseline; int offset; if (_renderedMonths.isEmpty) { baseline = _monthAtOffset(_scrollTop); @@ -700,7 +700,7 @@ class MaterialCalendarPickerComponent List<_Month> _renderedMonths = []; // Y-offsets corresponding to each rendered month. - List _renderedOffsets = []; + final List _renderedOffsets = []; // The .scroll-container element. HtmlElement _scroller; @@ -915,7 +915,9 @@ class _Month { months = -months; increment = result.prev; } - for (var i = 0; i < months; i++) increment(); + for (var i = 0; i < months; i++) { + increment(); + } return result; } diff --git a/angular_components/lib/material_datepicker/material_date_grid_base.dart b/angular_components/lib/material_datepicker/material_date_grid_base.dart index 6d7b7ed55..73afce438 100644 --- a/angular_components/lib/material_datepicker/material_date_grid_base.dart +++ b/angular_components/lib/material_datepicker/material_date_grid_base.dart @@ -183,13 +183,7 @@ abstract class MaterialDateGridBase /// Whether to enable compact calendar styles. @Input() - set compact(bool value) { - _compact = value; - } - - bool get compact => _compact; - - bool _compact = false; + bool compact = false; void onCalendarChange(CalendarState state); diff --git a/angular_components/lib/material_datepicker/material_date_range_picker.dart b/angular_components/lib/material_datepicker/material_date_range_picker.dart index 18bb9b3af..cf364a249 100644 --- a/angular_components/lib/material_datepicker/material_date_range_picker.dart +++ b/angular_components/lib/material_datepicker/material_date_range_picker.dart @@ -6,8 +6,7 @@ import 'dart:async'; import 'dart:html'; import 'package:angular/angular.dart'; -import 'package:intl/intl.dart'; -import 'package:quiver/time.dart'; +import 'package:angular/meta.dart'; import 'package:angular_components/button_decorator/button_decorator.dart'; import 'package:angular_components/content/deferred_content.dart'; import 'package:angular_components/focus/focus.dart'; @@ -17,11 +16,10 @@ import 'package:angular_components/interfaces/has_disabled.dart'; import 'package:angular_components/laminate/enums/alignment.dart'; import 'package:angular_components/laminate/popup/popup.dart'; import 'package:angular_components/material_button/material_button.dart'; -import 'package:angular_components/material_datepicker/comparison.dart'; import 'package:angular_components/material_datepicker/comparison_option.dart'; import 'package:angular_components/material_datepicker/config.dart'; -import 'package:angular_components/material_datepicker/date_range_editor_host.dart'; import 'package:angular_components/material_datepicker/date_range_editor.dart'; +import 'package:angular_components/material_datepicker/date_range_editor_host.dart'; import 'package:angular_components/material_datepicker/model.dart'; import 'package:angular_components/material_datepicker/module.dart'; import 'package:angular_components/material_datepicker/next_prev_buttons.dart'; @@ -29,12 +27,15 @@ import 'package:angular_components/material_datepicker/preset.dart'; import 'package:angular_components/material_datepicker/range.dart'; import 'package:angular_components/material_popup/material_popup.dart'; import 'package:angular_components/material_select/dropdown_button.dart'; +import 'package:angular_components/mixins/focusable_mixin.dart'; import 'package:angular_components/model/date/date.dart'; import 'package:angular_components/model/date/date_formatter.dart'; import 'package:angular_components/model/observable/observable.dart'; import 'package:angular_components/utils/angular/css/css.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; import 'package:angular_components/utils/disposer/disposer.dart'; +import 'package:intl/intl.dart'; +import 'package:quiver/time.dart'; /// Custom date range formatter interface. typedef RangeFormatter = String Function(DateRange range); @@ -77,6 +78,7 @@ const _defaultMaxHeight = 600; DateRangeEditorComponent, DeferredContentDirective, DropdownButtonComponent, + FocusableDirective, KeyboardOnlyFocusIndicatorDirective, MaterialButtonComponent, MaterialPopupComponent, @@ -87,11 +89,13 @@ const _defaultMaxHeight = 600; ], providers: [ ExistingProvider(DateRangeEditorHost, MaterialDateRangePickerComponent), + ExistingProvider(Focusable, MaterialDateRangePickerComponent), ExistingProvider(HasDisabled, MaterialDateRangePickerComponent), ExistingProvider(PopupSizeProvider, MaterialDateRangePickerComponent), ], ) class MaterialDateRangePickerComponent + with FocusableMixin implements HasDisabled, OnInit, @@ -101,10 +105,12 @@ class MaterialDateRangePickerComponent PopupSizeProvider { Focusable _dateRangeEditor; bool _focusOnDateRangeEditorInit = false; - PopupSizeProvider _popupSizeProvider; + final PopupSizeProvider _popupSizeProvider; - List get overlapAlignments => - RelativePosition.overlapAlignments; + @ViewChild(ButtonDirective) + set focusableElement(ButtonDirective button) { + focusable = button; + } @Deprecated('Use [presets] instead.') @Input('predefinedRanges') @@ -112,6 +118,13 @@ class MaterialDateRangePickerComponent presets = ranges.map((range) => DatepickerPreset.fromRange(range)).toList(); } + /// A list of positions for popup alignment. + /// + /// Defaults to [RelativePosition.overlapAlignments]. + @Input() + List preferredPositions = + RelativePosition.overlapAlignments; + /// A list of preset date ranges which the user can choose from. /// /// These are subject to clamping by `minDate` and `maxDate`, and are excluded @@ -130,7 +143,6 @@ class MaterialDateRangePickerComponent /// /// Calendar will be hidden when custom range is not supported. Custom range /// should be supported in "fullyLoaded" or "basic" mode. - bool get supportsCustomRange => configuration == DateRangePickerConfiguration.fullyLoaded || configuration == DateRangePickerConfiguration.basic; @@ -152,6 +164,7 @@ class MaterialDateRangePickerComponent /// [DateRangePickerConfiguration.predefinedRangesOnly]. /// /// Defaults to [DateRangePickerConfiguration.fullyLoaded]. + /// Should not be changed after initialization. @Input('configuration') DateRangePickerConfiguration configuration = DateRangePickerConfiguration.fullyLoaded; @@ -188,37 +201,19 @@ class MaterialDateRangePickerComponent /// /// Defaults to true. @Input() - set showNextPrevButtons(bool value) { - _showNextPrevButtons = value; - } - - bool get showNextPrevButtons => _showNextPrevButtons; - - bool _showNextPrevButtons = true; + bool showNextPrevButtons = true; /// Whether or not this date range picker includes a section to input 'N days /// to today' and 'N days to yesterday' ranges. /// /// Defaults to `true`. @Input() - set supportsDaysInputs(bool value) { - _supportsDaysInputs = value; - } - - bool get supportsDaysInputs => _supportsDaysInputs; - - bool _supportsDaysInputs = true; + bool supportsDaysInputs = true; /// Whether to enable compact calendar styles. @Input() - set compact(bool value) { - _compact = value; - } - @HostBinding('class.compact') - bool get compact => _compact; - - bool _compact = !window.matchMedia("(pointer: coarse)").matches; + bool compact = !window.matchMedia("(pointer: coarse)").matches; /// For date range selection, whether clicking to move the start date should /// also move the end date (preserving the length of the selected range). @@ -240,6 +235,10 @@ class MaterialDateRangePickerComponent @Input() String applyButtonLabel; + /// The ARIA label for the dropdown button. + @Input() + String dropdownButtonAriaLabel; + /// Whether changing the selected date range should be disabled. @Input() set disabled(bool value) { @@ -283,6 +282,14 @@ class MaterialDateRangePickerComponent Date get maxDate => _maxDate; Date _maxDate; + /// The [DateFormat] used to format dates. + @Input() + DateFormat dateFormat; + + /// The [DateFormat] used to format dates when the input is active. + @Input() + DateFormat activeDateFormat; + /// When 'requireFullPeriods' is true, 'prev/next' button will be disabled /// if previous or next period is not a full predefined period, like 'week'. @Input() @@ -292,6 +299,12 @@ class MaterialDateRangePickerComponent bool get requireFullPeriods => model.requireFullPeriods; + /// Whether to use menu-items-groups for presets for improved accessibility. + /// + /// Internal flag for safe transition. + @Input() + bool useMenuForPresets = false; + /// An error displayed below the dropdown button. @Input() String error; @@ -351,7 +364,7 @@ class MaterialDateRangePickerComponent /// saved or canceled when the popup is closed. bool _isApplying = false; - Disposer _disposer = Disposer.oneShot(); + final Disposer _disposer = Disposer.oneShot(); DatepickerComparison get range => selection.value; @@ -411,11 +424,12 @@ class MaterialDateRangePickerComponent ..maxDate = maxDate ..requireFullPeriods = requireFullPeriods ..basic = isBasic; - if (selection.value != null) + if (selection.value != null) { model.value = _maybeStripComparison(selection.value); + } _disposer.addFunction(model.dispose); - _needsApply(modelValue) => + bool _needsApply(DatepickerComparison modelValue) => modelValue != selection.value || !_isPreset(modelValue); // Wire the internal model and the external value up to each other. @@ -489,6 +503,7 @@ class MaterialDateRangePickerComponent DatepickerComparison.reclamp(model.value, minDate, maxDate); model.minDate = minDate; model.maxDate = maxDate; + model.basic = isBasic; _initCalendar(); setFocusToDateRangeEditor(); @@ -565,6 +580,12 @@ class MaterialDateRangePickerComponent focusOnClose.focus(event); } + @visibleForTemplate + void applyAndPreventDefault(UIEvent event) { + apply(event); + event.preventDefault(); + } + void cancel() { model.restore(_lastState); selection.value = _lastState.value; @@ -573,6 +594,12 @@ class MaterialDateRangePickerComponent focusOnClose.focus(); } + @visibleForTemplate + void cancelAndPreventDefault(UIEvent event) { + cancel(); + event.preventDefault(); + } + bool get hasTitle => selection.value?.range?.title != null; String get rangeTitle => selection.value?.range?.title ?? ''; @@ -636,8 +663,9 @@ class MaterialDateRangePickerComponent static final cancelButtonMsg = Intl.message('Cancel', meaning: 'Button in a date picker', - desc: 'Label for a "cancel" button -- abandon the current date selection ' - 'and go back to whatever it was before the user opened the date picker'); + desc: 'Label for a "cancel" button -- abandon the current date' + ' selection and go back to whatever it was before the user' + ' opened the date picker'); String get applyButtonMsg => applyButtonLabel ?? _applyButtonMsg; diff --git a/angular_components/lib/material_datepicker/material_date_range_picker.html b/angular_components/lib/material_datepicker/material_date_range_picker.html index c5a73915b..a5f1d7ddf 100644 --- a/angular_components/lib/material_datepicker/material_date_range_picker.html +++ b/angular_components/lib/material_datepicker/material_date_range_picker.html @@ -21,6 +21,7 @@ @@ -43,7 +44,7 @@ --> @@ -53,11 +54,14 @@
 
- + {{cancelButtonMsg}} - + {{applyButtonMsg}}
diff --git a/angular_components/lib/material_datepicker/material_date_range_picker.scss b/angular_components/lib/material_datepicker/material_date_range_picker.scss index 8357b00d0..034832d4f 100644 --- a/angular_components/lib/material_datepicker/material_date_range_picker.scss +++ b/angular_components/lib/material_datepicker/material_date_range_picker.scss @@ -45,7 +45,7 @@ $main-font-size: 13px; background-color: $mat-white; border-top: 1px solid $mat-gray-300; box-sizing: border-box; - color: $mat-blue-500; + color: $mat-blue-700; display: none; font-size: 13px; flex-shrink: 0; @@ -77,6 +77,7 @@ $main-font-size: 13px; color: $mat-light-transparent-black; font-size: 12px; margin-bottom: $mat-grid-type; + white-space: nowrap; } .comparison-title { @@ -86,7 +87,7 @@ $main-font-size: 13px; } .menu-lookalike { - @include dropdown-icon-spacing($mat-grid * 2); + @include dropdown-icon-spacing(0 0 0 $mat-grid * 2); } .next-prev-buttons { diff --git a/angular_components/lib/material_datepicker/material_date_time_picker.dart b/angular_components/lib/material_datepicker/material_date_time_picker.dart index a58e6d75f..60f8a6b1c 100644 --- a/angular_components/lib/material_datepicker/material_date_time_picker.dart +++ b/angular_components/lib/material_datepicker/material_date_time_picker.dart @@ -6,14 +6,14 @@ import 'dart:async'; import 'package:angular/angular.dart'; import 'package:angular/meta.dart'; -import 'package:intl/intl.dart'; -import 'package:quiver/time.dart'; import 'package:angular_components/interfaces/has_disabled.dart'; import 'package:angular_components/material_datepicker/material_datepicker.dart'; import 'package:angular_components/material_datepicker/material_time_picker.dart'; import 'package:angular_components/material_datepicker/module.dart'; import 'package:angular_components/material_input/material_input.dart'; import 'package:angular_components/model/date/date.dart'; +import 'package:intl/intl.dart'; +import 'package:quiver/time.dart'; /// A material-design-styled single date and time picker. /// @@ -35,9 +35,7 @@ import 'package:angular_components/model/date/date.dart'; MaterialTimePickerComponent, MaterialInputComponent, ], - providers: [ - Provider(HasDisabled, useExisting: MaterialDateTimePickerComponent), - ], + providers: [ExistingProvider(HasDisabled, MaterialDateTimePickerComponent)], ) class MaterialDateTimePickerComponent implements HasDisabled { final Clock _clock; @@ -94,22 +92,20 @@ class MaterialDateTimePickerComponent implements HasDisabled { return null; } - bool _disabled = false; - bool get disabled => _disabled; + /// Increment of time dropdown options in minutes, passed on to time picker. + @Input() + int increment; /// Whether changing the selected date and time should be disabled. @Input() - set disabled(bool value) => _disabled = value; - - bool _required = false; - bool get required => _required; + bool disabled = false; /// Whether the date and time entry is required. /// /// If true, the embedded text fields will display an error to the user if /// blank. If false, clearing the text fields will set [dateTime] to `null`. @Input() - set required(bool value) => _required = value; + bool required = false; bool get utc => _utc; bool _utc = false; @@ -217,7 +213,7 @@ class MaterialDateTimePickerComponent implements HasDisabled { _date.year, _date.month, _date.day, _time.hour, _time.minute)) : null; - if (_dateTime != null || !_required) { + if (_dateTime != null || !required) { _dateTimeController.add(_dateTime); } } diff --git a/angular_components/lib/material_datepicker/material_date_time_picker.html b/angular_components/lib/material_datepicker/material_date_time_picker.html index d43ab9c21..8eff992b7 100644 --- a/angular_components/lib/material_datepicker/material_date_time_picker.html +++ b/angular_components/lib/material_datepicker/material_date_time_picker.html @@ -15,6 +15,7 @@ [(time)]="time" [minTime]="minTime" [maxTime]="maxTime" + [increment]="increment" [outputFormat]="outputTimeFormat" [utc]="utc" [disabled]="disabled" diff --git a/angular_components/lib/material_datepicker/material_datepicker.dart b/angular_components/lib/material_datepicker/material_datepicker.dart index fd8253377..1e78a0ac3 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.dart +++ b/angular_components/lib/material_datepicker/material_datepicker.dart @@ -6,8 +6,6 @@ import 'dart:async'; import 'dart:html'; import 'package:angular/angular.dart'; -import 'package:intl/intl.dart'; -import 'package:quiver/time.dart'; import 'package:angular_components/button_decorator/button_decorator.dart'; import 'package:angular_components/content/deferred_content.dart'; import 'package:angular_components/focus/focus.dart'; @@ -15,7 +13,6 @@ import 'package:angular_components/focus/focus_trap.dart'; import 'package:angular_components/focus/keyboard_only_focus_indicator.dart'; import 'package:angular_components/interfaces/has_disabled.dart'; import 'package:angular_components/laminate/enums/alignment.dart'; -import 'package:angular_components/laminate/popup/popup.dart'; import 'package:angular_components/material_datepicker/calendar.dart'; import 'package:angular_components/material_datepicker/date_input.dart'; import 'package:angular_components/material_datepicker/material_calendar_picker.dart'; @@ -27,8 +24,11 @@ import 'package:angular_components/material_popup/material_popup.dart'; import 'package:angular_components/material_select/dropdown_button.dart'; import 'package:angular_components/material_select/material_select_item.dart'; import 'package:angular_components/mixins/focusable_mixin.dart'; +import 'package:angular_components/model/a11y/keyboard_handler_mixin.dart'; import 'package:angular_components/model/date/date.dart'; import 'package:angular_components/utils/angular/css/css.dart'; +import 'package:intl/intl.dart'; +import 'package:quiver/time.dart'; /// A material-design-styled single date picker -- a date parsing input and /// calendar picker. Users can type in their own custom dates, or click on the @@ -61,14 +61,13 @@ import 'package:angular_components/utils/angular/css/css.dart'; NgIf, PopupSourceDirective, ], - providers: [ - Provider(HasDisabled, useExisting: MaterialDatepickerComponent), - ], + providers: [ExistingProvider(HasDisabled, MaterialDatepickerComponent)], styleUrls: ['material_datepicker.scss.css'], templateUrl: 'material_datepicker.html', + changeDetection: ChangeDetectionStrategy.OnPush, ) class MaterialDatepickerComponent - with FocusableMixin + with FocusableMixin, KeyboardHandlerMixin implements AfterViewInit, HasDisabled { /// CSS classes from the root element, passed to the popup to allow scoping of /// mixins. @@ -76,6 +75,10 @@ class MaterialDatepickerComponent /// Only visible for the template. final String popupClassName; + /// aria-label attached to the dropdown button that opens the date picker. + @Input() + String ariaLabelForDropdownButton; + /// The format used to format dates. /// /// Defaults to `yMMMd`, e.g. 'Jan 23, 2015'. @@ -100,14 +103,8 @@ class MaterialDatepickerComponent /// Whether to enable compact calendar styles. @Input() - set compact(bool value) { - _compact = value; - } - @HostBinding('class.compact') - bool get compact => _compact; - - bool _compact = !window.matchMedia("(pointer: coarse)").matches; + bool compact = !window.matchMedia("(pointer: coarse)").matches; /// False if null dates are allowed. /// @@ -192,6 +189,11 @@ class MaterialDatepickerComponent focusable = _focusTarget; } + @override + void handleEscapeKey(KeyboardEvent event) { + dropdownButton.focus(); + } + @ViewChild(DropdownButtonComponent) DropdownButtonComponent dropdownButton; @@ -202,12 +204,14 @@ class MaterialDatepickerComponent disabled ? null : (_popupVisible ? textInput : dropdownButton); /// Gets the i18n'ed "Select a date" placeholder text. - get selectDatePlaceHolderMsg => Intl.message('Select a date', + @Input() + String selectDatePlaceHolderMsg = Intl.message('Select a date', name: 'selectDatePlaceHolderMsg', desc: 'Placeholder text for datepicker with an empty date.'); /// Gets the i18n'ed "Enter date" placeholder text. - get placeholderMsg => Intl.message('Enter date', + @Input() + String placeholderMsg = Intl.message('Enter date', name: 'placeholderMsg', desc: 'Placeholder text for an empty date picker text box.'); diff --git a/angular_components/lib/material_datepicker/material_datepicker.html b/angular_components/lib/material_datepicker/material_datepicker.html index 553faeb8e..bfd5b65c8 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.html +++ b/angular_components/lib/material_datepicker/material_datepicker.html @@ -10,6 +10,7 @@ {{labelMsg}}
-