From d6d09d275a0b07d3f698fe6c41b1b185fac851b0 Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Mon, 1 Jul 2024 22:57:28 -0400
Subject: [PATCH 01/95] chore: create 10.x.x LTS branch
---
.github/workflows/cherry-pick.yml | 2 +-
.github/workflows/ci.yml | 2 +-
.github/workflows/e2e.yml | 2 +-
.github/workflows/release-please.yml | 2 +-
.skyuxdev.json | 2 +-
nx.json | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml
index 95f84ab94a..091020596a 100644
--- a/.github/workflows/cherry-pick.yml
+++ b/.github/workflows/cherry-pick.yml
@@ -4,7 +4,7 @@ on:
types:
- closed
branches:
- - 7.x.x
+ - 10.x.x
env:
TARGET_BRANCH: main
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12344fb043..afa70d621c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ on:
pull_request:
push:
branches:
- - main
+ - 10.x.x
env:
NX_CLOUD_DISTRIBUTED_EXECUTION: true
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index fa94842ff0..2f68aab3e9 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -6,7 +6,7 @@ on:
pull_request_target:
push:
branches:
- - main
+ - 10.x.x
workflow_dispatch:
env:
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index dfcde4497b..20a7f3b761 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -5,7 +5,7 @@ on:
workflow_dispatch:
push:
branches:
- - main
+ - 10.x.x
env:
# Set to 'alpha', 'beta', or 'rc' to create a prerelease.
PRERELEASE: 'false'
diff --git a/.skyuxdev.json b/.skyuxdev.json
index 40267b8336..c020624a26 100644
--- a/.skyuxdev.json
+++ b/.skyuxdev.json
@@ -1,5 +1,5 @@
{
- "baseBranch": "main",
+ "baseBranch": "10.x.x",
"documentationExcludeProjects": [
"animations",
"assets",
diff --git a/nx.json b/nx.json
index 7d54ba77da..dbbb749490 100644
--- a/nx.json
+++ b/nx.json
@@ -123,5 +123,5 @@
]
},
"nxCloudAccessToken": "NzE5ZWYwYzUtMGU0OC00OTU3LTk4ZDYtOTc1Zjk3MTExMzY5fHJlYWQtd3JpdGU=",
- "defaultBase": "main"
+ "defaultBase": "10.x.x"
}
From 94677c154ea986849717233791a1566e1b7c6148 Mon Sep 17 00:00:00 2001
From: Erika McVey <50454925+Blackbaud-ErikaMcVey@users.noreply.github.com>
Date: Tue, 2 Jul 2024 09:54:14 -0400
Subject: [PATCH 02/95] chore: split alert, checkbox, and action button styles
into default and modern (#2405)
---
.../checkbox-label-text-label.component.scss | 3 -
.../checkbox-label-text-label.component.ts | 5 +-
...ox-label-text-label.default.component.scss | 6 +
...box-label-text-label.modern.component.scss | 6 +
.../modules/checkbox/checkbox.component.ts | 7 +-
.../checkbox/checkbox.default.component.scss | 37 +++
...nt.scss => checkbox.modern.component.scss} | 45 ++--
.../modules/radio/radio-label.component.scss | 0
.../modules/radio/radio-label.component.ts | 1 -
.../lib/modules/radio/radio.component.scss | 7 +-
.../lib/modules/alert/alert.component.scss | 193 ---------------
.../src/lib/modules/alert/alert.component.ts | 7 +-
.../alert/alert.default.component.scss | 132 ++++++++++
.../modules/alert/alert.modern.component.scss | 145 +++++++++++
.../action-button-container.component.scss | 98 --------
.../action-button-container.component.ts | 8 +-
...on-button-container.default.component.scss | 69 ++++++
...ion-button-container.modern.component.scss | 72 ++++++
.../action-button-header.component.scss | 20 --
.../action-button-header.component.ts | 7 +-
...ction-button-header.default.component.scss | 20 ++
...action-button-header.modern.component.scss | 6 +
.../action-button-icon.component.scss | 37 ---
.../action-button-icon.component.ts | 5 +-
.../action-button-icon.default.component.scss | 24 ++
.../action-button-icon.modern.component.scss | 22 ++
.../action-button.component.scss | 73 ------
.../action-button/action-button.component.ts | 5 +-
.../action-button.default.component.scss | 38 +++
.../action-button.modern.component.scss | 41 ++++
libs/components/theme/src/index.ts | 1 +
.../src/lib/styles/_public-api/_mixins.scss | 108 ++++++++
.../theme/src/lib/styles/_switch.scss | 172 ++++++-------
.../src/lib/styles/themes/modern/_switch.scss | 230 +++++++++++++-----
.../theme-component-class-test.component.html | 1 +
.../theme-component-class-test.component.ts | 11 +
.../theme-component-class.directive.spec.ts | 90 +++++++
.../theme-component-class.directive.ts | 30 +++
38 files changed, 1173 insertions(+), 609 deletions(-)
delete mode 100644 libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.scss
create mode 100644 libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.default.component.scss
create mode 100644 libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.modern.component.scss
create mode 100644 libs/components/forms/src/lib/modules/checkbox/checkbox.default.component.scss
rename libs/components/forms/src/lib/modules/checkbox/{checkbox.component.scss => checkbox.modern.component.scss} (60%)
delete mode 100644 libs/components/forms/src/lib/modules/radio/radio-label.component.scss
delete mode 100644 libs/components/indicators/src/lib/modules/alert/alert.component.scss
create mode 100644 libs/components/indicators/src/lib/modules/alert/alert.default.component.scss
create mode 100644 libs/components/indicators/src/lib/modules/alert/alert.modern.component.scss
delete mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-container.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-container.default.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-container.modern.component.scss
delete mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-header.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-header.default.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-header.modern.component.scss
delete mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-icon.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-icon.default.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button-icon.modern.component.scss
delete mode 100644 libs/components/layout/src/lib/modules/action-button/action-button.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button.default.component.scss
create mode 100644 libs/components/layout/src/lib/modules/action-button/action-button.modern.component.scss
create mode 100644 libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.html
create mode 100644 libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.ts
create mode 100644 libs/components/theme/src/lib/theming/theme-component-class.directive.spec.ts
create mode 100644 libs/components/theme/src/lib/theming/theme-component-class.directive.ts
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.scss b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.scss
deleted file mode 100644
index 3f55331c58..0000000000
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.sky-switch-label {
- margin-right: 0;
-}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.ts b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.ts
index b9b3f2d131..ba2e759bf1 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.component.ts
@@ -5,7 +5,10 @@ import { Component, Input } from '@angular/core';
*/
@Component({
selector: 'sky-checkbox-label-text-label',
- styleUrl: './checkbox-label-text-label.component.scss',
+ styleUrls: [
+ './checkbox-label-text-label.default.component.scss',
+ './checkbox-label-text-label.modern.component.scss',
+ ],
templateUrl: './checkbox-label-text-label.component.html',
})
export class SkyCheckboxLabelTextLabelComponent {
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.default.component.scss b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.default.component.scss
new file mode 100644
index 0000000000..a4da3bb243
--- /dev/null
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.default.component.scss
@@ -0,0 +1,6 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('default', '.sky-switch-label') {
+ margin-right: 0;
+}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.modern.component.scss b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.modern.component.scss
new file mode 100644
index 0000000000..4d2beaf1ff
--- /dev/null
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox-label-text-label.modern.component.scss
@@ -0,0 +1,6 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('modern', '.sky-switch-label') {
+ margin-right: 0;
+}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts b/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts
index 0b7405259f..eada79d0be 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox.component.ts
@@ -22,6 +22,7 @@ import {
Validators,
} from '@angular/forms';
import { SkyFormsUtility, SkyIdService, SkyLogService } from '@skyux/core';
+import { SkyThemeComponentClassDirective } from '@skyux/theme';
import { BehaviorSubject, Observable } from 'rxjs';
@@ -37,7 +38,11 @@ import { SkyCheckboxChange } from './checkbox-change';
@Component({
selector: 'sky-checkbox',
templateUrl: './checkbox.component.html',
- styleUrls: ['./checkbox.component.scss'],
+ styleUrls: [
+ './checkbox.default.component.scss',
+ './checkbox.modern.component.scss',
+ ],
+ hostDirectives: [SkyThemeComponentClassDirective],
providers: [
{ provide: NG_VALIDATORS, useExisting: SkyCheckboxComponent, multi: true },
{
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox.default.component.scss b/libs/components/forms/src/lib/modules/checkbox/checkbox.default.component.scss
new file mode 100644
index 0000000000..a9a22502ce
--- /dev/null
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox.default.component.scss
@@ -0,0 +1,37 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('default', '.sky-checkbox-outer-wrapper') {
+ display: inline-flex;
+
+ .sky-checkbox-help-inline {
+ display: inline-flex;
+ }
+
+ .sky-checkbox-icon-indeterminate {
+ font-size: 14px;
+ }
+}
+
+@include mixins.sky-component('default', '.sky-checkbox-hint-text') {
+ margin-top: var(--sky-margin-stacked-xs);
+}
+
+@include mixins.sky-component('default', '.sky-checkbox-form-margin') {
+ margin-left: calc(var(--sky-switch-size) + var(--sky-switch-margin));
+}
+
+@include mixins.sky-component-host('default', ':host.sky-margin-stacked-lg') {
+ display: block;
+}
+
+@include mixins.sky-component-host(
+ 'default',
+ ':host-context(sky-checkbox-group .sky-checkbox-group-stacked) :host'
+) {
+ display: block;
+
+ &:not(:last-child) {
+ margin-bottom: var(--sky-margin-stacked-sm);
+ }
+}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.scss b/libs/components/forms/src/lib/modules/checkbox/checkbox.modern.component.scss
similarity index 60%
rename from libs/components/forms/src/lib/modules/checkbox/checkbox.component.scss
rename to libs/components/forms/src/lib/modules/checkbox/checkbox.modern.component.scss
index 0860323882..cae9df617b 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox.component.scss
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox.modern.component.scss
@@ -1,31 +1,17 @@
@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
@use 'libs/components/theme/src/lib/styles/variables' as *;
-.sky-checkbox-outer-wrapper {
+@include mixins.sky-component('modern', '.sky-checkbox-outer-wrapper') {
display: inline-flex;
-}
-
-:host.sky-margin-stacked-lg {
- display: block;
-}
-
-.sky-checkbox-help-inline {
- display: inline-flex;
-}
-
-.sky-checkbox-icon-indeterminate {
- font-size: 14px;
-}
-.sky-checkbox-hint-text {
- margin-top: var(--sky-margin-stacked-xs);
-}
+ .sky-checkbox-help-inline {
+ display: inline-flex;
+ }
-.sky-checkbox-form-margin {
- margin-left: calc(var(--sky-switch-size) + var(--sky-switch-margin));
-}
+ .sky-checkbox-icon-indeterminate {
+ font-size: 14px;
+ }
-@include mixins.sky-theme-modern {
.sky-checkbox-icon-modern-checked,
.sky-checkbox-icon-modern-indeterminate {
color: $sky-theme-modern-background-color-primary-dark;
@@ -43,7 +29,22 @@
}
}
-:host-context(sky-checkbox-group .sky-checkbox-group-stacked) :host {
+@include mixins.sky-component('modern', '.sky-checkbox-hint-text') {
+ margin-top: var(--sky-margin-stacked-xs);
+}
+
+@include mixins.sky-component('modern', '.sky-checkbox-form-margin') {
+ margin-left: calc(var(--sky-switch-size) + var(--sky-switch-margin));
+}
+
+@include mixins.sky-component-host('modern', ':host.sky-margin-stacked-lg') {
+ display: block;
+}
+
+@include mixins.sky-component-host(
+ 'modern',
+ ':host-context(sky-checkbox-group .sky-checkbox-group-stacked) :host'
+) {
display: block;
&:not(:last-child) {
diff --git a/libs/components/forms/src/lib/modules/radio/radio-label.component.scss b/libs/components/forms/src/lib/modules/radio/radio-label.component.scss
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/libs/components/forms/src/lib/modules/radio/radio-label.component.ts b/libs/components/forms/src/lib/modules/radio/radio-label.component.ts
index ce8c736181..7ae879b6aa 100644
--- a/libs/components/forms/src/lib/modules/radio/radio-label.component.ts
+++ b/libs/components/forms/src/lib/modules/radio/radio-label.component.ts
@@ -10,7 +10,6 @@ import { SkyLogService } from '@skyux/core';
@Component({
selector: 'sky-radio-label',
templateUrl: './radio-label.component.html',
- styleUrls: ['./radio-label.component.scss'],
})
export class SkyRadioLabelComponent {
// When clicking on a checkbox label, Angular registers two click events.
diff --git a/libs/components/forms/src/lib/modules/radio/radio.component.scss b/libs/components/forms/src/lib/modules/radio/radio.component.scss
index 279297861c..83debaf447 100644
--- a/libs/components/forms/src/lib/modules/radio/radio.component.scss
+++ b/libs/components/forms/src/lib/modules/radio/radio.component.scss
@@ -13,7 +13,12 @@
display: inline-flex;
}
-.sky-switch-label {
+@include mixins.sky-component('default', '.sky-switch-label') {
+ margin-right: var(--sky-margin-inline-xs);
+}
+
+// TODO: move to the modern stylesheet when splitting out radio button
+@include mixins.sky-component('modern', '.sky-switch-label') {
margin-right: var(--sky-margin-inline-xs);
}
diff --git a/libs/components/indicators/src/lib/modules/alert/alert.component.scss b/libs/components/indicators/src/lib/modules/alert/alert.component.scss
deleted file mode 100644
index c2fda4794b..0000000000
--- a/libs/components/indicators/src/lib/modules/alert/alert.component.scss
+++ /dev/null
@@ -1,193 +0,0 @@
-@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
-@use 'libs/components/theme/src/lib/styles/variables' as *;
-
-:host {
- display: block;
-}
-
-/* Default theme */
-.sky-alert {
- --sky-alert-before-display: initial;
- --sky-alert-border-left-width: 30px;
- --sky-alert-close-btn-active-border: none;
- --sky-alert-close-btn-border-radius: #{$sky-border-radius};
- --sky-alert-close-btn-focus-border: none;
- --sky-alert-close-btn-focus-box-shadow: none;
- --sky-alert-close-btn-opacity: 0.8;
- --sky-alert-content-padding-top-bottom: #{$sky-padding};
- --sky-alert-content-padding-left-right: 0;
- --sky-alert-icon-default-display: initial;
- --sky-alert-icon-modern-display: none;
- --sky-alert-info-background-color: var(--sky-background-color-info);
-}
-
-/* Modern theme */
-@include mixins.sky-theme-modern {
- .sky-alert {
- --sky-alert-before-display: none;
- --sky-alert-border-left-width: 7px;
- --sky-alert-close-btn-active-border: var(
- --sky-background-color-primary-dark
- )
- solid 2px;
- --sky-alert-close-btn-border-radius: #{$sky-theme-modern-border-radius-md};
- --sky-alert-close-btn-focus-border: var(--sky-background-color-primary-dark)
- solid 1px;
- --sky-alert-close-btn-focus-box-shadow: #{$sky-theme-modern-elevation-1-shadow-size}
- #{$sky-theme-modern-elevation-3-shadow-color};
- --sky-alert-close-btn-opacity: 1;
- --sky-alert-content-padding-left-right: var(--sky-padding-even-md);
- --sky-alert-content-padding-top-bottom: var(--sky-padding-even-md);
- --sky-alert-icon-default-display: none;
- --sky-alert-icon-modern-display: initial;
- --sky-alert-info-background-color: var(--sky-background-color-info-light);
- }
-}
-
-.sky-alert {
- padding: 0 $sky-padding;
- border-left: solid var(--sky-alert-border-left-width);
- color: var(--sky-text-color-default);
- display: flex;
- flex-direction: row;
- align-items: center;
-
- .sky-alert-content {
- padding: var(--sky-alert-content-padding-top-bottom)
- var(--sky-alert-content-padding-left-right);
- width: 100%;
-
- ::ng-deep a {
- color: change-color($sky-text-color-default, $alpha: 0.8);
- text-decoration: underline;
-
- &:hover {
- color: var(--sky-text-color-default);
- }
- }
- }
-
- button {
- margin-left: auto;
- width: 32px;
- height: 32px;
- }
-
- &.sky-alert-info,
- &.sky-alert-success,
- &.sky-alert-warning,
- &.sky-alert-danger {
- &:before {
- font-family: FontAwesome;
- margin-left: -32px;
- margin-right: 19px;
- color: $sky-color-white;
- display: var(--sky-alert-before-display);
- }
- }
-
- &:not(.sky-alert-danger) {
- --sky-icon-stack-top-icon-color-override: #{$sky-text-color-default};
- }
-
- &.sky-alert-info {
- background-color: var(--sky-alert-info-background-color);
- border-color: var(--sky-highlight-color-info);
-
- &:before {
- content: '\f06a';
- margin-left: -31px;
- margin-right: 20px;
- }
- }
-
- &.sky-alert-success {
- background-color: var(--sky-background-color-success);
- border-color: var(--sky-highlight-color-success);
-
- &:before {
- content: '\f00c';
- }
- }
-
- &.sky-alert-warning {
- background-color: var(--sky-background-color-warning);
- border-color: var(--sky-highlight-color-warning);
-
- &:before {
- content: '\f071';
- }
- }
-
- &.sky-alert-danger {
- background-color: var(--sky-background-color-danger);
- border-color: var(--sky-highlight-color-danger);
-
- &:before {
- content: '\f071';
- }
- }
-}
-
-.sky-alert-close {
- cursor: pointer;
- font-weight: bold;
- line-height: 1;
- margin: 0;
- padding: 0;
- color: var(--sky-text-color-default);
- opacity: var(--sky-alert-close-btn-opacity);
- border: none;
- border-radius: var(--sky-alert-close-btn-border-radius);
- background-color: transparent;
- display: none;
- flex-shrink: 0;
-
- &:hover {
- opacity: 1;
- border: var(--sky-alert-close-btn-focus-border);
- }
-
- &:focus-visible {
- border: var(--sky-background-color-primary-dark) solid 2px;
- outline: none;
- }
-
- &:active {
- border: var(--sky-alert-close-btn-active-border);
- }
-
- &:focus-visible:not(:active) {
- box-shadow: var(--sky-alert-close-btn-focus-box-shadow);
- }
-}
-
-.sky-alert-closeable {
- .sky-alert-close {
- display: block;
- }
-}
-
-.sky-alert-icon-theme-default {
- display: var(--sky-alert-icon-default-display);
-}
-
-.sky-alert-icon-theme-modern {
- display: var(--sky-alert-icon-modern-display);
-}
-
-.sky-alert-info .sky-alert-icon-theme-modern {
- color: var(--sky-highlight-color-info);
-}
-
-.sky-alert-success .sky-alert-icon-theme-modern {
- color: var(--sky-highlight-color-success);
-}
-
-.sky-alert-warning .sky-alert-icon-theme-modern {
- color: var(--sky-highlight-color-warning);
-}
-
-.sky-alert-danger .sky-alert-icon-theme-modern {
- color: var(--sky-highlight-color-danger);
-}
diff --git a/libs/components/indicators/src/lib/modules/alert/alert.component.ts b/libs/components/indicators/src/lib/modules/alert/alert.component.ts
index 9b16670b08..9e8735b8bb 100644
--- a/libs/components/indicators/src/lib/modules/alert/alert.component.ts
+++ b/libs/components/indicators/src/lib/modules/alert/alert.component.ts
@@ -11,6 +11,7 @@ import {
import { SkyLogService } from '@skyux/core';
import { SkyLibResourcesService } from '@skyux/i18n';
import { SkyIconStackItem } from '@skyux/icon';
+import { SkyThemeComponentClassDirective } from '@skyux/theme';
import { Subscription } from 'rxjs';
@@ -22,8 +23,12 @@ const ALERT_TYPE_DEFAULT = 'warning';
@Component({
selector: 'sky-alert',
- styleUrls: ['./alert.component.scss'],
+ styleUrls: [
+ './alert.default.component.scss',
+ './alert.modern.component.scss',
+ ],
templateUrl: './alert.component.html',
+ hostDirectives: [SkyThemeComponentClassDirective],
})
export class SkyAlertComponent implements AfterViewChecked, OnInit, OnDestroy {
/**
diff --git a/libs/components/indicators/src/lib/modules/alert/alert.default.component.scss b/libs/components/indicators/src/lib/modules/alert/alert.default.component.scss
new file mode 100644
index 0000000000..c9e40e299b
--- /dev/null
+++ b/libs/components/indicators/src/lib/modules/alert/alert.default.component.scss
@@ -0,0 +1,132 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component-host('default') {
+ display: block;
+}
+
+@include mixins.sky-component('default', '.sky-alert') {
+ padding: 0 $sky-padding;
+ border-left: solid 30px;
+ color: var(--sky-text-color-default);
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .sky-alert-content {
+ padding: $sky-padding 0;
+ width: 100%;
+
+ ::ng-deep a {
+ color: change-color($sky-text-color-default, $alpha: 0.8);
+ text-decoration: underline;
+
+ &:hover {
+ color: var(--sky-text-color-default);
+ }
+ }
+ }
+
+ button {
+ margin-left: auto;
+ width: 32px;
+ height: 32px;
+ }
+
+ &.sky-alert-info,
+ &.sky-alert-success,
+ &.sky-alert-warning,
+ &.sky-alert-danger {
+ &:before {
+ font-family: FontAwesome;
+ margin-left: -32px;
+ margin-right: 19px;
+ color: $sky-color-white;
+ }
+ }
+
+ &:not(.sky-alert-danger) {
+ --sky-icon-stack-top-icon-color-override: #{$sky-text-color-default};
+ }
+
+ &.sky-alert-info {
+ background-color: var(--sky-background-color-info);
+ border-color: var(--sky-highlight-color-info);
+
+ &:before {
+ content: '\f06a';
+ margin-left: -31px;
+ margin-right: 20px;
+ }
+ }
+
+ &.sky-alert-success {
+ background-color: var(--sky-background-color-success);
+ border-color: var(--sky-highlight-color-success);
+
+ &:before {
+ content: '\f00c';
+ }
+ }
+
+ &.sky-alert-warning {
+ background-color: var(--sky-background-color-warning);
+ border-color: var(--sky-highlight-color-warning);
+
+ &:before {
+ content: '\f071';
+ }
+ }
+
+ &.sky-alert-danger {
+ background-color: var(--sky-background-color-danger);
+ border-color: var(--sky-highlight-color-danger);
+
+ &:before {
+ content: '\f071';
+ }
+ }
+
+ .sky-alert-close {
+ cursor: pointer;
+ font-weight: bold;
+ line-height: 1;
+ margin: 0;
+ padding: 0;
+ color: var(--sky-text-color-default);
+ opacity: 0.8;
+ border: none;
+ border-radius: $sky-border-radius;
+ background-color: transparent;
+ display: none;
+ flex-shrink: 0;
+
+ &:hover {
+ opacity: 1;
+ border: none;
+ }
+
+ &:focus-visible {
+ border: var(--sky-background-color-primary-dark) solid 2px;
+ outline: none;
+ }
+
+ &:active {
+ border: none;
+ }
+
+ &:focus-visible:not(:active) {
+ box-shadow: none;
+ }
+ }
+
+ &.sky-alert-closeable {
+ .sky-alert-close {
+ display: block;
+ }
+ }
+
+ .sky-alert-icon-theme-modern {
+ display: none;
+ }
+}
diff --git a/libs/components/indicators/src/lib/modules/alert/alert.modern.component.scss b/libs/components/indicators/src/lib/modules/alert/alert.modern.component.scss
new file mode 100644
index 0000000000..fc6775923a
--- /dev/null
+++ b/libs/components/indicators/src/lib/modules/alert/alert.modern.component.scss
@@ -0,0 +1,145 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component-host('modern') {
+ display: block;
+}
+
+@include mixins.sky-component('modern', '.sky-alert') {
+ padding: 0 $sky-padding;
+ border-left: solid 7px;
+ color: var(--sky-text-color-default);
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .sky-alert-content {
+ padding: var(--sky-padding-even-md);
+ width: 100%;
+
+ ::ng-deep a {
+ color: change-color($sky-text-color-default, $alpha: 0.8);
+ text-decoration: underline;
+
+ &:hover {
+ color: var(--sky-text-color-default);
+ }
+ }
+ }
+
+ button {
+ margin-left: auto;
+ width: 32px;
+ height: 32px;
+ }
+
+ &.sky-alert-info,
+ &.sky-alert-success,
+ &.sky-alert-warning,
+ &.sky-alert-danger {
+ &:before {
+ display: none;
+ }
+ }
+
+ &:not(.sky-alert-danger) {
+ --sky-icon-stack-top-icon-color-override: #{$sky-text-color-default};
+ }
+
+ &.sky-alert-info {
+ background-color: var(--sky-background-color-info-light);
+ border-color: var(--sky-highlight-color-info);
+
+ &:before {
+ content: '\f06a';
+ margin-left: -31px;
+ margin-right: 20px;
+ }
+ }
+
+ &.sky-alert-success {
+ background-color: var(--sky-background-color-success);
+ border-color: var(--sky-highlight-color-success);
+
+ &:before {
+ content: '\f00c';
+ }
+ }
+
+ &.sky-alert-warning {
+ background-color: var(--sky-background-color-warning);
+ border-color: var(--sky-highlight-color-warning);
+
+ &:before {
+ content: '\f071';
+ }
+ }
+
+ &.sky-alert-danger {
+ background-color: var(--sky-background-color-danger);
+ border-color: var(--sky-highlight-color-danger);
+
+ &:before {
+ content: '\f071';
+ }
+ }
+
+ .sky-alert-close {
+ cursor: pointer;
+ font-weight: bold;
+ line-height: 1;
+ margin: 0;
+ padding: 0;
+ color: var(--sky-text-color-default);
+ border: none;
+ border-radius: $sky-theme-modern-border-radius-md;
+ background-color: transparent;
+ display: none;
+ flex-shrink: 0;
+
+ &:hover {
+ opacity: 1;
+ border: var(--sky-background-color-primary-dark) solid 1px;
+ }
+
+ &:focus-visible {
+ border: var(--sky-background-color-primary-dark) solid 2px;
+ outline: none;
+ }
+
+ &:active {
+ border: var(--sky-background-color-primary-dark) solid 2px;
+ }
+
+ &:focus-visible:not(:active) {
+ box-shadow: $sky-theme-modern-elevation-1-shadow-size
+ $sky-theme-modern-elevation-3-shadow-color;
+ }
+ }
+
+ &.sky-alert-closeable {
+ .sky-alert-close {
+ display: block;
+ }
+ }
+
+ .sky-alert-icon-theme-modern {
+ display: block;
+ }
+
+ &.sky-alert-info .sky-alert-icon-theme-modern {
+ color: var(--sky-highlight-color-info);
+ }
+
+ &.sky-alert-success .sky-alert-icon-theme-modern {
+ color: var(--sky-highlight-color-success);
+ }
+
+ &.sky-alert-warning .sky-alert-icon-theme-modern {
+ color: var(--sky-highlight-color-warning);
+ }
+
+ &.sky-alert-danger .sky-alert-icon-theme-modern {
+ color: var(--sky-highlight-color-danger);
+ }
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-container.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-container.component.scss
deleted file mode 100644
index b4ed17f2a5..0000000000
--- a/libs/components/layout/src/lib/modules/action-button/action-button-container.component.scss
+++ /dev/null
@@ -1,98 +0,0 @@
-@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
-@use 'libs/components/theme/src/lib/styles/variables' as *;
-
-// Host element must be a block to allow FF to make proper getBoundingClientRect() calculations.
-:host {
- display: block;
-}
-
-.sky-action-button-flex {
- sky-action-button {
- display: block;
- }
-
- .sky-action-button {
- height: 100%;
- min-width: 236px;
- margin-left: $sky-margin;
- margin-right: $sky-margin;
- }
-}
-
-@include mixins.sky-host-responsive-container-xs-min(false) {
- .sky-action-button-flex {
- display: block;
- padding: 0;
- margin: calc(
- var(--sky-compat-action-button-flex-margin, $sky-margin-double) * -1
- )
- 0;
-
- sky-action-button {
- margin: $sky-margin-double 0;
- }
- }
-}
-
-@include mixins.sky-host-responsive-container-sm-min(false) {
- .sky-action-button-flex {
- display: flex;
- flex-flow: row wrap;
- padding: var(--sky-compat-action-button-flex-sm-padding, 0) 0;
- margin: calc(var(--sky-compat-action-button-flex-margin, $sky-margin) * -1)
- 0;
-
- &.sky-action-button-flex-align-center {
- justify-content: center;
- }
-
- &.sky-action-button-flex-align-left {
- justify-content: flex-start;
- }
-
- sky-action-button {
- margin: $sky-margin 0;
- }
- }
-}
-
-.sky-theme-modern {
- .sky-action-button-container {
- margin: 0 auto;
-
- // Grid based on the assumption that each action button is 446px wide with 30px inner margins.
- &.sky-action-button-container-sm {
- max-width: 446px; // 1 action button per row.
- }
-
- &.sky-action-button-container-md {
- max-width: 912px; // 2 action buttons per row.
- }
-
- &.sky-action-button-container-lg {
- max-width: 1378px; // 3 action buttons per row.
- }
-
- .sky-action-button-flex {
- .sky-action-button {
- height: auto;
- min-width: auto;
- margin: 0;
- }
- }
- }
-
- @include mixins.sky-host-responsive-container-xs-min(false) {
- .sky-action-button-flex {
- display: flex;
- flex-flow: row wrap;
- padding: 0;
- margin: 0 0 -20px -20px;
-
- sky-action-button {
- margin: 0 0 20px 20px;
- flex: 0 1 446px;
- }
- }
- }
-}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-container.component.ts b/libs/components/layout/src/lib/modules/action-button/action-button-container.component.ts
index 7345a70e25..9652cf9785 100644
--- a/libs/components/layout/src/lib/modules/action-button/action-button-container.component.ts
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-container.component.ts
@@ -14,7 +14,7 @@ import {
ViewEncapsulation,
} from '@angular/core';
import { SkyCoreAdapterService } from '@skyux/core';
-import { SkyThemeService } from '@skyux/theme';
+import { SkyThemeComponentClassDirective, SkyThemeService } from '@skyux/theme';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -29,10 +29,14 @@ import { SkyActionButtonContainerAlignItemsType } from './types/action-button-co
*/
@Component({
selector: 'sky-action-button-container',
- styleUrls: ['./action-button-container.component.scss'],
+ styleUrls: [
+ './action-button-container.default.component.scss',
+ './action-button-container.modern.component.scss',
+ ],
templateUrl: './action-button-container.component.html',
providers: [SkyActionButtonAdapterService],
encapsulation: ViewEncapsulation.None,
+ hostDirectives: [SkyThemeComponentClassDirective],
})
export class SkyActionButtonContainerComponent
implements AfterViewInit, OnDestroy, OnInit
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-container.default.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-container.default.component.scss
new file mode 100644
index 0000000000..e8dc7079e2
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-container.default.component.scss
@@ -0,0 +1,69 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+// Host element must be a block to allow FF to make proper getBoundingClientRect() calculations.
+@include mixins.sky-component-host('default') {
+ display: block;
+}
+
+@include mixins.sky-component('default', '.sky-action-button-container') {
+ .sky-action-button-flex {
+ sky-action-button {
+ display: block;
+ }
+
+ .sky-action-button {
+ height: 100%;
+ min-width: 236px;
+ margin-left: $sky-margin;
+ margin-right: $sky-margin;
+ }
+ }
+}
+
+@include mixins.sky-component(
+ 'default',
+ '.sky-action-button-container',
+ false,
+ 'xs'
+) {
+ .sky-action-button-flex {
+ display: block;
+ padding: 0;
+ margin: calc(
+ var(--sky-compat-action-button-flex-margin, $sky-margin-double) * -1
+ )
+ 0;
+
+ sky-action-button {
+ margin: $sky-margin-double 0;
+ }
+ }
+}
+
+@include mixins.sky-component(
+ 'default',
+ '.sky-action-button-container',
+ false,
+ 'sm'
+) {
+ .sky-action-button-flex {
+ display: flex;
+ flex-flow: row wrap;
+ padding: var(--sky-compat-action-button-flex-sm-padding, 0) 0;
+ margin: calc(var(--sky-compat-action-button-flex-margin, $sky-margin) * -1)
+ 0;
+
+ &.sky-action-button-flex-align-center {
+ justify-content: center;
+ }
+
+ &.sky-action-button-flex-align-left {
+ justify-content: flex-start;
+ }
+
+ sky-action-button {
+ margin: $sky-margin 0;
+ }
+ }
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-container.modern.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-container.modern.component.scss
new file mode 100644
index 0000000000..f37f0a1f93
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-container.modern.component.scss
@@ -0,0 +1,72 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+// Host element must be a block to allow FF to make proper getBoundingClientRect() calculations.
+@include mixins.sky-component-host('modern') {
+ display: block;
+}
+
+@include mixins.sky-component('modern', '.sky-action-button-container', false) {
+ margin: 0 auto;
+
+ // Grid based on the assumption that each action button is 446px wide with 30px inner margins.
+ &.sky-action-button-container-sm {
+ max-width: 446px; // 1 action button per row.
+ }
+
+ &.sky-action-button-container-md {
+ max-width: 912px; // 2 action buttons per row.
+ }
+
+ &.sky-action-button-container-lg {
+ max-width: 1378px; // 3 action buttons per row.
+ }
+
+ .sky-action-button-flex {
+ sky-action-button {
+ display: block;
+ }
+
+ .sky-action-button {
+ height: auto;
+ min-width: auto;
+ margin: 0;
+ }
+ }
+}
+
+@include mixins.sky-component(
+ 'modern',
+ '.sky-action-button-container',
+ false,
+ 'xs'
+) {
+ .sky-action-button-flex {
+ display: flex;
+ flex-flow: row wrap;
+ padding: 0;
+ margin: 0 0 -20px -20px;
+
+ sky-action-button {
+ margin: 0 0 20px 20px;
+ flex: 0 1 446px;
+ }
+ }
+}
+
+@include mixins.sky-component(
+ 'modern',
+ '.sky-action-button-container',
+ false,
+ 'sm'
+) {
+ .sky-action-button-flex {
+ &.sky-action-button-flex-align-center {
+ justify-content: center;
+ }
+
+ &.sky-action-button-flex-align-left {
+ justify-content: flex-start;
+ }
+ }
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-header.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-header.component.scss
deleted file mode 100644
index 31c49230ca..0000000000
--- a/libs/components/layout/src/lib/modules/action-button/action-button-header.component.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
-@use 'libs/components/theme/src/lib/styles/variables' as *;
-
-@include mixins.sky-host-responsive-container-xs-min() {
- .sky-action-button-header {
- margin: 0 $sky-margin-half;
- }
-}
-
-@include mixins.sky-host-responsive-container-sm-min() {
- .sky-action-button-header {
- margin: 0 0 $sky-margin-double;
- }
-}
-
-@include mixins.sky-theme-modern {
- .sky-action-button-header {
- margin: 0 0 $sky-theme-modern-space-sm 0;
- }
-}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-header.component.ts b/libs/components/layout/src/lib/modules/action-button/action-button-header.component.ts
index 48dd4c97de..45e25314f5 100644
--- a/libs/components/layout/src/lib/modules/action-button/action-button-header.component.ts
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-header.component.ts
@@ -1,11 +1,16 @@
import { Component } from '@angular/core';
+import { SkyThemeComponentClassDirective } from '@skyux/theme';
/**
* Specifies a header to display on an action button.
*/
@Component({
selector: 'sky-action-button-header',
- styleUrls: ['./action-button-header.component.scss'],
+ styleUrls: [
+ './action-button-header.default.component.scss',
+ './action-button-header.modern.component.scss',
+ ],
templateUrl: './action-button-header.component.html',
+ hostDirectives: [SkyThemeComponentClassDirective],
})
export class SkyActionButtonHeaderComponent {}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-header.default.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-header.default.component.scss
new file mode 100644
index 0000000000..e20c74c5e0
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-header.default.component.scss
@@ -0,0 +1,20 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component(
+ 'default',
+ '.sky-action-button-header',
+ true,
+ 'xs'
+) {
+ margin: 0 $sky-margin-half;
+}
+
+@include mixins.sky-component(
+ 'default',
+ '.sky-action-button-header',
+ true,
+ 'sm'
+) {
+ margin: 0 0 $sky-margin-double;
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-header.modern.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-header.modern.component.scss
new file mode 100644
index 0000000000..202b22aa14
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-header.modern.component.scss
@@ -0,0 +1,6 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('modern', '.sky-action-button-header') {
+ margin: 0 0 $sky-theme-modern-space-sm 0;
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.scss
deleted file mode 100644
index 4c526a6deb..0000000000
--- a/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
-@use 'libs/components/theme/src/lib/styles/variables' as *;
-
-@include mixins.sky-host-responsive-container-xs-min() {
- .sky-action-button-icon-container {
- margin: 0 $sky-margin-half;
- }
-}
-
-@include mixins.sky-host-responsive-container-sm-min() {
- .sky-action-button-icon-container {
- margin: 0 0 $sky-margin-double;
- }
-}
-
-.sky-action-button-icon {
- color: $sky-text-color-action-primary;
-}
-
-@include mixins.sky-theme-modern {
- .sky-action-button-icon-container {
- color: $sky-text-color-action-primary;
- background: $sky-color-blue-05;
- margin: 0 $sky-space-xl 0 0;
- border-radius: 50%;
- width: 42px;
- height: 42px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex: 0 0 auto;
-
- ::ng-deep .sky-icon {
- font-size: 24px !important;
- }
- }
-}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.ts b/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.ts
index 63d4670df0..a27e80c171 100644
--- a/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.ts
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-icon.component.ts
@@ -11,7 +11,10 @@ const FONTSIZECLASS_LARGE = '3x';
*/
@Component({
selector: 'sky-action-button-icon',
- styleUrls: ['./action-button-icon.component.scss'],
+ styleUrls: [
+ './action-button-icon.default.component.scss',
+ './action-button-icon.modern.component.scss',
+ ],
templateUrl: './action-button-icon.component.html',
})
export class SkyActionButtonIconComponent implements OnDestroy {
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-icon.default.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-icon.default.component.scss
new file mode 100644
index 0000000000..78dd4cb844
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-icon.default.component.scss
@@ -0,0 +1,24 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component(
+ 'default',
+ '.sky-action-button-icon-container',
+ true,
+ 'xs'
+) {
+ margin: 0 $sky-margin-half;
+
+ .sky-action-button-icon {
+ color: $sky-text-color-action-primary;
+ }
+}
+
+@include mixins.sky-component(
+ 'default',
+ '.sky-action-button-icon-container',
+ true,
+ 'sm'
+) {
+ margin: 0 0 $sky-margin-double;
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button-icon.modern.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button-icon.modern.component.scss
new file mode 100644
index 0000000000..3141b913fd
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button-icon.modern.component.scss
@@ -0,0 +1,22 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('modern', '.sky-action-button-icon-container') {
+ color: $sky-text-color-action-primary;
+ background: $sky-color-blue-05;
+ margin: 0 $sky-space-xl 0 0;
+ border-radius: 50%;
+ width: 42px;
+ height: 42px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ ::ng-deep .sky-icon {
+ font-size: 24px !important;
+ }
+
+ .sky-action-button-icon {
+ color: $sky-text-color-action-primary;
+ }
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button.component.scss
deleted file mode 100644
index 27c1633ecf..0000000000
--- a/libs/components/layout/src/lib/modules/action-button/action-button.component.scss
+++ /dev/null
@@ -1,73 +0,0 @@
-@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
-@use 'libs/components/theme/src/lib/styles/variables' as *;
-
-@include mixins.sky-host-responsive-container-xs-min(false) {
- .sky-action-button {
- --sky-action-button-padding: #{$sky-padding-double $sky-padding-double
- $sky-padding-triple};
- --sky-action-button-max-width: none;
- --sky-action-button-margin-bottom: #{$sky-margin-double};
- --sky-action-button-icon-header-container-flex-direction: unset;
- }
-}
-
-@include mixins.sky-host-responsive-container-sm-min(false) {
- .sky-action-button {
- --sky-action-button-padding: #{$sky-padding-triple $sky-padding-double};
- --sky-action-button-max-width: 236px;
- --sky-action-button-margin-bottom: 0;
- --sky-action-button-icon-header-container-flex-direction: column;
- }
-}
-
-.sky-action-button {
- @include mixins.sky-border(dark, top, bottom, left, right);
- cursor: pointer;
- text-align: center;
- text-decoration: none !important;
- display: block;
- padding: var(--sky-action-button-padding);
- max-width: var(--sky-action-button-max-width);
-
- &:hover {
- border-color: darken($sky-border-color-neutral-light, 12%);
- }
-}
-
-.sky-action-button-icon-header-container {
- display: flex;
- flex-direction: var(--sky-action-button-icon-header-container-flex-direction);
- justify-content: center;
- margin-bottom: var(--sky-action-button-margin-bottom);
-}
-
-.sky-theme-modern {
- .sky-action-button {
- display: flex;
- flex-flow: row nowrap;
- padding: $sky-theme-modern-padding-even-xl;
- text-align: left;
- border: none;
-
- .sky-action-button-content {
- flex: 1 1 auto;
- margin: 0 $sky-space-md 0 0;
- white-space: initial;
- }
- }
-
- @include mixins.sky-host-responsive-container-xs-min(false) {
- .sky-action-button {
- padding: $sky-theme-modern-padding-even-xl;
- margin: 0;
- max-width: 446px;
- }
- }
-
- @include mixins.sky-host-responsive-container-sm-min(false) {
- .sky-action-button {
- padding: $sky-theme-modern-padding-even-xl;
- margin: 0;
- }
- }
-}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button.component.ts b/libs/components/layout/src/lib/modules/action-button/action-button.component.ts
index c2dc705aa1..f61bf46ce5 100644
--- a/libs/components/layout/src/lib/modules/action-button/action-button.component.ts
+++ b/libs/components/layout/src/lib/modules/action-button/action-button.component.ts
@@ -17,7 +17,10 @@ import { SkyActionButtonPermalink } from './action-button-permalink';
*/
@Component({
selector: 'sky-action-button',
- styleUrls: ['./action-button.component.scss'],
+ styleUrls: [
+ './action-button.default.component.scss',
+ './action-button.modern.component.scss',
+ ],
templateUrl: './action-button.component.html',
encapsulation: ViewEncapsulation.None,
})
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button.default.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button.default.component.scss
new file mode 100644
index 0000000000..b334350a6d
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button.default.component.scss
@@ -0,0 +1,38 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('default', '.sky-action-button', false, 'xs') {
+ --sky-action-button-padding: #{$sky-padding-double $sky-padding-double
+ $sky-padding-triple};
+ --sky-action-button-max-width: none;
+ --sky-action-button-margin-bottom: #{$sky-margin-double};
+ --sky-action-button-icon-header-container-flex-direction: unset;
+
+ @include mixins.sky-border(dark, top, bottom, left, right);
+ cursor: pointer;
+ text-align: center;
+ text-decoration: none !important;
+ display: block;
+ padding: var(--sky-action-button-padding);
+ max-width: var(--sky-action-button-max-width);
+
+ &:hover {
+ border-color: darken($sky-border-color-neutral-light, 12%);
+ }
+
+ .sky-action-button-icon-header-container {
+ display: flex;
+ flex-direction: var(
+ --sky-action-button-icon-header-container-flex-direction
+ );
+ justify-content: center;
+ margin-bottom: var(--sky-action-button-margin-bottom);
+ }
+}
+
+@include mixins.sky-component('default', '.sky-action-button', false, 'sm') {
+ --sky-action-button-padding: #{$sky-padding-triple $sky-padding-double};
+ --sky-action-button-max-width: 236px;
+ --sky-action-button-margin-bottom: 0;
+ --sky-action-button-icon-header-container-flex-direction: column;
+}
diff --git a/libs/components/layout/src/lib/modules/action-button/action-button.modern.component.scss b/libs/components/layout/src/lib/modules/action-button/action-button.modern.component.scss
new file mode 100644
index 0000000000..0183740228
--- /dev/null
+++ b/libs/components/layout/src/lib/modules/action-button/action-button.modern.component.scss
@@ -0,0 +1,41 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('modern', '.sky-action-button', false, 'xs') {
+ --sky-action-button-margin-bottom: #{$sky-margin-double};
+ --sky-action-button-icon-header-container-flex-direction: unset;
+
+ cursor: pointer;
+ text-decoration: none !important;
+ max-width: 446px;
+ display: flex;
+ flex-flow: row nowrap;
+ padding: $sky-theme-modern-padding-even-xl;
+ text-align: left;
+ border: none;
+ margin: 0;
+
+ &:hover {
+ border-color: darken($sky-border-color-neutral-light, 12%);
+ }
+
+ .sky-action-button-content {
+ flex: 1 1 auto;
+ margin: 0 $sky-space-md 0 0;
+ white-space: initial;
+ }
+
+ .sky-action-button-icon-header-container {
+ display: flex;
+ flex-direction: var(
+ --sky-action-button-icon-header-container-flex-direction
+ );
+ justify-content: center;
+ margin-bottom: var(--sky-action-button-margin-bottom);
+ }
+}
+
+@include mixins.sky-component('modern', '.sky-action-button', false, 'sm') {
+ --sky-action-button-margin-bottom: 0;
+ --sky-action-button-icon-header-container-flex-direction: column;
+}
diff --git a/libs/components/theme/src/index.ts b/libs/components/theme/src/index.ts
index 3b108d0559..2b356838c0 100644
--- a/libs/components/theme/src/index.ts
+++ b/libs/components/theme/src/index.ts
@@ -4,6 +4,7 @@ export { SkyThemeIconManifestService } from './lib/icons/icon-manifest.service';
export { SkyAppStyleLoader } from './lib/style-loader';
export { SkyThemeModule } from './lib/theme.module';
export { SkyTheme } from './lib/theming/theme';
+export { SkyThemeComponentClassDirective } from './lib/theming/theme-component-class.directive';
export { SkyThemeMode } from './lib/theming/theme-mode';
export { SkyThemeSettings } from './lib/theming/theme-settings';
export { SkyThemeSettingsChange } from './lib/theming/theme-settings-change';
diff --git a/libs/components/theme/src/lib/styles/_public-api/_mixins.scss b/libs/components/theme/src/lib/styles/_public-api/_mixins.scss
index 1d6a13803e..1835acffb1 100644
--- a/libs/components/theme/src/lib/styles/_public-api/_mixins.scss
+++ b/libs/components/theme/src/lib/styles/_public-api/_mixins.scss
@@ -100,6 +100,114 @@
}
}
+@mixin sky-component($theme, $selector, $encapsulate: true, $breakpoint: '') {
+ @if $breakpoint == '' {
+ @include sky-component-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ } @else if $breakpoint == 'xs' {
+ @include sky-host-responsive-container-xs-min($encapsulate) {
+ @include sky-component-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ } @else if $breakpoint == 'sm' {
+ @include sky-host-responsive-container-sm-min($encapsulate) {
+ @include sky-component-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ } @else if $breakpoint == 'md' {
+ @include sky-host-responsive-container-md-min($encapsulate) {
+ @include sky-component-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ } @else if $breakpoint == 'lg' {
+ @include sky-host-responsive-container-lg-min($encapsulate) {
+ @include sky-component-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ }
+}
+@mixin sky-component-theme($theme, $selector, $encapsulate: true) {
+ @if $theme == 'default' {
+ #{$selector}:not(.sky-theme-modern *) {
+ @content;
+ }
+ } @else {
+ @if $encapsulate {
+ :host-context(.sky-theme-modern) #{$selector} {
+ @content;
+ }
+ } @else {
+ .sky-theme-modern #{$selector} {
+ @content;
+ }
+ }
+ }
+}
+
+@mixin sky-component-host(
+ $theme,
+ $selector: ':host',
+ $encapsulate: true,
+ $breakpoint: ''
+) {
+ @if $breakpoint == '' {
+ @include sky-component-host-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ } @else if $breakpoint == 'xs' {
+ @include sky-host-responsive-container-xs-min($encapsulate) {
+ @include sky-component-host-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ } @else if $breakpoint == 'sm' {
+ @include sky-host-responsive-container-sm-min($encapsulate) {
+ @include sky-component-host-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ } @else if $breakpoint == 'md' {
+ @include sky-host-responsive-container-md-min($encapsulate) {
+ @include sky-component-host-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ } @else if $breakpoint == 'lg' {
+ @include sky-host-responsive-container-lg-min($encapsulate) {
+ @include sky-component-host-theme($theme, $selector, $encapsulate) {
+ @content;
+ }
+ }
+ }
+}
+
+@mixin sky-component-host-theme(
+ $theme,
+ $selector: ':host',
+ $encapsulate: true
+) {
+ @if $theme == 'default' {
+ #{$selector}.sky-cmp-theme-default {
+ @content;
+ }
+ } @else {
+ @if $encapsulate {
+ :host-context(.sky-theme-modern) #{$selector}.sky-cmp-theme-modern {
+ @content;
+ }
+ } @else {
+ .sky-theme-modern #{$selector}.sky-cmp-theme-modern {
+ @content;
+ }
+ }
+ }
+}
+
@mixin sky-theme-modern {
:host-context(.sky-theme-modern) {
@content;
diff --git a/libs/components/theme/src/lib/styles/_switch.scss b/libs/components/theme/src/lib/styles/_switch.scss
index ccfd0dd6b9..97893aac8e 100644
--- a/libs/components/theme/src/lib/styles/_switch.scss
+++ b/libs/components/theme/src/lib/styles/_switch.scss
@@ -1,119 +1,119 @@
@use 'mixins' as mixins;
@use 'variables' as *;
-.sky-switch {
+@include mixins.sky-component('default', '.sky-switch') {
cursor: pointer;
display: inline-flex;
position: relative;
- &:hover .sky-switch-control {
- border-color: var(--sky-highlight-color-info);
- border-width: 2px;
- }
-}
-
-.sky-switch-disabled {
- cursor: default;
-
- input {
+ &.sky-switch-disabled {
cursor: default;
+
+ input {
+ cursor: default;
+ }
}
-}
-.sky-switch-input {
- border: 0;
- clip: rect(0 0 0 0);
- height: 1px;
- margin: -1px;
- overflow: hidden;
- padding: 0;
- position: absolute;
- width: 1px;
- outline: 0;
- -webkit-appearance: none;
-
- &.sky-switch-invalid + .sky-switch-control {
- @include mixins.sky-field-status('invalid');
+ &.sky-control-label-required {
+ .sky-switch-label {
+ margin-right: 0;
+ }
}
- &:checked:not(:disabled) + .sky-switch-control,
- &[type='checkbox']:indeterminate:not(:disabled) + .sky-switch-control {
- background-color: var(--sky-background-color-input-selected);
+ &:hover .sky-switch-control {
border-color: var(--sky-highlight-color-info);
border-width: 2px;
+ }
- &.sky-switch-control-success {
- background-color: lighten($sky-background-color-success, 10%);
- border-color: var(--sky-highlight-color-success);
+ .sky-switch-input {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ outline: 0;
+ -webkit-appearance: none;
+
+ &.sky-switch-invalid + .sky-switch-control {
+ @include mixins.sky-field-status('invalid');
}
- &.sky-switch-control-warning {
- background-color: lighten($sky-background-color-warning, 10%);
- border-color: var(--sky-highlight-color-warning);
+ &:checked:not(:disabled) + .sky-switch-control,
+ &[type='checkbox']:indeterminate:not(:disabled) + .sky-switch-control {
+ background-color: var(--sky-background-color-input-selected);
+ border-color: var(--sky-highlight-color-info);
+ border-width: 2px;
+
+ &.sky-switch-control-success {
+ background-color: lighten($sky-background-color-success, 10%);
+ border-color: var(--sky-highlight-color-success);
+ }
+
+ &.sky-switch-control-warning {
+ background-color: lighten($sky-background-color-warning, 10%);
+ border-color: var(--sky-highlight-color-warning);
+ }
+
+ &.sky-switch-control-danger {
+ background-color: lighten($sky-background-color-danger, 10%);
+ border-color: var(--sky-highlight-color-danger);
+ }
}
- &.sky-switch-control-danger {
- background-color: lighten($sky-background-color-danger, 10%);
- border-color: var(--sky-highlight-color-danger);
+ &:disabled + .sky-switch-control {
+ background-color: var(--sky-background-color-disabled);
}
- }
- &:disabled + .sky-switch-control {
- background-color: var(--sky-background-color-disabled);
- }
-
- &:focus + .sky-switch-control {
- @include mixins.sky-focus-outline;
+ &:focus + .sky-switch-control {
+ @include mixins.sky-focus-outline;
+ }
}
-}
-.sky-switch-control {
- width: var(--sky-switch-size);
- max-width: var(--sky-switch-size);
- height: var(--sky-switch-size);
- flex: 1 0 var(--sky-switch-size);
- margin: 0 var(--sky-switch-margin) auto auto;
- display: inline-flex;
- position: relative;
- border: 1px solid $sky-background-color-disabled;
- background-color: $sky-color-white;
- color: var(--sky-text-color-default);
- text-align: center;
- line-height: 1;
- align-items: center;
- justify-content: center;
-
- &.sky-switch-control-icon {
- max-width: none;
- width: 35px;
- height: 35px;
- flex: 1 0 35px;
- font-size: 18px;
- }
+ .sky-switch-control {
+ width: var(--sky-switch-size);
+ max-width: var(--sky-switch-size);
+ height: var(--sky-switch-size);
+ flex: 1 0 var(--sky-switch-size);
+ margin: 0 var(--sky-switch-margin) auto auto;
+ display: inline-flex;
+ position: relative;
+ border: 1px solid $sky-background-color-disabled;
+ background-color: $sky-color-white;
+ color: var(--sky-text-color-default);
+ text-align: center;
+ line-height: 1;
+ align-items: center;
+ justify-content: center;
+
+ &.sky-switch-control-icon {
+ max-width: none;
+ width: 35px;
+ height: 35px;
+ flex: 1 0 35px;
+ font-size: 18px;
+ }
- &::before {
- content: '';
+ &::before {
+ content: '';
+ }
}
-}
-
-.sky-switch-label {
- line-height: var(--sky-switch-size);
- flex: 1 1 auto;
- width: 100%;
- margin-right: $sky-margin;
- // Prevent truncated text from spilling out of bounds.
- // See: https://css-tricks.com/flexbox-truncated-text/
- min-width: 0;
-}
-
-.sky-control-label-required {
.sky-switch-label {
- margin-right: 0;
+ line-height: var(--sky-switch-size);
+ flex: 1 1 auto;
+ width: 100%;
+ margin-right: $sky-margin;
+
+ // Prevent truncated text from spilling out of bounds.
+ // See: https://css-tricks.com/flexbox-truncated-text/
+ min-width: 0;
}
}
-.sky-switch-icon-group {
+@include mixins.sky-component('default', '.sky-switch-icon-group') {
.sky-switch-control-icon {
margin-left: 0;
margin-right: 0;
diff --git a/libs/components/theme/src/lib/styles/themes/modern/_switch.scss b/libs/components/theme/src/lib/styles/themes/modern/_switch.scss
index b39e093122..aae867e8dd 100644
--- a/libs/components/theme/src/lib/styles/themes/modern/_switch.scss
+++ b/libs/components/theme/src/lib/styles/themes/modern/_switch.scss
@@ -1,102 +1,198 @@
@use '../../variables' as *;
+@use '../../mixins' as mixins;
-.sky-theme-modern {
- .sky-switch-input:not(:disabled) {
- &.sky-switch-invalid + .sky-switch-control {
- border: solid 2px var(--sky-highlight-color-danger);
- box-shadow: none;
- }
-
- &:checked + .sky-switch-control,
- &[type='checkbox']:indeterminate + .sky-switch-control,
- &:hover + .sky-switch-control {
- border: solid 1px var(--sky-background-color-primary-dark);
- }
-
- &:focus + .sky-switch-control,
- &:active + .sky-switch-control {
- border: solid 2px var(--sky-background-color-primary-dark);
- }
+@include mixins.sky-component('modern', '.sky-switch', false) {
+ cursor: pointer;
+ display: inline-flex;
+ position: relative;
- &:focus:not(:active) + .sky-switch-control {
- box-shadow: $sky-theme-modern-elevation-3-shadow-size
- $sky-theme-modern-elevation-3-shadow-color;
- outline: none;
- }
+ &.sky-switch-disabled {
+ cursor: not-allowed;
}
- .sky-switch-control {
- transition: $sky-form-border-and-color-transitions;
- border: 1px solid var(--sky-border-color-neutral-medium-dark);
+ &.sky-control-label-required {
+ .sky-switch-label {
+ margin-right: 0;
+ }
}
- .sky-switch-input:disabled + .sky-switch-control {
- background-color: $sky-theme-modern-background-color-neutral-medium;
+ &:hover .sky-switch-control {
+ border-color: var(--sky-highlight-color-info);
+ border-width: 2px;
}
- .sky-switch-disabled {
- cursor: not-allowed;
- }
+ .sky-switch-input {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ outline: 0;
+ -webkit-appearance: none;
- .sky-switch-icon-group {
- .sky-switch-control {
- background-color: transparent;
+ &.sky-switch-invalid + .sky-switch-control {
+ @include mixins.sky-field-status('invalid');
}
- .sky-switch-input {
- &:checked + .sky-switch-control,
- &[type='checkbox']:indeterminate + .sky-switch-control {
- background-color: var(--sky-background-color-input-selected);
+ &:checked:not(:disabled) + .sky-switch-control,
+ &[type='checkbox']:indeterminate:not(:disabled) + .sky-switch-control {
+ background-color: var(--sky-background-color-input-selected);
+
+ &.sky-switch-control-success {
+ background-color: lighten($sky-background-color-success, 10%);
+ }
+
+ &.sky-switch-control-warning {
+ background-color: lighten($sky-background-color-warning, 10%);
}
- &:disabled + .sky-switch-control {
- background-color: $sky-theme-modern-background-color-neutral-medium;
- border: none;
- color: var(--sky-text-color-deemphasized);
+ &.sky-switch-control-danger {
+ background-color: lighten($sky-background-color-danger, 10%);
+ }
+ }
+
+ &:disabled + .sky-switch-control {
+ background-color: var(--sky-background-color-disabled);
+ }
+
+ &:focus + .sky-switch-control {
+ @include mixins.sky-focus-outline;
+ }
+
+ &:not(:disabled) {
+ &.sky-switch-invalid + .sky-switch-control {
+ border: solid 2px var(--sky-highlight-color-danger);
+ box-shadow: none;
}
&:checked + .sky-switch-control,
- &:disabled:checked + .sky-switch-control,
&[type='checkbox']:indeterminate + .sky-switch-control,
- &[type='checkbox']:disabled:indeterminate + .sky-switch-control {
- color: var(--sky-background-color-primary-dark);
+ &:hover + .sky-switch-control {
+ border: solid 1px var(--sky-background-color-primary-dark);
}
- }
- .sky-switch-control {
- border: none;
- border-radius: $sky-theme-modern-box-border-radius-default;
+ &:focus + .sky-switch-control,
+ &:active + .sky-switch-control {
+ border: solid 2px var(--sky-background-color-primary-dark);
+ }
- &.sky-switch-control-icon {
- font-size: 20px;
- height: 40px;
- width: 40px;
+ &:focus:not(:active) + .sky-switch-control {
+ box-shadow: $sky-theme-modern-elevation-3-shadow-size
+ $sky-theme-modern-elevation-3-shadow-color;
+ outline: none;
}
}
+ }
+
+ .sky-switch-control {
+ width: var(--sky-switch-size);
+ max-width: var(--sky-switch-size);
+ height: var(--sky-switch-size);
+ flex: 1 0 var(--sky-switch-size);
+ margin: 0 var(--sky-switch-margin) auto auto;
+ display: inline-flex;
+ position: relative;
+ border: 1px solid var(--sky-border-color-neutral-medium-dark);
+ background-color: $sky-color-white;
+ color: var(--sky-text-color-default);
+ text-align: center;
+ line-height: 1;
+ align-items: center;
+ justify-content: center;
+ transition: $sky-form-border-and-color-transitions;
+
+ &.sky-switch-control-icon {
+ max-width: none;
+ width: 35px;
+ height: 35px;
+ flex: 1 0 35px;
+ font-size: 18px;
+ }
- sky-checkbox .sky-switch-control-icon {
- margin-right: $sky-theme-modern-space-sm;
+ &::before {
+ content: '';
}
+ }
- sky-checkbox:last-of-type .sky-switch-control-icon {
- margin-right: 0;
+ .sky-switch-label {
+ line-height: var(--sky-switch-size);
+ flex: 1 1 auto;
+ width: 100%;
+ margin-right: $sky-margin;
+
+ // Prevent truncated text from spilling out of bounds.
+ // See: https://css-tricks.com/flexbox-truncated-text/
+ min-width: 0;
+ }
+}
+
+@include mixins.sky-component('modern', '.sky-switch-icon-group', false) {
+ .sky-switch-control-icon {
+ margin-left: 0;
+ margin-right: 0;
+ border-radius: 0;
+ }
+
+ .sky-switch-control {
+ background-color: transparent;
+ }
+
+ .sky-switch-input {
+ &:checked + .sky-switch-control,
+ &[type='checkbox']:indeterminate + .sky-switch-control {
+ background-color: var(--sky-background-color-input-selected);
+ }
+
+ &:disabled + .sky-switch-control {
+ background-color: $sky-theme-modern-background-color-neutral-medium;
+ border: none;
+ color: var(--sky-text-color-deemphasized);
}
- sky-radio .sky-switch-control-icon {
- border-radius: $sky-theme-modern-box-border-radius-default;
+ &:checked + .sky-switch-control,
+ &:disabled:checked + .sky-switch-control,
+ &[type='checkbox']:indeterminate + .sky-switch-control,
+ &[type='checkbox']:disabled:indeterminate + .sky-switch-control {
+ color: var(--sky-background-color-primary-dark);
}
}
- &.sky-theme-mode-dark {
- .sky-switch-input:disabled + .sky-switch-control {
- background-color: $sky-theme-modern-mode-dark-background-color-elevation-3;
- border-color: $sky-theme-modern-mode-dark-border-color-neutral-medium;
- color: $sky-theme-modern-mode-dark-font-deemphasized-color;
+ .sky-switch-control {
+ border: none;
+ border-radius: $sky-theme-modern-box-border-radius-default;
+
+ &.sky-switch-control-icon {
+ font-size: 20px;
+ height: 40px;
+ width: 40px;
}
- .sky-switch-icon-group {
- .sky-switch-control {
- color: $sky-theme-modern-mode-dark-font-body-default-color;
- }
+ }
+
+ sky-checkbox .sky-switch-control-icon {
+ margin-right: $sky-theme-modern-space-sm;
+ }
+
+ sky-checkbox:last-of-type .sky-switch-control-icon {
+ margin-right: 0;
+ }
+
+ sky-radio .sky-switch-control-icon {
+ border-radius: $sky-theme-modern-box-border-radius-default;
+ }
+}
+
+.sky-theme-modern.sky-theme-mode-dark {
+ .sky-switch-input:disabled + .sky-switch-control {
+ background-color: $sky-theme-modern-mode-dark-background-color-elevation-3;
+ border-color: $sky-theme-modern-mode-dark-border-color-neutral-medium;
+ color: $sky-theme-modern-mode-dark-font-deemphasized-color;
+ }
+ .sky-switch-icon-group {
+ .sky-switch-control {
+ color: $sky-theme-modern-mode-dark-font-body-default-color;
}
}
}
diff --git a/libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.html b/libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.html
new file mode 100644
index 0000000000..8c64b41d2b
--- /dev/null
+++ b/libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.html
@@ -0,0 +1 @@
+
test component
diff --git a/libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.ts b/libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.ts
new file mode 100644
index 0000000000..b97318e02d
--- /dev/null
+++ b/libs/components/theme/src/lib/theming/fixtures/theme-component-class-test.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+import { SkyThemeComponentClassDirective } from '../theme-component-class.directive';
+
+@Component({
+ selector: 'app-theme-component-class-test',
+ templateUrl: './theme-component-class-test.component.html',
+ standalone: true,
+ hostDirectives: [SkyThemeComponentClassDirective],
+})
+export class SkyThemeComponentClassTestComponent {}
diff --git a/libs/components/theme/src/lib/theming/theme-component-class.directive.spec.ts b/libs/components/theme/src/lib/theming/theme-component-class.directive.spec.ts
new file mode 100644
index 0000000000..dac850b219
--- /dev/null
+++ b/libs/components/theme/src/lib/theming/theme-component-class.directive.spec.ts
@@ -0,0 +1,90 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@skyux-sdk/testing';
+
+import { BehaviorSubject } from 'rxjs';
+
+import { SkyThemeModule } from '../theme.module';
+
+import { MockThemeService } from './fixtures/mock-theme.service';
+import { SkyThemeComponentClassTestComponent } from './fixtures/theme-component-class-test.component';
+import { SkyTheme } from './theme';
+import { SkyThemeMode } from './theme-mode';
+import { SkyThemeSettings } from './theme-settings';
+import { SkyThemeSettingsChange } from './theme-settings-change';
+import { SkyThemeService } from './theme.service';
+
+const DEFAULT_THEME = new SkyThemeSettings(
+ SkyTheme.presets.default,
+ SkyThemeMode.presets.light,
+);
+const MODERN_THEME = new SkyThemeSettings(
+ SkyTheme.presets.modern,
+ SkyThemeMode.presets.light,
+);
+
+describe('ThemeComponentClass directive', () => {
+ //#region helpers
+ async function changeTheme(
+ fixture: ComponentFixture,
+ mockThemeSvc: MockThemeService,
+ theme: SkyThemeSettings,
+ ): Promise {
+ mockThemeSvc.settingsChange!.next({
+ currentSettings: theme,
+ previousSettings: mockThemeSvc.settingsChange!.getValue().currentSettings,
+ });
+ fixture.detectChanges();
+ await fixture.whenStable();
+ return;
+ }
+ //#endregion
+
+ describe('without SkyThemeService provider', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [SkyThemeComponentClassTestComponent, SkyThemeModule],
+ });
+ fixture = TestBed.createComponent(SkyThemeComponentClassTestComponent);
+ fixture.detectChanges();
+ await fixture.whenStable();
+ });
+
+ it('should default to the default class', () => {
+ expect(fixture.nativeElement).toHaveClass('sky-cmp-theme-default');
+ });
+ });
+
+ describe('with SkyThemeService provider', () => {
+ let fixture: ComponentFixture;
+ let mockThemeSvc: MockThemeService;
+
+ beforeEach(async () => {
+ mockThemeSvc = new MockThemeService();
+ mockThemeSvc.settingsChange = new BehaviorSubject(
+ {
+ currentSettings: DEFAULT_THEME,
+ previousSettings: undefined,
+ },
+ );
+
+ TestBed.configureTestingModule({
+ imports: [SkyThemeComponentClassTestComponent, SkyThemeModule],
+ providers: [{ provide: SkyThemeService, useValue: mockThemeSvc }],
+ });
+ fixture = TestBed.createComponent(SkyThemeComponentClassTestComponent);
+ fixture.detectChanges();
+ await fixture.whenStable();
+ });
+
+ it('should have the default theme class when theme is default', () => {
+ expect(fixture.nativeElement).toHaveClass('sky-cmp-theme-default');
+ });
+
+ it('should have the modern theme class when theme is modern', async () => {
+ await changeTheme(fixture, mockThemeSvc, MODERN_THEME);
+ expect(fixture.nativeElement).toHaveClass('sky-cmp-theme-modern');
+ });
+ });
+});
diff --git a/libs/components/theme/src/lib/theming/theme-component-class.directive.ts b/libs/components/theme/src/lib/theming/theme-component-class.directive.ts
new file mode 100644
index 0000000000..07ad675cbc
--- /dev/null
+++ b/libs/components/theme/src/lib/theming/theme-component-class.directive.ts
@@ -0,0 +1,30 @@
+import {
+ ChangeDetectorRef,
+ Directive,
+ HostBinding,
+ inject,
+} from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+
+import { SkyThemeService } from '../theming/theme.service';
+
+@Directive({
+ selector: '[skyThemeClass]',
+ standalone: true,
+})
+export class SkyThemeComponentClassDirective {
+ @HostBinding('class')
+ public theme = 'sky-cmp-theme-default';
+
+ #changeDetector = inject(ChangeDetectorRef);
+ #themeService = inject(SkyThemeService, { optional: true });
+
+ constructor() {
+ this.#themeService?.settingsChange
+ .pipe(takeUntilDestroyed())
+ .subscribe((change) => {
+ this.theme = `sky-cmp-theme-${change.currentSettings.theme.name}`;
+ this.#changeDetector.markForCheck();
+ });
+ }
+}
From 26c36f5d745315fcd79c8a1392455fdf5735e1a4 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Tue, 2 Jul 2024 14:18:23 -0400
Subject: [PATCH 03/95] fix(code-examples): satisfy ESLint rules for action
button and AG Grid code examples (#2423)
---
apps/code-examples/src/app/app.component.ts | 2 +-
apps/code-examples/src/app/app.module.ts | 2 +-
.../basic/context-menu.component.ts | 18 +++++-----
.../ag-grid/data-entry-grid/basic/data.ts | 2 +-
.../data-entry-grid/basic/demo.component.ts | 19 +++++++----
.../basic/edit-modal.component.ts | 23 ++++++-------
.../context-menu.component.ts | 8 +++--
.../data-manager-added/demo.component.ts | 4 +--
.../edit-modal.component.ts | 23 ++++++-------
.../filter-modal.component.ts | 10 +++---
.../data-manager-added/filters.ts | 4 +++
.../data-manager-added/view-grid.component.ts | 27 +++++++++------
.../inline-help/context-menu.component.ts | 8 +++--
.../data-entry-grid/inline-help/data.ts | 2 +-
.../inline-help/demo.component.ts | 18 ++++++----
.../inline-help/edit-modal.component.ts | 23 ++++++-------
.../context-menu.component.ts | 8 +++--
.../basic-multiselect/demo.component.ts | 16 ++++++---
.../data-grid/basic/context-menu.component.ts | 8 +++--
.../ag-grid/data-grid/basic/demo.component.ts | 18 ++++++----
.../context-menu.component.ts | 8 +++--
.../demo.component.ts | 3 +-
.../filter-modal.component.ts | 10 +++---
.../data-manager-multiselect/filters.ts | 4 +++
.../view-grid.component.ts | 27 +++++++++------
.../data-manager/context-menu.component.ts | 8 +++--
.../data-grid/data-manager/demo.component.ts | 3 +-
.../data-manager/filter-modal.component.ts | 10 +++---
.../ag-grid/data-grid/data-manager/filters.ts | 4 +++
.../data-manager/view-grid.component.ts | 30 ++++++++++------
.../inline-help/context-menu.component.ts | 8 +++--
.../data-grid/inline-help/demo.component.ts | 12 ++++---
.../paging/context-menu.component.ts | 8 +++--
.../data-grid/paging/demo.component.ts | 34 +++++++++++--------
.../top-scroll/context-menu.component.ts | 8 +++--
.../data-grid/top-scroll/demo.component.ts | 16 ++++++---
.../data-manager/basic/demo.component.ts | 3 +-
.../basic/filter-modal.component.ts | 11 +++---
.../data-manager/basic/filters.ts | 4 +++
.../data-manager/basic/view-grid.component.ts | 19 ++++++-----
.../basic/view-repeater.component.ts | 30 +++++++++-------
apps/code-examples/src/main.ts | 5 ++-
42 files changed, 312 insertions(+), 196 deletions(-)
create mode 100644 apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filters.ts
create mode 100644 apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filters.ts
create mode 100644 apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filters.ts
create mode 100644 apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filters.ts
diff --git a/apps/code-examples/src/app/app.component.ts b/apps/code-examples/src/app/app.component.ts
index 5103ddc744..767af5402a 100644
--- a/apps/code-examples/src/app/app.component.ts
+++ b/apps/code-examples/src/app/app.component.ts
@@ -29,7 +29,7 @@ export class AppComponent {
});
const themeSettings = new SkyThemeSettings(
- SkyTheme.presets['modern'],
+ SkyTheme.presets.modern,
SkyThemeMode.presets.light,
);
diff --git a/apps/code-examples/src/app/app.module.ts b/apps/code-examples/src/app/app.module.ts
index 56715ac932..00210979ee 100644
--- a/apps/code-examples/src/app/app.module.ts
+++ b/apps/code-examples/src/app/app.module.ts
@@ -8,7 +8,7 @@ import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
- imports: [BrowserAnimationsModule, BrowserModule, AppRoutingModule],
+ imports: [AppRoutingModule, BrowserAnimationsModule, BrowserModule],
providers: [SkyThemeService],
bootstrap: [AppComponent],
})
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/context-menu.component.ts
index 346476d03d..2539b1f86a 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -12,15 +14,15 @@ import { ICellRendererParams } from 'ag-grid-community';
imports: [SkyDropdownModule],
})
export class ContextMenuComponent implements ICellRendererAngularComp {
- public contextMenuAriaLabel = '';
- public deleteAriaLabel = '';
- public markInactiveAriaLabel = '';
- public moreInfoAriaLabel = '';
+ protected contextMenuAriaLabel = '';
+ protected deleteAriaLabel = '';
+ protected markInactiveAriaLabel = '';
+ protected moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
@@ -32,6 +34,6 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
}
protected actionClicked(action: string): void {
- alert(`${action} clicked for ${this.#name}`);
+ console.error(`${action} clicked for ${this.#name}`);
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/data.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/data.ts
index 69167d2b8c..c19a452159 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/data.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/data.ts
@@ -95,7 +95,7 @@ export interface AgGridDemoRow {
jobTitle?: AutocompleteOption;
}
-export const AG_GRID_DEMO_DATA = [
+export const AG_GRID_DEMO_DATA: AgGridDemoRow[] = [
{
selected: true,
name: 'Billy Bob',
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/demo.component.ts
index ec5cac36b3..fcc5116316 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/demo.component.ts
@@ -85,7 +85,8 @@ export class DemoComponent {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -127,7 +128,9 @@ export class DemoComponent {
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
};
this.gridOptions = this.#agGridSvc.getEditableGridOptions({
@@ -165,7 +168,7 @@ export class DemoComponent {
if (result.reason === 'cancel' || result.reason === 'close') {
alert('Edits canceled!');
} else {
- this.gridData = result.data;
+ this.gridData = result.data as AgGridDemoRow[];
if (this.#gridApi) {
this.#gridApi.refreshCells();
@@ -192,11 +195,13 @@ export class DemoComponent {
}
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
-
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/edit-modal.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/edit-modal.component.ts
index 30c5ebe6c3..159e729388 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/edit-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/basic/edit-modal.component.ts
@@ -96,7 +96,7 @@ export class EditModalComponent {
type: SkyCellType.Date,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridDatepickerProperties } => {
return { skyComponentProperties: { minDate: params.data.startDate } };
},
@@ -107,7 +107,7 @@ export class EditModalComponent {
type: SkyCellType.Autocomplete,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridAutocompleteProperties } => {
return {
skyComponentProperties: {
@@ -132,12 +132,9 @@ export class EditModalComponent {
type: SkyCellType.Autocomplete,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridAutocompleteProperties } => {
- const selectedDepartment: string =
- params.data &&
- params.data.department &&
- params.data.department.name;
+ const selectedDepartment = params.data?.department?.name;
const editParams: {
skyComponentProperties: SkyAgGridAutocompleteProperties;
@@ -178,7 +175,9 @@ export class EditModalComponent {
this.gridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
};
this.gridOptions = this.#agGridSvc.getEditableGridOptions({
@@ -196,15 +195,15 @@ export class EditModalComponent {
#departmentSelectionChange(
change: SkyAutocompleteSelectionChange,
- node: IRowNode,
+ node: IRowNode,
): void {
- if (change.selectedItem && change.selectedItem !== node.data.department) {
+ if (change.selectedItem && change.selectedItem !== node.data?.department) {
this.#clearJobTitle(node);
}
}
- #clearJobTitle(node: IRowNode | null): void {
- if (node) {
+ #clearJobTitle(node: IRowNode | null): void {
+ if (node?.data) {
node.data.jobTitle = undefined;
this.#changeDetectorRef.markForCheck();
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/demo.component.ts
index 8663a5b340..adeeeaf44e 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/demo.component.ts
@@ -15,7 +15,7 @@ import { SkyModalConfigurationInterface, SkyModalService } from '@skyux/modals';
import { Subject, takeUntil } from 'rxjs';
-import { AG_GRID_DEMO_DATA } from './data';
+import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
import { EditModalContext } from './edit-modal-context';
import { EditModalComponent } from './edit-modal.component';
import { FilterModalComponent } from './filter-modal.component';
@@ -130,7 +130,7 @@ export class DemoComponent implements OnInit, OnDestroy {
if (result.reason === 'cancel' || result.reason === 'close') {
alert('Edits canceled!');
} else {
- this.items = result.data;
+ this.items = result.data as AgGridDemoRow[];
alert('Saving data!');
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/edit-modal.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/edit-modal.component.ts
index 6a6e427253..ecb1ec7b1b 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/edit-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/edit-modal.component.ts
@@ -71,7 +71,7 @@ export class EditModalComponent {
type: SkyCellType.Date,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridDatepickerProperties } => {
return {
skyComponentProperties: {
@@ -86,7 +86,7 @@ export class EditModalComponent {
type: SkyCellType.Autocomplete,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridAutocompleteProperties } => {
return {
skyComponentProperties: {
@@ -109,12 +109,9 @@ export class EditModalComponent {
type: SkyCellType.Autocomplete,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridAutocompleteProperties } => {
- const selectedDepartment: string =
- params.data &&
- params.data.department &&
- params.data.department.name;
+ const selectedDepartment = params.data?.department?.name;
const editParams: {
skyComponentProperties: SkyAgGridAutocompleteProperties;
@@ -155,7 +152,9 @@ export class EditModalComponent {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
};
this.gridOptions = this.#agGridService.getEditableGridOptions({
@@ -173,15 +172,15 @@ export class EditModalComponent {
#departmentSelectionChange(
change: SkyAutocompleteSelectionChange,
- node: IRowNode,
+ node: IRowNode,
): void {
- if (change.selectedItem && change.selectedItem !== node.data.department) {
+ if (change.selectedItem && change.selectedItem !== node.data?.department) {
this.#clearJobTitle(node);
}
}
- #clearJobTitle(node: IRowNode | null): void {
- if (node) {
+ #clearJobTitle(node: IRowNode | null): void {
+ if (node?.data) {
node.data.jobTitle = undefined;
if (this.#gridApi) {
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filter-modal.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filter-modal.component.ts
index d6e436fc20..ff896f8d7c 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filter-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filter-modal.component.ts
@@ -13,6 +13,8 @@ import {
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
+import { Filters } from './filters';
+
@Component({
standalone: true,
selector: 'app-filter-modal',
@@ -30,10 +32,10 @@ export class FilterModalComponent {
constructor() {
if (this.#context.filterData?.filters) {
- const filters = this.#context.filterData.filters;
+ const filters = this.#context.filterData.filters as Filters;
- this.jobTitle = filters.jobTitle || 'any';
- this.hideSales = filters.hideSales || false;
+ this.jobTitle = filters.jobTitle ?? 'any';
+ this.hideSales = filters.hideSales ?? false;
}
this.#changeDetector.markForCheck();
@@ -46,7 +48,7 @@ export class FilterModalComponent {
result.filters = {
jobTitle: this.jobTitle,
hideSales: this.hideSales,
- };
+ } satisfies Filters;
this.#changeDetector.markForCheck();
this.#instance.save(result);
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filters.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filters.ts
new file mode 100644
index 0000000000..bbf4a0fe8d
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/filters.ts
@@ -0,0 +1,4 @@
+export interface Filters {
+ jobTitle?: string;
+ hideSales?: boolean;
+}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/view-grid.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/view-grid.component.ts
index 8f67dc8840..68fe4a5b01 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/view-grid.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/data-manager-added/view-grid.component.ts
@@ -28,6 +28,7 @@ import { Subject, of, takeUntil } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AgGridDemoRow } from './data';
+import { Filters } from './filters';
@Component({
standalone: true,
@@ -85,7 +86,8 @@ export class ViewGridComponent implements OnInit, OnDestroy {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -238,16 +240,21 @@ export class ViewGridComponent implements OnInit, OnDestroy {
this.#changeDetectorRef.markForCheck();
}
- protected onRowSelected(rowSelectedEvent: RowSelectedEvent): void {
- if (!rowSelectedEvent.data.selected) {
+ protected onRowSelected(
+ rowSelectedEvent: RowSelectedEvent,
+ ): void {
+ if (!rowSelectedEvent.data?.selected) {
this.#updateData();
}
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
@@ -256,11 +263,11 @@ export class ViewGridComponent implements OnInit, OnDestroy {
const filterData = this.#dataState.filterData;
if (filterData?.filters) {
- const filters = filterData.filters;
+ const filters = filterData.filters as Filters;
filteredItems = items.filter((item) => {
return (
- ((filters.hideSales && item.department.name !== 'Sales') ||
+ (!!(filters.hideSales && item.department.name !== 'Sales') ||
!filters.hideSales) &&
((filters.jobTitle !== 'any' &&
item.jobTitle?.name === filters.jobTitle) ||
@@ -286,7 +293,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
property === 'name'
) {
const propertyText = item[property]?.toLowerCase();
- if (propertyText.indexOf(searchText) > -1) {
+ if (propertyText.includes(searchText)) {
return true;
}
}
@@ -301,7 +308,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
#setInitialColumnOrder(): void {
const viewState = this.#dataState.getViewStateById(this.#viewId);
- const visibleColumns = viewState?.displayedColumnIds || [];
+ const visibleColumns = viewState?.displayedColumnIds ?? [];
this.#columnDefs.sort((col1, col2) => {
const col1Index = visibleColumns.findIndex(
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/data.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/data.ts
index 69167d2b8c..c19a452159 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/data.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/data.ts
@@ -95,7 +95,7 @@ export interface AgGridDemoRow {
jobTitle?: AutocompleteOption;
}
-export const AG_GRID_DEMO_DATA = [
+export const AG_GRID_DEMO_DATA: AgGridDemoRow[] = [
{
selected: true,
name: 'Billy Bob',
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/demo.component.ts
index 8df9c2d6a3..56bfe1ad47 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/demo.component.ts
@@ -99,7 +99,8 @@ export class DemoComponent {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
@@ -155,7 +156,9 @@ export class DemoComponent {
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
};
this.gridOptions = this.#agGridSvc.getEditableGridOptions({
@@ -192,7 +195,7 @@ export class DemoComponent {
if (result.reason === 'cancel' || result.reason === 'close') {
alert('Edits canceled!');
} else {
- this.gridData = result.data;
+ this.gridData = result.data as AgGridDemoRow[];
this.#gridApi?.refreshCells();
alert('Saving data!');
@@ -215,10 +218,13 @@ export class DemoComponent {
}
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/edit-modal.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/edit-modal.component.ts
index 04ea2ec9b6..09565ddc57 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/edit-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-entry-grid/inline-help/edit-modal.component.ts
@@ -95,7 +95,7 @@ export class EditModalComponent {
type: SkyCellType.Date,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridDatepickerProperties } => {
return { skyComponentProperties: { minDate: params.data.startDate } };
},
@@ -106,7 +106,7 @@ export class EditModalComponent {
type: SkyCellType.Autocomplete,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridAutocompleteProperties } => {
return {
skyComponentProperties: {
@@ -129,12 +129,9 @@ export class EditModalComponent {
type: SkyCellType.Autocomplete,
editable: true,
cellEditorParams: (
- params: ICellEditorParams,
+ params: ICellEditorParams,
): { skyComponentProperties: SkyAgGridAutocompleteProperties } => {
- const selectedDepartment: string =
- params.data &&
- params.data.department &&
- params.data.department.name;
+ const selectedDepartment = params.data?.department?.name;
const editParams: {
skyComponentProperties: SkyAgGridAutocompleteProperties;
} = { skyComponentProperties: { data: [] } };
@@ -174,7 +171,9 @@ export class EditModalComponent {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
};
this.gridOptions = this.#agGridSvc.getEditableGridOptions({
@@ -192,15 +191,15 @@ export class EditModalComponent {
#departmentSelectionChange(
change: SkyAutocompleteSelectionChange,
- node: IRowNode,
+ node: IRowNode,
): void {
- if (change.selectedItem && change.selectedItem !== node.data.department) {
+ if (change.selectedItem && change.selectedItem !== node.data?.department) {
this.#clearJobTitle(node);
}
}
- #clearJobTitle(node: IRowNode | null): void {
- if (node) {
+ #clearJobTitle(node: IRowNode | null): void {
+ if (node?.data) {
node.data.jobTitle = undefined;
this.#changeDetectorRef.markForCheck();
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/demo.component.ts
index 3a3dac0c82..ee0a910475 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic-multiselect/demo.component.ts
@@ -62,7 +62,8 @@ export class DemoComponent {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -83,7 +84,9 @@ export class DemoComponent {
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
rowSelection: 'multiple',
};
@@ -97,10 +100,13 @@ export class DemoComponent {
this.#gridApi.sizeColumnsToFit();
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/demo.component.ts
index f34586edbe..dc35e1cf5f 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/basic/demo.component.ts
@@ -12,7 +12,7 @@ import {
} from 'ag-grid-community';
import { ContextMenuComponent } from './context-menu.component';
-import { AG_GRID_DEMO_DATA } from './data';
+import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
@Component({
standalone: true,
@@ -53,7 +53,8 @@ export class DemoComponent {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -74,7 +75,9 @@ export class DemoComponent {
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
rowSelection: 'single',
};
@@ -88,10 +91,13 @@ export class DemoComponent {
this.#gridApi.sizeColumnsToFit();
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/demo.component.ts
index 6f71036596..5d26068f41 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/demo.component.ts
@@ -17,6 +17,7 @@ import { Subject, takeUntil } from 'rxjs';
import { AG_GRID_DEMO_DATA } from './data';
import { FilterModalComponent } from './filter-modal.component';
+import { Filters } from './filters';
import { ViewGridComponent } from './view-grid.component';
@Component({
@@ -54,7 +55,7 @@ export class DemoComponent implements OnInit, OnDestroy {
filters: {
hideSales: false,
jobTitle: 'any',
- },
+ } satisfies Filters,
},
views: [
{
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filter-modal.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filter-modal.component.ts
index b6663f960a..28109d2ee9 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filter-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filter-modal.component.ts
@@ -13,6 +13,8 @@ import {
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
+import { Filters } from './filters';
+
@Component({
standalone: true,
selector: 'app-filter-modal',
@@ -30,10 +32,10 @@ export class FilterModalComponent {
constructor() {
if (this.#context.filterData && this.#context.filterData.filters) {
- const filters = this.#context.filterData.filters;
+ const filters = this.#context.filterData.filters as Filters;
- this.jobTitle = filters.jobTitle || 'any';
- this.hideSales = filters.hideSales || false;
+ this.jobTitle = filters.jobTitle ?? 'any';
+ this.hideSales = filters.hideSales ?? false;
}
this.#changeDetectorRef.markForCheck();
@@ -46,7 +48,7 @@ export class FilterModalComponent {
result.filters = {
jobTitle: this.jobTitle,
hideSales: this.hideSales,
- };
+ } satisfies Filters;
this.#changeDetectorRef.markForCheck();
this.#instance.save(result);
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filters.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filters.ts
new file mode 100644
index 0000000000..bbf4a0fe8d
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/filters.ts
@@ -0,0 +1,4 @@
+export interface Filters {
+ jobTitle?: string;
+ hideSales?: boolean;
+}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/view-grid.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/view-grid.component.ts
index a873e21bbc..e6525d4928 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/view-grid.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager-multiselect/view-grid.component.ts
@@ -31,6 +31,7 @@ import { Subject, of, takeUntil } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AgGridDemoRow } from './data';
+import { Filters } from './filters';
@Component({
standalone: true,
@@ -85,7 +86,8 @@ export class ViewGridComponent implements OnInit, OnDestroy {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -210,8 +212,10 @@ export class ViewGridComponent implements OnInit, OnDestroy {
this.#updateDisplayedItems();
}
- protected onRowSelected(rowSelectedEvent: RowSelectedEvent): void {
- if (!rowSelectedEvent.data.selected) {
+ protected onRowSelected(
+ rowSelectedEvent: RowSelectedEvent,
+ ): void {
+ if (!rowSelectedEvent.data?.selected) {
this.#updateDisplayedItems();
}
}
@@ -230,7 +234,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
property === 'name'
) {
const propertyText = item[property].toLowerCase();
- if (propertyText.indexOf(searchText) > -1) {
+ if (propertyText.includes(searchText)) {
return true;
}
}
@@ -247,12 +251,12 @@ export class ViewGridComponent implements OnInit, OnDestroy {
let filteredItems = items;
const filterData = this.#dataState && this.#dataState.filterData;
- if (filterData && filterData.filters) {
- const filters = filterData.filters;
+ if (filterData?.filters) {
+ const filters = filterData.filters as Filters;
filteredItems = items.filter((item) => {
return (
- ((filters.hideSales && item.department.name !== 'Sales') ||
+ (!!(filters.hideSales && item.department.name !== 'Sales') ||
!filters.hideSales) &&
((filters.jobTitle !== 'any' &&
item.jobTitle?.name === filters.jobTitle) ||
@@ -265,10 +269,13 @@ export class ViewGridComponent implements OnInit, OnDestroy {
return filteredItems;
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/demo.component.ts
index cdd5ae997d..fa35ea2ccb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/demo.component.ts
@@ -16,6 +16,7 @@ import { Subject, takeUntil } from 'rxjs';
import { AG_GRID_DEMO_DATA } from './data';
import { FilterModalComponent } from './filter-modal.component';
+import { Filters } from './filters';
import { ViewGridComponent } from './view-grid.component';
@Component({
@@ -55,7 +56,7 @@ export class DemoComponent implements OnInit, OnDestroy {
filters: {
hideSales: false,
jobTitle: 'any',
- },
+ } satisfies Filters,
},
views: [
{
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filter-modal.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filter-modal.component.ts
index b6663f960a..28109d2ee9 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filter-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filter-modal.component.ts
@@ -13,6 +13,8 @@ import {
import { SkyCheckboxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
+import { Filters } from './filters';
+
@Component({
standalone: true,
selector: 'app-filter-modal',
@@ -30,10 +32,10 @@ export class FilterModalComponent {
constructor() {
if (this.#context.filterData && this.#context.filterData.filters) {
- const filters = this.#context.filterData.filters;
+ const filters = this.#context.filterData.filters as Filters;
- this.jobTitle = filters.jobTitle || 'any';
- this.hideSales = filters.hideSales || false;
+ this.jobTitle = filters.jobTitle ?? 'any';
+ this.hideSales = filters.hideSales ?? false;
}
this.#changeDetectorRef.markForCheck();
@@ -46,7 +48,7 @@ export class FilterModalComponent {
result.filters = {
jobTitle: this.jobTitle,
hideSales: this.hideSales,
- };
+ } satisfies Filters;
this.#changeDetectorRef.markForCheck();
this.#instance.save(result);
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filters.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filters.ts
new file mode 100644
index 0000000000..bbf4a0fe8d
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/filters.ts
@@ -0,0 +1,4 @@
+export interface Filters {
+ jobTitle?: string;
+ hideSales?: boolean;
+}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/view-grid.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/view-grid.component.ts
index 42d5a82e2b..e7d272c08e 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/view-grid.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/data-manager/view-grid.component.ts
@@ -30,6 +30,7 @@ import { Subject, takeUntil } from 'rxjs';
import { ContextMenuComponent } from './context-menu.component';
import { AgGridDemoRow } from './data';
+import { Filters } from './filters';
@Component({
standalone: true,
@@ -76,7 +77,8 @@ export class ViewGridComponent implements OnInit, OnDestroy {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -193,8 +195,10 @@ export class ViewGridComponent implements OnInit, OnDestroy {
this.#updateDisplayedItems();
}
- protected onRowSelected(rowSelectedEvent: RowSelectedEvent): void {
- if (!rowSelectedEvent.data.selected) {
+ protected onRowSelected(
+ rowSelectedEvent: RowSelectedEvent,
+ ): void {
+ if (!rowSelectedEvent.data?.selected) {
this.#updateDisplayedItems();
}
}
@@ -204,7 +208,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
const searchText = this.#dataState && this.#dataState.searchText;
if (searchText) {
- searchedItems = items.filter(function (item: AgGridDemoRow) {
+ searchedItems = items.filter((item: AgGridDemoRow) => {
let property: keyof typeof item;
for (property in item) {
@@ -214,7 +218,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
) {
const propertyText = item[property].toLowerCase();
- if (propertyText.indexOf(searchText) > -1) {
+ if (propertyText.includes(searchText)) {
return true;
}
}
@@ -231,11 +235,12 @@ export class ViewGridComponent implements OnInit, OnDestroy {
let filteredItems = items;
const filterData = this.#dataState && this.#dataState.filterData;
- if (filterData && filterData.filters) {
- const filters = filterData.filters;
+ if (filterData?.filters) {
+ const filters = filterData.filters as Filters;
+
filteredItems = items.filter((item: AgGridDemoRow) => {
return (
- ((filters.hideSales && item.department.name !== 'Sales') ||
+ (!!(filters.hideSales && item.department.name !== 'Sales') ||
!filters.hideSales) &&
((filters.jobTitle !== 'any' &&
item.jobTitle?.name === filters.jobTitle) ||
@@ -248,10 +253,13 @@ export class ViewGridComponent implements OnInit, OnDestroy {
return filteredItems;
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/demo.component.ts
index b50790f82b..4e2b620aa7 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/inline-help/demo.component.ts
@@ -81,7 +81,8 @@ export class DemoComponent {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
headerComponentParams: {
inlineHelpComponent: InlineHelpComponent,
},
@@ -146,10 +147,13 @@ export class DemoComponent {
}
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/demo.component.ts
index 7c6e5993ae..fe7e40d11c 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/paging/demo.component.ts
@@ -23,7 +23,7 @@ import { Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { ContextMenuComponent } from './context-menu.component';
-import { AG_GRID_DEMO_DATA } from './data';
+import { AG_GRID_DEMO_DATA, AgGridDemoRow } from './data';
@Component({
standalone: true,
@@ -65,7 +65,8 @@ export class DemoComponent implements OnInit, OnDestroy {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -93,7 +94,9 @@ export class DemoComponent implements OnInit, OnDestroy {
constructor() {
const gridOptions: GridOptions = {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
rowSelection: 'single',
pagination: true,
suppressPaginationPanel: true,
@@ -108,7 +111,7 @@ export class DemoComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
this.#subscriptions.add(
this.#activatedRoute.queryParamMap
- .pipe(map((params) => params.get('page') || '1'))
+ .pipe(map((params) => params.get('page') ?? '1'))
.subscribe((page) => {
this.currentPage = Number(page);
this.#gridApi?.paginationGoToPage(this.currentPage - 1);
@@ -142,20 +145,21 @@ export class DemoComponent implements OnInit, OnDestroy {
this.#gridApi.paginationGoToPage(this.currentPage - 1);
}
- protected onPageChange(page: number): void {
- this.#router
- .navigate(['.'], {
- relativeTo: this.#activatedRoute,
- queryParams: { page: page.toString(10) },
- queryParamsHandling: 'merge',
- })
- .then();
+ protected async onPageChange(page: number): Promise {
+ await this.#router.navigate(['.'], {
+ relativeTo: this.#activatedRoute,
+ queryParams: { page: page.toString(10) },
+ queryParamsHandling: 'merge',
+ });
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/context-menu.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/context-menu.component.ts
index 346476d03d..acfc353ddb 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { AgGridDemoRow } from './data';
+
@Component({
standalone: true,
selector: 'app-context-menu',
@@ -17,10 +19,10 @@ export class ContextMenuComponent implements ICellRendererAngularComp {
public markInactiveAriaLabel = '';
public moreInfoAriaLabel = '';
- #name = '';
+ #name: string | undefined;
- public agInit(params: ICellRendererParams): void {
- this.#name = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.#name = params.data?.name;
this.contextMenuAriaLabel = `Context menu for ${this.#name}`;
this.deleteAriaLabel = `Delete ${this.#name}`;
this.markInactiveAriaLabel = `Mark ${this.#name} inactive`;
diff --git a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/demo.component.ts b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/demo.component.ts
index 34eead39e8..2e853df75b 100644
--- a/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/ag-grid/data-grid/top-scroll/demo.component.ts
@@ -66,7 +66,8 @@ export class DemoComponent {
field: 'endDate',
headerName: 'End date',
type: SkyCellType.Date,
- valueFormatter: this.#endDateFormatter,
+ valueFormatter: (params: ValueFormatterParams) =>
+ this.#endDateFormatter(params),
},
{
field: 'department',
@@ -88,7 +89,9 @@ export class DemoComponent {
this.gridOptions = this.#agGridSvc.getGridOptions({
gridOptions: {
columnDefs: this.#columnDefs,
- onGridReady: (gridReadyEvent): void => this.onGridReady(gridReadyEvent),
+ onGridReady: (gridReadyEvent): void => {
+ this.onGridReady(gridReadyEvent);
+ },
context: {
enableTopScroll: true,
},
@@ -115,10 +118,13 @@ export class DemoComponent {
}
}
- #endDateFormatter(params: ValueFormatterParams): string {
- const dateConfig = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ #endDateFormatter(params: ValueFormatterParams): string {
return params.value
- ? params.value.toLocaleDateString('en-us', dateConfig)
+ ? params.value.toLocaleDateString('en-us', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ })
: 'N/A';
}
}
diff --git a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/demo.component.ts
index 95b58cc720..1bcafcc0ff 100644
--- a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/demo.component.ts
@@ -13,6 +13,7 @@ import {
import { DATA_MANAGER_DEMO_DATA, DataManagerDemoRow } from './data';
import { FilterModalComponent } from './filter-modal.component';
+import { Filters } from './filters';
import { ViewGridComponent } from './view-grid.component';
import { ViewRepeaterComponent } from './view-repeater.component';
@@ -54,7 +55,7 @@ export class DemoComponent implements OnInit {
filtersApplied: true,
filters: {
hideOrange: true,
- },
+ } satisfies Filters,
},
views: [
{
diff --git a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filter-modal.component.ts b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filter-modal.component.ts
index 1ba14df3f9..2a28d09ffe 100644
--- a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filter-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filter-modal.component.ts
@@ -7,6 +7,8 @@ import {
import { SkyCheckboxModule, SkyInputBoxModule } from '@skyux/forms';
import { SkyModalInstance, SkyModalModule } from '@skyux/modals';
+import { Filters } from './filters';
+
@Component({
standalone: true,
selector: 'app-filter-modal',
@@ -28,9 +30,10 @@ export class FilterModalComponent implements OnInit {
public ngOnInit(): void {
if (this.#context.filterData?.filters) {
- const filters = this.#context.filterData.filters;
- this.fruitType = filters.type || 'any';
- this.hideOrange = filters.hideOrange || false;
+ const filters = this.#context.filterData.filters as Filters;
+
+ this.fruitType = filters.type ?? 'any';
+ this.hideOrange = filters.hideOrange ?? false;
}
}
@@ -41,7 +44,7 @@ export class FilterModalComponent implements OnInit {
result.filters = {
type: this.fruitType,
hideOrange: this.hideOrange,
- };
+ } satisfies Filters;
this.#instance.save(result);
}
diff --git a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filters.ts b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filters.ts
new file mode 100644
index 0000000000..a4e9ab9c2d
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/filters.ts
@@ -0,0 +1,4 @@
+export interface Filters {
+ hideOrange?: boolean;
+ type?: string;
+}
diff --git a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-grid.component.ts b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-grid.component.ts
index baa733d8a3..7b0982696b 100644
--- a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-grid.component.ts
+++ b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-grid.component.ts
@@ -27,6 +27,7 @@ import { Subject, of } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DataManagerDemoRow } from './data';
+import { Filters } from './filters';
@Component({
standalone: true,
@@ -150,8 +151,10 @@ export class ViewGridComponent implements OnInit, OnDestroy {
this.#ngUnsubscribe.complete();
}
- protected onRowSelected(rowSelectedEvent: RowSelectedEvent): void {
- if (!rowSelectedEvent.data.selected) {
+ protected onRowSelected(
+ rowSelectedEvent: RowSelectedEvent,
+ ): void {
+ if (!rowSelectedEvent.data?.selected) {
this.#updateData();
}
}
@@ -161,12 +164,12 @@ export class ViewGridComponent implements OnInit, OnDestroy {
const filterData = this.#dataState && this.#dataState.filterData;
- if (filterData && filterData.filters) {
- const filters = filterData.filters;
+ if (filterData?.filters) {
+ const filters = filterData.filters as Filters;
filteredItems = items.filter((item) => {
return (
- ((filters.hideOrange && item.color !== 'orange') ||
+ ((filters.hideOrange && item.color !== 'orange') ??
!filters.hideOrange) &&
((filters.type !== 'any' && item.type === filters.type) ||
!filters.type ||
@@ -189,7 +192,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
const searchText = this.#dataState && this.#dataState.searchText;
if (searchText) {
- searchedItems = items.filter(function (item: DataManagerDemoRow) {
+ searchedItems = items.filter((item: DataManagerDemoRow) => {
let property: keyof typeof item;
for (property in item) {
@@ -198,7 +201,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
(property === 'name' || property === 'description')
) {
const propertyText = item[property].toLowerCase();
- if (propertyText.indexOf(searchText) > -1) {
+ if (propertyText.includes(searchText)) {
return true;
}
}
@@ -212,7 +215,7 @@ export class ViewGridComponent implements OnInit, OnDestroy {
#setInitialColumnOrder(): void {
const viewState = this.#dataState.getViewStateById(this.viewId);
- const visibleColumns = viewState?.displayedColumnIds || [];
+ const visibleColumns = viewState?.displayedColumnIds ?? [];
this.#columnDefs.sort((col1, col2) => {
const col1Index = visibleColumns.findIndex(
diff --git a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-repeater.component.ts b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-repeater.component.ts
index 9418a23c77..95eb01d0c9 100644
--- a/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-repeater.component.ts
+++ b/apps/code-examples/src/app/code-examples/data-manager/data-manager/basic/view-repeater.component.ts
@@ -20,6 +20,7 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DataManagerDemoRow } from './data';
+import { Filters } from './filters';
@Component({
standalone: true,
@@ -48,8 +49,12 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
sortEnabled: true,
filterButtonEnabled: true,
multiselectToolbarEnabled: true,
- onClearAllClick: () => this.#clearAll(),
- onSelectAllClick: () => this.#selectAll(),
+ onClearAllClick: () => {
+ this.#clearAll();
+ },
+ onSelectAllClick: () => {
+ this.#selectAll();
+ },
};
readonly #changeDetector = inject(ChangeDetectorRef);
@@ -83,7 +88,7 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
}
protected onItemSelect(isSelected: boolean, item: DataManagerDemoRow): void {
- const selectedItems = this.#dataState.selectedIds || [];
+ const selectedItems = this.#dataState.selectedIds ?? [];
const itemIndex = selectedItems.indexOf(item.id);
if (isSelected && itemIndex === -1) {
@@ -102,10 +107,10 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
}
#updateData(): void {
- const selectedIds = this.#dataState.selectedIds || [];
+ const selectedIds = this.#dataState.selectedIds ?? [];
this.items.forEach((item) => {
- item.selected = selectedIds.indexOf(item.id) !== -1;
+ item.selected = selectedIds.includes(item.id);
});
this.displayedItems = this.#filterItems(this.#searchItems(this.items));
@@ -131,7 +136,7 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
this.#dataState && this.#dataState.searchText?.toUpperCase();
if (searchText) {
- searchedItems = items.filter(function (item: DataManagerDemoRow) {
+ searchedItems = items.filter((item: DataManagerDemoRow) => {
let property: keyof typeof item;
for (property in item) {
@@ -140,7 +145,7 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
(property === 'name' || property === 'description')
) {
const propertyText = item[property].toUpperCase();
- if (propertyText.indexOf(searchText) > -1) {
+ if (propertyText.includes(searchText)) {
return true;
}
}
@@ -157,11 +162,12 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
let filteredItems = items;
const filterData = this.#dataState && this.#dataState.filterData;
- if (filterData && filterData.filters) {
- const filters = filterData.filters;
+ if (filterData?.filters) {
+ const filters = filterData.filters as Filters;
+
filteredItems = items.filter((item: DataManagerDemoRow) => {
if (
- ((filters.hideOrange && item.color !== 'orange') ||
+ ((filters.hideOrange && item.color !== 'orange') ??
!filters.hideOrange) &&
((filters.type !== 'any' && item.type === filters.type) ||
!filters.type ||
@@ -178,7 +184,7 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
}
#selectAll(): void {
- const selectedIds = this.#dataState.selectedIds || [];
+ const selectedIds = this.#dataState.selectedIds ?? [];
this.displayedItems.forEach((item) => {
if (!item.selected) {
@@ -193,7 +199,7 @@ export class ViewRepeaterComponent implements OnInit, OnDestroy {
}
#clearAll(): void {
- const selectedIds = this.#dataState.selectedIds || [];
+ const selectedIds = this.#dataState.selectedIds ?? [];
this.displayedItems.forEach((item) => {
if (item.selected) {
diff --git a/apps/code-examples/src/main.ts b/apps/code-examples/src/main.ts
index d9a2e7e4a5..cd9cbc4fad 100644
--- a/apps/code-examples/src/main.ts
+++ b/apps/code-examples/src/main.ts
@@ -10,4 +10,7 @@ if (environment.production) {
platformBrowserDynamic()
.bootstrapModule(AppModule)
- .catch((err) => console.error(err));
+ .catch((err) => {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ });
From 7d0d79b3ed0cc3b333f24672b7e2cd2a816f3a24 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Tue, 2 Jul 2024 16:04:22 -0400
Subject: [PATCH 04/95] fix(code-examples): satisfy ESLint rules for
colorpicker code examples (#2424)
---
.../colorpicker/basic/demo.component.ts | 42 ++++++++++++-------
.../programmatic/demo.component.ts | 39 ++++++++++-------
2 files changed, 51 insertions(+), 30 deletions(-)
diff --git a/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/basic/demo.component.ts
index 1847d4b646..803ba9ab7e 100644
--- a/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/basic/demo.component.ts
@@ -1,7 +1,6 @@
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
- AbstractControl,
FormBuilder,
FormControl,
FormGroup,
@@ -10,6 +9,14 @@ import {
} from '@angular/forms';
import { SkyColorpickerModule, SkyColorpickerOutput } from '@skyux/colorpicker';
+interface DemoForm {
+ favoriteColor: FormControl;
+}
+
+function isColorpickerOutput(value: unknown): value is SkyColorpickerOutput {
+ return !!(value && typeof value === 'object' && 'rgba' in value);
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -17,8 +24,8 @@ import { SkyColorpickerModule, SkyColorpickerOutput } from '@skyux/colorpicker';
imports: [CommonModule, ReactiveFormsModule, SkyColorpickerModule],
})
export class DemoComponent {
- protected formGroup: FormGroup;
- protected favoriteColor: FormControl;
+ protected favoriteColor: FormControl;
+ protected formGroup: FormGroup;
protected swatches: string[] = [
'#BD4040',
@@ -30,17 +37,19 @@ export class DemoComponent {
];
constructor() {
- this.favoriteColor = new FormControl('#f00', [
- (control: AbstractControl): ValidationErrors | null => {
- if (control.value?.rgba?.alpha < 0.8) {
- return { opaque: true };
- }
-
- return null;
- },
- ]);
+ this.favoriteColor = new FormControl('#f00', {
+ nonNullable: true,
+ validators: [
+ (control): ValidationErrors | null => {
+ return isColorpickerOutput(control.value) &&
+ control.value.rgba.alpha < 0.8
+ ? { opaque: true }
+ : null;
+ },
+ ],
+ });
- this.formGroup = inject(FormBuilder).group({
+ this.formGroup = inject(FormBuilder).group({
favoriteColor: this.favoriteColor,
});
}
@@ -50,8 +59,11 @@ export class DemoComponent {
}
protected submit(): void {
- const controlValue = this.formGroup.get('favoriteColor')?.value;
- const favoriteColor: string = controlValue.hex || controlValue;
+ const controlValue = this.favoriteColor.value;
+ const favoriteColor = isColorpickerOutput(controlValue)
+ ? controlValue.hex
+ : controlValue;
+
alert('Your favorite color is: \n' + favoriteColor);
}
}
diff --git a/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/programmatic/demo.component.ts b/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/programmatic/demo.component.ts
index 15d9eb55d8..bb3b3da4be 100644
--- a/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/programmatic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/colorpicker/colorpicker/programmatic/demo.component.ts
@@ -1,6 +1,5 @@
import { Component, inject } from '@angular/core';
import {
- AbstractControl,
FormBuilder,
FormControl,
FormGroup,
@@ -12,10 +11,19 @@ import {
SkyColorpickerMessage,
SkyColorpickerMessageType,
SkyColorpickerModule,
+ SkyColorpickerOutput,
} from '@skyux/colorpicker';
import { Subject } from 'rxjs';
+interface DemoForm {
+ favoriteColor: FormControl;
+}
+
+function isColorpickerOutput(value: unknown): value is SkyColorpickerOutput {
+ return !!(value && typeof value === 'object' && 'rgba' in value);
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -24,22 +32,24 @@ import { Subject } from 'rxjs';
})
export class DemoComponent {
protected colorpickerController = new Subject();
- protected favoriteColor: FormControl;
- protected formGroup: FormGroup;
+ protected favoriteColor: FormControl;
+ protected formGroup: FormGroup;
protected showResetButton = false;
constructor() {
- this.favoriteColor = new FormControl('#f00', [
- (control: AbstractControl): ValidationErrors | null => {
- if (control.value?.rgba?.alpha < 0.8) {
- return { opaque: true };
- }
-
- return null;
- },
- ]);
+ this.favoriteColor = new FormControl('#f00', {
+ nonNullable: true,
+ validators: [
+ (control): ValidationErrors | null => {
+ return isColorpickerOutput(control.value) &&
+ control.value.rgba.alpha < 0.8
+ ? { opaque: true }
+ : null;
+ },
+ ],
+ });
- this.formGroup = inject(FormBuilder).group({
+ this.formGroup = inject(FormBuilder).group({
favoriteColor: this.favoriteColor,
});
}
@@ -57,7 +67,6 @@ export class DemoComponent {
}
#sendMessage(type: SkyColorpickerMessageType): void {
- const message: SkyColorpickerMessage = { type };
- this.colorpickerController.next(message);
+ this.colorpickerController.next({ type });
}
}
From e222a736564509baee593daa877d80bd42d8f966 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Wed, 3 Jul 2024 14:56:38 -0400
Subject: [PATCH 05/95] fix(code-examples): satisfy ESLint rules for datetime
code examples (#2427)
---
.../custom-calculator/demo.component.ts | 2 +-
.../datepicker/basic/demo.component.html | 2 +-
.../datepicker/basic/demo.component.ts | 43 ++++++++++--------
.../custom-dates/demo.component.html | 2 +-
.../datepicker/custom-dates/demo.component.ts | 2 +
.../timepicker/basic/demo.component.ts | 44 ++++++++++++-------
6 files changed, 58 insertions(+), 37 deletions(-)
diff --git a/apps/code-examples/src/app/code-examples/datetime/date-range-picker/custom-calculator/demo.component.ts b/apps/code-examples/src/app/code-examples/datetime/date-range-picker/custom-calculator/demo.component.ts
index 878a108f44..f8602c4af8 100644
--- a/apps/code-examples/src/app/code-examples/datetime/date-range-picker/custom-calculator/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/datetime/date-range-picker/custom-calculator/demo.component.ts
@@ -55,7 +55,7 @@ export class DemoComponent {
shortDescription: 'Date before today',
type: SkyDateRangeCalculatorType.Before,
validate: (value): ValidationErrors | null => {
- if (value && value.endDate && value.endDate > new Date()) {
+ if (value?.endDate && value.endDate > new Date()) {
return {
dateIsAfterToday: true,
};
diff --git a/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.html b/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.html
index 7777130d01..2961fe6917 100644
--- a/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.html
@@ -17,6 +17,6 @@
- Selected date: {{ formGroup.value.myDate }}
+ Selected date: {{ startDate.value | json }}
diff --git a/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.ts
index 3af2148931..e409c02038 100644
--- a/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/datetime/datepicker/basic/demo.component.ts
@@ -13,6 +13,23 @@ import {
import { SkyDatepickerModule } from '@skyux/datetime';
import { SkyInputBoxModule } from '@skyux/forms';
+interface DemoForm {
+ startDate: FormControl;
+}
+
+function validateDate(
+ control: AbstractControl,
+): ValidationErrors | null {
+ const date = control.value;
+ const day = date?.getDay();
+
+ return day !== undefined && (day === 0 || day === 6)
+ ? {
+ invalidWeekend: true,
+ }
+ : null;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -26,31 +43,21 @@ import { SkyInputBoxModule } from '@skyux/forms';
],
})
export class DemoComponent {
- protected formGroup: FormGroup;
- protected startDate: FormControl;
+ protected formGroup: FormGroup;
+ protected startDate: FormControl;
+
protected helpPopoverContent =
'If you need help with registration, choose a date at least 8 business days after you arrive. The process takes up to 7 business days from the start date.';
- protected hintText = 'Must be before your 1 year anniversary.';
- #formBuilder = inject(FormBuilder);
+ protected hintText = 'Must be before your 1 year anniversary.';
constructor() {
- this.startDate = this.#formBuilder.control(undefined, {
- validators: [Validators.required, this.#validateDate],
+ this.startDate = new FormControl(null, {
+ validators: [Validators.required, validateDate],
});
- this.formGroup = inject(FormBuilder).group({
+
+ this.formGroup = inject(FormBuilder).group({
startDate: this.startDate,
});
}
-
- #validateDate(control: AbstractControl): ValidationErrors | null {
- const date: Date = control.value;
- const day = date?.getDay();
- if (day !== undefined && (day === 0 || day === 6)) {
- return {
- invalidWeekend: true,
- };
- }
- return null;
- }
}
diff --git a/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.html b/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.html
index fea5ceba7e..dcf62da00e 100644
--- a/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.html
@@ -10,6 +10,6 @@
- Selected date: {{ formGroup.value.myDate }}
+ Selected date: {{ formGroup.value.startDate | json }}
diff --git a/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.ts b/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.ts
index 976f33e9cc..41977ca5ae 100644
--- a/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/datetime/datepicker/custom-dates/demo.component.ts
@@ -1,3 +1,4 @@
+import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
FormBuilder,
@@ -21,6 +22,7 @@ import { delay } from 'rxjs/operators';
selector: 'app-demo',
templateUrl: './demo.component.html',
imports: [
+ CommonModule,
FormsModule,
ReactiveFormsModule,
SkyDatepickerModule,
diff --git a/apps/code-examples/src/app/code-examples/datetime/timepicker/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/datetime/timepicker/basic/demo.component.ts
index 08e2acdbb7..682662604c 100644
--- a/apps/code-examples/src/app/code-examples/datetime/timepicker/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/datetime/timepicker/basic/demo.component.ts
@@ -10,9 +10,27 @@ import {
ValidationErrors,
Validators,
} from '@angular/forms';
-import { SkyTimepickerModule } from '@skyux/datetime';
+import { SkyTimepickerModule, SkyTimepickerTimeOutput } from '@skyux/datetime';
import { SkyInputBoxModule } from '@skyux/forms';
+interface DemoForm {
+ time: FormControl;
+}
+
+function isTimepickerOutput(value: unknown): value is SkyTimepickerTimeOutput {
+ return !!(value && typeof value === 'object' && 'minute' in value);
+}
+
+function validateTime(
+ control: AbstractControl,
+): ValidationErrors | null {
+ const minute = isTimepickerOutput(control.value)
+ ? control.value.minute
+ : undefined;
+
+ return minute && minute % 15 !== 0 ? { invalidMinute: true } : null;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -26,28 +44,22 @@ import { SkyInputBoxModule } from '@skyux/forms';
],
})
export class DemoComponent {
- protected formGroup: FormGroup;
- protected time: FormControl;
+ protected formGroup: FormGroup;
+ protected time: FormControl;
+
protected hintText = 'Choose a time that allows for late arrivals.';
+
protected helpPopoverContent =
'Allow time to complete all activities that your team signed up for. All activities take about 30 minutes, except the ropes course, which takes 60 minutes.';
- #formBuilder = inject(FormBuilder);
-
constructor() {
- this.time = this.#formBuilder.control('2:45', {
- validators: [Validators.required, this.#validateTime],
+ this.time = new FormControl('2:45', {
+ nonNullable: true,
+ validators: [Validators.required, validateTime],
});
- this.formGroup = this.#formBuilder.group({
+
+ this.formGroup = inject(FormBuilder).group({
time: this.time,
});
}
-
- #validateTime(control: AbstractControl): ValidationErrors | null {
- const minute = control.value?.minute;
- if (minute && minute % 15 !== 0) {
- return { invalidMinute: true };
- }
- return null;
- }
}
From 3d61fe0c635209111a8a7b2de09492f48475d82f Mon Sep 17 00:00:00 2001
From: Paul Crowder
Date: Wed, 3 Jul 2024 15:36:34 -0400
Subject: [PATCH 06/95] feat(components/icon): add internal support for
SVG-based icons (#2433)
---
.cspell.json | 2 +-
.../src/e2e/icon.component.cy.ts | 3 +
.../src/app/icon/icon.component.html | 42 ++++++
.../indicators/icon/icon-routing.module.ts | 26 ++++
.../indicators/icon/icon.component.html | 40 ++++++
.../indicators/icon/icon.component.ts | 17 +++
.../components/indicators/icon/icon.module.ts | 13 ++
.../indicators/indicators.module.ts | 4 +
.../icon/icon-svg-resolver.service.spec.ts | 129 ++++++++++++++++++
.../src/lib/icon/icon-svg-resolver.service.ts | 116 ++++++++++++++++
.../icon/src/lib/icon/icon-svg.component.html | 7 +
.../src/lib/icon/icon-svg.component.spec.ts | 99 ++++++++++++++
.../icon/src/lib/icon/icon-svg.component.ts | 61 +++++++++
.../lib/icon/icon-svg.default.component.scss | 40 ++++++
.../lib/icon/icon-svg.modern.component.scss | 40 ++++++
.../icon/src/lib/icon/icon.component.html | 36 ++---
.../icon/src/lib/icon/icon.component.ts | 7 +
.../icon/src/lib/icon/icon.module.ts | 6 +-
18 files changed, 670 insertions(+), 18 deletions(-)
create mode 100644 apps/playground/src/app/components/indicators/icon/icon-routing.module.ts
create mode 100644 apps/playground/src/app/components/indicators/icon/icon.component.html
create mode 100644 apps/playground/src/app/components/indicators/icon/icon.component.ts
create mode 100644 apps/playground/src/app/components/indicators/icon/icon.module.ts
create mode 100644 libs/components/icon/src/lib/icon/icon-svg-resolver.service.spec.ts
create mode 100644 libs/components/icon/src/lib/icon/icon-svg-resolver.service.ts
create mode 100644 libs/components/icon/src/lib/icon/icon-svg.component.html
create mode 100644 libs/components/icon/src/lib/icon/icon-svg.component.spec.ts
create mode 100644 libs/components/icon/src/lib/icon/icon-svg.component.ts
create mode 100644 libs/components/icon/src/lib/icon/icon-svg.default.component.scss
create mode 100644 libs/components/icon/src/lib/icon/icon-svg.modern.component.scss
diff --git a/.cspell.json b/.cspell.json
index 498ef18b64..263351d4a1 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -1,4 +1,4 @@
{
"import": "node_modules/@skyux/dev-infra-private/.cspell-shared.json",
- "words": ["novalidate", "tabindex", "interactable", "fetchpriority"]
+ "words": ["novalidate", "tabindex", "interactable", "fetchpriority", "xlink"]
}
diff --git a/apps/e2e/indicators-storybook-e2e/src/e2e/icon.component.cy.ts b/apps/e2e/indicators-storybook-e2e/src/e2e/icon.component.cy.ts
index aab1755122..20649c08d2 100644
--- a/apps/e2e/indicators-storybook-e2e/src/e2e/icon.component.cy.ts
+++ b/apps/e2e/indicators-storybook-e2e/src/e2e/icon.component.cy.ts
@@ -10,6 +10,9 @@ describe('indicators-storybook', () => {
);
it('should render the component', () => {
cy.get('#ready')
+ .should('exist')
+ .end()
+ .get('#sky-icon-svg-sprite')
.should('exist')
.end()
.get('app-icon')
diff --git a/apps/e2e/indicators-storybook/src/app/icon/icon.component.html b/apps/e2e/indicators-storybook/src/app/icon/icon.component.html
index bd8fa44caf..fff685dfec 100644
--- a/apps/e2e/indicators-storybook/src/app/icon/icon.component.html
+++ b/apps/e2e/indicators-storybook/src/app/icon/icon.component.html
@@ -37,4 +37,46 @@
{{ fa }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some text.
+
+
+
+
+
+
+
diff --git a/apps/playground/src/app/components/indicators/icon/icon-routing.module.ts b/apps/playground/src/app/components/indicators/icon/icon-routing.module.ts
new file mode 100644
index 0000000000..6208fefc44
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/icon/icon-routing.module.ts
@@ -0,0 +1,26 @@
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { ComponentRouteInfo } from '../../../shared/component-info/component-route-info';
+
+import { IconDemoComponent } from './icon.component';
+
+const routes: ComponentRouteInfo[] = [
+ {
+ path: '',
+ component: IconDemoComponent,
+ data: {
+ name: 'Icon',
+ icon: 'picture-o',
+ library: 'indicators',
+ },
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class IconRoutingModule {
+ public static routes = routes;
+}
diff --git a/apps/playground/src/app/components/indicators/icon/icon.component.html b/apps/playground/src/app/components/indicators/icon/icon.component.html
new file mode 100644
index 0000000000..445fe847cd
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/icon/icon.component.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some text.
+
+
+
+
+
+
diff --git a/apps/playground/src/app/components/indicators/icon/icon.component.ts b/apps/playground/src/app/components/indicators/icon/icon.component.ts
new file mode 100644
index 0000000000..36783dff0e
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/icon/icon.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { SkyIconModule } from '@skyux/icon';
+
+@Component({
+ selector: 'app-icon',
+ standalone: true,
+ imports: [SkyIconModule],
+ templateUrl: './icon.component.html',
+ styles: [
+ `
+ :host {
+ --sky-icon-color: pink;
+ }
+ `,
+ ],
+})
+export class IconDemoComponent {}
diff --git a/apps/playground/src/app/components/indicators/icon/icon.module.ts b/apps/playground/src/app/components/indicators/icon/icon.module.ts
new file mode 100644
index 0000000000..e02ad9bd99
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/icon/icon.module.ts
@@ -0,0 +1,13 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { SkyAlertModule } from '@skyux/indicators';
+
+import { IconRoutingModule } from './icon-routing.module';
+import { IconDemoComponent } from './icon.component';
+
+@NgModule({
+ imports: [CommonModule, IconDemoComponent, IconRoutingModule, SkyAlertModule],
+})
+export class IconModule {
+ public static routes = IconRoutingModule.routes;
+}
diff --git a/apps/playground/src/app/components/indicators/indicators.module.ts b/apps/playground/src/app/components/indicators/indicators.module.ts
index fabd63c5c6..2487c616e9 100644
--- a/apps/playground/src/app/components/indicators/indicators.module.ts
+++ b/apps/playground/src/app/components/indicators/indicators.module.ts
@@ -7,6 +7,10 @@ const routes: Routes = [
loadChildren: () =>
import('./alert/alert.module').then((m) => m.AlertModule),
},
+ {
+ path: 'icon',
+ loadChildren: () => import('./icon/icon.module').then((m) => m.IconModule),
+ },
{
path: 'illustration',
loadChildren: () =>
diff --git a/libs/components/icon/src/lib/icon/icon-svg-resolver.service.spec.ts b/libs/components/icon/src/lib/icon/icon-svg-resolver.service.spec.ts
new file mode 100644
index 0000000000..b5b4f4bb43
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg-resolver.service.spec.ts
@@ -0,0 +1,129 @@
+import { provideHttpClient } from '@angular/common/http';
+import {
+ HttpTestingController,
+ provideHttpClientTesting,
+} from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+
+import { firstValueFrom } from 'rxjs';
+
+import { SkyIconSvgResolverService } from './icon-svg-resolver.service';
+import { SkyIconVariantType } from './types/icon-variant-type';
+
+describe('Icon SVG resolver service', () => {
+ let resolverSvc: SkyIconSvgResolverService;
+ let httpTestingController: HttpTestingController;
+ let spriteLoaded = false;
+
+ function buildSymbolHtml(
+ name: string,
+ size: number,
+ variant: SkyIconVariantType,
+ ): string {
+ return `
+
+`;
+ }
+
+ async function validate(
+ name: string,
+ expectedHref?: string,
+ size?: number,
+ variant?: SkyIconVariantType,
+ expectedError?: string,
+ ): Promise {
+ const hrefPromise = firstValueFrom(
+ resolverSvc.resolveHref(name, size, variant),
+ );
+
+ if (!spriteLoaded) {
+ const testRequest = httpTestingController.expectOne(
+ 'https://sky.blackbaudcdn.net/static/skyux-icons/7/assets/svg/skyux-icons.svg',
+ );
+
+ testRequest.flush(``);
+
+ spriteLoaded = true;
+ }
+
+ if (expectedError) {
+ await expectAsync(hrefPromise).toBeRejectedWithError(expectedError);
+ } else if (expectedHref) {
+ await expectAsync(hrefPromise).toBeResolvedTo(expectedHref);
+ }
+ }
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ provideHttpClient(),
+ provideHttpClientTesting(),
+ SkyIconSvgResolverService,
+ ],
+ });
+
+ resolverSvc = TestBed.inject(SkyIconSvgResolverService);
+ httpTestingController = TestBed.inject(HttpTestingController);
+ });
+
+ afterEach(() => {
+ document.getElementById('sky-icon-svg-sprite')?.remove();
+ spriteLoaded = false;
+ });
+
+ it('should resolve the expected variant', async () => {
+ await validate('single-size', '#sky-i-single-size-12-line', 12, 'line');
+ await validate('single-size', '#sky-i-single-size-12-solid', 12, 'solid');
+ });
+
+ it('should throw an error when a matching icon is not found', async () => {
+ await validate(
+ 'invalid',
+ undefined,
+ undefined,
+ undefined,
+ `Icon with name 'invalid' was not found.`,
+ );
+ });
+
+ describe('with single size icons', () => {
+ it('should resolve the expected icon regardless of specified size', async () => {
+ await validate('single-size', '#sky-i-single-size-12-line');
+ await validate('single-size', '#sky-i-single-size-12-line', 1);
+ await validate('single-size', '#sky-i-single-size-12-line', 100);
+ });
+ });
+
+ describe('with multiple size icons', () => {
+ it('should resolve to the icon size that is an exact match of the specified size', async () => {
+ await validate('multi-size', '#sky-i-multi-size-12-line', 12);
+ });
+
+ it('should resolve to the icon size closest to the specified size when no exact match exists', async () => {
+ await validate('multi-size', '#sky-i-multi-size-12-line', -Infinity);
+ await validate('multi-size', '#sky-i-multi-size-12-line', -1);
+ await validate('multi-size', '#sky-i-multi-size-12-line', 0);
+ await validate('multi-size', '#sky-i-multi-size-12-line', 11);
+ await validate('multi-size', '#sky-i-multi-size-12-line', 13);
+ await validate('multi-size', '#sky-i-multi-size-12-line', 17);
+ await validate('multi-size', '#sky-i-multi-size-24-line', 18);
+ await validate('multi-size', '#sky-i-multi-size-24-line', 20);
+ await validate('multi-size', '#sky-i-multi-size-48-line', 37);
+ await validate('multi-size', '#sky-i-multi-size-48-line', 100);
+ await validate('multi-size', '#sky-i-multi-size-48-line', Infinity);
+ });
+
+ it('should resolve to the icon size closest to the default size when size is not specified', async () => {
+ await validate('multi-size', '#sky-i-multi-size-12-line');
+ });
+ });
+});
diff --git a/libs/components/icon/src/lib/icon/icon-svg-resolver.service.ts b/libs/components/icon/src/lib/icon/icon-svg-resolver.service.ts
new file mode 100644
index 0000000000..3623fa5200
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg-resolver.service.ts
@@ -0,0 +1,116 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable, inject } from '@angular/core';
+
+import { Observable, map, shareReplay, tap } from 'rxjs';
+
+import { SkyIconVariantType } from './types/icon-variant-type';
+
+function insertSprite(markup: string): void {
+ document.body.insertAdjacentHTML('afterbegin', markup);
+}
+
+function getIconsSizes(): Map {
+ const iconsSizes = Array.from(
+ document.querySelectorAll('#sky-icon-svg-sprite symbol'),
+ ).reduce((map, el) => {
+ const idParts = el.id.split('-');
+
+ // Construct the icon name by removing `sky-i-` from the beginning
+ // and `--` from the end.
+ const name = idParts.slice(2, idParts.length - 2).join('-');
+
+ let sizes = map.get(name);
+
+ if (!sizes) {
+ sizes = [];
+ map.set(name, sizes);
+ }
+
+ // The penultimate segment is the size for which the icon has
+ // been optimized.
+ sizes.push(+idParts[idParts.length - 2]);
+
+ return map;
+ }, new Map());
+
+ // Sort all the sizes for later comparison.
+ for (const id of iconsSizes.keys()) {
+ // Dedupe and sort the icon sizes.
+ iconsSizes.set(id, [...new Set(iconsSizes.get(id))].sort());
+ }
+
+ return iconsSizes;
+}
+
+function getNearestSize(
+ iconsSizes: Map,
+ name: string,
+ pixelSize: number,
+): number | undefined {
+ const sizes = iconsSizes.get(name);
+
+ if (sizes) {
+ let nearestSizeUnder = -Infinity;
+ let nearestSizeOver = Infinity;
+
+ for (const availableSize of sizes) {
+ if (availableSize === pixelSize) {
+ return pixelSize;
+ } else if (availableSize < pixelSize) {
+ nearestSizeUnder = availableSize;
+ } else {
+ nearestSizeOver = availableSize;
+ break;
+ }
+ }
+
+ const underDiff = Math.abs(pixelSize - nearestSizeUnder);
+ const overDiff = Math.abs(pixelSize - nearestSizeOver);
+
+ return isNaN(overDiff) || underDiff < overDiff
+ ? nearestSizeUnder
+ : nearestSizeOver;
+ }
+
+ return undefined;
+}
+
+/**
+ * @internal
+ */
+@Injectable()
+export class SkyIconSvgResolverService {
+ readonly #http = inject(HttpClient);
+
+ readonly #spriteObs = this.#http
+ .get(
+ `https://sky.blackbaudcdn.net/static/skyux-icons/7/assets/svg/skyux-icons.svg`,
+ {
+ responseType: 'text',
+ },
+ )
+ .pipe(tap(insertSprite), map(getIconsSizes), shareReplay(1));
+
+ public resolveHref(
+ name: string,
+ pixelSize = 16,
+ variant: SkyIconVariantType = 'line',
+ ): Observable {
+ return this.#spriteObs.pipe(
+ map((iconsSizes) => {
+ let href = `#sky-i-${name}`;
+
+ // Find the icon with the optimal size nearest to the requested size.
+ const nearestSize = getNearestSize(iconsSizes, name, pixelSize);
+
+ if (!nearestSize) {
+ throw new Error(`Icon with name '${name}' was not found.`);
+ }
+
+ href = `${href}-${nearestSize}-${variant}`;
+
+ return href;
+ }),
+ );
+ }
+}
diff --git a/libs/components/icon/src/lib/icon/icon-svg.component.html b/libs/components/icon/src/lib/icon/icon-svg.component.html
new file mode 100644
index 0000000000..60813b7854
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg.component.html
@@ -0,0 +1,7 @@
+
diff --git a/libs/components/icon/src/lib/icon/icon-svg.component.spec.ts b/libs/components/icon/src/lib/icon/icon-svg.component.spec.ts
new file mode 100644
index 0000000000..041f946961
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg.component.spec.ts
@@ -0,0 +1,99 @@
+import {
+ ComponentFixture,
+ TestBed,
+ fakeAsync,
+ tick,
+} from '@angular/core/testing';
+
+import { of } from 'rxjs';
+
+import { SkyIconSvgResolverService } from './icon-svg-resolver.service';
+import { SkyIconSvgComponent } from './icon-svg.component';
+import { SkyIconModule } from './icon.module';
+
+describe('Icon SVG component', () => {
+ let resolverSvc: jasmine.SpyObj;
+ let fixture: ComponentFixture;
+
+ function detectUrlChanges(): void {
+ fixture.detectChanges();
+
+ // Resolve icon ID Observable and apply changes.
+ tick();
+ fixture.detectChanges();
+ }
+
+ function getSvgEl(): SVGElement {
+ return fixture.nativeElement.querySelector('.sky-icon-svg-img');
+ }
+
+ function validateIconId(expectedId: string): void {
+ const useEl = getSvgEl().querySelector('use');
+
+ expect(useEl?.href.baseVal).toBe(expectedId);
+ }
+
+ beforeEach(() => {
+ resolverSvc = jasmine.createSpyObj(
+ 'SkyIconSvgResolverService',
+ ['resolveHref'],
+ );
+
+ resolverSvc.resolveHref.and.callFake((src, size, variant) => {
+ return of(`#${src}-${size}-${variant ?? 'line'}`);
+ });
+
+ TestBed.configureTestingModule({
+ imports: [SkyIconModule],
+ providers: [
+ {
+ provide: SkyIconSvgResolverService,
+ useValue: resolverSvc,
+ },
+ ],
+ });
+
+ fixture = TestBed.createComponent(SkyIconSvgComponent);
+ });
+
+ it('should display the resolved icon by ID', fakeAsync(() => {
+ fixture.componentRef.setInput('iconName', 'test');
+ detectUrlChanges();
+
+ validateIconId('#test-16-line');
+ }));
+
+ it('should display the resolved icon by ID and size', fakeAsync(() => {
+ fixture.componentRef.setInput('iconName', 'test');
+ fixture.componentRef.setInput('iconSize', '2x');
+ detectUrlChanges();
+
+ validateIconId('#test-32-line');
+ }));
+
+ it('should display the resolved icon by ID and variant', fakeAsync(() => {
+ fixture.componentRef.setInput('iconName', 'test');
+ fixture.componentRef.setInput('iconVariant', 'solid');
+ detectUrlChanges();
+
+ validateIconId('#test-16-solid');
+ }));
+
+ it("should use the host element's text color as its fill color", fakeAsync(() => {
+ fixture.nativeElement.style.color = '#0f0';
+
+ fixture.componentRef.setInput('iconName', 'test');
+ detectUrlChanges();
+
+ expect(getComputedStyle(getSvgEl()).fill).toBe('rgb(0, 255, 0)');
+ }));
+
+ it('should handle errors', fakeAsync(() => {
+ resolverSvc.resolveHref.and.throwError('Icon could not be resolved');
+
+ fixture.componentRef.setInput('iconName', 'test');
+ detectUrlChanges();
+
+ validateIconId('');
+ }));
+});
diff --git a/libs/components/icon/src/lib/icon/icon-svg.component.ts b/libs/components/icon/src/lib/icon/icon-svg.component.ts
new file mode 100644
index 0000000000..fe1b1bcee6
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg.component.ts
@@ -0,0 +1,61 @@
+import { NgClass, NgStyle } from '@angular/common';
+import { Component, computed, inject, input } from '@angular/core';
+import { toObservable, toSignal } from '@angular/core/rxjs-interop';
+
+import { catchError, of, switchMap } from 'rxjs';
+
+import { SkyIconSvgResolverService } from './icon-svg-resolver.service';
+import { SkyIconVariantType } from './types/icon-variant-type';
+
+const SIZE_BASE = 16;
+
+const SIZES = new Map([
+ ['', SIZE_BASE],
+ ['lg', 21.333 /* SIZE_BASE * (4/3) */],
+ ['2x', 32 /* SIZE_BASE * 2 */],
+ ['3x', 48 /* SIZE_BASE * 3 */],
+ ['4x', 64 /* SIZE_BASE * 4 */],
+ ['5x', 80 /* SIZE_BASE * 5 */],
+]);
+
+/**
+ * @internal
+ */
+@Component({
+ selector: 'sky-icon-svg',
+ standalone: true,
+ imports: [NgClass, NgStyle],
+ templateUrl: './icon-svg.component.html',
+ styleUrls: [
+ './icon-svg.default.component.scss',
+ './icon-svg.modern.component.scss',
+ ],
+})
+export class SkyIconSvgComponent {
+ readonly #resolverSvc = inject(SkyIconSvgResolverService);
+
+ public readonly iconName = input.required();
+ public readonly iconSize = input();
+ public readonly iconVariant = input();
+
+ readonly #iconInfo = computed(() => {
+ return {
+ src: this.iconName(),
+ size: this.iconSize(),
+ variant: this.iconVariant(),
+ };
+ });
+
+ protected readonly iconHref = toSignal(
+ toObservable(this.#iconInfo).pipe(
+ switchMap((info) =>
+ this.#resolverSvc.resolveHref(
+ info.src,
+ SIZES.get(info.size ?? ''),
+ info.variant,
+ ),
+ ),
+ catchError(() => of('')),
+ ),
+ );
+}
diff --git a/libs/components/icon/src/lib/icon/icon-svg.default.component.scss b/libs/components/icon/src/lib/icon/icon-svg.default.component.scss
new file mode 100644
index 0000000000..6aedbe1f57
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg.default.component.scss
@@ -0,0 +1,40 @@
+:host {
+ display: inline-block;
+ --sky-icon-svg-img-size: 1em;
+ --sky-icon-svg-img-top: 0.15em;
+}
+
+.sky-icon-svg-img {
+ display: inline-block;
+ fill: var(--sky-icon-svg-color-input, currentColor);
+ height: var(--sky-icon-svg-img-size);
+ width: var(--sky-icon-svg-img-size);
+ text-align: center;
+ position: relative;
+ top: var(--sky-icon-svg-img-top);
+
+ &-lg {
+ --sky-icon-svg-img-size: 1.333em;
+ --sky-icon-svg-img-top: 0.2em;
+ }
+
+ &-2x {
+ --sky-icon-svg-img-size: 2em;
+ --sky-icon-svg-img-top: 0.3em;
+ }
+
+ &-3x {
+ --sky-icon-svg-img-size: 3em;
+ --sky-icon-svg-img-top: 0.45em;
+ }
+
+ &-4x {
+ --sky-icon-svg-img-size: 4em;
+ --sky-icon-svg-img-top: 0.6em;
+ }
+
+ &-5x {
+ --sky-icon-svg-img-size: 5em;
+ --sky-icon-svg-img-top: 0.75em;
+ }
+}
diff --git a/libs/components/icon/src/lib/icon/icon-svg.modern.component.scss b/libs/components/icon/src/lib/icon/icon-svg.modern.component.scss
new file mode 100644
index 0000000000..6aedbe1f57
--- /dev/null
+++ b/libs/components/icon/src/lib/icon/icon-svg.modern.component.scss
@@ -0,0 +1,40 @@
+:host {
+ display: inline-block;
+ --sky-icon-svg-img-size: 1em;
+ --sky-icon-svg-img-top: 0.15em;
+}
+
+.sky-icon-svg-img {
+ display: inline-block;
+ fill: var(--sky-icon-svg-color-input, currentColor);
+ height: var(--sky-icon-svg-img-size);
+ width: var(--sky-icon-svg-img-size);
+ text-align: center;
+ position: relative;
+ top: var(--sky-icon-svg-img-top);
+
+ &-lg {
+ --sky-icon-svg-img-size: 1.333em;
+ --sky-icon-svg-img-top: 0.2em;
+ }
+
+ &-2x {
+ --sky-icon-svg-img-size: 2em;
+ --sky-icon-svg-img-top: 0.3em;
+ }
+
+ &-3x {
+ --sky-icon-svg-img-size: 3em;
+ --sky-icon-svg-img-top: 0.45em;
+ }
+
+ &-4x {
+ --sky-icon-svg-img-size: 4em;
+ --sky-icon-svg-img-top: 0.6em;
+ }
+
+ &-5x {
+ --sky-icon-svg-img-size: 5em;
+ --sky-icon-svg-img-top: 0.75em;
+ }
+}
diff --git a/libs/components/icon/src/lib/icon/icon.component.html b/libs/components/icon/src/lib/icon/icon.component.html
index 6e13c23139..8b10843504 100644
--- a/libs/components/icon/src/lib/icon/icon.component.html
+++ b/libs/components/icon/src/lib/icon/icon.component.html
@@ -1,16 +1,20 @@
-
+@if (iconName) {
+
+} @else {
+
+}
diff --git a/libs/components/icon/src/lib/icon/icon.component.ts b/libs/components/icon/src/lib/icon/icon.component.ts
index 7fa667cdd6..ec4aaac3cc 100644
--- a/libs/components/icon/src/lib/icon/icon.component.ts
+++ b/libs/components/icon/src/lib/icon/icon.component.ts
@@ -25,6 +25,13 @@ export class SkyIconComponent {
@Input()
public icon: string | undefined;
+ /**
+ * The name of the Blackbaud SVG icon to display. For internal use only.
+ * @internal
+ */
+ @Input()
+ public iconName: string | undefined;
+
/**
* The type of icon to display. Specifying `"fa"` displays a Font Awesome icon,
* while specifying `"skyux"` displays an icon from the custom SKY UX icon font. Note that
diff --git a/libs/components/icon/src/lib/icon/icon.module.ts b/libs/components/icon/src/lib/icon/icon.module.ts
index 27d78ef048..8babdc9107 100644
--- a/libs/components/icon/src/lib/icon/icon.module.ts
+++ b/libs/components/icon/src/lib/icon/icon.module.ts
@@ -1,13 +1,17 @@
import { CommonModule } from '@angular/common';
+import { provideHttpClient, withFetch } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { SkyIconClassListPipe } from './icon-class-list.pipe';
import { SkyIconStackComponent } from './icon-stack.component';
+import { SkyIconSvgResolverService } from './icon-svg-resolver.service';
+import { SkyIconSvgComponent } from './icon-svg.component';
import { SkyIconComponent } from './icon.component';
@NgModule({
declarations: [SkyIconClassListPipe, SkyIconComponent, SkyIconStackComponent],
- imports: [CommonModule],
+ imports: [CommonModule, SkyIconSvgComponent],
exports: [SkyIconComponent, SkyIconStackComponent],
+ providers: [provideHttpClient(withFetch()), SkyIconSvgResolverService],
})
export class SkyIconModule {}
From 8768cf9ecbadaa7845d9093f3c2ed1516487095a Mon Sep 17 00:00:00 2001
From: Corey Archer
Date: Wed, 3 Jul 2024 17:07:59 -0400
Subject: [PATCH 07/95] fix(components/forms): file attachment components
report file size errors with appropriate orders of magnitude (#2437)
---
.../src/assets/locales/resources_en_US.json | 8 +++----
.../file-attachment.component.html | 4 ++--
.../file-attachment.component.spec.ts | 4 ++--
.../file-attachment/file-drop.component.html | 8 +++++--
.../file-drop.component.spec.ts | 6 ++---
.../file-attachment/file-size.pipe.spec.ts | 22 +++++++++++++++++--
.../modules/file-attachment/file-size.pipe.ts | 6 ++++-
.../shared/sky-forms-resources.module.ts | 8 +++----
8 files changed, 46 insertions(+), 20 deletions(-)
diff --git a/libs/components/forms/src/assets/locales/resources_en_US.json b/libs/components/forms/src/assets/locales/resources_en_US.json
index a7b930c6d7..620010086c 100644
--- a/libs/components/forms/src/assets/locales/resources_en_US.json
+++ b/libs/components/forms/src/assets/locales/resources_en_US.json
@@ -185,19 +185,19 @@
},
"skyux_file_attachment_max_file_size_error_label_text": {
"_description": "The error message for file attachment max file size error",
- "message": "Upload a file under {0}KB."
+ "message": "Upload a file under {0}."
},
"skyux_file_attachment_max_file_size_error_label_text_with_name": {
"_description": "The error message for file attachment max file size error",
- "message": "{0}: Upload a file under {1}KB."
+ "message": "{0}: Upload a file under {1}."
},
"skyux_file_attachment_min_file_size_error_label_text": {
"_description": "The error message for file attachment min file size error",
- "message": "Upload a file over {0}KB."
+ "message": "Upload a file over {0}."
},
"skyux_file_attachment_min_file_size_error_label_text_with_name": {
"_description": "The error message for file attachment min file size error",
- "message": "{0}: Upload a file over {1}KB."
+ "message": "{0}: Upload a file over {1}."
},
"skyux_file_attachment_label_no_file_chosen": {
"_description": "Label for single file attachment when no file is chosen",
diff --git a/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.html b/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.html
index a7ddb455ea..bb9c2e624a 100644
--- a/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.html
+++ b/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.html
@@ -187,7 +187,7 @@
[errorName]="'maxFileSize'"
[errorText]="
'skyux_file_attachment_max_file_size_error_label_text'
- | skyLibResources: fileErrorParam
+ | skyLibResources: (fileErrorParam | skyFileSize)
"
/>
diff --git a/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.spec.ts b/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.spec.ts
index e0efbac628..1e32ba8549 100644
--- a/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.spec.ts
+++ b/libs/components/forms/src/lib/modules/file-attachment/file-attachment.component.spec.ts
@@ -1453,7 +1453,7 @@ describe('File attachment', () => {
expect(
fixture.nativeElement.querySelector('sky-form-error')?.textContent.trim(),
- ).toBe('Error: Upload a file under 50KB.');
+ ).toBe('Error: Upload a file under 50 bytes.');
});
it('should render file errors and NgControl errors when label text is set', () => {
@@ -1482,7 +1482,7 @@ describe('File attachment', () => {
fixture.nativeElement
.querySelectorAll('sky-form-error')[1]
?.textContent.trim(),
- ).toBe('Error: Upload a file under 50KB.');
+ ).toBe('Error: Upload a file under 50 bytes.');
});
it('should render `labelText` and not label element if `labelText` is set', () => {
diff --git a/libs/components/forms/src/lib/modules/file-attachment/file-drop.component.html b/libs/components/forms/src/lib/modules/file-attachment/file-drop.component.html
index 269cbe2bf9..91fe8a77f6 100644
--- a/libs/components/forms/src/lib/modules/file-attachment/file-drop.component.html
+++ b/libs/components/forms/src/lib/modules/file-attachment/file-drop.component.html
@@ -190,7 +190,9 @@
errorName="maxFileSize"
[errorText]="
'skyux_file_attachment_max_file_size_error_label_text_with_name'
- | skyLibResources: rejectedFile.file.name : rejectedFile.errorParam
+ | skyLibResources
+ : rejectedFile.file.name
+ : (rejectedFile.errorParam | skyFileSize)
"
/>
{
componentInstance.labelText = 'Label';
componentInstance.minFileSize = 1500;
- componentInstance.maxFileSize = 3000;
+ componentInstance.maxFileSize = 3073;
componentInstance.acceptedTypes = 'image/png, image/jpeg';
fixture.detectChanges();
@@ -465,11 +465,11 @@ describe('File drop component', () => {
expect(minSizeError).toBeVisible();
expect(minSizeError.textContent).toContain(
- 'foo.txt: Upload a file over 1500KB.',
+ 'foo.txt: Upload a file over 1 KB.',
);
expect(maxSizeError).toBeVisible();
expect(maxSizeError.textContent).toContain(
- 'bar.jpeg: Upload a file under 3000KB.',
+ 'bar.jpeg: Upload a file under 3 KB.',
);
expect(typeError).toBeVisible();
diff --git a/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.spec.ts b/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.spec.ts
index a3d4a49ec5..9dcc2efa6c 100644
--- a/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.spec.ts
+++ b/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.spec.ts
@@ -10,9 +10,9 @@ describe('File size pipe', () => {
let decimalPipe: DecimalPipe;
function validateFormatted(
- value: number | undefined | null,
+ value: number | string | undefined | null,
expected: string,
- ) {
+ ): void {
const result = fileSizePipe.transform(value);
expect(result).toBe(expected);
@@ -34,6 +34,9 @@ describe('File size pipe', () => {
validateFormatted(1, '1 byte');
validateFormatted(100, '100 bytes');
validateFormatted(999, '999 bytes');
+ validateFormatted('1', '1 byte');
+ validateFormatted('100', '100 bytes');
+ validateFormatted('999', '999 bytes');
});
it('should format kilobytes', function () {
@@ -41,6 +44,10 @@ describe('File size pipe', () => {
validateFormatted(1024, '1 KB');
validateFormatted(102400, '100 KB');
validateFormatted(1022976, '999 KB');
+ validateFormatted('1000', '1,000 bytes');
+ validateFormatted('1024', '1 KB');
+ validateFormatted('102400', '100 KB');
+ validateFormatted('1022976', '999 KB');
});
it('should format megabytes', function () {
@@ -49,6 +56,11 @@ describe('File size pipe', () => {
validateFormatted(1992300, '1.9 MB');
validateFormatted(104857600, '100 MB');
validateFormatted(1048471150, '999.9 MB');
+ validateFormatted('1048575', '1,023 KB');
+ validateFormatted('1048576', '1 MB');
+ validateFormatted('1992300', '1.9 MB');
+ validateFormatted('104857600', '100 MB');
+ validateFormatted('1048471150', '999.9 MB');
});
it('should format gigabytes', function () {
@@ -56,11 +68,17 @@ describe('File size pipe', () => {
validateFormatted(1073741824, '1 GB');
validateFormatted(107374182400, '100 GB');
validateFormatted(1073634449818, '999.9 GB');
+ validateFormatted('1073741823', '1,023.9 MB');
+ validateFormatted('1073741824', '1 GB');
+ validateFormatted('107374182400', '100 GB');
+ validateFormatted('1073634449818', '999.9 GB');
});
it('should format values over 1,000 gigabytes as gigabytes', function () {
validateFormatted(1073741824000, '1,000 GB');
validateFormatted(10737310865818, '9,999.9 GB');
+ validateFormatted('1073741824000', '1,000 GB');
+ validateFormatted('10737310865818', '9,999.9 GB');
});
it('should return an empty string when the input is null or undefined', function () {
diff --git a/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.ts b/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.ts
index 959a450819..d2d326acee 100644
--- a/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.ts
+++ b/libs/components/forms/src/lib/modules/file-attachment/file-size.pipe.ts
@@ -20,7 +20,7 @@ export class SkyFileSizePipe implements PipeTransform {
this.#resourcesService = resourcesService;
}
- public transform(input?: number | null): string {
+ public transform(input?: number | string | null): string {
let decimalPlaces = 0,
dividend = 1,
template: string;
@@ -29,6 +29,10 @@ export class SkyFileSizePipe implements PipeTransform {
return '';
}
+ if (typeof input === 'string') {
+ input = Number.parseInt(input);
+ }
+
if (Math.abs(input) === 1) {
template = 'skyux_file_attachment_file_size_b_singular';
} else if (input < 1024) {
diff --git a/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts b/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts
index 31f0552add..44110f2b64 100644
--- a/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts
+++ b/libs/components/forms/src/lib/modules/shared/sky-forms-resources.module.ts
@@ -119,16 +119,16 @@ const RESOURCES: Record = {
message: '{0}: Upload a file of type {1}.',
},
skyux_file_attachment_max_file_size_error_label_text: {
- message: 'Upload a file under {0}KB.',
+ message: 'Upload a file under {0}.',
},
skyux_file_attachment_max_file_size_error_label_text_with_name: {
- message: '{0}: Upload a file under {1}KB.',
+ message: '{0}: Upload a file under {1}.',
},
skyux_file_attachment_min_file_size_error_label_text: {
- message: 'Upload a file over {0}KB.',
+ message: 'Upload a file over {0}.',
},
skyux_file_attachment_min_file_size_error_label_text_with_name: {
- message: '{0}: Upload a file over {1}KB.',
+ message: '{0}: Upload a file over {1}.',
},
skyux_file_attachment_label_no_file_chosen: { message: 'No file chosen.' },
skyux_file_attachment_required: { message: 'Required' },
From ffa79466cfa356db4495ec4b5116d9668b0cb54f Mon Sep 17 00:00:00 2001
From: Corey Archer
Date: Mon, 8 Jul 2024 12:53:09 -0400
Subject: [PATCH 08/95] fix(components/forms): improve default value handling
for heading styles on form group components (#2420)
---
.../forms/radio/radio.component.html | 10 ++++++++--
.../components/forms/radio/radio.component.ts | 11 +++++++++++
.../checkbox/checkbox-group.component.spec.ts | 11 ++++++++---
.../checkbox/checkbox-group.component.ts | 15 +++++++++------
.../standard-checkbox-group.component.ts | 2 +-
.../field-group/field-group.component.spec.ts | 18 +++++++++++++++---
.../field-group/field-group.component.ts | 8 ++++++--
.../fixtures/field-group.component.fixture.ts | 6 ++++--
.../radio-group-reactive.component.fixture.ts | 2 +-
.../radio/radio-group.component.spec.ts | 11 ++++++++---
.../lib/modules/radio/radio-group.component.ts | 16 ++++++++++------
11 files changed, 81 insertions(+), 29 deletions(-)
diff --git a/apps/playground/src/app/components/forms/radio/radio.component.html b/apps/playground/src/app/components/forms/radio/radio.component.html
index a96af32d60..4faaf85fc4 100644
--- a/apps/playground/src/app/components/forms/radio/radio.component.html
+++ b/apps/playground/src/app/components/forms/radio/radio.component.html
@@ -29,6 +29,13 @@
>
Mark all fields as touched
+
Radio buttons (reactive)
@@ -41,8 +48,7 @@ Radio buttons (reactive)
headingText="What is your favorite season? (reactive)"
[required]="required"
[stacked]="true"
- headingLevel="3"
- headingStyle="3"
+ [headingStyle]="headingStyle"
>
-
diff --git a/apps/playground/src/app/components/forms/radio/radio.component.ts b/apps/playground/src/app/components/forms/radio/radio.component.ts
index eff22e10c5..c76f479408 100644
--- a/apps/playground/src/app/components/forms/radio/radio.component.ts
+++ b/apps/playground/src/app/components/forms/radio/radio.component.ts
@@ -36,6 +36,8 @@ export class RadioComponent {
public selectedValue = '3';
+ public headingStyle: number | undefined;
+
constructor(formBuilder: UntypedFormBuilder) {
this.favoriteSeason = new UntypedFormControl(
{
@@ -77,4 +79,13 @@ export class RadioComponent {
model.control.markAsTouched();
model.control.updateValueAndValidity();
}
+
+ public toggleHeadingStyle(): void {
+ const newStyle = (this.headingStyle ?? 2) + 1;
+ if (newStyle > 5) {
+ this.headingStyle = undefined;
+ } else {
+ this.headingStyle = newStyle;
+ }
+ }
}
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.spec.ts b/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.spec.ts
index 33f39eb094..27c169f786 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.spec.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.spec.ts
@@ -88,7 +88,12 @@ describe('Checkbox group component', function () {
4,
5,
];
- const headingStyles: SkyCheckboxGroupHeadingStyle[] = [3, 4, 5];
+ const headingStyles: (SkyCheckboxGroupHeadingStyle | undefined)[] = [
+ undefined,
+ 3,
+ 4,
+ 5,
+ ];
headingLevels.forEach((headingLevel) => {
headingStyles.forEach((headingStyle) => {
componentInstance.headingLevel = headingLevel;
@@ -96,8 +101,8 @@ describe('Checkbox group component', function () {
fixture.detectChanges();
const selector = headingLevel
- ? `h${headingLevel}.sky-font-heading-${headingStyle}`
- : `span.sky-font-heading-${headingStyle}`;
+ ? `h${headingLevel}.sky-font-heading-${headingStyle ?? 4}`
+ : `span.sky-font-heading-${headingStyle ?? 4}`;
const heading = fixture.nativeElement.querySelector(selector);
expect(heading).toExist();
diff --git a/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.ts b/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.ts
index db2ff21657..5382f0c5d2 100644
--- a/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/checkbox-group.component.ts
@@ -24,6 +24,10 @@ import { SkyFormsResourcesModule } from '../shared/sky-forms-resources.module';
import { SkyCheckboxGroupHeadingLevel } from './checkbox-group-heading-level';
import { SkyCheckboxGroupHeadingStyle } from './checkbox-group-heading-style';
+function numberAttribute4(value: unknown): number {
+ return numberAttribute(value, 4);
+}
+
/**
* Organizes checkboxes into a group.
*/
@@ -93,8 +97,10 @@ export class SkyCheckboxGroupComponent implements Validator {
* The heading [font style](https://developer.blackbaud.com/skyux/design/styles/typography#headings).
* @default 4
*/
- @Input({ transform: numberAttribute })
- public headingStyle: SkyCheckboxGroupHeadingStyle = 4;
+ @Input({ transform: numberAttribute4 })
+ public set headingStyle(value: SkyCheckboxGroupHeadingStyle) {
+ this.headingClass = `sky-font-heading-${value}`;
+ }
/**
* [Persistent inline help text](https://developer.blackbaud.com/skyux/design/guidelines/user-assistance#inline-help) that provides
@@ -137,12 +143,9 @@ export class SkyCheckboxGroupComponent implements Validator {
@HostBinding('class.sky-margin-stacked-xl')
public stackedXL = false;
- public get headingClass(): string {
- return `sky-font-heading-${this.headingStyle}`;
- }
-
readonly #idSvc = inject(SkyIdService);
protected errorId = this.#idSvc.generateId();
+ protected headingClass = 'sky-font-heading-4';
protected formErrorsDataId = 'checkbox-group-form-errors';
protected formGroup: FormGroup | null | undefined;
diff --git a/libs/components/forms/src/lib/modules/checkbox/fixtures/standard-checkbox-group.component.ts b/libs/components/forms/src/lib/modules/checkbox/fixtures/standard-checkbox-group.component.ts
index ad965bf8c2..da858ba2a3 100644
--- a/libs/components/forms/src/lib/modules/checkbox/fixtures/standard-checkbox-group.component.ts
+++ b/libs/components/forms/src/lib/modules/checkbox/fixtures/standard-checkbox-group.component.ts
@@ -32,7 +32,7 @@ export class SkyStandardCheckboxGroupComponent {
public hintText: string | undefined;
public headingHidden = false;
public headingLevel: SkyCheckboxGroupHeadingLevel | undefined = 3;
- public headingStyle: SkyCheckboxGroupHeadingStyle = 3;
+ public headingStyle: SkyCheckboxGroupHeadingStyle | undefined = 3;
public required: boolean | undefined = false;
public stacked: boolean | undefined = true;
diff --git a/libs/components/forms/src/lib/modules/field-group/field-group.component.spec.ts b/libs/components/forms/src/lib/modules/field-group/field-group.component.spec.ts
index fbd967e834..1973fc40b6 100644
--- a/libs/components/forms/src/lib/modules/field-group/field-group.component.spec.ts
+++ b/libs/components/forms/src/lib/modules/field-group/field-group.component.spec.ts
@@ -5,6 +5,8 @@ import {
SkyHelpTestingModule,
} from '@skyux/core/testing';
+import { SkyFieldGroupHeadingLevel } from './field-group-heading-level';
+import { SkyFieldGroupHeadingStyle } from './field-group-heading-style';
import { FieldGroupComponent } from './fixtures/field-group.component.fixture';
describe('Field group component', function () {
@@ -65,14 +67,24 @@ describe('Field group component', function () {
});
it('should render the correct heading level and styles', () => {
- [3, 4].forEach((headingLevel) => {
- [3, 4].forEach((headingStyle) => {
+ const headingLevels: (SkyFieldGroupHeadingLevel | undefined)[] = [
+ undefined,
+ 3,
+ 4,
+ ];
+ const headingStyles: (SkyFieldGroupHeadingStyle | undefined)[] = [
+ undefined,
+ 3,
+ 4,
+ ];
+ headingLevels.forEach((headingLevel) => {
+ headingStyles.forEach((headingStyle) => {
componentInstance.headingLevel = headingLevel;
componentInstance.headingStyle = headingStyle;
fixture.detectChanges();
const heading = fixture.nativeElement.querySelector(
- `h${headingLevel}.sky-font-heading-${headingStyle}`,
+ `h${headingLevel ?? 3}.sky-font-heading-${headingStyle ?? 3}`,
);
expect(heading).toExist();
diff --git a/libs/components/forms/src/lib/modules/field-group/field-group.component.ts b/libs/components/forms/src/lib/modules/field-group/field-group.component.ts
index 82a3d7ed0e..724f3fc7cb 100644
--- a/libs/components/forms/src/lib/modules/field-group/field-group.component.ts
+++ b/libs/components/forms/src/lib/modules/field-group/field-group.component.ts
@@ -15,6 +15,10 @@ import { SkyFormFieldLabelTextRequiredService } from '../shared/form-field-label
import { SkyFieldGroupHeadingLevel } from './field-group-heading-level';
import { SkyFieldGroupHeadingStyle } from './field-group-heading-style';
+function numberAttribute3(value: unknown): number {
+ return numberAttribute(value, 3);
+}
+
/**
* Organizes form fields into a group.
*/
@@ -58,14 +62,14 @@ export class SkyFieldGroupComponent {
* The semantic heading level in the document structure.
* @default 3
*/
- @Input({ transform: numberAttribute })
+ @Input({ transform: numberAttribute3 })
public headingLevel: SkyFieldGroupHeadingLevel = 3;
/**
* The heading [font style](https://developer.blackbaud.com/skyux/design/styles/typography#headings).
* @default 3
*/
- @Input({ transform: numberAttribute })
+ @Input({ transform: numberAttribute3 })
public set headingStyle(value: SkyFieldGroupHeadingStyle) {
this.headingClass = `sky-font-heading-${value}`;
}
diff --git a/libs/components/forms/src/lib/modules/field-group/fixtures/field-group.component.fixture.ts b/libs/components/forms/src/lib/modules/field-group/fixtures/field-group.component.fixture.ts
index d034d5dcb5..a426cb0e14 100644
--- a/libs/components/forms/src/lib/modules/field-group/fixtures/field-group.component.fixture.ts
+++ b/libs/components/forms/src/lib/modules/field-group/fixtures/field-group.component.fixture.ts
@@ -8,6 +8,8 @@ import {
} from '@angular/forms';
import { SkyInputBoxModule } from '../../input-box/input-box.module';
+import { SkyFieldGroupHeadingLevel } from '../field-group-heading-level';
+import { SkyFieldGroupHeadingStyle } from '../field-group-heading-style';
import { SkyFieldGroupModule } from '../field-group.module';
@Component({
@@ -27,8 +29,8 @@ export class FieldGroupComponent {
public headingText = 'Heading text';
public hintText: string | undefined;
public headingHidden = false;
- public headingStyle = 3;
- public headingLevel = 3;
+ public headingStyle: SkyFieldGroupHeadingStyle | undefined = 3;
+ public headingLevel: SkyFieldGroupHeadingLevel | undefined = 3;
public helpKey: string | undefined;
public helpPopoverContent: string | undefined;
public helpPopoverTitle: string | undefined;
diff --git a/libs/components/forms/src/lib/modules/radio/fixtures/radio-group-reactive.component.fixture.ts b/libs/components/forms/src/lib/modules/radio/fixtures/radio-group-reactive.component.fixture.ts
index a08683745d..3e7f911755 100644
--- a/libs/components/forms/src/lib/modules/radio/fixtures/radio-group-reactive.component.fixture.ts
+++ b/libs/components/forms/src/lib/modules/radio/fixtures/radio-group-reactive.component.fixture.ts
@@ -55,7 +55,7 @@ export class SkyRadioGroupReactiveFixtureComponent implements OnInit {
public headingLevel: SkyRadioGroupHeadingLevel | undefined = 3;
- public headingStyle: SkyRadioGroupHeadingStyle = 3;
+ public headingStyle: SkyRadioGroupHeadingStyle | undefined = 3;
@ViewChild(SkyRadioGroupComponent)
public radioGroupComponent: SkyRadioGroupComponent | undefined;
diff --git a/libs/components/forms/src/lib/modules/radio/radio-group.component.spec.ts b/libs/components/forms/src/lib/modules/radio/radio-group.component.spec.ts
index c674df67bb..13f9a34afe 100644
--- a/libs/components/forms/src/lib/modules/radio/radio-group.component.spec.ts
+++ b/libs/components/forms/src/lib/modules/radio/radio-group.component.spec.ts
@@ -646,7 +646,12 @@ describe('Radio group component (reactive)', function () {
4,
5,
];
- const headingStyles: SkyRadioGroupHeadingStyle[] = [3, 4, 5];
+ const headingStyles: (SkyRadioGroupHeadingStyle | undefined)[] = [
+ undefined,
+ 3,
+ 4,
+ 5,
+ ];
headingLevels.forEach((headingLevel) => {
headingStyles.forEach((headingStyle) => {
componentInstance.headingText = 'Label text';
@@ -655,8 +660,8 @@ describe('Radio group component (reactive)', function () {
fixture.detectChanges();
const selector = headingLevel
- ? `h${headingLevel}.sky-font-heading-${headingStyle}`
- : `span.sky-font-heading-${headingStyle}`;
+ ? `h${headingLevel}.sky-font-heading-${headingStyle ?? 4}`
+ : `span.sky-font-heading-${headingStyle ?? 4}`;
const heading = fixture.nativeElement.querySelector(selector);
expect(heading).toExist();
diff --git a/libs/components/forms/src/lib/modules/radio/radio-group.component.ts b/libs/components/forms/src/lib/modules/radio/radio-group.component.ts
index a61fb8ddb6..a1ea5178eb 100644
--- a/libs/components/forms/src/lib/modules/radio/radio-group.component.ts
+++ b/libs/components/forms/src/lib/modules/radio/radio-group.component.ts
@@ -33,6 +33,10 @@ import { SkyRadioGroupHeadingStyle } from './types/radio-group-heading-style';
let nextUniqueId = 0;
+function numberAttribute4(value: unknown): number {
+ return numberAttribute(value, 4);
+}
+
/**
* Organizes radio buttons into a group. It is required for radio
* buttons on Angular reactive forms, and we recommend using it with all radio buttons.
@@ -132,8 +136,10 @@ export class SkyRadioGroupComponent
* The heading [font style](https://developer.blackbaud.com/skyux/design/styles/typography#headings).
* @default 4
*/
- @Input({ transform: numberAttribute })
- public headingStyle: SkyRadioGroupHeadingStyle = 4;
+ @Input({ transform: numberAttribute4 })
+ public set headingStyle(value: SkyRadioGroupHeadingStyle) {
+ this.headingClass = `sky-font-heading-${value}`;
+ }
/**
* The name for the collection of radio buttons that the component groups together.
@@ -257,10 +263,6 @@ export class SkyRadioGroupComponent
@Input()
public helpKey: string | undefined;
- public get headingClass(): string {
- return `sky-font-heading-${this.headingStyle}`;
- }
-
/**
* Our radio components are usually implemented using an unordered list. This is an
* accessibility violation because the unordered list has an implicit role which
@@ -282,6 +284,8 @@ export class SkyRadioGroupComponent
@HostBinding('class.sky-margin-stacked-xl')
public stackedXL = false;
+ protected headingClass = 'sky-font-heading-4';
+
#controlValue: any;
#defaultName = `sky-radio-group-${nextUniqueId++}`;
From 89276711d0b4a19a54e9cd1f00e72df3c0ee1596 Mon Sep 17 00:00:00 2001
From: Sandhya Raja Sabeson
Date: Mon, 8 Jul 2024 14:02:54 -0400
Subject: [PATCH 09/95] feat(components/help-inline): update documentation and
deprecate `indicators/help-inline` (#2428)
---
apps/playground/src/app/app.module.ts | 3 +-
.../help-inline/help-inline.component.html | 13 +-----
.../help-inline/help-inline.component.html | 20 ++++++++++
.../help-inline/help-inline.component.ts | 16 ++++++++
.../help-inline/help-inline.module.ts | 40 +++++++++++++++++++
.../indicators/indicators.module.ts | 7 ++++
.../help-inline/help-inline.component.ts | 8 ++--
.../src/help-inline/help-inline-harness.ts | 6 +--
.../help-inline/help-inline.component.spec.ts | 18 ++++++++-
.../help-inline/help-inline.component.ts | 14 ++++++-
10 files changed, 123 insertions(+), 22 deletions(-)
create mode 100644 apps/playground/src/app/components/indicators/help-inline/help-inline.component.html
create mode 100644 apps/playground/src/app/components/indicators/help-inline/help-inline.component.ts
create mode 100644 apps/playground/src/app/components/indicators/help-inline/help-inline.module.ts
diff --git a/apps/playground/src/app/app.module.ts b/apps/playground/src/app/app.module.ts
index 16de4563b2..5ca3ab0f93 100644
--- a/apps/playground/src/app/app.module.ts
+++ b/apps/playground/src/app/app.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { SkyHelpService } from '@skyux/core';
+import { SKY_LOG_LEVEL, SkyHelpService, SkyLogLevel } from '@skyux/core';
import { SkyFluidGridModule } from '@skyux/layout';
import { SkyThemeService } from '@skyux/theme';
@@ -25,6 +25,7 @@ import { SkyThemeSelectorComponent } from './shared/theme-selector/theme-selecto
providers: [
SkyThemeService,
{ provide: SkyHelpService, useClass: PlaygroundHelpService },
+ { provide: SKY_LOG_LEVEL, useValue: SkyLogLevel.Info },
],
bootstrap: [AppComponent],
})
diff --git a/apps/playground/src/app/components/help-inline/help-inline.component.html b/apps/playground/src/app/components/help-inline/help-inline.component.html
index b6bb30b9d1..19172e39d8 100644
--- a/apps/playground/src/app/components/help-inline/help-inline.component.html
+++ b/apps/playground/src/app/components/help-inline/help-inline.component.html
@@ -3,18 +3,9 @@
Giving
-
- This is a popover.
-
diff --git a/apps/playground/src/app/components/indicators/help-inline/help-inline.component.html b/apps/playground/src/app/components/indicators/help-inline/help-inline.component.html
new file mode 100644
index 0000000000..b6bb30b9d1
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/help-inline/help-inline.component.html
@@ -0,0 +1,20 @@
+
+
+ Giving
+
+
+
+ This is a popover.
+
+
+
diff --git a/apps/playground/src/app/components/indicators/help-inline/help-inline.component.ts b/apps/playground/src/app/components/indicators/help-inline/help-inline.component.ts
new file mode 100644
index 0000000000..10b876605d
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/help-inline/help-inline.component.ts
@@ -0,0 +1,16 @@
+import { ChangeDetectorRef, Component, inject } from '@angular/core';
+
+@Component({
+ selector: 'app-help-inline',
+ templateUrl: './help-inline.component.html',
+})
+export class HelpInlineComponent {
+ public popoverOpen = false;
+
+ #changeDetector = inject(ChangeDetectorRef);
+
+ public popoverChange(isOpen): void {
+ this.popoverOpen = isOpen;
+ this.#changeDetector.markForCheck();
+ }
+}
diff --git a/apps/playground/src/app/components/indicators/help-inline/help-inline.module.ts b/apps/playground/src/app/components/indicators/help-inline/help-inline.module.ts
new file mode 100644
index 0000000000..54fca169f0
--- /dev/null
+++ b/apps/playground/src/app/components/indicators/help-inline/help-inline.module.ts
@@ -0,0 +1,40 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { SkyHelpInlineModule } from '@skyux/indicators';
+import { SkyPopoverModule } from '@skyux/popovers';
+
+import { ComponentRouteInfo } from '../../../shared/component-info/component-route-info';
+
+import { HelpInlineComponent } from './help-inline.component';
+
+const routes: ComponentRouteInfo[] = [
+ {
+ path: '',
+ component: HelpInlineComponent,
+ data: {
+ name: 'Legacy help inline',
+ icon: 'question',
+ library: 'indicators/help-inline',
+ },
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class HelpInlineRoutingModule {}
+
+@NgModule({
+ declarations: [HelpInlineComponent],
+ imports: [
+ CommonModule,
+ HelpInlineRoutingModule,
+ SkyHelpInlineModule,
+ SkyPopoverModule,
+ ],
+})
+export class HelpInlineModule {
+ public static routes = routes;
+}
diff --git a/apps/playground/src/app/components/indicators/indicators.module.ts b/apps/playground/src/app/components/indicators/indicators.module.ts
index 2487c616e9..22432cb26f 100644
--- a/apps/playground/src/app/components/indicators/indicators.module.ts
+++ b/apps/playground/src/app/components/indicators/indicators.module.ts
@@ -7,6 +7,13 @@ const routes: Routes = [
loadChildren: () =>
import('./alert/alert.module').then((m) => m.AlertModule),
},
+ {
+ path: 'help-inline-legacy',
+ loadChildren: () =>
+ import('./help-inline/help-inline.module').then(
+ (m) => m.HelpInlineModule,
+ ),
+ },
{
path: 'icon',
loadChildren: () => import('./icon/icon.module').then((m) => m.IconModule),
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
index 55c5d41172..9a71b5b7ce 100644
--- a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
+++ b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
@@ -20,7 +20,6 @@ import { SkyHelpInlineAriaHaspopupPipe } from './help-inline-aria-haspopup.pipe'
/**
* Inserts a help button beside an element, such as a field, to display contextual information about the element.
- * @internal
*/
@Component({
selector: 'sky-help-inline',
@@ -44,7 +43,6 @@ export class SkyHelpInlineComponent {
* The ID of the element that the help inline button controls.
* This property [supports accessibility rules for disclosures](https://www.w3.org/TR/wai-aria-practices-1.1/#disclosure).
* For more information about the `aria-controls` attribute, see the [WAI-ARIA definition](https://www.w3.org/TR/wai-aria/#aria-controls).
- * @internal
*/
@Input()
public ariaControls: string | undefined;
@@ -65,7 +63,7 @@ export class SkyHelpInlineComponent {
public ariaLabel: string | undefined;
/**
- * A unique key that identifies the global help content to display when the button is clicked.
+ * A unique key that identifies the [global help](https://developer.blackbaud.com/skyux/learn/develop/global-help) content to display when the button is clicked.
*/
@Input()
public helpKey: string | undefined;
@@ -79,13 +77,13 @@ export class SkyHelpInlineComponent {
public labelledBy: string | undefined;
/**
- * The label of the component help inline is attached to.
+ * The label of the component the help inline button is attached to.
*/
@Input()
public labelText: string | undefined;
/**
- * The content of the popover. When specified, clicking the help inline button opens a popover with this content and optional title.
+ * The content of the help popover. When specified, clicking the help inline button opens a popover with this content and optional title.
*/
@Input()
public set popoverContent(value: string | TemplateRef | undefined) {
diff --git a/libs/components/help-inline/testing/src/help-inline/help-inline-harness.ts b/libs/components/help-inline/testing/src/help-inline/help-inline-harness.ts
index 3445e75463..4360f34544 100644
--- a/libs/components/help-inline/testing/src/help-inline/help-inline-harness.ts
+++ b/libs/components/help-inline/testing/src/help-inline/help-inline-harness.ts
@@ -9,7 +9,7 @@ import {
import { SkyHelpInlineHarnessFilters } from './help-inline-harness.filters';
/**
- * Harness for interacting with a help inline component in tests.
+ * Harness for interacting with a help inline button component in tests.
*/
export class SkyHelpInlineHarness extends SkyComponentHarness {
/**
@@ -103,7 +103,7 @@ export class SkyHelpInlineHarness extends SkyComponentHarness {
}
/**
- * Gets the help inline popover content.
+ * Gets the help popover content.
*/
public async getPopoverContent(): Promise<
TemplateRef | string | undefined
@@ -112,7 +112,7 @@ export class SkyHelpInlineHarness extends SkyComponentHarness {
}
/**
- * Gets the help inline popover title.
+ * Gets the help popover title.
*/
public async getPopoverTitle(): Promise {
return (await this.#getPopoverHarnessContent())?.getTitleText();
diff --git a/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.spec.ts b/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.spec.ts
index 84d90efd36..1a60c0b0f4 100644
--- a/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.spec.ts
+++ b/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.spec.ts
@@ -2,6 +2,7 @@ import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserModule, By } from '@angular/platform-browser';
import { expect, expectAsync } from '@skyux-sdk/testing';
+import { SkyLogService } from '@skyux/core';
import { SkyHelpInlineModule } from '../help-inline/help-inline.module';
@@ -24,13 +25,16 @@ describe('Help inline component', () => {
let fixture: ComponentFixture;
let cmp: HelpInlineTestComponent;
let debugElement: DebugElement;
+ let logService: SkyLogService;
+ let deprecatedLogSpy: jasmine.Spy;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [HelpInlineTestComponent],
imports: [BrowserModule, SkyHelpInlineModule],
});
-
+ logService = TestBed.inject(SkyLogService);
+ deprecatedLogSpy = spyOn(logService, 'deprecated');
fixture = TestBed.createComponent(HelpInlineTestComponent);
cmp = fixture.componentInstance as HelpInlineTestComponent;
debugElement = fixture.debugElement;
@@ -38,6 +42,18 @@ describe('Help inline component', () => {
fixture.detectChanges();
});
+ it('should log that the component is deprecated', () => {
+ fixture.detectChanges();
+ expect(deprecatedLogSpy).toHaveBeenCalledWith(
+ 'SkyHelpInlineComponent',
+ Object({
+ deprecationMajorVersion: 10,
+ replacementRecommendation:
+ 'Use the help inline button component in the `@skyux/help-inline` library instead.',
+ }),
+ );
+ });
+
it('should emit a click event on button click', () => {
debugElement
.query(By.css('.sky-help-inline'))
diff --git a/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.ts b/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.ts
index a8f7988a8f..bc75ebf039 100644
--- a/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.ts
+++ b/libs/components/indicators/src/lib/modules/help-inline/help-inline.component.ts
@@ -1,5 +1,9 @@
-import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
+import { SkyLogService } from '@skyux/core';
+/**
+ * @deprecated Use the help inline button component in the `@skyux/help-inline` library instead.
+ */
@Component({
selector: 'sky-help-inline',
templateUrl: './help-inline.component.html',
@@ -38,6 +42,14 @@ export class SkyHelpInlineComponent {
@Output()
public actionClick = new EventEmitter();
+ constructor() {
+ inject(SkyLogService).deprecated('SkyHelpInlineComponent', {
+ deprecationMajorVersion: 10,
+ replacementRecommendation:
+ 'Use the help inline button component in the `@skyux/help-inline` library instead.',
+ });
+ }
+
public onClick(): void {
this.actionClick.emit();
}
From febfde3967f5833d602df199efd351334fe51a20 Mon Sep 17 00:00:00 2001
From: Blackbaud Sky Build User
Date: Mon, 8 Jul 2024 16:27:44 -0400
Subject: [PATCH 10/95] chore: release 10.34.0 (#2426)
---
CHANGELOG.md | 17 +++++++++++++++++
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4195a3cacc..d3613d0b0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog
+## [10.34.0](https://github.com/blackbaud/skyux/compare/10.33.0...10.34.0) (2024-07-08)
+
+
+### Features
+
+* **components/help-inline:** update documentation and deprecate `indicators/help-inline` ([#2428](https://github.com/blackbaud/skyux/issues/2428)) ([8927671](https://github.com/blackbaud/skyux/commit/89276711d0b4a19a54e9cd1f00e72df3c0ee1596))
+* **components/icon:** add internal support for SVG-based icons ([#2433](https://github.com/blackbaud/skyux/issues/2433)) ([3d61fe0](https://github.com/blackbaud/skyux/commit/3d61fe0c635209111a8a7b2de09492f48475d82f))
+
+
+### Bug Fixes
+
+* **code-examples:** satisfy ESLint rules for action button and AG Grid code examples ([#2423](https://github.com/blackbaud/skyux/issues/2423)) ([26c36f5](https://github.com/blackbaud/skyux/commit/26c36f5d745315fcd79c8a1392455fdf5735e1a4))
+* **code-examples:** satisfy ESLint rules for colorpicker code examples ([#2424](https://github.com/blackbaud/skyux/issues/2424)) ([7d0d79b](https://github.com/blackbaud/skyux/commit/7d0d79b3ed0cc3b333f24672b7e2cd2a816f3a24))
+* **code-examples:** satisfy ESLint rules for datetime code examples ([#2427](https://github.com/blackbaud/skyux/issues/2427)) ([e222a73](https://github.com/blackbaud/skyux/commit/e222a736564509baee593daa877d80bd42d8f966))
+* **components/forms:** file attachment components report file size errors with appropriate orders of magnitude ([#2437](https://github.com/blackbaud/skyux/issues/2437)) ([8768cf9](https://github.com/blackbaud/skyux/commit/8768cf9ecbadaa7845d9093f3c2ed1516487095a))
+* **components/forms:** improve default value handling for heading styles on form group components ([#2420](https://github.com/blackbaud/skyux/issues/2420)) ([ffa7946](https://github.com/blackbaud/skyux/commit/ffa79466cfa356db4495ec4b5116d9668b0cb54f))
+
## [10.33.0](https://github.com/blackbaud/skyux/compare/10.32.0...10.33.0) (2024-07-01)
diff --git a/package-lock.json b/package-lock.json
index b7ce3d247d..9817f6dc16 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "skyux",
- "version": "10.33.0",
+ "version": "10.34.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "skyux",
- "version": "10.33.0",
+ "version": "10.34.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 49af080df0..2a0833d69e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "skyux",
- "version": "10.33.0",
+ "version": "10.34.0",
"license": "MIT",
"scripts": {
"ng": "nx",
From 6bd118283fbccbab4c67764d061a51da2b0c80cd Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Tue, 9 Jul 2024 08:20:55 -0400
Subject: [PATCH 11/95] fix(code-examples): satisfy ESLint rules for core,
forms, indicators, and inline form code examples (#2432)
---
.../core/numeric/basic/demo.component.spec.ts | 26 ++++++++++---------
.../character-count/demo.component.spec.ts | 11 ++++----
.../file-attachment/basic/demo.component.ts | 2 +-
.../input-box/basic/demo.component.spec.ts | 24 ++++++++++-------
.../forms/radio/standard/demo.component.ts | 26 +++++++++++--------
.../selection-box/checkbox/demo.component.ts | 19 ++------------
.../selection-box/radio/demo.component.ts | 19 ++------------
.../basic/demo.component.ts | 4 +--
.../illustration-demo-resolver.service.ts | 11 ++++----
.../tokens/custom/demo.component.spec.ts | 14 ++++++----
.../wait/element/demo.component.spec.ts | 5 +++-
.../wait/page/demo.component.spec.ts | 14 ++++++----
.../indicators/wait/page/demo.component.ts | 8 +++---
.../inline-form/basic/demo.component.html | 10 +++----
.../inline-form/basic/demo.component.ts | 14 ++++++----
.../custom-buttons/demo.component.html | 10 +++----
.../custom-buttons/demo.component.ts | 16 +++++++-----
.../inline-form/repeaters/demo.component.ts | 20 +++++++++-----
18 files changed, 128 insertions(+), 125 deletions(-)
diff --git a/apps/code-examples/src/app/code-examples/core/numeric/basic/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/core/numeric/basic/demo.component.spec.ts
index 9e4a436f0e..9d17384369 100644
--- a/apps/code-examples/src/app/code-examples/core/numeric/basic/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/core/numeric/basic/demo.component.spec.ts
@@ -1,5 +1,4 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { By } from '@angular/platform-browser';
import { SkyNumericOptions } from '@skyux/core';
import { DemoComponent } from './demo.component';
@@ -25,11 +24,22 @@ describe('Basic numeric options', () => {
}
fixture.detectChanges();
- await fixture.whenStable().then();
+ await fixture.whenStable();
return { fixture };
}
+ function getTextContent(
+ fixture: ComponentFixture,
+ selector: string,
+ ): string {
+ const el = (
+ fixture.nativeElement as HTMLElement
+ ).querySelector(selector);
+
+ return el?.textContent?.trim() ?? '';
+ }
+
beforeEach(() => {
TestBed.configureTestingModule({
imports: [DemoComponent],
@@ -41,11 +51,7 @@ describe('Basic numeric options', () => {
fixture.detectChanges();
- expect(
- fixture.debugElement
- .query(By.css('.default-value'))
- .nativeElement.innerText.trim(),
- ).toBe('123.5K');
+ expect(getTextContent(fixture, '.default-value')).toEqual('123.5K');
});
it('should show the expected number in a specified format', async () => {
@@ -54,10 +60,6 @@ describe('Basic numeric options', () => {
config: { truncate: false },
});
- expect(
- fixture.debugElement
- .query(By.css('.configured-value'))
- .nativeElement.innerText.trim(),
- ).toBe('5,000,000');
+ expect(getTextContent(fixture, '.configured-value')).toEqual('5,000,000');
});
});
diff --git a/apps/code-examples/src/app/code-examples/forms/character-count/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/forms/character-count/demo.component.spec.ts
index 3259c83294..6b2c1cbb93 100644
--- a/apps/code-examples/src/app/code-examples/forms/character-count/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/forms/character-count/demo.component.spec.ts
@@ -42,12 +42,13 @@ describe('Character count demo', () => {
await expectAsync(harness.isOverLimit()).toBeResolvedTo(false);
// Update the value to exceed the limit and validate.
- const inputEl = document.querySelector(
- '.description-input',
- ) as HTMLInputElement;
+ const inputEl =
+ document.querySelector('.description-input');
- inputEl.value += ' scholarship fund';
- inputEl.dispatchEvent(new Event('input'));
+ if (inputEl) {
+ inputEl.value += ' scholarship fund';
+ inputEl.dispatchEvent(new Event('input'));
+ }
await expectAsync(harness.getCharacterCount()).toBeResolvedTo(63);
await expectAsync(harness.isOverLimit()).toBeResolvedTo(true);
diff --git a/apps/code-examples/src/app/code-examples/forms/file-attachment/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/file-attachment/basic/demo.component.ts
index 669508f39f..c2d61fa8c1 100644
--- a/apps/code-examples/src/app/code-examples/forms/file-attachment/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/file-attachment/basic/demo.component.ts
@@ -47,7 +47,7 @@ export class DemoComponent {
}
protected validateFile(file: SkyFileItem): string | undefined {
- return file.file.name.indexOf('a') === 0
+ return file.file.name.startsWith('a')
? 'Upload a file that does not begin with the letter "a"'
: undefined;
}
diff --git a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
index 34ba0988f9..8c6349880a 100644
--- a/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/forms/input-box/basic/demo.component.spec.ts
@@ -46,12 +46,16 @@ describe('Basic input box demo', () => {
const harness = await setupTest({
dataSkyId: 'input-box-last-name',
});
- const inputEl = document.querySelector(
+
+ const inputEl = document.querySelector(
'input.last-name-input-box',
- ) as HTMLInputElement;
- inputEl.value = '';
- SkyAppTestUtility.fireDomEvent(inputEl, 'input');
- SkyAppTestUtility.fireDomEvent(inputEl, 'blur');
+ );
+
+ if (inputEl) {
+ inputEl.value = '';
+ SkyAppTestUtility.fireDomEvent(inputEl, 'input');
+ SkyAppTestUtility.fireDomEvent(inputEl, 'blur');
+ }
await expectAsync(harness.hasRequiredError()).toBeResolvedTo(true);
});
@@ -108,12 +112,14 @@ describe('Basic input box demo', () => {
dataSkyId: 'input-box-favorite-color',
});
- const selectEl = document.querySelector(
+ const selectEl = document.querySelector(
'.input-box-favorite-color-select',
- ) as HTMLSelectElement;
+ );
- selectEl.value = 'invalid';
- selectEl.dispatchEvent(new Event('change'));
+ if (selectEl) {
+ selectEl.value = 'invalid';
+ selectEl.dispatchEvent(new Event('change'));
+ }
await expectAsync(harness.hasCustomFormError('invalid')).toBeResolvedTo(
true,
diff --git a/apps/code-examples/src/app/code-examples/forms/radio/standard/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/radio/standard/demo.component.ts
index cdaaa0121c..787b7f0a07 100644
--- a/apps/code-examples/src/app/code-examples/forms/radio/standard/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/radio/standard/demo.component.ts
@@ -11,6 +11,10 @@ import {
} from '@angular/forms';
import { SkyRadioModule } from '@skyux/forms';
+interface DemoForm {
+ paymentMethod: FormControl;
+}
+
interface Item {
name: string;
value: string;
@@ -19,6 +23,12 @@ interface Item {
helpContent?: string;
}
+function validatePaymentMethod(
+ control: AbstractControl,
+): ValidationErrors | null {
+ return control.value === 'check' ? { processingIssue: true } : null;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -26,7 +36,7 @@ interface Item {
imports: [CommonModule, FormsModule, ReactiveFormsModule, SkyRadioModule],
})
export class DemoComponent {
- protected formGroup: FormGroup<{ paymentMethod: FormControl }>;
+ protected formGroup: FormGroup;
protected helpPopoverContent =
"We don't charge fees for any payment method. The only exception is when credit card payments are late, which incurs a 2% fee.";
protected helpPopoverTitle = 'Are there fees?';
@@ -50,24 +60,18 @@ export class DemoComponent {
{ name: 'Debit', value: 'debit' },
];
- #formBuilder = inject(FormBuilder);
+ readonly #formBuilder = inject(FormBuilder);
constructor() {
this.paymentMethod = this.#formBuilder.control(
this.paymentOptions[0].name,
{
- validators: this.#validatePaymentMethod,
+ validators: [validatePaymentMethod],
},
);
- this.formGroup = inject(FormBuilder).group({
+
+ this.formGroup = this.#formBuilder.group({
paymentMethod: this.paymentMethod,
});
}
-
- #validatePaymentMethod(control: AbstractControl): ValidationErrors | null {
- if (control.value === 'check') {
- return { processingIssue: true };
- }
- return null;
- }
}
diff --git a/apps/code-examples/src/app/code-examples/forms/selection-box/checkbox/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/selection-box/checkbox/demo.component.ts
index b474c18bd9..49efa0f6a1 100644
--- a/apps/code-examples/src/app/code-examples/forms/selection-box/checkbox/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/selection-box/checkbox/demo.component.ts
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
-import { Component, OnDestroy, OnInit, inject } from '@angular/core';
+import { Component, inject } from '@angular/core';
import {
FormArray,
FormBuilder,
@@ -12,8 +12,6 @@ import { SkyIdModule } from '@skyux/core';
import { SkyCheckboxModule, SkySelectionBoxModule } from '@skyux/forms';
import { SkyIconModule } from '@skyux/indicators';
-import { Subject, takeUntil } from 'rxjs';
-
@Component({
standalone: true,
selector: 'app-demo',
@@ -28,7 +26,7 @@ import { Subject, takeUntil } from 'rxjs';
SkySelectionBoxModule,
],
})
-export class DemoComponent implements OnInit, OnDestroy {
+export class DemoComponent {
protected checkboxControls: FormControl[] | undefined;
protected selectionBoxes: {
@@ -58,8 +56,6 @@ export class DemoComponent implements OnInit, OnDestroy {
protected formGroup: FormGroup;
- #ngUnsubscribe = new Subject();
-
readonly #formBuilder = inject(FormBuilder);
constructor() {
@@ -71,17 +67,6 @@ export class DemoComponent implements OnInit, OnDestroy {
});
}
- public ngOnInit(): void {
- this.formGroup.valueChanges
- .pipe(takeUntil(this.#ngUnsubscribe))
- .subscribe((value) => console.log(value));
- }
-
- public ngOnDestroy(): void {
- this.#ngUnsubscribe.next();
- this.#ngUnsubscribe.complete();
- }
-
#buildCheckboxes(): FormArray {
const checkboxArray = this.selectionBoxes.map((checkbox) =>
this.#formBuilder.control(checkbox.selected),
diff --git a/apps/code-examples/src/app/code-examples/forms/selection-box/radio/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/selection-box/radio/demo.component.ts
index 3ee5413f5b..07ab306dca 100644
--- a/apps/code-examples/src/app/code-examples/forms/selection-box/radio/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/selection-box/radio/demo.component.ts
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
-import { Component, OnDestroy, OnInit, inject } from '@angular/core';
+import { Component, inject } from '@angular/core';
import {
FormBuilder,
FormGroup,
@@ -10,8 +10,6 @@ import { SkyIdModule } from '@skyux/core';
import { SkyRadioModule, SkySelectionBoxModule } from '@skyux/forms';
import { SkyIconModule } from '@skyux/indicators';
-import { Subject, takeUntil } from 'rxjs';
-
@Component({
standalone: true,
selector: 'app-demo',
@@ -26,7 +24,7 @@ import { Subject, takeUntil } from 'rxjs';
SkySelectionBoxModule,
],
})
-export class DemoComponent implements OnInit, OnDestroy {
+export class DemoComponent {
protected items: Record[] = [
{
name: 'Save time and effort',
@@ -52,22 +50,9 @@ export class DemoComponent implements OnInit, OnDestroy {
protected formGroup: FormGroup;
- #ngUnsubscribe = new Subject();
-
constructor() {
this.formGroup = inject(FormBuilder).group({
myOption: this.items[2]['value'],
});
}
-
- public ngOnInit(): void {
- this.formGroup.valueChanges
- .pipe(takeUntil(this.#ngUnsubscribe))
- .subscribe((value) => console.log(value));
- }
-
- public ngOnDestroy(): void {
- this.#ngUnsubscribe.next();
- this.#ngUnsubscribe.complete();
- }
}
diff --git a/apps/code-examples/src/app/code-examples/forms/single-file-attachment/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/forms/single-file-attachment/basic/demo.component.ts
index f733005c31..a66228fa46 100644
--- a/apps/code-examples/src/app/code-examples/forms/single-file-attachment/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/forms/single-file-attachment/basic/demo.component.ts
@@ -47,7 +47,7 @@ export class DemoComponent {
protected onFileChange(result: SkyFileAttachmentChange): void {
const file = result.file;
- if (file && file.errorType) {
+ if (file?.errorType) {
this.#reactiveFile?.setValue(undefined);
} else {
this.#reactiveFile?.setValue(file);
@@ -71,6 +71,6 @@ export class DemoComponent {
}
protected validateFile(file: SkyFileItem): string {
- return file.file.name.indexOf('a') === 0 ? 'invalidStartingLetter' : '';
+ return file.file.name.startsWith('a') ? 'invalidStartingLetter' : '';
}
}
diff --git a/apps/code-examples/src/app/code-examples/indicators/illustration/basic/illustration-demo-resolver.service.ts b/apps/code-examples/src/app/code-examples/indicators/illustration/basic/illustration-demo-resolver.service.ts
index b32c0beb8c..22f8627fdd 100644
--- a/apps/code-examples/src/app/code-examples/indicators/illustration/basic/illustration-demo-resolver.service.ts
+++ b/apps/code-examples/src/app/code-examples/indicators/illustration/basic/illustration-demo-resolver.service.ts
@@ -3,11 +3,12 @@ import { SkyIllustrationResolverService } from '@skyux/indicators';
@Injectable()
export class IllustrationDemoResolverService extends SkyIllustrationResolverService {
- public override async resolveUrl(name: string): Promise {
- if (name === 'analytics-graph') {
- return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yIC0yIDk2IDk2IiBoZWlnaHQ9Ijk2IiB3aWR0aD0iOTYiPjxkZWZzPjwvZGVmcz48cGF0aCBkPSJNOTAuMDgzMzMzMzMzMzMzMzQgOTAuMDgzMzMzMzMzMzMzMzRIMy44MzMzMzMzMzMzMzMzMzM1YTEuOTE2NjY2NjY2NjY2NjY2NyAxLjkxNjY2NjY2NjY2NjY2NjcgMCAwIDEgLTEuOTE2NjY2NjY2NjY2NjY2NyAtMS45MTY2NjY2NjY2NjY2NjY3VjEuOTE2NjY2NjY2NjY2NjY2NyIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Ik0yOC43NSA2OUE1Ljc1IDUuNzUgMCAxIDAgMjMgNjMuMjUgNS43NSA1Ljc1IDAgMCAwIDI4Ljc1IDY5WiIgZmlsbD0iIzZkZThhYiIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Im02NC44NTIzMzMzMzMzMzMzMyAzMS44ODE4MzMzMzMzMzMzMzYgMTUuNjI0NjY2NjY2NjY2NjY2IC0xOS45NjAxNjY2NjY2NjY2NjYiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJtMzguMDg0MTY2NjY2NjY2NjcgMjguNTk2NjY2NjY2NjY2NjY4IDE3LjcwMjMzMzMzMzMzMzMzNSA2LjMxNzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Im0xLjkxNjY2NjY2NjY2NjY2NjcgNDUuMTc1ODMzMzMzMzMzMzQgMjUuNzU2MTY2NjY2NjY2NjcgLTE1LjMzMzMzMzMzMzMzMzMzNCIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Im02Mi40ODMzMzMzMzMzMzMzNCA2MC4zOTggMTYuODg1ODMzMzMzMzMzMzM0IC05LjU4MzMzMzMzMzMzMzMzNCIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Ik0zNC41IDYzLjI1aDE3LjI1IiBzdHJva2U9IiMwMDQwNTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSI0Ij48L3BhdGg+PHBhdGggZD0ibTEuOTE2NjY2NjY2NjY2NjY2NyA4MC41IDIxLjkwMzY2NjY2NjY2NjY3IC0xNC4yOTgzMzMzMzMzMzMzMzQiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJNMzIuNTgzMzMzMzMzMzMzMzM2IDMyLjU4MzMzMzMzMzMzMzMzNkE1Ljc1IDUuNzUgMCAxIDAgMjYuODMzMzMzMzMzMzMzMzM2IDI2LjgzMzMzMzMzMzMzMzMzNmE1Ljc1IDUuNzUgMCAwIDAgNS43NSA1Ljc1WiIgZmlsbD0iIzZkZThhYiIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Ik02MS4zMzMzMzMzMzMzMzMzMzYgNDIuMTY2NjY2NjY2NjY2NjdhNS43NSA1Ljc1IDAgMSAwIC01Ljc1IC01Ljc1QTUuNzUgNS43NSAwIDAgMCA2MS4zMzMzMzMzMzMzMzMzMzYgNDIuMTY2NjY2NjY2NjY2NjdaIiBmaWxsPSIjNmRlOGFiIiBzdHJva2U9IiMwMDQwNTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0Ij48L3BhdGg+PHBhdGggZD0iTTg0LjMzMzMzMzMzMzMzMzM0IDEzLjQxNjY2NjY2NjY2NjY2OEE1Ljc1IDUuNzUgMCAxIDAgNzguNTgzMzMzMzMzMzMzMzQgNy42NjY2NjY2NjY2NjY2NjcgNS43NSA1Ljc1IDAgMCAwIDg0LjMzMzMzMzMzMzMzMzM0IDEzLjQxNjY2NjY2NjY2NjY2OFoiIGZpbGw9IiM2ZGU4YWIiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJNODQuMzMzMzMzMzMzMzMzMzQgNTMuNjY2NjY2NjY2NjY2NjdhNS43NSA1Ljc1IDAgMSAwIC01Ljc1IC01Ljc1QTUuNzUgNS43NSAwIDAgMCA4NC4zMzMzMzMzMzMzMzMzNCA1My42NjY2NjY2NjY2NjY2N1oiIGZpbGw9IiM2ZGU4YWIiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJNNTcuNSA2OWE1Ljc1IDUuNzUgMCAxIDAgLTUuNzUgLTUuNzVBNS43NSA1Ljc1IDAgMCAwIDU3LjUgNjlaIiBmaWxsPSIjNmRlOGFiIiBzdHJva2U9IiMwMDQwNTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0Ij48L3BhdGg+PC9zdmc+';
- }
+ public override resolveUrl(name: string): Promise {
+ const url =
+ name === 'analytics-graph'
+ ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yIC0yIDk2IDk2IiBoZWlnaHQ9Ijk2IiB3aWR0aD0iOTYiPjxkZWZzPjwvZGVmcz48cGF0aCBkPSJNOTAuMDgzMzMzMzMzMzMzMzQgOTAuMDgzMzMzMzMzMzMzMzRIMy44MzMzMzMzMzMzMzMzMzM1YTEuOTE2NjY2NjY2NjY2NjY2NyAxLjkxNjY2NjY2NjY2NjY2NjcgMCAwIDEgLTEuOTE2NjY2NjY2NjY2NjY2NyAtMS45MTY2NjY2NjY2NjY2NjY3VjEuOTE2NjY2NjY2NjY2NjY2NyIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Ik0yOC43NSA2OUE1Ljc1IDUuNzUgMCAxIDAgMjMgNjMuMjUgNS43NSA1Ljc1IDAgMCAwIDI4Ljc1IDY5WiIgZmlsbD0iIzZkZThhYiIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Im02NC44NTIzMzMzMzMzMzMzMyAzMS44ODE4MzMzMzMzMzMzMzYgMTUuNjI0NjY2NjY2NjY2NjY2IC0xOS45NjAxNjY2NjY2NjY2NjYiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJtMzguMDg0MTY2NjY2NjY2NjcgMjguNTk2NjY2NjY2NjY2NjY4IDE3LjcwMjMzMzMzMzMzMzMzNSA2LjMxNzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Im0xLjkxNjY2NjY2NjY2NjY2NjcgNDUuMTc1ODMzMzMzMzMzMzQgMjUuNzU2MTY2NjY2NjY2NjcgLTE1LjMzMzMzMzMzMzMzMzMzNCIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Im02Mi40ODMzMzMzMzMzMzMzNCA2MC4zOTggMTYuODg1ODMzMzMzMzMzMzM0IC05LjU4MzMzMzMzMzMzMzMzNCIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Ik0zNC41IDYzLjI1aDE3LjI1IiBzdHJva2U9IiMwMDQwNTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSI0Ij48L3BhdGg+PHBhdGggZD0ibTEuOTE2NjY2NjY2NjY2NjY2NyA4MC41IDIxLjkwMzY2NjY2NjY2NjY3IC0xNC4yOTgzMzMzMzMzMzMzMzQiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJNMzIuNTgzMzMzMzMzMzMzMzM2IDMyLjU4MzMzMzMzMzMzMzMzNkE1Ljc1IDUuNzUgMCAxIDAgMjYuODMzMzMzMzMzMzMzMzM2IDI2LjgzMzMzMzMzMzMzMzMzNmE1Ljc1IDUuNzUgMCAwIDAgNS43NSA1Ljc1WiIgZmlsbD0iIzZkZThhYiIgc3Ryb2tlPSIjMDA0MDU0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iNCI+PC9wYXRoPjxwYXRoIGQ9Ik02MS4zMzMzMzMzMzMzMzMzMzYgNDIuMTY2NjY2NjY2NjY2NjdhNS43NSA1Ljc1IDAgMSAwIC01Ljc1IC01Ljc1QTUuNzUgNS43NSAwIDAgMCA2MS4zMzMzMzMzMzMzMzMzMzYgNDIuMTY2NjY2NjY2NjY2NjdaIiBmaWxsPSIjNmRlOGFiIiBzdHJva2U9IiMwMDQwNTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0Ij48L3BhdGg+PHBhdGggZD0iTTg0LjMzMzMzMzMzMzMzMzM0IDEzLjQxNjY2NjY2NjY2NjY2OEE1Ljc1IDUuNzUgMCAxIDAgNzguNTgzMzMzMzMzMzMzMzQgNy42NjY2NjY2NjY2NjY2NjcgNS43NSA1Ljc1IDAgMCAwIDg0LjMzMzMzMzMzMzMzMzM0IDEzLjQxNjY2NjY2NjY2NjY2OFoiIGZpbGw9IiM2ZGU4YWIiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJNODQuMzMzMzMzMzMzMzMzMzQgNTMuNjY2NjY2NjY2NjY2NjdhNS43NSA1Ljc1IDAgMSAwIC01Ljc1IC01Ljc1QTUuNzUgNS43NSAwIDAgMCA4NC4zMzMzMzMzMzMzMzMzNCA1My42NjY2NjY2NjY2NjY2N1oiIGZpbGw9IiM2ZGU4YWIiIHN0cm9rZT0iIzAwNDA1NCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjQiPjwvcGF0aD48cGF0aCBkPSJNNTcuNSA2OWE1Ljc1IDUuNzUgMCAxIDAgLTUuNzUgLTUuNzVBNS43NSA1Ljc1IDAgMCAwIDU3LjUgNjlaIiBmaWxsPSIjNmRlOGFiIiBzdHJva2U9IiMwMDQwNTQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSI0Ij48L3BhdGg+PC9zdmc+'
+ : '';
- return '';
+ return Promise.resolve(url);
}
}
diff --git a/apps/code-examples/src/app/code-examples/indicators/tokens/custom/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/indicators/tokens/custom/demo.component.spec.ts
index 14bb17ca42..ce4c02aa73 100644
--- a/apps/code-examples/src/app/code-examples/indicators/tokens/custom/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/indicators/tokens/custom/demo.component.spec.ts
@@ -26,9 +26,11 @@ describe('Tokens basic demo', () => {
fixture: ComponentFixture,
buttonName: 'change' | 'destroy' | 'focus-last' | 'reset',
): void {
- fixture.nativeElement
- .querySelector(`.tokens-demo-${buttonName}-btn`)
- .click();
+ const btn = (
+ fixture.nativeElement as HTMLElement
+ ).querySelector(`.tokens-demo-${buttonName}-btn`);
+
+ btn?.click();
}
beforeEach(() => {
@@ -59,8 +61,10 @@ describe('Tokens basic demo', () => {
await employedToken.select();
expect(
- fixture.nativeElement.querySelector('.tokens-demo-selected').innerText,
- ).toBe('Employed');
+ (fixture.nativeElement as HTMLElement).querySelector(
+ '.tokens-demo-selected',
+ )?.textContent,
+ ).toEqual('Employed');
});
it('should change tokens when the user clicks the "Change tokens" button', async () => {
diff --git a/apps/code-examples/src/app/code-examples/indicators/wait/element/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/indicators/wait/element/demo.component.spec.ts
index 09eec2c8c9..421d95ff8e 100644
--- a/apps/code-examples/src/app/code-examples/indicators/wait/element/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/indicators/wait/element/demo.component.spec.ts
@@ -27,7 +27,10 @@ describe('Basic wait', () => {
it('should show the wait component when the user performs an action', async () => {
const { waitHarness, fixture } = await setupTest();
- fixture.nativeElement.querySelector('.sky-btn').click();
+ (fixture.nativeElement as HTMLElement)
+ .querySelector('.sky-btn')
+ ?.click();
+
fixture.detectChanges();
await expectAsync(waitHarness.isWaiting()).toBeResolvedTo(true);
diff --git a/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.spec.ts
index 6f48453823..2acfa7999a 100644
--- a/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.spec.ts
@@ -8,12 +8,14 @@ import { DemoComponent } from './demo.component';
describe('Page wait', () => {
function setupTest(): {
rootLoader: HarnessLoader;
+ el: HTMLElement;
fixture: ComponentFixture;
} {
const fixture = TestBed.createComponent(DemoComponent);
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
+ const el = fixture.nativeElement as HTMLElement;
- return { rootLoader, fixture };
+ return { rootLoader, el, fixture };
}
beforeEach(() => {
@@ -23,8 +25,9 @@ describe('Page wait', () => {
});
it('should show the page wait component when the user performs an action', async () => {
- const { rootLoader, fixture } = setupTest();
- const buttons = fixture.nativeElement.querySelectorAll('.sky-btn');
+ const { rootLoader, el } = setupTest();
+
+ const buttons = el.querySelectorAll('.sky-btn');
buttons[0].click();
@@ -38,8 +41,9 @@ describe('Page wait', () => {
});
it('should show the non-blocking page wait component when the user performs an action', async () => {
- const { rootLoader, fixture } = setupTest();
- const buttons = fixture.nativeElement.querySelectorAll('.sky-btn');
+ const { rootLoader, el } = setupTest();
+
+ const buttons = el.querySelectorAll('.sky-btn');
buttons[1].click();
diff --git a/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.ts b/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.ts
index d189695efc..f69d90f345 100644
--- a/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/indicators/wait/page/demo.component.ts
@@ -22,12 +22,10 @@ export class DemoComponent implements OnDestroy {
} else {
this.#waitSvc.beginNonBlockingPageWait();
}
+ } else if (isBlocking) {
+ this.#waitSvc.endBlockingPageWait();
} else {
- if (isBlocking) {
- this.#waitSvc.endBlockingPageWait();
- } else {
- this.#waitSvc.endNonBlockingPageWait();
- }
+ this.#waitSvc.endNonBlockingPageWait();
}
this.isWaiting = !this.isWaiting;
diff --git a/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.html b/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.html
index fb9fcab1ad..35b9e8dce3 100644
--- a/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.html
@@ -25,11 +25,9 @@
-
diff --git a/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.ts
index 3c24513c27..36f475c57f 100644
--- a/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/inline-form/inline-form/basic/demo.component.ts
@@ -15,6 +15,10 @@ import {
SkyInlineFormModule,
} from '@skyux/inline-form';
+interface DemoForm {
+ firstName: FormControl;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -29,7 +33,7 @@ import {
})
export class DemoComponent {
protected firstName = 'Jane';
- protected formGroup: FormGroup;
+ protected formGroup: FormGroup;
protected inlineFormConfig: SkyInlineFormConfig = {
buttonLayout: SkyInlineFormButtonLayout.SaveCancel,
@@ -39,25 +43,25 @@ export class DemoComponent {
constructor() {
this.formGroup = inject(FormBuilder).group({
- myFirstName: new FormControl(),
+ firstName: new FormControl('', { nonNullable: true }),
});
}
protected onInlineFormClose(args: SkyInlineFormCloseArgs): void {
if (args.reason === 'save') {
- this.firstName = this.formGroup.get('myFirstName')?.value;
+ this.firstName = this.formGroup.value.firstName ?? '';
}
this.showForm = false;
this.formGroup.patchValue({
- myFirstName: undefined,
+ firstName: undefined,
});
}
protected onInlineFormOpen(): void {
this.showForm = true;
this.formGroup.patchValue({
- myFirstName: this.firstName,
+ firstName: this.firstName,
});
}
}
diff --git a/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.html b/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.html
index d82531c82a..004db6ffe1 100644
--- a/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.html
@@ -25,11 +25,9 @@
-
diff --git a/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.ts b/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.ts
index 64ed1db249..ffcf724a6f 100644
--- a/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/inline-form/inline-form/custom-buttons/demo.component.ts
@@ -15,6 +15,10 @@ import {
SkyInlineFormModule,
} from '@skyux/inline-form';
+interface DemoForm {
+ firstName: FormControl;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -29,7 +33,7 @@ import {
})
export class DemoComponent implements OnInit {
protected firstName = 'Jane';
- protected formGroup: FormGroup;
+ protected formGroup: FormGroup;
protected inlineFormConfig: SkyInlineFormConfig = {
buttonLayout: SkyInlineFormButtonLayout.Custom,
@@ -61,7 +65,7 @@ export class DemoComponent implements OnInit {
constructor() {
this.formGroup = inject(FormBuilder).group({
- myFirstName: new FormControl(),
+ firstName: new FormControl('', { nonNullable: true }),
});
}
@@ -80,16 +84,16 @@ export class DemoComponent implements OnInit {
protected onInlineFormClose(args: SkyInlineFormCloseArgs): void {
switch (args.reason) {
case 'save':
- this.firstName = this.formGroup.get('myFirstName')?.value;
+ this.firstName = this.formGroup.value.firstName ?? '';
this.showForm = false;
break;
case 'clear':
- this.formGroup.get('myFirstName')?.patchValue(undefined);
+ this.formGroup.patchValue({ firstName: '' });
break;
case 'reset':
- this.formGroup.get('myFirstName')?.setValue(this.firstName);
+ this.formGroup.setValue({ firstName: this.firstName });
break;
default:
@@ -101,7 +105,7 @@ export class DemoComponent implements OnInit {
protected onInlineFormOpen(): void {
this.showForm = true;
this.formGroup.patchValue({
- myFirstName: this.firstName,
+ firstName: this.firstName,
});
}
}
diff --git a/apps/code-examples/src/app/code-examples/inline-form/inline-form/repeaters/demo.component.ts b/apps/code-examples/src/app/code-examples/inline-form/inline-form/repeaters/demo.component.ts
index 1053b0ca58..a10254dda3 100644
--- a/apps/code-examples/src/app/code-examples/inline-form/inline-form/repeaters/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/inline-form/inline-form/repeaters/demo.component.ts
@@ -16,6 +16,12 @@ import {
} from '@skyux/inline-form';
import { SkyRepeaterModule } from '@skyux/lists';
+interface DemoForm {
+ id: FormControl;
+ note: FormControl;
+ title: FormControl;
+}
+
interface Item {
id: string;
title: string | undefined;
@@ -37,6 +43,7 @@ interface Item {
})
export class DemoComponent {
protected activeInlineFormId: string | undefined;
+ protected formGroup: FormGroup;
protected inlineFormConfig: SkyInlineFormConfig = {
buttonLayout: SkyInlineFormButtonLayout.SaveCancel,
@@ -65,13 +72,11 @@ export class DemoComponent {
},
];
- protected formGroup: FormGroup;
-
constructor() {
this.formGroup = inject(FormBuilder).group({
- id: new FormControl(),
- title: new FormControl(),
- note: new FormControl(),
+ id: new FormControl('', { nonNullable: true }),
+ title: new FormControl('', { nonNullable: true }),
+ note: new FormControl('', { nonNullable: true }),
});
}
@@ -88,9 +93,10 @@ export class DemoComponent {
const found = this.items.find(
(item) => item.id === this.activeInlineFormId,
);
+
if (found) {
- found.note = this.formGroup.get('note')?.value;
- found.title = this.formGroup.get('title')?.value;
+ found.note = this.formGroup.value.note;
+ found.title = this.formGroup.value.title;
}
}
From 84e1382a2233b381c8c8c859b38bd8f743ef672f Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Tue, 9 Jul 2024 13:14:12 -0400
Subject: [PATCH 12/95] fix(components/phone-field): required phone field
controls are not "touched" on initialization (#2443)
---
.../app/phone-field/phone-field.component.ts | 1 +
.../phone-field-adapter.service.ts | 11 +
.../phone-field-input.directive.ts | 385 +++++++-----------
.../phone-field/phone-field.component.html | 121 +++---
.../phone-field/phone-field.component.spec.ts | 40 +-
.../phone-field/phone-field.component.ts | 39 +-
6 files changed, 275 insertions(+), 322 deletions(-)
diff --git a/apps/e2e/phone-field-storybook/src/app/phone-field/phone-field.component.ts b/apps/e2e/phone-field-storybook/src/app/phone-field/phone-field.component.ts
index ae7e7de956..1d9a1619bc 100644
--- a/apps/e2e/phone-field-storybook/src/app/phone-field/phone-field.component.ts
+++ b/apps/e2e/phone-field-storybook/src/app/phone-field/phone-field.component.ts
@@ -30,6 +30,7 @@ export class PhoneFieldComponent implements AfterViewInit {
phoneControlError: this.phoneControlError,
});
this.phoneControlError.setValue('bbb');
+ this.phoneControlError.markAsTouched();
this.phoneControlInput = new UntypedFormControl();
this.phoneFormInput = new UntypedFormGroup({
diff --git a/libs/components/phone-field/src/lib/modules/phone-field/phone-field-adapter.service.ts b/libs/components/phone-field/src/lib/modules/phone-field/phone-field-adapter.service.ts
index b540202bea..da5a21a203 100644
--- a/libs/components/phone-field/src/lib/modules/phone-field/phone-field-adapter.service.ts
+++ b/libs/components/phone-field/src/lib/modules/phone-field/phone-field-adapter.service.ts
@@ -26,6 +26,17 @@ export class SkyPhoneFieldAdapterService implements OnDestroy {
this.#renderer.addClass(elementRef.nativeElement, className);
}
+ public getInputValue(elementRef: ElementRef): string | undefined {
+ const el = elementRef.nativeElement as HTMLElement | null;
+
+ if (el && 'value' in el) {
+ return (el as HTMLInputElement).value;
+ }
+
+ /* istanbul ignore next: safety check */
+ return undefined;
+ }
+
public setElementDisabledState(
elementRef: ElementRef,
disabled: boolean,
diff --git a/libs/components/phone-field/src/lib/modules/phone-field/phone-field-input.directive.ts b/libs/components/phone-field/src/lib/modules/phone-field/phone-field-input.directive.ts
index 364bdca496..ffe43bc89c 100644
--- a/libs/components/phone-field/src/lib/modules/phone-field/phone-field-input.directive.ts
+++ b/libs/components/phone-field/src/lib/modules/phone-field/phone-field-input.directive.ts
@@ -1,15 +1,12 @@
-import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
- AfterViewInit,
- ChangeDetectorRef,
Directive,
ElementRef,
HostListener,
Input,
OnDestroy,
OnInit,
- Optional,
- forwardRef,
+ booleanAttribute,
+ inject,
} from '@angular/core';
import {
AbstractControl,
@@ -21,24 +18,11 @@ import {
} from '@angular/forms';
import { PhoneNumberFormat, PhoneNumberUtil } from 'google-libphonenumber';
-import { BehaviorSubject, Subject } from 'rxjs';
-import { debounceTime, takeUntil } from 'rxjs/operators';
+import { Subject, takeUntil } from 'rxjs';
import { SkyPhoneFieldAdapterService } from './phone-field-adapter.service';
import { SkyPhoneFieldComponent } from './phone-field.component';
-const SKY_PHONE_FIELD_VALUE_ACCESSOR = {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => SkyPhoneFieldInputDirective),
- multi: true,
-};
-
-const SKY_PHONE_FIELD_VALIDATOR = {
- provide: NG_VALIDATORS,
- useExisting: forwardRef(() => SkyPhoneFieldInputDirective),
- multi: true,
-};
-
/**
* Creates a button, search input, and text input for entering and validating
* international phone numbers. Place this attribute on an `input` element, and wrap
@@ -54,24 +38,35 @@ const SKY_PHONE_FIELD_VALIDATOR = {
*/
@Directive({
selector: '[skyPhoneFieldInput]',
- providers: [SKY_PHONE_FIELD_VALUE_ACCESSOR, SKY_PHONE_FIELD_VALIDATOR],
+ providers: [
+ {
+ provide: NG_VALIDATORS,
+ useExisting: SkyPhoneFieldInputDirective,
+ multi: true,
+ },
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: SkyPhoneFieldInputDirective,
+ multi: true,
+ },
+ ],
})
export class SkyPhoneFieldInputDirective
- implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor, Validator
+ implements OnInit, OnDestroy, ControlValueAccessor, Validator
{
/**
* Whether to disable the phone field on template-driven forms. Don't use this input on reactive forms because they may overwrite the input or leave the control out of sync.
* To set the disabled state on reactive forms, use the `FormControl` instead.
* @default false
*/
- @Input()
- public set disabled(value: boolean | undefined) {
- const coercedValue = coerceBooleanProperty(value);
+ @Input({ transform: booleanAttribute })
+ public set disabled(value: boolean) {
if (this.#phoneFieldComponent) {
- this.#phoneFieldComponent.countrySelectDisabled = coercedValue;
- this.#adapterService?.setElementDisabledState(this.#elRef, coercedValue);
+ this.#phoneFieldComponent.countrySelectDisabled = value;
+ this.#adapterSvc?.setElementDisabledState(this.#elRef, value);
}
- this.#_disabled = coercedValue;
+
+ this.#_disabled = value;
}
public get disabled(): boolean {
@@ -85,57 +80,28 @@ export class SkyPhoneFieldInputDirective
* set this property to `true`.
* @default false
*/
- @Input()
- public skyPhoneFieldNoValidate: boolean | undefined = false;
-
- set #modelValue(value: string | undefined) {
- const valueOrDefault = value ?? '';
- this.#_modelValue = valueOrDefault;
- this.#adapterService?.setElementValue(this.#elRef, valueOrDefault);
-
- if (valueOrDefault) {
- const formattedValue = this.#formatNumber(valueOrDefault.toString());
-
- this.#onChange(formattedValue);
- } else {
- this.#onChange(valueOrDefault);
- }
- this.#validatorChange();
- }
-
- get #modelValue(): string {
- return this.#_modelValue;
- }
+ @Input({ transform: booleanAttribute })
+ public skyPhoneFieldNoValidate = false;
+ #_disabled = false;
+ #_value = '';
#control: AbstractControl | undefined;
-
- #textChanges: BehaviorSubject | undefined;
-
#ngUnsubscribe = new Subject();
-
+ #notifyChange: ((value: string) => void) | undefined;
+ #notifyTouched: (() => void) | undefined;
#phoneUtils = PhoneNumberUtil.getInstance();
- #_disabled!: boolean;
+ readonly #adapterSvc = inject(SkyPhoneFieldAdapterService, {
+ host: true,
+ optional: true,
+ skipSelf: true,
+ });
- #_modelValue = '';
-
- #changeDetector: ChangeDetectorRef;
- #elRef: ElementRef;
-
- #adapterService: SkyPhoneFieldAdapterService | undefined;
- #phoneFieldComponent: SkyPhoneFieldComponent | undefined;
-
- constructor(
- changeDetector: ChangeDetectorRef,
- elRef: ElementRef,
- @Optional() adapterService?: SkyPhoneFieldAdapterService,
- @Optional() phoneFieldComponent?: SkyPhoneFieldComponent,
- ) {
- this.#changeDetector = changeDetector;
- this.#elRef = elRef;
- this.#adapterService = adapterService;
- this.#phoneFieldComponent = phoneFieldComponent;
- }
+ readonly #elRef = inject(ElementRef);
+ readonly #phoneFieldComponent = inject(SkyPhoneFieldComponent, {
+ host: true,
+ optional: true,
+ });
public ngOnInit(): void {
if (!this.#phoneFieldComponent) {
@@ -145,24 +111,17 @@ export class SkyPhoneFieldInputDirective
);
}
- this.#adapterService?.setElementType(this.#elRef);
- this.#adapterService?.addElementClass(this.#elRef, 'sky-form-control');
- }
+ this.#adapterSvc?.setElementType(this.#elRef);
+ this.#adapterSvc?.addElementClass(this.#elRef, 'sky-form-control');
- public ngAfterViewInit(): void {
this.#phoneFieldComponent?.selectedCountryChange
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe(() => {
- this.#modelValue = this.#elRef.nativeElement.value;
+ const value = this.#adapterSvc?.getInputValue(this.#elRef);
+ this.#setValue(value);
+ this.#notifyChange?.(this.#getValue());
+ this.#notifyTouched?.();
});
-
- // This is needed to address a bug in Angular 4, where the value is not changed on the view.
- // See: https://github.com/angular/angular/issues/13792
- /* istanbul ignore else */
- if (this.#control && this.#modelValue) {
- this.#control.setValue(this.#modelValue, { emitEvent: false });
- this.#changeDetector.detectChanges();
- }
}
public ngOnDestroy(): void {
@@ -170,198 +129,168 @@ export class SkyPhoneFieldInputDirective
this.#ngUnsubscribe.complete();
}
- /**
- * Writes the new value for reactive forms on change events on the input element
- * @param event The change event that was received
- */
- @HostListener('change', ['$event'])
- public onInputChange(event: any): void {
- if (!this.#textChanges) {
- this.#setupTextChangeSubscription(event.target.value);
- } else {
- this.#textChanges.next(event.target.value);
- }
- }
-
- /**
- * Marks reactive form controls as touched on input blur events
- */
- @HostListener('blur')
- public onInputBlur(): void {
- this.#onTouched();
- }
-
- @HostListener('input', ['$event'])
- public onInputTyping(event: any): void {
- if (!this.#textChanges) {
- this.#setupTextChangeSubscription(event.target.value);
- } else {
- this.#textChanges.next(event.target.value);
- }
- }
-
- /**
- * Writes the new value for reactive forms
- * @param value The new value for the input
- */
- public writeValue(value: string): void {
- this.#phoneFieldComponent?.setCountryByDialCode(value);
-
- this.#modelValue = value;
- }
-
- public registerOnChange(fn: (value: string | undefined) => void): void {
- this.#onChange = fn;
+ public registerOnChange(fn: (value: string) => void): void {
+ this.#notifyChange = fn;
}
public registerOnTouched(fn: () => void): void {
- this.#onTouched = fn;
- }
-
- public registerOnValidatorChange(fn: () => void): void {
- this.#validatorChange = fn;
+ this.#notifyTouched = fn;
}
- /**
- * Sets the disabled state on the input
- * @param isDisabled the new value of the input's disabled state
- */
public setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
- /**
- * Validate's the form control's current value
- * @param control the form control for the input
- */
public validate(control: AbstractControl): ValidationErrors | null {
- if (!this.#control) {
- this.#control = control;
- }
-
- if (this.skyPhoneFieldNoValidate) {
- return null;
- }
+ this.#control ??= control;
const value = control.value;
- if (!value) {
+ if (!value || this.skyPhoneFieldNoValidate) {
return null;
}
- if (
- this.#phoneFieldComponent?.selectedCountry &&
- !this.#validateNumber(value)
- ) {
- if (!this.#textChanges) {
- // Mark the invalid control as touched so that the input's invalid CSS styles appear.
- // (This is only required when the invalid value is set by the FormControl constructor.)
- // We don't do this if the input is the active element so that we don't show validation
- // errors unless it is invalid on initialization or the input has been blurred.
- control.markAsTouched();
- }
-
+ if (!this.#isValidPhoneNumber(value)) {
return {
skyPhoneField: {
invalid: value,
},
};
}
+
return null;
}
- #setupTextChangeSubscription(text: string): void {
- this.#textChanges = new BehaviorSubject(text);
+ public writeValue(value: unknown): void {
+ const rawValue = typeof value === 'string' ? value : '';
+
+ this.#phoneFieldComponent?.setCountryByDialCode(rawValue);
+ this.#adapterSvc?.setElementValue(this.#elRef, rawValue);
+
+ this.#setValue(rawValue);
+ const newValue = this.#getValue();
+
+ if (rawValue !== newValue) {
+ // If the value is set before the control is initialized, wait for the
+ // first cycle to complete before triggering a value change event.
+ // (This occurs when the control is initialized with an unformatted value
+ // but is formatted into a new value immediately in the `writeValue`
+ // method.)
+ if (!this.#control) {
+ setTimeout(() => {
+ this.#notifyChange?.(newValue);
+ });
+ } else {
+ this.#notifyChange?.(newValue);
+ }
+ }
+ }
- this.#textChanges
- .pipe(debounceTime(500), takeUntil(this.#ngUnsubscribe))
- .subscribe((newValue) => {
- this.writeValue(newValue);
- this.#changeDetector.markForCheck();
- });
+ @HostListener('blur')
+ protected onBlur(): void {
+ this.#notifyTouched?.();
}
- #validateNumber(phoneNumber: string): boolean {
- try {
- const numberObj = this.#phoneUtils.parseAndKeepRawInput(
- phoneNumber,
- this.#phoneFieldComponent?.selectedCountry?.iso2,
- );
+ @HostListener('change')
+ protected onChange(): void {
+ const value = this.#adapterSvc?.getInputValue(this.#elRef);
+ this.#setValue(value);
+ this.#notifyChange?.(this.#getValue());
+ }
- if (
- this.#phoneFieldComponent &&
- !this.#phoneFieldComponent.allowExtensions &&
- numberObj.getExtension()
- ) {
- return false;
- }
+ @HostListener('input')
+ protected onInput(): void {
+ const value = this.#adapterSvc?.getInputValue(this.#elRef);
+ this.#phoneFieldComponent?.setCountryByDialCode(value);
+ }
- return this.#phoneUtils.isValidNumberForRegion(
- numberObj,
- this.#phoneFieldComponent?.selectedCountry?.iso2,
- );
- } catch (e) {
- return false;
+ #formatPhoneNumber(value: string | undefined): string | undefined {
+ if (!value) {
+ return;
}
- }
- /**
- * Format's the given phone number based on the currently selected country.
- * @param phoneNumber The number to format
- */
- #formatNumber(phoneNumber: string): string {
+ const defaultCountry = this.#getDefaultCountry();
+ const regionCode = this.#getRegionCode();
+ const returnFormat = this.#phoneFieldComponent?.returnFormat;
+
try {
- const numberObj = this.#phoneUtils.parseAndKeepRawInput(
- phoneNumber,
- this.#phoneFieldComponent?.selectedCountry?.iso2,
+ const phoneNumber = this.#phoneUtils.parseAndKeepRawInput(
+ value,
+ regionCode ?? defaultCountry,
);
- if (this.#phoneUtils.isPossibleNumber(numberObj)) {
- switch (this.#phoneFieldComponent?.returnFormat) {
+
+ if (this.#phoneUtils.isPossibleNumber(phoneNumber)) {
+ switch (returnFormat) {
case 'international':
return this.#phoneUtils.format(
- numberObj,
+ phoneNumber,
PhoneNumberFormat.INTERNATIONAL,
);
+
case 'national':
return this.#phoneUtils.format(
- numberObj,
+ phoneNumber,
PhoneNumberFormat.NATIONAL,
);
+
case 'default':
default:
- if (
- this.#phoneFieldComponent?.selectedCountry?.iso2 !==
- this.#phoneFieldComponent?.defaultCountry
- ) {
- return this.#phoneUtils.format(
- numberObj,
- PhoneNumberFormat.INTERNATIONAL,
- );
- } else {
- return this.#phoneUtils.format(
- numberObj,
- PhoneNumberFormat.NATIONAL,
- );
- }
+ return regionCode && regionCode !== defaultCountry
+ ? this.#phoneUtils.format(
+ phoneNumber,
+ PhoneNumberFormat.INTERNATIONAL,
+ )
+ : this.#phoneUtils.format(
+ phoneNumber,
+ PhoneNumberFormat.NATIONAL,
+ );
}
- } else {
- return phoneNumber;
}
- } catch (e) {
- /* sanity check */
- /* istanbul ignore next */
- return phoneNumber;
+ } catch (err) {
+ /* */
}
+
+ return;
}
- // eslint-disable-next-line @typescript-eslint/no-empty-function , @typescript-eslint/no-unused-vars
- #onChange = (_: string | undefined) => {};
+ #getDefaultCountry(): string | undefined {
+ return this.#phoneFieldComponent?.defaultCountry;
+ }
- // istanbul ignore next
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- #onTouched = () => {};
+ #getRegionCode(): string | undefined {
+ return this.#phoneFieldComponent?.selectedCountry?.iso2;
+ }
+
+ #getValue(): string {
+ return this.#_value;
+ }
- // istanbul ignore next
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- #validatorChange = () => {};
+ #isValidPhoneNumber(value: string): boolean {
+ const defaultCountry = this.#getDefaultCountry();
+ const regionCode = this.#getRegionCode() ?? defaultCountry;
+ const allowExtensions = !!this.#phoneFieldComponent?.allowExtensions;
+
+ try {
+ const phoneNumber = this.#phoneUtils.parseAndKeepRawInput(
+ value,
+ regionCode,
+ );
+
+ if (!allowExtensions && phoneNumber.getExtension()) {
+ return false;
+ }
+
+ return this.#phoneUtils.isValidNumberForRegion(phoneNumber, regionCode);
+ } catch (e) {
+ return false;
+ }
+ }
+
+ #setValue(value: string | undefined): void {
+ /* istanbul ignore else */
+ if (value !== undefined) {
+ const formatted = this.#formatPhoneNumber(value);
+ this.#_value = formatted ?? value;
+ }
+ }
}
diff --git a/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.html b/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.html
index ab33d3360a..3aec627cd6 100644
--- a/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.html
+++ b/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.html
@@ -1,10 +1,10 @@
-
+@if (!inputBoxHostSvc) {
-
-
-
+
+
+
-
+}
-
-
-
-
-
-
-
-
+ @if (phoneInputShown) {
+
+
+
+ } @else if (countrySearchShown) {
+
+
+
+
+
+ }
-
-
-
+
+
+ }
diff --git a/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.spec.ts b/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.spec.ts
index 6a3c2e0c68..3c66617af6 100644
--- a/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.spec.ts
+++ b/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.spec.ts
@@ -210,8 +210,8 @@ describe('Phone Field Component', () => {
}
function validateInputAndModel(
- modelValue: string,
- formattedValue: string,
+ inputValue: string,
+ modelValue: string | undefined,
isValid: boolean,
isTouched: boolean,
model: NgModel | UntypedFormControl | undefined,
@@ -222,9 +222,9 @@ describe('Phone Field Component', () => {
>,
): void {
fixture.detectChanges();
- expect(fixture.nativeElement.querySelector('input').value).toBe(modelValue);
+ expect(fixture.nativeElement.querySelector('input').value).toBe(inputValue);
- expect(model?.value).toBe(formattedValue);
+ expect(model?.value).toBe(modelValue);
expect(model?.valid).toBe(isValid);
@@ -233,7 +233,7 @@ describe('Phone Field Component', () => {
} else {
expect(model?.errors).toEqual({
skyPhoneField: {
- invalid: formattedValue,
+ invalid: modelValue,
},
});
}
@@ -539,7 +539,7 @@ describe('Phone Field Component', () => {
await fixture.whenStable();
fixture.detectChanges();
- validateInputAndModel('1234', '1234', false, true, ngModel, fixture);
+ validateInputAndModel('1234', '1234', false, false, ngModel, fixture);
blurInput(fixture.nativeElement, fixture, true);
@@ -558,7 +558,7 @@ describe('Phone Field Component', () => {
'667-555-530',
'667-555-530',
false,
- true,
+ false,
ngModel,
fixture,
);
@@ -615,7 +615,7 @@ describe('Phone Field Component', () => {
fixture.detectChanges();
await fixture.whenStable();
- validateInputAndModel('1234', '1234', false, true, ngModel, fixture);
+ validateInputAndModel('1234', '1234', false, false, ngModel, fixture);
});
it('should validate properly when input changed to empty string', async () => {
@@ -702,7 +702,7 @@ describe('Phone Field Component', () => {
'667-555-5309ext3',
'(667) 555-5309 ext. 3',
false,
- true,
+ false,
ngModel,
fixture,
);
@@ -867,7 +867,7 @@ describe('Phone Field Component', () => {
'+3556675555309',
'+3556675555309',
false,
- true,
+ false,
ngModel,
fixture,
);
@@ -1238,7 +1238,7 @@ describe('Phone Field Component', () => {
'+12045555555',
'+1 204-555-5555',
true,
- true,
+ false,
ngModel,
fixture,
);
@@ -1297,7 +1297,7 @@ describe('Phone Field Component', () => {
fixture.detectChanges();
tick();
- validateInputAndModel('', '', true, false, ngModel, fixture);
+ validateInputAndModel('', undefined, true, false, ngModel, fixture);
setCountry('Albania', fixture);
@@ -1308,7 +1308,7 @@ describe('Phone Field Component', () => {
'024569874',
'+355 24 569 874',
true,
- false,
+ true,
ngModel,
fixture,
);
@@ -1606,7 +1606,7 @@ describe('Phone Field Component', () => {
'1234',
'1234',
false,
- true,
+ false,
component.phoneControl,
fixture,
);
@@ -1632,7 +1632,7 @@ describe('Phone Field Component', () => {
'667-555-530',
'667-555-530',
false,
- true,
+ false,
component.phoneControl,
fixture,
);
@@ -1686,7 +1686,7 @@ describe('Phone Field Component', () => {
'1234',
'1234',
false,
- true,
+ false,
component.phoneControl,
fixture,
);
@@ -1778,7 +1778,7 @@ describe('Phone Field Component', () => {
'667-555-5309ext3',
'(667) 555-5309 ext. 3',
false,
- true,
+ false,
component.phoneControl,
fixture,
);
@@ -1793,7 +1793,7 @@ describe('Phone Field Component', () => {
'8675558309',
'(867) 555-8309',
false,
- true,
+ false,
component.phoneControl,
fixture,
);
@@ -1974,7 +1974,7 @@ describe('Phone Field Component', () => {
'+3556675555309',
'+3556675555309',
false,
- true,
+ false,
component.phoneControl,
fixture,
);
@@ -2178,7 +2178,7 @@ describe('Phone Field Component', () => {
'024569874',
'+355 24 569 874',
true,
- false,
+ true,
component.phoneControl,
fixture,
);
diff --git a/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.ts b/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.ts
index e58cf4d4d8..53e4d48fcb 100644
--- a/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.ts
+++ b/libs/components/phone-field/src/lib/modules/phone-field/phone-field.component.ts
@@ -43,6 +43,8 @@ import { SkyPhoneFieldAdapterService } from './phone-field-adapter.service';
import { SkyPhoneFieldCountry } from './types/country';
import { SkyPhoneFieldNumberReturnFormat } from './types/number-return-format';
+const DEFAULT_COUNTRY_CODE = 'us';
+
// NOTE: The no-op animation is here in order to block the input's "fade in" animation
// from firing on initial load. For more information on this technique you can see
// https://www.bennadel.com/blog/3417-using-no-op-transitions-to-prevent-animation-during-the-initial-render-of-ngfor-in-angular-5-2-6.htm
@@ -159,16 +161,14 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
*/
@Input()
public set defaultCountry(value: string | undefined) {
- if (value && value !== this.#_defaultCountry) {
- this.#_defaultCountry = value.toLowerCase();
-
- this.#defaultCountryData = this.countries.find(
- (country) => country.iso2 === this.#_defaultCountry,
- );
+ if (value !== this.#_defaultCountry) {
+ value ??= DEFAULT_COUNTRY_CODE;
+ this.#_defaultCountry = value.toLocaleLowerCase();
+ this.#defaultCountryData = this.#getDefaultCountryData();
}
}
- public get defaultCountry(): string | undefined {
+ public get defaultCountry(): string {
return this.#_defaultCountry;
}
@@ -199,7 +199,6 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
* Emits a `SkyPhoneFieldCountry` object when the selected country in the country search
* input changes.
*/
-
@Output()
public selectedCountryChange = new EventEmitter();
@@ -234,6 +233,7 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
newCountry.iso2,
PhoneNumberType.FIXED_LINE,
);
+
this.#_selectedCountry.exampleNumber = this.#phoneUtils.format(
numberObj,
PhoneNumberFormat.NATIONAL,
@@ -241,8 +241,6 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
}
this.#populateInputBoxHelpText();
-
- this.selectedCountryChange.emit(this.#_selectedCountry);
}
}
@@ -278,7 +276,7 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
#longestDialCodeLength = 0;
- #_defaultCountry: string | undefined;
+ #_defaultCountry = DEFAULT_COUNTRY_CODE;
#_selectedCountry: SkyPhoneFieldCountry | undefined;
@@ -314,6 +312,7 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
JSON.stringify((window as any).intlTelInputGlobals.getCountryData()),
);
+
for (const country of this.countries) {
country.dialCode = '+' + country.dialCode;
@@ -322,6 +321,8 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
}
}
+ this.#defaultCountryData = this.#getDefaultCountryData();
+
this.countrySearchForm = this.#formBuilder.group({
countrySearch: this.#countrySearchFormControl,
});
@@ -347,13 +348,8 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
});
}
- if (!this.defaultCountry) {
- this.defaultCountry = 'us';
- }
+ this.selectedCountry ??= this.#defaultCountryData;
- if (!this.selectedCountry) {
- this.selectedCountry = this.#defaultCountryData;
- }
this.#changeDetector.markForCheck();
}, 0);
@@ -361,6 +357,7 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
(newValue: SkyCountryFieldCountry | undefined | null) => {
if (newValue?.iso2 !== this.selectedCountry?.iso2) {
this.selectedCountry = newValue || undefined;
+ this.selectedCountryChange.emit(this.selectedCountry);
}
},
);
@@ -428,7 +425,7 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
this.#changeDetector.markForCheck();
}
- public setCountryByDialCode(phoneNumberRaw: string): boolean {
+ public setCountryByDialCode(phoneNumberRaw: string | undefined): boolean {
if (!phoneNumberRaw || !phoneNumberRaw.startsWith('+')) {
return false;
}
@@ -495,6 +492,12 @@ export class SkyPhoneFieldComponent implements OnDestroy, OnInit {
return false;
}
+ #getDefaultCountryData(): SkyPhoneFieldCountry | undefined {
+ return this.countries.find(
+ (country) => country.iso2 === this.#_defaultCountry,
+ );
+ }
+
#populateInputBoxHelpText(): void {
if (this.inputBoxHostSvc && this.inputTemplateRef) {
this.inputBoxHostSvc?.setHintText(
From 082130e05c500792ce900f33f9a3c69725f4bd2e Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Tue, 9 Jul 2024 16:20:37 -0400
Subject: [PATCH 13/95] build: update implicit dependencies for testing (#2449)
---
libs/components/indicators/testing/project.json | 8 +-------
libs/components/modals/testing/project.json | 8 +++++++-
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/libs/components/indicators/testing/project.json b/libs/components/indicators/testing/project.json
index f9c6ad9b50..8fa6635b45 100644
--- a/libs/components/indicators/testing/project.json
+++ b/libs/components/indicators/testing/project.json
@@ -5,13 +5,7 @@
"sourceRoot": "libs/components/indicators/testing/src",
"prefix": "sky",
"tags": ["testing"],
- "implicitDependencies": [
- "core-testing",
- "icon",
- "indicators",
- "testing",
- "theme"
- ],
+ "implicitDependencies": ["core-testing", "indicators", "testing"],
"targets": {
"build": {
"command": "echo ' 🏗️ build indicators-testing'",
diff --git a/libs/components/modals/testing/project.json b/libs/components/modals/testing/project.json
index 4884438413..a9f3c9827e 100644
--- a/libs/components/modals/testing/project.json
+++ b/libs/components/modals/testing/project.json
@@ -5,7 +5,13 @@
"sourceRoot": "libs/components/modals/testing/src",
"prefix": "sky",
"tags": ["testing"],
- "implicitDependencies": ["core-testing", "modals", "testing", "theme"],
+ "implicitDependencies": [
+ "core-testing",
+ "help-inline-testing",
+ "modals",
+ "testing",
+ "theme"
+ ],
"targets": {
"build": {
"command": "echo ' 🏗️ build modals-testing'",
From a3d1a968d8a37442146220a7691acad38b13683b Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Tue, 9 Jul 2024 17:00:50 -0400
Subject: [PATCH 14/95] ci: update changelog cherry-pick message (#2451)
---
.github/workflows/cherry-pick.yml | 4 +++-
package-lock.json | 6 +++---
package.json | 2 +-
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml
index 091020596a..8737e15e0c 100644
--- a/.github/workflows/cherry-pick.yml
+++ b/.github/workflows/cherry-pick.yml
@@ -72,6 +72,8 @@ jobs:
echo "CHERRY_PICK_RESULT=failed" >> $GITHUB_ENV
exit 0
fi
+
+ echo "COMMIT_MESSAGE=$(git log -1 --pretty=%B)" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
@@ -91,7 +93,7 @@ jobs:
repo: context.repo.repo,
head: process.env.CHERRY_PICK_BRANCH,
base: process.env.TARGET_BRANCH,
- title: `${pr.title} (#${pr.number})`,
+ title: `${process.env.COMMIT_MESSAGE} (#${pr.number})`,
body
}).then(result => {
console.log(`Created PR #${result.data.number}: ${result.data.html_url}`);
diff --git a/package-lock.json b/package-lock.json
index 9817f6dc16..99cc66a7bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -75,7 +75,7 @@
"@ryansonshine/commitizen": "4.2.8",
"@ryansonshine/cz-conventional-changelog": "3.3.4",
"@schematics/angular": "17.3.2",
- "@skyux/dev-infra-private": "github:blackbaud/skyux-dev-infra-private-builds#10.0.0-alpha.5",
+ "@skyux/dev-infra-private": "github:blackbaud/skyux-dev-infra-private-builds#10.0.0-alpha.7",
"@storybook/addon-a11y": "8.1.10",
"@storybook/addon-actions": "8.1.10",
"@storybook/addon-controls": "8.1.10",
@@ -9146,8 +9146,8 @@
}
},
"node_modules/@skyux/dev-infra-private": {
- "version": "10.0.0-alpha.5",
- "resolved": "git+ssh://git@github.com/blackbaud/skyux-dev-infra-private-builds.git#a0b36ddd4de5723ddff68659439e305544f4456e",
+ "version": "10.0.0-alpha.7",
+ "resolved": "git+ssh://git@github.com/blackbaud/skyux-dev-infra-private-builds.git#8f26d24cfaee3d0c6eddb1f5d5eecb31f430bb50",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 2a0833d69e..fc4f7b5415 100644
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
"@ryansonshine/commitizen": "4.2.8",
"@ryansonshine/cz-conventional-changelog": "3.3.4",
"@schematics/angular": "17.3.2",
- "@skyux/dev-infra-private": "github:blackbaud/skyux-dev-infra-private-builds#10.0.0-alpha.5",
+ "@skyux/dev-infra-private": "github:blackbaud/skyux-dev-infra-private-builds#10.0.0-alpha.7",
"@storybook/addon-a11y": "8.1.10",
"@storybook/addon-actions": "8.1.10",
"@storybook/addon-controls": "8.1.10",
From b228868a59b14715b09dc278c1fcf00829ea674e Mon Sep 17 00:00:00 2001
From: Paul Crowder
Date: Wed, 10 Jul 2024 10:03:19 -0400
Subject: [PATCH 15/95] chore(components/indicators): split expansion indicator
styles (#2453)
---
.../expansion-indicator.component.ts | 5 +-
...xpansion-indicator.default.component.scss} | 47 ++-----------
.../expansion-indicator.modern.component.scss | 70 +++++++++++++++++++
3 files changed, 78 insertions(+), 44 deletions(-)
rename libs/components/indicators/src/lib/modules/expansion-indicator/{expansion-indicator.component.scss => expansion-indicator.default.component.scss} (55%)
create mode 100644 libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.modern.component.scss
diff --git a/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.ts b/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.ts
index 290f311878..de6205e565 100644
--- a/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.ts
+++ b/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.ts
@@ -6,7 +6,10 @@ import { Component, Input } from '@angular/core';
*/
@Component({
selector: 'sky-expansion-indicator',
- styleUrls: ['./expansion-indicator.component.scss'],
+ styleUrls: [
+ './expansion-indicator.default.component.scss',
+ './expansion-indicator.modern.component.scss',
+ ],
templateUrl: './expansion-indicator.component.html',
})
export class SkyExpansionIndicatorComponent {
diff --git a/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.scss b/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.default.component.scss
similarity index 55%
rename from libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.scss
rename to libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.default.component.scss
index b794266009..5ef833b1bd 100644
--- a/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.component.scss
+++ b/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.default.component.scss
@@ -1,7 +1,7 @@
@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
@use 'libs/components/theme/src/lib/styles/variables' as *;
-.sky-expansion-indicator {
+@include mixins.sky-component('default', '.sky-expansion-indicator') {
display: inline-block;
border: none;
background-color: transparent;
@@ -14,7 +14,7 @@
vertical-align: top;
}
-.sky-expansion-indicator-part {
+@include mixins.sky-component('default', '.sky-expansion-indicator-part') {
border-color: $sky-text-color-icon-borderless;
border-style: solid;
border-width: 3px 0 0 0;
@@ -29,7 +29,7 @@
width: 10px;
}
-.sky-expansion-indicator-up {
+@include mixins.sky-component('default', '.sky-expansion-indicator-up') {
.sky-expansion-indicator-left {
left: 7px;
transform: rotate(-45deg);
@@ -41,7 +41,7 @@
}
}
-.sky-expansion-indicator-down {
+@include mixins.sky-component('default', '.sky-expansion-indicator-down') {
.sky-expansion-indicator-left {
left: 2px;
transform: rotate(45deg);
@@ -52,42 +52,3 @@
transform: rotate(-45deg);
}
}
-
-@include mixins.sky-theme-modern {
- .sky-expansion-indicator {
- height: 26px;
- width: 26px;
- }
-
- .sky-expansion-indicator-part {
- background: $sky-theme-modern-font-deemphasized-color;
- border: none;
- height: 2px;
- width: 11px;
- top: 13px;
- }
-
- .sky-expansion-indicator-glyph-container {
- transform: scale(0.68);
- display: inline-block;
- position: absolute;
- top: 3.5px;
- left: 4px;
- }
-
- .sky-expansion-indicator-left {
- border-radius: 1px 0 0 1px;
- }
-
- .sky-expansion-indicator-right {
- border-radius: 0 1px 1px 0;
- }
-
- .sky-expansion-indicator-left {
- left: 4px;
- }
-
- .sky-expansion-indicator-right {
- left: 10.5px;
- }
-}
diff --git a/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.modern.component.scss b/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.modern.component.scss
new file mode 100644
index 0000000000..03422a184f
--- /dev/null
+++ b/libs/components/indicators/src/lib/modules/expansion-indicator/expansion-indicator.modern.component.scss
@@ -0,0 +1,70 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('modern', '.sky-expansion-indicator') {
+ display: inline-block;
+ border: none;
+ background-color: transparent;
+ flex-shrink: 0;
+ height: 26px;
+ margin: 0;
+ overflow: hidden;
+ position: relative;
+ vertical-align: top;
+ width: 26px;
+}
+
+@include mixins.sky-component('modern', '.sky-expansion-indicator-part') {
+ background: $sky-theme-modern-font-deemphasized-color;
+ border: none;
+ display: inline-block;
+ height: 2px;
+ position: absolute;
+ top: 13px;
+ transition:
+ transform $sky-transition-time-medium,
+ left $sky-transition-time-medium;
+ vertical-align: top;
+ width: 11px;
+}
+
+@include mixins.sky-component(
+ 'modern',
+ '.sky-expansion-indicator-glyph-container'
+) {
+ left: 4px;
+ display: inline-block;
+ position: absolute;
+ top: 3.5px;
+ transform: scale(0.68);
+}
+
+@include mixins.sky-component('modern', '.sky-expansion-indicator-left') {
+ border-radius: 1px 0 0 1px;
+ left: 4px;
+}
+
+@include mixins.sky-component('modern', '.sky-expansion-indicator-right') {
+ border-radius: 0 1px 1px 0;
+ left: 10.5px;
+}
+
+@include mixins.sky-component('modern', '.sky-expansion-indicator-up') {
+ .sky-expansion-indicator-left {
+ transform: rotate(-45deg);
+ }
+
+ .sky-expansion-indicator-right {
+ transform: rotate(45deg);
+ }
+}
+
+@include mixins.sky-component('modern', '.sky-expansion-indicator-down') {
+ .sky-expansion-indicator-left {
+ transform: rotate(45deg);
+ }
+
+ .sky-expansion-indicator-right {
+ transform: rotate(-45deg);
+ }
+}
From 73a47639b6349818b42de5c8d4dcbbf402c13884 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Wed, 10 Jul 2024 10:18:38 -0400
Subject: [PATCH 16/95] fix(code-examples): satisfy ESLint rules for layout,
lists, lookup, modals, pages, and others (#2435)
---
apps/code-examples/.eslintrc.json | 7 ++
.../infinite-scroll/demo.component.ts | 15 +--
.../layout/box/basic/demo.component.spec.ts | 2 +-
.../lists/filter/modal/demo.component.ts | 2 +-
.../repeater/demo.component.ts | 15 +--
.../paging/with-content/demo.component.ts | 8 +-
.../add-remove/demo.component.spec.ts | 105 +++++++++---------
.../repeater/inline-form/demo.component.ts | 19 ++--
.../lists/sort/basic/demo.component.ts | 2 +-
.../autocomplete/advanced/demo.component.ts | 2 +-
.../custom-search/demo.component.ts | 2 +-
.../country-field/basic/demo.component.html | 2 +-
.../country-field/basic/demo.component.ts | 44 ++++----
.../lookup/add-item/demo.component.spec.ts | 2 +-
.../lookup/lookup/add-item/demo.component.ts | 14 ++-
.../lookup/async/demo.component.spec.ts | 2 +-
.../lookup/lookup/async/demo.component.ts | 3 +-
.../custom-picker/demo.component.spec.ts | 2 +-
.../lookup/custom-picker/demo.component.ts | 4 +-
.../custom-picker/picker-modal.component.ts | 9 +-
.../lookup/multi-select/demo.component.ts | 2 +-
.../result-templates/demo.component.spec.ts | 2 +-
.../lookup/result-templates/demo.component.ts | 2 +-
.../lookup/single-select/demo.component.ts | 2 +-
.../lookup/search/basic/demo.component.ts | 4 +-
.../add-item/demo.component.spec.ts | 19 ++--
.../add-item/demo.component.ts | 9 +-
.../basic/demo.component.spec.ts | 18 +--
.../demo.component.spec.ts | 12 +-
.../basic-with-harness/demo.component.spec.ts | 9 +-
.../basic-with-controller/demo.component.ts | 4 +-
.../basic-with-harness/demo.component.ts | 3 +-
.../modals/modal/with-error/demo.component.ts | 3 +-
.../modal/with-error/modal.component.ts | 2 +-
.../dashboards-grid-context-menu.component.ts | 6 +-
.../demo.component.spec.ts | 2 +-
.../page/list-page-list-layout-demo/item.ts | 5 +
.../list-page-content.component.ts | 3 +-
.../contact-context-menu.component.ts | 6 +-
.../list-page-tabs-layout-demo/contact.ts | 5 +
.../demo.component.spec.ts | 2 +-
.../list-page-content.component.ts | 3 +-
.../demo.component.spec.ts | 2 +-
.../attachment.ts | 6 +
...attachments-grid-context-menu.component.ts | 6 +-
.../demo.component.spec.ts | 2 +-
.../record-page-tabs-layout-demo/detail.ts | 4 +
.../record-page-attachments-tab.component.ts | 3 +-
.../record-page-overview-tab.component.ts | 4 +-
.../demo.component.spec.ts | 2 +-
.../custom-sky-href-resolver.service.spec.ts | 58 +++++-----
.../custom-sky-href-resolver.service.ts | 41 ++++---
.../split-view/basic/demo.component.ts | 26 +++--
.../split-view/page-bound/demo.component.ts | 26 +++--
.../modal/information-form.component.ts | 23 ++--
.../text-editor/text-editor/demo.component.ts | 17 ++-
56 files changed, 350 insertions(+), 254 deletions(-)
create mode 100644 apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/item.ts
create mode 100644 apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact.ts
create mode 100644 apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachment.ts
create mode 100644 apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/detail.ts
diff --git a/apps/code-examples/.eslintrc.json b/apps/code-examples/.eslintrc.json
index 73af0ebb9d..bb83826810 100644
--- a/apps/code-examples/.eslintrc.json
+++ b/apps/code-examples/.eslintrc.json
@@ -31,7 +31,14 @@
},
{
"files": ["./src/app/code-examples/**/*.ts"],
+ "extends": ["../../libs/sdk/eslint-config/recommended"],
+ "parserOptions": {
+ "project": ["apps/code-examples/tsconfig.editor.json"],
+ "tsconfigRootDir": "."
+ },
"rules": {
+ "no-alert": "warn",
+ "no-console": "warn",
"no-restricted-imports": [
"error",
{
diff --git a/apps/code-examples/src/app/code-examples/layout/back-to-top/infinite-scroll/demo.component.ts b/apps/code-examples/src/app/code-examples/layout/back-to-top/infinite-scroll/demo.component.ts
index 61b9031fbf..5c185fe9ff 100644
--- a/apps/code-examples/src/app/code-examples/layout/back-to-top/infinite-scroll/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/layout/back-to-top/infinite-scroll/demo.component.ts
@@ -129,21 +129,18 @@ export class DemoComponent implements OnInit {
];
public ngOnInit(): void {
- this.addData(0, 5);
+ void this.#addData(0, 5);
}
public onScrollEnd(): void {
- this.addData(this.personList.length, 5);
+ void this.#addData(this.personList.length, 5);
}
- private addData(start: number, rowSize: number): void {
+ async #addData(start: number, rowSize: number): Promise {
if (this.hasMore) {
- this.mockRemote(start, rowSize).then(
- (result: { data: Person[]; hasMore: boolean }) => {
- this.personList = this.personList.concat(result.data);
- this.hasMore = result.hasMore;
- },
- );
+ const result = await this.mockRemote(start, rowSize);
+ this.personList = this.personList.concat(result.data);
+ this.hasMore = result.hasMore;
}
}
diff --git a/apps/code-examples/src/app/code-examples/layout/box/basic/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/layout/box/basic/demo.component.spec.ts
index 44bac8fd95..04debb4409 100644
--- a/apps/code-examples/src/app/code-examples/layout/box/basic/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/layout/box/basic/demo.component.spec.ts
@@ -4,7 +4,7 @@ import { SkyBoxHarness } from '@skyux/layout/testing';
import { DemoComponent } from './demo.component';
-describe('Basic box', async () => {
+describe('Basic box', () => {
async function setupTest(): Promise<{
boxHarness: SkyBoxHarness;
fixture: ComponentFixture;
diff --git a/apps/code-examples/src/app/code-examples/lists/filter/modal/demo.component.ts b/apps/code-examples/src/app/code-examples/lists/filter/modal/demo.component.ts
index 03e028b0d2..feb85c9a3a 100644
--- a/apps/code-examples/src/app/code-examples/lists/filter/modal/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lists/filter/modal/demo.component.ts
@@ -82,7 +82,7 @@ export class DemoComponent {
modalInstance.closed.subscribe((result: SkyModalCloseArgs) => {
if (result.reason === 'save') {
- this.appliedFilters = result.data.slice();
+ this.appliedFilters = (result.data as Filter[]).slice();
this.filteredItems = this.#filterItems(this.items, this.appliedFilters);
this.#changeDetectorRef.markForCheck();
}
diff --git a/apps/code-examples/src/app/code-examples/lists/infinite-scroll/repeater/demo.component.ts b/apps/code-examples/src/app/code-examples/lists/infinite-scroll/repeater/demo.component.ts
index e559918326..ca99fb3f36 100644
--- a/apps/code-examples/src/app/code-examples/lists/infinite-scroll/repeater/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lists/infinite-scroll/repeater/demo.component.ts
@@ -17,22 +17,19 @@ export class DemoComponent implements OnInit {
protected itemsHaveMore = true;
public ngOnInit(): void {
- this.#addData();
+ void this.#addData();
}
protected onScrollEnd(): void {
if (this.itemsHaveMore) {
- this.#addData();
+ void this.#addData();
}
}
- #addData(): void {
- this.#mockRemote().then(
- (result: { data: InfiniteScrollDemoItem[]; hasMore: boolean }) => {
- this.items = this.items.concat(result.data);
- this.itemsHaveMore = result.hasMore;
- },
- );
+ async #addData(): Promise {
+ const result = await this.#mockRemote();
+ this.items = this.items.concat(result.data);
+ this.itemsHaveMore = result.hasMore;
}
#mockRemote(): Promise<{
diff --git a/apps/code-examples/src/app/code-examples/lists/paging/with-content/demo.component.ts b/apps/code-examples/src/app/code-examples/lists/paging/with-content/demo.component.ts
index d56b6d4f31..195e2a0f48 100644
--- a/apps/code-examples/src/app/code-examples/lists/paging/with-content/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lists/paging/with-content/demo.component.ts
@@ -32,9 +32,11 @@ export class DemoComponent {
protected pagedData = this.contentChange.pipe(
switchMap((args) =>
- this.#demoDataSvc
- .getPagedData(args.currentPage, this.pageSize)
- .pipe(tap(() => args.loadingComplete())),
+ this.#demoDataSvc.getPagedData(args.currentPage, this.pageSize).pipe(
+ tap(() => {
+ args.loadingComplete();
+ }),
+ ),
),
shareReplay(1),
);
diff --git a/apps/code-examples/src/app/code-examples/lists/repeater/add-remove/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lists/repeater/add-remove/demo.component.spec.ts
index 52cc6a1b12..7b71b1d78d 100644
--- a/apps/code-examples/src/app/code-examples/lists/repeater/add-remove/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lists/repeater/add-remove/demo.component.spec.ts
@@ -1,18 +1,15 @@
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
-import {
- SkyRepeaterHarness,
- SkyRepeaterItemHarness,
-} from '@skyux/lists/testing';
+import { SkyRepeaterHarness } from '@skyux/lists/testing';
import { DemoComponent } from './demo.component';
describe('Repeater add remove demo', () => {
async function setupTest(): Promise<{
- repeaterHarness: SkyRepeaterHarness | null;
- repeaterItems: SkyRepeaterItemHarness[] | null;
+ el: HTMLElement;
fixture: ComponentFixture;
+ repeaterHarness: SkyRepeaterHarness;
}> {
const fixture = TestBed.createComponent(DemoComponent);
const loader = TestbedHarnessEnvironment.loader(fixture);
@@ -21,9 +18,9 @@ describe('Repeater add remove demo', () => {
SkyRepeaterHarness.with({ dataSkyId: 'repeater-demo' }),
);
- const repeaterItems = await repeaterHarness.getRepeaterItems();
+ const el = fixture.nativeElement as HTMLElement;
- return { repeaterHarness, repeaterItems, fixture };
+ return { el, fixture, repeaterHarness };
}
beforeEach(() => {
@@ -33,11 +30,13 @@ describe('Repeater add remove demo', () => {
});
it('should allow items to be expanded and collapsed', async () => {
- const { repeaterItems } = await setupTest();
+ const { repeaterHarness } = await setupTest();
+
+ const repeaterItems = await repeaterHarness.getRepeaterItems();
let first = true;
- for (const item of repeaterItems!) {
+ for (const item of repeaterItems) {
await expectAsync(item.isCollapsible()).toBeResolvedTo(true);
// in single expand mode, the first item is expanded by default
@@ -75,72 +74,68 @@ describe('Repeater add remove demo', () => {
},
];
- let repeaterItems = await repeaterHarness?.getRepeaterItems();
+ let repeaterItems = await repeaterHarness.getRepeaterItems();
expect(repeaterItems).toBeDefined();
- expect(repeaterItems?.length).toBe(expectedContent.length);
+ expect(repeaterItems.length).toBe(expectedContent.length);
- if (repeaterItems) {
- for (const item of repeaterItems) {
- await expectAsync(item.isReorderable()).toBeResolvedTo(true);
- }
+ for (const item of repeaterItems) {
+ await expectAsync(item.isReorderable()).toBeResolvedTo(true);
+ }
- await expectAsync(repeaterItems?.[1].getTitleText()).toBeResolvedTo(
- expectedContent[1].title,
- );
+ await expectAsync(repeaterItems[1].getTitleText()).toBeResolvedTo(
+ expectedContent[1].title,
+ );
- await repeaterItems?.[1].sendToTop();
- repeaterItems = await repeaterHarness?.getRepeaterItems();
+ await repeaterItems[1].sendToTop();
+ repeaterItems = await repeaterHarness.getRepeaterItems();
- await expectAsync(repeaterItems?.[1].getTitleText()).toBeResolvedTo(
- expectedContent[0].title,
- );
- }
+ await expectAsync(repeaterItems[1].getTitleText()).toBeResolvedTo(
+ expectedContent[0].title,
+ );
});
it('should allow items to be added and removed', async () => {
- const { repeaterHarness, fixture } = await setupTest();
+ const { repeaterHarness, el, fixture } = await setupTest();
- let repeaterItems = await repeaterHarness?.getRepeaterItems();
+ let repeaterItems = await repeaterHarness.getRepeaterItems();
expect(repeaterItems).toBeDefined();
- expect(repeaterItems?.length).toBe(4);
+ expect(repeaterItems.length).toBe(4);
- if (repeaterItems) {
- for (const item of repeaterItems) {
- await expectAsync(item.isSelectable()).toBeResolvedTo(true);
- }
+ for (const item of repeaterItems) {
+ await expectAsync(item.isSelectable()).toBeResolvedTo(true);
+ }
- const addButton = fixture.nativeElement.querySelector(
- '[data-sky-id="add-button"]',
- );
+ const addButton = el.querySelector(
+ '[data-sky-id="add-button"]',
+ );
- const removeButton = fixture.nativeElement.querySelector(
- '[data-sky-id="remove-button"]',
- );
+ const removeButton = el.querySelector(
+ '[data-sky-id="remove-button"]',
+ );
- addButton.click();
- fixture.detectChanges();
+ addButton?.click();
+ fixture.detectChanges();
- repeaterItems = await repeaterHarness?.getRepeaterItems();
- expect(repeaterItems).toBeDefined();
- expect(repeaterItems?.length).toBe(5);
+ repeaterItems = await repeaterHarness.getRepeaterItems();
+ expect(repeaterItems).toBeDefined();
+ expect(repeaterItems.length).toBe(5);
- await expectAsync(repeaterItems?.[0].isSelected()).toBeResolvedTo(false);
- await repeaterItems?.[0].select();
+ await expectAsync(repeaterItems[0].isSelected()).toBeResolvedTo(false);
+ await repeaterItems[0].select();
- await expectAsync(repeaterItems?.[0].isSelected()).toBeResolvedTo(true);
- await expectAsync(repeaterItems?.[1].isSelected()).toBeResolvedTo(false);
+ await expectAsync(repeaterItems[0].isSelected()).toBeResolvedTo(true);
+ await expectAsync(repeaterItems[1].isSelected()).toBeResolvedTo(false);
- await repeaterItems?.[1].select();
- await expectAsync(repeaterItems?.[1].isSelected()).toBeResolvedTo(true);
+ await repeaterItems[1].select();
+ await expectAsync(repeaterItems[1].isSelected()).toBeResolvedTo(true);
- removeButton.click();
- fixture.detectChanges();
+ removeButton?.click();
+ fixture.detectChanges();
- repeaterItems = await repeaterHarness?.getRepeaterItems();
- expect(repeaterItems).toBeDefined();
- expect(repeaterItems?.length).toBe(3);
- }
+ repeaterItems = await repeaterHarness.getRepeaterItems();
+ expect(repeaterItems).toBeDefined();
+ expect(repeaterItems.length).toBe(3);
});
});
diff --git a/apps/code-examples/src/app/code-examples/lists/repeater/inline-form/demo.component.ts b/apps/code-examples/src/app/code-examples/lists/repeater/inline-form/demo.component.ts
index 1053b0ca58..c7514e826e 100644
--- a/apps/code-examples/src/app/code-examples/lists/repeater/inline-form/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lists/repeater/inline-form/demo.component.ts
@@ -16,6 +16,12 @@ import {
} from '@skyux/inline-form';
import { SkyRepeaterModule } from '@skyux/lists';
+interface DemoForm {
+ id: FormControl;
+ note: FormControl;
+ title: FormControl;
+}
+
interface Item {
id: string;
title: string | undefined;
@@ -37,6 +43,7 @@ interface Item {
})
export class DemoComponent {
protected activeInlineFormId: string | undefined;
+ protected formGroup: FormGroup;
protected inlineFormConfig: SkyInlineFormConfig = {
buttonLayout: SkyInlineFormButtonLayout.SaveCancel,
@@ -65,13 +72,11 @@ export class DemoComponent {
},
];
- protected formGroup: FormGroup;
-
constructor() {
this.formGroup = inject(FormBuilder).group({
- id: new FormControl(),
- title: new FormControl(),
- note: new FormControl(),
+ id: new FormControl('', { nonNullable: true }),
+ title: new FormControl('', { nonNullable: true }),
+ note: new FormControl('', { nonNullable: true }),
});
}
@@ -89,8 +94,8 @@ export class DemoComponent {
(item) => item.id === this.activeInlineFormId,
);
if (found) {
- found.note = this.formGroup.get('note')?.value;
- found.title = this.formGroup.get('title')?.value;
+ found.note = this.formGroup.value.note;
+ found.title = this.formGroup.value.title;
}
}
diff --git a/apps/code-examples/src/app/code-examples/lists/sort/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/lists/sort/basic/demo.component.ts
index c7d9eda81d..0a33ff86c7 100644
--- a/apps/code-examples/src/app/code-examples/lists/sort/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lists/sort/basic/demo.component.ts
@@ -102,7 +102,7 @@ export class DemoComponent implements OnInit {
}
protected sortItems(option: SortOption): void {
- this.sortedItems = this.sortedItems.sort(function (a: Item, b: Item) {
+ this.sortedItems = this.sortedItems.sort((a, b) => {
const descending = option.descending ? -1 : 1;
const sortProperty: keyof typeof a = option.name;
diff --git a/apps/code-examples/src/app/code-examples/lookup/autocomplete/advanced/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/autocomplete/advanced/demo.component.ts
index fc7cde552c..f61f04b2cd 100644
--- a/apps/code-examples/src/app/code-examples/lookup/autocomplete/advanced/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/autocomplete/advanced/demo.component.ts
@@ -68,6 +68,6 @@ export class DemoComponent {
}
protected onPlanetSelection(args: SkyAutocompleteSelectionChange): void {
- alert(`You selected ${args.selectedItem.name}`);
+ alert(`You selected ${(args.selectedItem as Planet).name}`);
}
}
diff --git a/apps/code-examples/src/app/code-examples/lookup/autocomplete/custom-search/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/autocomplete/custom-search/demo.component.ts
index d12c1be29b..a91d8b8cdc 100644
--- a/apps/code-examples/src/app/code-examples/lookup/autocomplete/custom-search/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/autocomplete/custom-search/demo.component.ts
@@ -61,7 +61,7 @@ export class DemoComponent {
const results = oceans.filter((ocean: Ocean) => {
const val = ocean.title;
const isMatch =
- val && val.toString().toLowerCase().indexOf(searchTextLower) > -1;
+ val && val.toString().toLowerCase().includes(searchTextLower);
return isMatch;
});
diff --git a/apps/code-examples/src/app/code-examples/lookup/country-field/basic/demo.component.html b/apps/code-examples/src/app/code-examples/lookup/country-field/basic/demo.component.html
index 54f11adf5d..b7c743d524 100644
--- a/apps/code-examples/src/app/code-examples/lookup/country-field/basic/demo.component.html
+++ b/apps/code-examples/src/app/code-examples/lookup/country-field/basic/demo.component.html
@@ -4,7 +4,7 @@
labelText="Country"
[helpPopoverContent]="helpPopoverContent"
>
-
+
;
+}
+
+function validateCountry(
+ control: AbstractControl,
+): ValidationErrors | null {
+ return control.value?.name === 'Mexico' ? { invalidCountry: true } : null;
+}
@Component({
standalone: true,
@@ -25,30 +35,26 @@ import { SkyCountryFieldModule } from '@skyux/lookup';
],
})
export class DemoComponent {
- protected countryControl: FormControl;
- protected countryForm: FormGroup;
+ protected countryControl: FormControl;
+ protected countryForm: FormGroup;
+
protected helpPopoverContent =
'We use the country to validate your passport within 10 business days. You can update it at any time.';
constructor() {
- this.countryControl = new FormControl(undefined, {
- validators: [this.#validateCountry, Validators.required],
- });
-
- this.countryControl.setValue({
- name: 'Australia',
- iso2: 'au',
- });
+ this.countryControl = new FormControl(
+ {
+ name: 'Australia',
+ iso2: 'au',
+ },
+ {
+ nonNullable: true,
+ validators: [validateCountry, Validators.required],
+ },
+ );
this.countryForm = new FormGroup({
- countryControl: this.countryControl,
+ country: this.countryControl,
});
}
-
- #validateCountry(control: AbstractControl): ValidationErrors | null {
- if (control.value?.name === 'Mexico') {
- return { invalidCountry: true };
- }
- return null;
- }
}
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.spec.ts
index 7bc096e035..110644bef1 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.spec.ts
@@ -31,7 +31,7 @@ describe('Lookup asynchronous search demo', () => {
beforeEach(() => {
// Create a mock search service. In a real-world application, the search
// service would make a web request which should be avoided in unit tests.
- mockSvc = jasmine.createSpyObj('DemoService', ['search']);
+ mockSvc = jasmine.createSpyObj('DemoService', ['search']);
TestBed.configureTestingModule({
imports: [DemoComponent, NoopAnimationsModule],
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.ts
index dee894089e..0babab6dac 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/add-item/demo.component.ts
@@ -16,7 +16,7 @@ import {
SkyLookupModule,
SkyLookupShowMoreConfig,
} from '@skyux/lookup';
-import { SkyModalCloseArgs, SkyModalService } from '@skyux/modals';
+import { SkyModalService } from '@skyux/modals';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -52,7 +52,6 @@ export class DemoComponent implements OnInit, OnDestroy {
#subscriptions = new Subscription();
readonly #svc = inject(DemoService);
-
readonly #modalSvc = inject(SkyModalService);
readonly #waitSvc = inject(SkyWaitService);
@@ -96,16 +95,19 @@ export class DemoComponent implements OnInit, OnDestroy {
public addClick(args: SkyLookupAddClickEventArgs): void {
const modal = this.#modalSvc.open(AddItemModalComponent);
+
this.#subscriptions.add(
- modal.closed.subscribe((close: SkyModalCloseArgs) => {
+ modal.closed.subscribe((close) => {
if (close.reason === 'save') {
+ const person = close.data as Person;
+
this.#subscriptions.add(
this.#waitSvc
- .blockingWrap(this.#svc.addPerson(close.data))
+ .blockingWrap(this.#svc.addPerson(person))
.subscribe((data) => {
args.itemAdded({
- item: close.data,
- data: data,
+ item: person,
+ data,
});
}),
);
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.spec.ts
index c7e3d3865d..fc8c35b894 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.spec.ts
@@ -31,7 +31,7 @@ describe('Lookup asynchronous search demo', () => {
beforeEach(() => {
// Create a mock search service. In a real-world application, the search
// service would make a web request which should be avoided in unit tests.
- mockSvc = jasmine.createSpyObj('DemoService', ['search']);
+ mockSvc = jasmine.createSpyObj('DemoService', ['search']);
TestBed.configureTestingModule({
imports: [DemoComponent, NoopAnimationsModule],
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.ts
index f3b6edf0b7..1fd8d8d5b5 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/async/demo.component.ts
@@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import {
+ AbstractControl,
FormBuilder,
FormControl,
FormGroup,
@@ -52,7 +53,7 @@ export class DemoComponent implements OnInit {
constructor() {
const names = new FormControl([{ name: 'Shirley' }], {
validators: [
- (control): ValidationErrors => {
+ (control: AbstractControl): ValidationErrors => {
if (
control.value?.some((person: Person) => !person.name.match(/e/i))
) {
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.spec.ts
index 9ccbd06f5f..71003017f5 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.spec.ts
@@ -44,7 +44,7 @@ describe('Lookup custom picker demo', () => {
await lookupHarness.enterText('Be');
const allResultHarnesses = await lookupHarness.getSearchResults();
- const firstResultHarness = allResultHarnesses && allResultHarnesses[0];
+ const firstResultHarness = allResultHarnesses[0];
if (firstResultHarness) {
await firstResultHarness.select();
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.ts
index da8b9378fc..62129758b7 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/demo.component.ts
@@ -130,7 +130,7 @@ export class DemoComponent implements OnInit {
});
this.searchFilters = [
- (_, item): boolean => {
+ (_, item: Person): boolean => {
const names = this.favoritesForm.value.favoriteNames;
// Only show people in the search results that have not been chosen already.
@@ -154,7 +154,7 @@ export class DemoComponent implements OnInit {
instance.closed.subscribe((closeArgs) => {
if (closeArgs.reason === 'save') {
this.favoritesForm.controls.favoriteNames.setValue(
- closeArgs.data,
+ closeArgs.data as Person[],
);
}
});
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/picker-modal.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/picker-modal.component.ts
index 062aa15812..a6a6bdbebc 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/picker-modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/custom-picker/picker-modal.component.ts
@@ -42,13 +42,16 @@ export class PickerModalComponent {
constructor() {
// This list of people will be rendered as selection boxes.
- this.people = this.context.items;
+ this.people = this.context.items as Person[];
// Create a control for each selection box.
this.peopleForm = this.#formBuilder.group({
people: this.#formBuilder.array(
- this.context.items.map((item) =>
- this.#formBuilder.control(this.context.initialValue?.includes(item)),
+ this.context.items.map((item: Person) =>
+ this.#formBuilder.control(
+ (this.context.initialValue as Person[]).includes(item),
+ { nonNullable: true },
+ ),
),
),
});
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/multi-select/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/multi-select/demo.component.ts
index c44f508c12..ca6511965e 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/multi-select/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/multi-select/demo.component.ts
@@ -78,7 +78,7 @@ export class DemoComponent implements OnInit {
});
this.searchFilters = [
- (_, item, args): boolean => {
+ (_, item: Person, args): boolean => {
// When in the modal view, show all people in the search results, regardless if they have been chosen already.
if (args?.context === 'modal') {
return true;
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.spec.ts
index 38ec0724ec..299d95a56e 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.spec.ts
@@ -77,7 +77,7 @@ describe('Lookup result templates demo', () => {
await lookupHarness.enterText('be');
const allResultHarnesses = await lookupHarness.getSearchResults();
- const firstResultHarness = allResultHarnesses && allResultHarnesses[0];
+ const firstResultHarness = allResultHarnesses[0];
await firstResultHarness.select();
expect(
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.ts
index 716229fe6e..e7c0066051 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/result-templates/demo.component.ts
@@ -145,7 +145,7 @@ export class DemoComponent implements OnInit {
});
this.searchFilters = [
- (_, item): boolean => {
+ (_, item: Person): boolean => {
const names = this.favoritesForm.value.favoriteNames;
// Only show people in the search results that have not been chosen already.
diff --git a/apps/code-examples/src/app/code-examples/lookup/lookup/single-select/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/lookup/single-select/demo.component.ts
index 8509b8be82..f7796240cd 100644
--- a/apps/code-examples/src/app/code-examples/lookup/lookup/single-select/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/lookup/single-select/demo.component.ts
@@ -72,7 +72,7 @@ export class DemoComponent implements OnInit {
});
this.searchFilters = [
- (_, item): boolean => {
+ (_, item: Person): boolean => {
const names = this.favoritesForm.value.favoriteName;
// Only show people in the search results that have not been chosen already.
diff --git a/apps/code-examples/src/app/code-examples/lookup/search/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/search/basic/demo.component.ts
index e9e049d4ec..9b55f85c7b 100644
--- a/apps/code-examples/src/app/code-examples/lookup/search/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/search/basic/demo.component.ts
@@ -50,7 +50,7 @@ export class DemoComponent {
this.searchText = searchText;
if (searchText) {
- filteredItems = this.items.filter(function (item: Item) {
+ filteredItems = this.items.filter((item: Item) => {
let property: keyof typeof item;
for (property in item) {
@@ -58,7 +58,7 @@ export class DemoComponent {
Object.prototype.hasOwnProperty.call(item, property) &&
(property === 'title' || property === 'note')
) {
- if (item[property].indexOf(searchText) > -1) {
+ if (item[property].includes(searchText)) {
return true;
}
}
diff --git a/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.spec.ts
index aabdb9599b..7adf078d58 100644
--- a/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.spec.ts
@@ -13,26 +13,29 @@ describe('Selection modal demo', () => {
async function setupTest(): Promise<{
harness: SkySelectionModalHarness;
+ el: HTMLElement;
fixture: ComponentFixture;
}> {
const fixture = TestBed.createComponent(DemoComponent);
- const openBtn = fixture.nativeElement.querySelector(
+ const el = fixture.nativeElement as HTMLElement;
+
+ const openBtn = el.querySelector(
'.selection-modal-demo-show-btn',
);
- openBtn.click();
+ openBtn?.click();
fixture.detectChanges();
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const harness = await rootLoader.getHarness(SkySelectionModalHarness);
- return { harness, fixture };
+ return { harness, el, fixture };
}
beforeEach(() => {
// Create a mock search service. In a real-world application, the search
// service would make a web request which should be avoided in unit tests.
- mockSvc = jasmine.createSpyObj('DemoService', ['search']);
+ mockSvc = jasmine.createSpyObj('DemoService', ['search']);
mockSvc.search.and.callFake((searchText) => {
return of({
@@ -56,7 +59,7 @@ describe('Selection modal demo', () => {
});
it('should update the selected items list when an item is selected', async () => {
- const { harness, fixture } = await setupTest();
+ const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
@@ -64,7 +67,7 @@ describe('Selection modal demo', () => {
});
await harness.saveAndClose();
- const selectedItemEls = fixture.nativeElement.querySelectorAll(
+ const selectedItemEls = el.querySelectorAll(
'.selection-modal-demo-selected li',
);
@@ -73,7 +76,7 @@ describe('Selection modal demo', () => {
});
it('should not update the selected items list when the user cancels the selection modal', async () => {
- const { harness, fixture } = await setupTest();
+ const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
@@ -81,7 +84,7 @@ describe('Selection modal demo', () => {
});
await harness.cancel();
- const selectedItemEls = fixture.nativeElement.querySelectorAll(
+ const selectedItemEls = el.querySelectorAll(
'.selection-modal-demo-selected li',
);
diff --git a/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.ts b/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.ts
index eb55ed58b2..043441474d 100644
--- a/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/selection-modal/add-item/demo.component.ts
@@ -6,7 +6,7 @@ import {
SkySelectionModalSearchResult,
SkySelectionModalService,
} from '@skyux/lookup';
-import { SkyModalCloseArgs, SkyModalService } from '@skyux/modals';
+import { SkyModalService } from '@skyux/modals';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -55,10 +55,11 @@ export class DemoComponent implements OnDestroy {
const modal = this.#modalSvc.open(AddItemModalComponent);
this.#subscriptions.add(
- modal.closed.subscribe((close: SkyModalCloseArgs) => {
+ modal.closed.subscribe((close) => {
if (close.reason === 'save') {
- this.#searchSvc.addItem(close.data);
- args.itemAdded({ item: close.data });
+ const person = close.data as Person;
+ this.#searchSvc.addItem(person);
+ args.itemAdded({ item: person });
}
}),
);
diff --git a/apps/code-examples/src/app/code-examples/lookup/selection-modal/basic/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/lookup/selection-modal/basic/demo.component.spec.ts
index aabdb9599b..028eaacd4d 100644
--- a/apps/code-examples/src/app/code-examples/lookup/selection-modal/basic/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/lookup/selection-modal/basic/demo.component.spec.ts
@@ -13,26 +13,28 @@ describe('Selection modal demo', () => {
async function setupTest(): Promise<{
harness: SkySelectionModalHarness;
+ el: HTMLElement;
fixture: ComponentFixture;
}> {
const fixture = TestBed.createComponent(DemoComponent);
- const openBtn = fixture.nativeElement.querySelector(
+ const el = fixture.nativeElement as HTMLElement;
+ const openBtn = el.querySelector(
'.selection-modal-demo-show-btn',
);
- openBtn.click();
+ openBtn?.click();
fixture.detectChanges();
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const harness = await rootLoader.getHarness(SkySelectionModalHarness);
- return { harness, fixture };
+ return { harness, el, fixture };
}
beforeEach(() => {
// Create a mock search service. In a real-world application, the search
// service would make a web request which should be avoided in unit tests.
- mockSvc = jasmine.createSpyObj('DemoService', ['search']);
+ mockSvc = jasmine.createSpyObj('DemoService', ['search']);
mockSvc.search.and.callFake((searchText) => {
return of({
@@ -56,7 +58,7 @@ describe('Selection modal demo', () => {
});
it('should update the selected items list when an item is selected', async () => {
- const { harness, fixture } = await setupTest();
+ const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
@@ -64,7 +66,7 @@ describe('Selection modal demo', () => {
});
await harness.saveAndClose();
- const selectedItemEls = fixture.nativeElement.querySelectorAll(
+ const selectedItemEls = el.querySelectorAll(
'.selection-modal-demo-selected li',
);
@@ -73,7 +75,7 @@ describe('Selection modal demo', () => {
});
it('should not update the selected items list when the user cancels the selection modal', async () => {
- const { harness, fixture } = await setupTest();
+ const { harness, el } = await setupTest();
await harness.enterSearchText('ra');
await harness.selectSearchResult({
@@ -81,7 +83,7 @@ describe('Selection modal demo', () => {
});
await harness.cancel();
- const selectedItemEls = fixture.nativeElement.querySelectorAll(
+ const selectedItemEls = el.querySelectorAll(
'.selection-modal-demo-selected li',
);
diff --git a/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-controller/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-controller/demo.component.spec.ts
index 03076fd7e8..db8e5aec46 100644
--- a/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-controller/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-controller/demo.component.spec.ts
@@ -7,10 +7,10 @@ import {
import { DemoComponent } from './demo.component';
describe('Testing with SkyConfirmTestingController', () => {
- async function setupTest(): Promise<{
+ function setupTest(): {
confirmController: SkyConfirmTestingController;
fixture: ComponentFixture;
- }> {
+ } {
const confirmController = TestBed.inject(SkyConfirmTestingController);
const fixture = TestBed.createComponent(DemoComponent);
@@ -23,8 +23,8 @@ describe('Testing with SkyConfirmTestingController', () => {
});
});
- it('should click "OK" on a confirmation dialog', async () => {
- const { confirmController, fixture } = await setupTest();
+ it('should click "OK" on a confirmation dialog', () => {
+ const { confirmController, fixture } = setupTest();
fixture.componentInstance.launchConfirm();
fixture.detectChanges();
@@ -39,8 +39,8 @@ describe('Testing with SkyConfirmTestingController', () => {
expect(fixture.componentInstance.selectedAction).toEqual('ok');
});
- it('should cancel the confirmation dialog', async () => {
- const { confirmController, fixture } = await setupTest();
+ it('should cancel the confirmation dialog', () => {
+ const { confirmController, fixture } = setupTest();
fixture.componentInstance.launchConfirm();
fixture.detectChanges();
diff --git a/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-harness/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-harness/demo.component.spec.ts
index 4ed2baab77..6bd8c07a54 100644
--- a/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-harness/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/modals/confirm/basic-with-harness/demo.component.spec.ts
@@ -10,9 +10,10 @@ describe('Testing with SkyConfirmHarness', () => {
fixture: ComponentFixture;
}> {
const fixture = TestBed.createComponent(DemoComponent);
- const openBtn = fixture.nativeElement.querySelector(confirmBtnClass);
+ const el = fixture.nativeElement as HTMLElement;
+ const openBtn = el.querySelector(confirmBtnClass);
- openBtn.click();
+ openBtn?.click();
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const confirmHarness = await rootLoader.getHarness(SkyConfirmHarness);
@@ -25,7 +26,9 @@ describe('Testing with SkyConfirmHarness', () => {
expectedText: string,
): void {
expect(
- fixture.nativeElement.querySelector('.displayed-text')?.innerText,
+ (fixture.nativeElement as HTMLElement).querySelector(
+ '.displayed-text',
+ )?.innerText,
).toEqual(expectedText);
}
diff --git a/apps/code-examples/src/app/code-examples/modals/modal/basic-with-controller/demo.component.ts b/apps/code-examples/src/app/code-examples/modals/modal/basic-with-controller/demo.component.ts
index 0423564b6c..7fbbba03dd 100644
--- a/apps/code-examples/src/app/code-examples/modals/modal/basic-with-controller/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/modals/modal/basic-with-controller/demo.component.ts
@@ -31,7 +31,9 @@ export class DemoComponent implements OnDestroy {
readonly #modalSvc = inject(SkyModalService);
public ngOnDestroy(): void {
- this.#instances.forEach((i) => i.close());
+ this.#instances.forEach((i) => {
+ i.close();
+ });
}
public openModal(): void {
diff --git a/apps/code-examples/src/app/code-examples/modals/modal/basic-with-harness/demo.component.ts b/apps/code-examples/src/app/code-examples/modals/modal/basic-with-harness/demo.component.ts
index bd0a29f0a7..35e62ed1c2 100644
--- a/apps/code-examples/src/app/code-examples/modals/modal/basic-with-harness/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/modals/modal/basic-with-harness/demo.component.ts
@@ -54,8 +54,7 @@ export class DemoComponent implements OnDestroy {
instance.closed.subscribe((result) => {
if (result.reason === 'save') {
// Display the updated value.
- const data = result.data as ModalDemoData;
- this.demoValue = data.value1;
+ this.demoValue = (result.data as ModalDemoData).value1;
}
});
});
diff --git a/apps/code-examples/src/app/code-examples/modals/modal/with-error/demo.component.ts b/apps/code-examples/src/app/code-examples/modals/modal/with-error/demo.component.ts
index bd0a29f0a7..35e62ed1c2 100644
--- a/apps/code-examples/src/app/code-examples/modals/modal/with-error/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/modals/modal/with-error/demo.component.ts
@@ -54,8 +54,7 @@ export class DemoComponent implements OnDestroy {
instance.closed.subscribe((result) => {
if (result.reason === 'save') {
// Display the updated value.
- const data = result.data as ModalDemoData;
- this.demoValue = data.value1;
+ this.demoValue = (result.data as ModalDemoData).value1;
}
});
});
diff --git a/apps/code-examples/src/app/code-examples/modals/modal/with-error/modal.component.ts b/apps/code-examples/src/app/code-examples/modals/modal/with-error/modal.component.ts
index 903751a980..2bb207bf53 100644
--- a/apps/code-examples/src/app/code-examples/modals/modal/with-error/modal.component.ts
+++ b/apps/code-examples/src/app/code-examples/modals/modal/with-error/modal.component.ts
@@ -47,7 +47,7 @@ export class ModalComponent {
this.#waitSvc
.blockingWrap(this.#dataSvc.save(this.demoForm.value, true))
- .subscribe((data) => {
+ .subscribe(() => {
this.errors = [{ message: 'There was an error saving the form.' }];
});
}
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/dashboards-grid-context-menu.component.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/dashboards-grid-context-menu.component.ts
index b4027838b4..638784dd9b 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/dashboards-grid-context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/dashboards-grid-context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { Item } from './item';
+
@Component({
standalone: true,
selector: 'app-dashboards-grid-context-menu',
@@ -15,8 +17,8 @@ export class DashboardGridContextMenuComponent
{
protected dashboardName = '';
- public agInit(params: ICellRendererParams): void {
- this.dashboardName = params.data && params.data.dashboard;
+ public agInit(params: ICellRendererParams- ): void {
+ this.dashboardName = params.data?.dashboard ?? '';
}
public refresh(): boolean {
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/demo.component.spec.ts
index b4f1823f49..09a41eb22b 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/demo.component.spec.ts
@@ -5,7 +5,7 @@ import { SkyPageHarness } from '@skyux/pages/testing';
import { DemoComponent } from './demo.component';
-describe('List page list layout demo', async () => {
+describe('List page list layout demo', () => {
async function setupTest(): Promise<{
pageHarness: SkyPageHarness;
fixture: ComponentFixture;
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/item.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/item.ts
new file mode 100644
index 0000000000..fdbea72423
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/item.ts
@@ -0,0 +1,5 @@
+export interface Item {
+ dashboard: string;
+ name: string;
+ lastUpdated: string;
+}
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/list-page-content.component.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/list-page-content.component.ts
index b9c0c42fe1..69b71d2165 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/list-page-content.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-list-layout-demo/list-page-content.component.ts
@@ -12,6 +12,7 @@ import { AgGridModule } from 'ag-grid-angular';
import { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community';
import { DashboardGridContextMenuComponent } from './dashboards-grid-context-menu.component';
+import { Item } from './item';
@Component({
standalone: true,
@@ -27,7 +28,7 @@ import { DashboardGridContextMenuComponent } from './dashboards-grid-context-men
],
})
export class ListPageContentComponent implements OnInit {
- protected items = [
+ protected items: Item[] = [
{
dashboard: 'Cash Flow Tracker',
name: 'Kanesha Hutto',
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact-context-menu.component.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact-context-menu.component.ts
index 5b60e65779..dc496c7534 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact-context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact-context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { Contact } from './contact';
+
@Component({
standalone: true,
selector: 'app-contacts-grid-context-menu',
@@ -13,8 +15,8 @@ import { ICellRendererParams } from 'ag-grid-community';
export class ContactContextMenuComponent implements ICellRendererAngularComp {
protected contactName = '';
- public agInit(params: ICellRendererParams): void {
- this.contactName = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.contactName = params.data?.name ?? '';
}
public refresh(): boolean {
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact.ts
new file mode 100644
index 0000000000..9c2dc362e4
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/contact.ts
@@ -0,0 +1,5 @@
+export interface Contact {
+ name: string;
+ organization: string;
+ emailAddress: string;
+}
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/demo.component.spec.ts
index c3a058f9db..ceaa921f10 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/demo.component.spec.ts
@@ -6,7 +6,7 @@ import { SkyPageHarness } from '@skyux/pages/testing';
import { DemoComponent } from './demo.component';
-describe('List page tabs layout demo', async () => {
+describe('List page tabs layout demo', () => {
async function setupTest(): Promise<{
pageHarness: SkyPageHarness;
fixture: ComponentFixture;
diff --git a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/list-page-content.component.ts b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/list-page-content.component.ts
index 6196c864e0..88df6191c7 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/list-page-content.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/list-page-tabs-layout-demo/list-page-content.component.ts
@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { SkyTabIndex, SkyTabsModule } from '@skyux/tabs';
+import { Contact } from './contact';
import { ListPageContactsGridComponent } from './list-page-contacts-grid.component';
@Component({
@@ -13,7 +14,7 @@ import { ListPageContactsGridComponent } from './list-page-contacts-grid.compone
export class ListPageContentComponent {
protected activeTabIndex: SkyTabIndex = 0;
- protected myContacts = [
+ protected myContacts: Contact[] = [
{
name: 'Wonda Lumpkin',
organization: 'Riverfront College of the Arts',
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.spec.ts
index ae5e22d726..5961ebf8c5 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.spec.ts
@@ -6,7 +6,7 @@ import { SkyPageHarness } from '@skyux/pages/testing';
import { DemoComponent } from './demo.component';
-describe('Record page blocks layout demo', async () => {
+describe('Record page blocks layout demo', () => {
async function setupTest(): Promise<{
pageHarness: SkyPageHarness;
fixture: ComponentFixture;
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachment.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachment.ts
new file mode 100644
index 0000000000..746fdbb3c4
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachment.ts
@@ -0,0 +1,6 @@
+export interface Attachment {
+ name: string;
+ description: string;
+ size: string;
+ dateAdded: string;
+}
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachments-grid-context-menu.component.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachments-grid-context-menu.component.ts
index 94d173294b..66173da05d 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachments-grid-context-menu.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/attachments-grid-context-menu.component.ts
@@ -4,6 +4,8 @@ import { SkyDropdownModule } from '@skyux/popovers';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
+import { Attachment } from './attachment';
+
@Component({
standalone: true,
selector: 'app-attachments-grid-context-menu',
@@ -15,8 +17,8 @@ export class AttachmentsGridContextMenuComponent
{
protected attachmentName = '';
- public agInit(params: ICellRendererParams): void {
- this.attachmentName = params.data && params.data.name;
+ public agInit(params: ICellRendererParams): void {
+ this.attachmentName = params.data?.name ?? '';
}
public refresh(): boolean {
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/demo.component.spec.ts
index 21f5a3240e..709a51bf58 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/demo.component.spec.ts
@@ -6,7 +6,7 @@ import { SkyPageHarness } from '@skyux/pages/testing';
import { DemoComponent } from './demo.component';
-describe('Record page tabs layout demo', async () => {
+describe('Record page tabs layout demo', () => {
async function setupTest(): Promise<{
pageHarness: SkyPageHarness;
fixture: ComponentFixture;
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/detail.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/detail.ts
new file mode 100644
index 0000000000..158a7675bd
--- /dev/null
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/detail.ts
@@ -0,0 +1,4 @@
+export interface Detail {
+ detail: string;
+ info: string;
+}
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-attachments-tab.component.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-attachments-tab.component.ts
index 66e165c989..d25b5a8ded 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-attachments-tab.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-attachments-tab.component.ts
@@ -12,6 +12,7 @@ import { SkyIconModule, SkyKeyInfoModule } from '@skyux/indicators';
import { AgGridModule } from 'ag-grid-angular';
import { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community';
+import { Attachment } from './attachment';
import { AttachmentsGridContextMenuComponent } from './attachments-grid-context-menu.component';
@Component({
@@ -29,7 +30,7 @@ import { AttachmentsGridContextMenuComponent } from './attachments-grid-context-
],
})
export class RecordPageAttachmentsTabComponent implements OnInit {
- protected items = [
+ protected items: Attachment[] = [
{
name: 'Agreement.pdf',
description: 'Cardholder agreement',
diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-overview-tab.component.ts b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-overview-tab.component.ts
index a6b272e97e..4a2e01e324 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-overview-tab.component.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-tabs-layout-demo/record-page-overview-tab.component.ts
@@ -8,6 +8,8 @@ import {
} from '@skyux/layout';
import { SkyRepeaterModule } from '@skyux/lists';
+import { Detail } from './detail';
+
@Component({
standalone: true,
selector: 'app-record-page-overview-tab',
@@ -24,7 +26,7 @@ import { SkyRepeaterModule } from '@skyux/lists';
],
})
export class RecordPageOverviewTabComponent {
- protected recordDetails = [
+ protected recordDetails: Detail[] = [
{
detail: 'Designation',
info: 'General operating',
diff --git a/apps/code-examples/src/app/code-examples/pages/page/split-view-page-fit-layout-demo/demo.component.spec.ts b/apps/code-examples/src/app/code-examples/pages/page/split-view-page-fit-layout-demo/demo.component.spec.ts
index b0174483be..69201f03ba 100644
--- a/apps/code-examples/src/app/code-examples/pages/page/split-view-page-fit-layout-demo/demo.component.spec.ts
+++ b/apps/code-examples/src/app/code-examples/pages/page/split-view-page-fit-layout-demo/demo.component.spec.ts
@@ -6,7 +6,7 @@ import { SkyPageHarness } from '@skyux/pages/testing';
import { DemoComponent } from './demo.component';
-describe('Split view page fit layout demo', async () => {
+describe('Split view page fit layout demo', () => {
async function setupTest(): Promise<{
pageHarness: SkyPageHarness;
fixture: ComponentFixture;
diff --git a/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.spec.ts b/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.spec.ts
index 10a29a35fc..700362d7a8 100644
--- a/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.spec.ts
+++ b/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.spec.ts
@@ -14,49 +14,51 @@ describe('CustomSkyHrefResolverService', () => {
expect(service).toBeTruthy();
});
- it('should return a link as-is', () => {
+ it('should return a link as-is', async () => {
const url = 'https://www.blackbaud.com';
- const result = service.resolveHref({ url });
- result.then((href) => {
- expect(href.url).toEqual(url);
- expect(href.userHasAccess).toEqual(true);
- });
+ const href = await service.resolveHref({ url });
+
+ expect(href.url).toEqual(url);
+ expect(href.userHasAccess).toEqual(true);
});
- it('should return a link with allow protocol', () => {
+ it('should return a link with allow protocol', async () => {
const url = 'allow://www.blackbaud.com';
- const result = service.resolveHref({ url });
- result.then((href) => {
- expect(href.url).toEqual('https://www.blackbaud.com');
- expect(href.userHasAccess).toEqual(true);
- });
+ const href = await service.resolveHref({ url });
+
+ expect(href.url).toEqual('https://www.blackbaud.com');
+ expect(href.userHasAccess).toEqual(true);
});
- it('should return a link with deny protocol', () => {
+ it('should return a link with deny protocol', async () => {
const url = 'deny://www.blackbaud.com';
- const result = service.resolveHref({ url });
- result.then((href) => {
- expect(href.url).toEqual(url);
- expect(href.userHasAccess).toEqual(false);
- });
+ const href = await service.resolveHref({ url });
+
+ expect(href.url).toEqual(url);
+ expect(href.userHasAccess).toEqual(false);
});
it('should return a link with slow protocol', fakeAsync(() => {
const url = 'slow://www.blackbaud.com';
const result = service.resolveHref({ url });
- result.then((href) => {
- expect(href.url).toEqual('https://www.blackbaud.com');
- expect(href.userHasAccess).toEqual(true);
- });
+
+ result
+ .then((href) => {
+ expect(href.url).toEqual('https://www.blackbaud.com');
+ expect(href.userHasAccess).toEqual(true);
+ })
+ .catch(() => {
+ fail('expected test to resolve');
+ });
+
tick(3000);
}));
- it('should return a link with unknown protocol', () => {
+ it('should return a link with unknown protocol', async () => {
const url = 'unknown://www.blackbaud.com';
- const result = service.resolveHref({ url });
- result.then((href) => {
- expect(href.url).toEqual(url);
- expect(href.userHasAccess).toEqual(false);
- });
+ const href = await service.resolveHref({ url });
+
+ expect(href.url).toEqual(url);
+ expect(href.userHasAccess).toEqual(false);
});
});
diff --git a/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.ts b/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.ts
index ba07a6056c..7de7212048 100644
--- a/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.ts
+++ b/apps/code-examples/src/app/code-examples/router/href/basic/custom-resolver/custom-sky-href-resolver.service.ts
@@ -12,23 +12,30 @@ import { SkyHref, SkyHrefResolver } from '@skyux/router';
export class CustomSkyHrefResolverService implements SkyHrefResolver {
public resolveHref(param: { url: string }): Promise {
const url = param.url;
+
if (url.startsWith('http:') || url.startsWith('https:')) {
- return Promise.resolve({
- url: url,
+ return Promise.resolve({
+ url,
userHasAccess: true,
});
- } else if (url.startsWith('allow:')) {
- return Promise.resolve({
+ }
+
+ if (url.startsWith('allow:')) {
+ return Promise.resolve({
url: url.replace('allow:', 'https:'),
userHasAccess: true,
});
- } else if (url.startsWith('deny:')) {
- return Promise.resolve({
- url: url,
+ }
+
+ if (url.startsWith('deny:')) {
+ return Promise.resolve({
+ url,
userHasAccess: false,
});
- } else if (url.startsWith('slow:')) {
- return new Promise((resolve) => {
+ }
+
+ if (url.startsWith('slow:')) {
+ return new Promise((resolve) => {
setTimeout(() => {
resolve({
url: url.replace('slow:', 'https:'),
@@ -36,16 +43,18 @@ export class CustomSkyHrefResolverService implements SkyHrefResolver {
});
}, 3000);
});
- } else if (url.startsWith('1bb-nav:')) {
- return Promise.resolve({
+ }
+
+ if (url.startsWith('1bb-nav:')) {
+ return Promise.resolve({
url: `https://docs.blackbaud.com/engineering-system-docs/learn/spa/spa-navigation/spa-to-spa-navigation`,
userHasAccess: true,
});
- } else {
- return Promise.resolve({
- url: url,
- userHasAccess: false,
- });
}
+
+ return Promise.resolve({
+ url,
+ userHasAccess: false,
+ });
}
}
diff --git a/apps/code-examples/src/app/code-examples/split-view/split-view/basic/demo.component.ts b/apps/code-examples/src/app/code-examples/split-view/split-view/basic/demo.component.ts
index 28d8170c28..4340830e04 100644
--- a/apps/code-examples/src/app/code-examples/split-view/split-view/basic/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/split-view/split-view/basic/demo.component.ts
@@ -21,6 +21,11 @@ import { Subject } from 'rxjs';
import { Record } from './record';
+interface DemoForm {
+ approvedAmount: FormControl;
+ comments: FormControl;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -87,7 +92,7 @@ export class DemoComponent {
];
protected activeRecord: Record;
- protected splitViewDemoForm: FormGroup;
+ protected splitViewDemoForm: FormGroup;
protected splitViewStream = new Subject();
#_activeIndex = 0;
@@ -98,9 +103,14 @@ export class DemoComponent {
// Start with the first item selected.
this.activeIndex = 0;
this.activeRecord = this.items[this.activeIndex];
+
this.splitViewDemoForm = new FormGroup({
- approvedAmount: new FormControl(this.activeRecord.approvedAmount),
- comments: new FormControl(this.activeRecord.comments),
+ approvedAmount: new FormControl(this.activeRecord.approvedAmount, {
+ nonNullable: true,
+ }),
+ comments: new FormControl(this.activeRecord.comments, {
+ nonNullable: true,
+ }),
});
}
@@ -124,8 +134,10 @@ export class DemoComponent {
#loadFormGroup(record: Record): void {
this.splitViewDemoForm = new FormGroup({
- approvedAmount: new FormControl(record.approvedAmount),
- comments: new FormControl(record.comments),
+ approvedAmount: new FormControl(record.approvedAmount, {
+ nonNullable: true,
+ }),
+ comments: new FormControl(record.comments, { nonNullable: true }),
});
}
@@ -164,9 +176,9 @@ export class DemoComponent {
#saveForm(): void {
this.activeRecord.approvedAmount =
- this.splitViewDemoForm.value.approvedAmount;
+ this.splitViewDemoForm.value.approvedAmount ?? 0;
+ this.activeRecord.comments = this.splitViewDemoForm.value.comments ?? '';
- this.activeRecord.comments = this.splitViewDemoForm.value.comments;
this.splitViewDemoForm.reset(this.splitViewDemoForm.value);
}
diff --git a/apps/code-examples/src/app/code-examples/split-view/split-view/page-bound/demo.component.ts b/apps/code-examples/src/app/code-examples/split-view/split-view/page-bound/demo.component.ts
index 13e69f47f3..2b1586a1d6 100644
--- a/apps/code-examples/src/app/code-examples/split-view/split-view/page-bound/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/split-view/split-view/page-bound/demo.component.ts
@@ -26,6 +26,11 @@ import { Subject } from 'rxjs';
import { Record } from './record';
+interface DemoForm {
+ approvedAmount: FormControl;
+ comments: FormControl;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -141,7 +146,7 @@ export class DemoComponent {
];
protected activeRecord: Record;
- protected splitViewDemoForm: FormGroup;
+ protected splitViewDemoForm: FormGroup;
protected splitViewStream = new Subject();
protected unapprovedTransaction = true;
@@ -155,8 +160,12 @@ export class DemoComponent {
this.activeRecord = this.items[this.activeIndex];
this.splitViewDemoForm = new FormGroup({
- approvedAmount: new FormControl(this.activeRecord.approvedAmount),
- comments: new FormControl(this.activeRecord.comments),
+ approvedAmount: new FormControl(this.activeRecord.approvedAmount, {
+ nonNullable: true,
+ }),
+ comments: new FormControl(this.activeRecord.comments, {
+ nonNullable: true,
+ }),
});
}
@@ -180,8 +189,10 @@ export class DemoComponent {
#loadFormGroup(record: Record): void {
this.splitViewDemoForm = new FormGroup({
- approvedAmount: new FormControl(record.approvedAmount),
- comments: new FormControl(record.comments),
+ approvedAmount: new FormControl(record.approvedAmount, {
+ nonNullable: true,
+ }),
+ comments: new FormControl(record.comments, { nonNullable: true }),
});
}
@@ -220,9 +231,10 @@ export class DemoComponent {
#saveForm(): void {
this.activeRecord.approvedAmount = parseFloat(
- this.splitViewDemoForm.value.approvedAmount,
+ `${this.splitViewDemoForm.value.approvedAmount ?? 0}`,
);
- this.activeRecord.comments = this.splitViewDemoForm.value.comments;
+
+ this.activeRecord.comments = this.splitViewDemoForm.value.comments ?? '';
this.unapprovedTransaction =
this.items.findIndex((item) => item.amount !== item.approvedAmount) >= 0;
diff --git a/apps/code-examples/src/app/code-examples/tabs/sectioned-form/modal/information-form.component.ts b/apps/code-examples/src/app/code-examples/tabs/sectioned-form/modal/information-form.component.ts
index c35d38b205..65a302ed94 100644
--- a/apps/code-examples/src/app/code-examples/tabs/sectioned-form/modal/information-form.component.ts
+++ b/apps/code-examples/src/app/code-examples/tabs/sectioned-form/modal/information-form.component.ts
@@ -8,6 +8,7 @@ import {
} from '@angular/core';
import {
FormBuilder,
+ FormControl,
FormGroup,
ReactiveFormsModule,
Validators,
@@ -32,7 +33,11 @@ import { SkySectionedFormService } from '@skyux/tabs';
})
export class InformationFormComponent implements OnInit {
protected id = '5324901';
- protected formGroup: FormGroup;
+ protected formGroup: FormGroup<{
+ name: FormControl;
+ nameRequired: FormControl;
+ id: FormControl;
+ }>;
protected name = '';
protected nameRequired = false;
@@ -41,18 +46,20 @@ export class InformationFormComponent implements OnInit {
constructor() {
this.formGroup = inject(FormBuilder).group({
- name: [this.name],
- nameRequired: [this.nameRequired],
- id: [this.id, Validators.pattern('^[0-9]+$')],
+ name: new FormControl(this.name, { nonNullable: true }),
+ nameRequired: new FormControl(this.nameRequired, { nonNullable: true }),
+ id: new FormControl(this.id, {
+ nonNullable: true,
+ validators: [Validators.pattern('^[0-9]+$')],
+ }),
});
}
public ngOnInit(): void {
this.formGroup.valueChanges.subscribe((changes) => {
- console.log(changes);
- this.id = changes.id;
- this.name = changes.name;
- this.nameRequired = changes.nameRequired;
+ this.id = changes.id ?? '';
+ this.name = changes.name ?? '';
+ this.nameRequired = !!changes.nameRequired;
this.#checkValidity();
});
diff --git a/apps/code-examples/src/app/code-examples/text-editor/text-editor/demo.component.ts b/apps/code-examples/src/app/code-examples/text-editor/text-editor/demo.component.ts
index 5294a37baa..73302a1100 100644
--- a/apps/code-examples/src/app/code-examples/text-editor/text-editor/demo.component.ts
+++ b/apps/code-examples/src/app/code-examples/text-editor/text-editor/demo.component.ts
@@ -12,6 +12,12 @@ import {
} from '@angular/forms';
import { SkyTextEditorModule } from '@skyux/text-editor';
+function validateText(
+ control: AbstractControl,
+): ValidationErrors | null {
+ return !control.value?.includes('Blackbaud') ? { companyName: true } : null;
+}
+
@Component({
standalone: true,
selector: 'app-demo',
@@ -32,18 +38,11 @@ export class DemoComponent {
constructor() {
this.myText = new FormControl(this.#richText, {
nonNullable: true,
- validators: [Validators.required, this.#validateText],
+ validators: [Validators.required, validateText],
});
+
this.formGroup = inject(FormBuilder).group({
myText: this.myText,
});
}
-
- #validateText(control: AbstractControl): ValidationErrors | null {
- if (!control.value || !control.value.includes('Blackbaud')) {
- return { companyName: true };
- } else {
- return null;
- }
- }
}
From 3b3823c61c29ca29863b0a9dcdb0468836fc95f8 Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Wed, 10 Jul 2024 10:32:05 -0400
Subject: [PATCH 17/95] ci: fix cherry-pick title (#2455)
---
.github/workflows/cherry-pick.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml
index 8737e15e0c..7f45f66797 100644
--- a/.github/workflows/cherry-pick.yml
+++ b/.github/workflows/cherry-pick.yml
@@ -93,7 +93,7 @@ jobs:
repo: context.repo.repo,
head: process.env.CHERRY_PICK_BRANCH,
base: process.env.TARGET_BRANCH,
- title: `${process.env.COMMIT_MESSAGE} (#${pr.number})`,
+ title: process.env.COMMIT_MESSAGE,
body
}).then(result => {
console.log(`Created PR #${result.data.number}: ${result.data.html_url}`);
From 4d2d70a378d69af7b30370ed44641b036703ce47 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Wed, 10 Jul 2024 12:16:10 -0400
Subject: [PATCH 18/95] fix(components/datetime): ignore extraneous properties
when setting calculator value (#2459)
---
.../date-range-picker.component.spec.ts | 9 +++++++++
.../date-range-picker/date-range-picker.component.ts | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.spec.ts b/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.spec.ts
index 4ed575401d..a7cd9b70a9 100644
--- a/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.spec.ts
+++ b/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.spec.ts
@@ -714,6 +714,15 @@ describe('Date range picker', function () {
);
}));
+ it('should ignore extraneous properties when setting the value', () => {
+ component.dateRange?.setValue({
+ foo: 'bar',
+ calculatorId: SkyDateRangeCalculatorId.LastWeek,
+ });
+
+ expect(() => fixture.detectChanges()).not.toThrow();
+ });
+
describe('accessibility', () => {
function verifyFormFieldsRequired(expectation: boolean): void {
const inputBoxes =
diff --git a/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.ts b/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.ts
index 7a2ab2490a..575f67c8ae 100644
--- a/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.ts
+++ b/libs/components/datetime/src/lib/modules/date-range-picker/date-range-picker.component.ts
@@ -604,7 +604,7 @@ export class SkyDateRangePickerComponent
}
if (options?.emitEvent) {
- this.formGroup.setValue(valueOrDefault);
+ this.formGroup.patchValue(valueOrDefault);
}
}
}
From cea996db6216e223c9998916e50ba7a4b78aa08f Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Wed, 10 Jul 2024 14:08:21 -0400
Subject: [PATCH 19/95] feat(components/ag-grid): add support for AG Grid
31.3.2 (#2450)
* feat(components/ag-grid): add support for AG Grid 31.3.2
* Update peer range
---
libs/components/ag-grid/package.json | 4 ++--
...grid-data-manager-adapter.directive.spec.ts | 1 +
.../ag-grid/ag-grid-wrapper.component.spec.ts | 4 ++++
libs/components/packages/package.json | 6 +++---
.../ag-grid-migrate.schematic.spec.ts | 2 +-
.../ag-grid-migrate.schematic.ts | 6 ++++--
.../ag-grid/ag-grid.schematic.spec.ts | 4 ++--
.../update-10/ag-grid/ag-grid.schematic.ts | 2 +-
package-lock.json | 18 +++++++++---------
package.json | 4 ++--
10 files changed, 29 insertions(+), 22 deletions(-)
diff --git a/libs/components/ag-grid/package.json b/libs/components/ag-grid/package.json
index 68dbc14d1d..12ba59c127 100644
--- a/libs/components/ag-grid/package.json
+++ b/libs/components/ag-grid/package.json
@@ -35,8 +35,8 @@
"@skyux/lookup": "0.0.0-PLACEHOLDER",
"@skyux/popovers": "0.0.0-PLACEHOLDER",
"@skyux/theme": "0.0.0-PLACEHOLDER",
- "ag-grid-angular": "~31.2.0",
- "ag-grid-community": "~31.2.0"
+ "ag-grid-angular": "^31.3.2",
+ "ag-grid-community": "^31.3.2"
},
"dependencies": {
"@skyux/icon": "0.0.0-PLACEHOLDER",
diff --git a/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-data-manager-adapter.directive.spec.ts b/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-data-manager-adapter.directive.spec.ts
index 3826846d95..db1331a8fa 100644
--- a/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-data-manager-adapter.directive.spec.ts
+++ b/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-data-manager-adapter.directive.spec.ts
@@ -532,6 +532,7 @@ it('should move the horizontal scroll based on enableTopScroll check', async ()
'ag-floating-top',
'ag-body',
'ag-sticky-top',
+ 'ag-sticky-bottom',
'ag-floating-bottom',
'ag-overlay',
]);
diff --git a/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-wrapper.component.spec.ts b/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-wrapper.component.spec.ts
index 195809fdf0..db6962f4c6 100644
--- a/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-wrapper.component.spec.ts
+++ b/libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-wrapper.component.spec.ts
@@ -509,6 +509,7 @@ describe('SkyAgGridWrapperComponent via fixture', () => {
'ag-floating-top',
'ag-body',
'ag-sticky-top',
+ 'ag-sticky-bottom',
'ag-floating-bottom',
'ag-overlay',
]);
@@ -530,6 +531,7 @@ describe('SkyAgGridWrapperComponent via fixture', () => {
'ag-floating-top',
'ag-body',
'ag-sticky-top',
+ 'ag-sticky-bottom',
'ag-floating-bottom',
'ag-body-horizontal-scroll',
'ag-overlay',
@@ -547,6 +549,7 @@ describe('SkyAgGridWrapperComponent via fixture', () => {
'ag-floating-top',
'ag-body',
'ag-sticky-top',
+ 'ag-sticky-bottom',
'ag-floating-bottom',
'ag-overlay',
]);
@@ -562,6 +565,7 @@ describe('SkyAgGridWrapperComponent via fixture', () => {
'ag-floating-top',
'ag-body',
'ag-sticky-top',
+ 'ag-sticky-bottom',
'ag-floating-bottom',
'ag-body-horizontal-scroll',
'ag-overlay',
diff --git a/libs/components/packages/package.json b/libs/components/packages/package.json
index 71a2fef96b..674ffcb645 100644
--- a/libs/components/packages/package.json
+++ b/libs/components/packages/package.json
@@ -85,9 +85,9 @@
"@skyux/tiles": "0.0.0-PLACEHOLDER",
"@skyux/toast": "0.0.0-PLACEHOLDER",
"@skyux/validation": "0.0.0-PLACEHOLDER",
- "ag-grid-angular": "~31.2.0",
- "ag-grid-community": "~31.2.0",
- "ag-grid-enterprise": "~31.2.0",
+ "ag-grid-angular": "^31.3.2",
+ "ag-grid-community": "^31.3.2",
+ "ag-grid-enterprise": "^31.3.2",
"autonumeric": "^4.10.5"
}
},
diff --git a/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.spec.ts b/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.spec.ts
index 4048064899..60c24c502d 100644
--- a/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.spec.ts
+++ b/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.spec.ts
@@ -19,7 +19,7 @@ interface TestSetup {
schematic: (options: Schema) => Rule;
}
-const UPDATE_TO_VERSION = '31.2.0';
+const UPDATE_TO_VERSION = '31.3.2';
describe('ag-grid-migrate.schematic', () => {
async function setupTest(): Promise {
diff --git a/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.ts b/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.ts
index 08cc962589..4b7a435091 100644
--- a/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.ts
+++ b/libs/components/packages/src/schematics/ag-grid-migrate/ag-grid-migrate.schematic.ts
@@ -5,7 +5,7 @@ import { platform } from 'os';
import { Schema } from './schema';
-const AG_GRID_MIGRATIONS = ['31.0.0', '31.1.0', '31.2.0'];
+const AG_GRID_MIGRATIONS = ['31.0.0', '31.1.0', '31.2.1', '31.3.2'];
const AG_GRID_VERSION = AG_GRID_MIGRATIONS.slice().pop();
export default function (options: Schema): Rule {
@@ -42,10 +42,12 @@ export default function (options: Schema): Rule {
},
);
for (const migration of AG_GRID_MIGRATIONS) {
+ const patchVersionZero =
+ migration.split('.').slice(0, 2).join('.') + '.0';
const cmdArgs = [
'node_modules/@ag-grid-community/cli/index.cjs',
'migrate',
- `--to=${migration}`,
+ `--to=${patchVersionZero}`,
'--allow-dirty',
...agGridFiles,
];
diff --git a/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.spec.ts b/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.spec.ts
index a9e0d2d3e8..19c5cf4a1c 100644
--- a/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.spec.ts
+++ b/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.spec.ts
@@ -5,7 +5,7 @@ import fs from 'fs-extra';
import { joinPathFragments } from 'nx/src/utils/path';
import { workspaceRoot } from 'nx/src/utils/workspace-root';
-const UPDATE_TO_VERSION = '31.2.0';
+const UPDATE_TO_VERSION = '31.3.2';
describe('ag-grid.schematic', () => {
const runner = new SchematicTestRunner(
@@ -70,7 +70,7 @@ describe('ag-grid.schematic', () => {
await runner.runSchematic('ag-grid', {}, tree);
expect(JSON.parse(tree.readText('/package.json'))).toEqual({
dependencies: {
- 'ag-grid-community': `~${UPDATE_TO_VERSION}`,
+ 'ag-grid-community': `^${UPDATE_TO_VERSION}`,
'ag-grid-angular': UPDATE_TO_VERSION,
},
});
diff --git a/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.ts b/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.ts
index 94df8665f8..42c11db28a 100644
--- a/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.ts
+++ b/libs/components/packages/src/schematics/migrations/update-10/ag-grid/ag-grid.schematic.ts
@@ -19,7 +19,7 @@ const AG_GRID_ENT = 'ag-grid-enterprise';
const AG_GRID_NG = 'ag-grid-angular';
const AG_GRID_SKY = '@skyux/ag-grid';
-const AG_GRID_VERSION = '~31.2.0';
+const AG_GRID_VERSION = '^31.3.2';
/**
* Check package.json for AG Grid dependencies.
diff --git a/package-lock.json b/package-lock.json
index 99cc66a7bb..55575a07bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,8 +23,8 @@
"@blackbaud/skyux-design-tokens": "0.0.28",
"@nx/angular": "19.3.1",
"@skyux/icons": "7.3.0",
- "ag-grid-angular": "31.2.0",
- "ag-grid-community": "31.2.0",
+ "ag-grid-angular": "31.3.2",
+ "ag-grid-community": "31.3.2",
"autonumeric": "4.10.5",
"axe-core": "4.9.0",
"comment-json": "4.2.3",
@@ -12642,22 +12642,22 @@
}
},
"node_modules/ag-grid-angular": {
- "version": "31.2.0",
- "resolved": "https://registry.npmjs.org/ag-grid-angular/-/ag-grid-angular-31.2.0.tgz",
- "integrity": "sha512-dVRB9bQzbt3LBKQgHDjJ4NG9s8b1kXh/h0iS0jcrjgPzCLg4GTHomd5FLvbDWBiKOwCpurT4dy/O5+NKtGrGdg==",
+ "version": "31.3.2",
+ "resolved": "https://registry.npmjs.org/ag-grid-angular/-/ag-grid-angular-31.3.2.tgz",
+ "integrity": "sha512-k8nMrOGcoZeTNY7lXoPIWgn2ANE+v169v9X6XqddlJgYWoRrommG1Av2zSxPYUqayLzV0z/mKTHv7UjdLtnCGw==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">= 14.0.0",
"@angular/core": ">= 14.0.0",
- "ag-grid-community": "31.2.0"
+ "ag-grid-community": "31.3.2"
}
},
"node_modules/ag-grid-community": {
- "version": "31.2.0",
- "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.2.0.tgz",
- "integrity": "sha512-Ija6X171Iq3mFZASZlriQIIdEFqA71rZIsjQD6KHy5lMmxnoseZTX2neThBav1gvr6SA6n5B2PD6eUHdZnrUfw=="
+ "version": "31.3.2",
+ "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.3.2.tgz",
+ "integrity": "sha512-GxqFRD0OcjaVRE1gwLgoP0oERNPH8Lk8wKJ1txulsxysEQ5dZWHhiIoXXSiHjvOCVMkK/F5qzY6HNrn6VeDMTQ=="
},
"node_modules/agent-base": {
"version": "7.1.1",
diff --git a/package.json b/package.json
index fc4f7b5415..0cf7e8da44 100644
--- a/package.json
+++ b/package.json
@@ -101,8 +101,8 @@
"@blackbaud/skyux-design-tokens": "0.0.28",
"@nx/angular": "19.3.1",
"@skyux/icons": "7.3.0",
- "ag-grid-angular": "31.2.0",
- "ag-grid-community": "31.2.0",
+ "ag-grid-angular": "31.3.2",
+ "ag-grid-community": "31.3.2",
"autonumeric": "4.10.5",
"axe-core": "4.9.0",
"comment-json": "4.2.3",
From e08d88366c391455df53922ddc1719a6c66d0021 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Wed, 10 Jul 2024 14:27:27 -0400
Subject: [PATCH 20/95] revert(components/help-inline): stop propagation of
click events (#2458)
---
.../help-inline/help-inline.component.html | 2 +-
.../help-inline/help-inline.component.spec.ts | 19 -------------------
.../help-inline/help-inline.component.ts | 4 +---
3 files changed, 2 insertions(+), 23 deletions(-)
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.html b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.html
index 804d675182..023941d65c 100644
--- a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.html
+++ b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.html
@@ -24,7 +24,7 @@
'sky-help-inline-hidden': helpKey && !helpSvc && !popoverContent
}"
[skyPopover]="popoverContent ? popoverRef : undefined"
- (click)="onClick($event)"
+ (click)="onClick()"
>
{
ariaHaspopup: null,
});
});
-
- it('should suppress click events', () => {
- const clickSpy = spyOn(component, 'onClick');
-
- const actionClickSpy = spyOn(
- component,
- 'onActionClick',
- ).and.callThrough();
-
- fixture.detectChanges();
-
- const helpButton = getHelpButton(fixture);
- helpButton.click();
-
- fixture.detectChanges();
-
- expect(clickSpy).not.toHaveBeenCalled();
- expect(actionClickSpy).toHaveBeenCalled();
- });
});
describe('with global options', () => {
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
index 9a71b5b7ce..41b1efe6e7 100644
--- a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
+++ b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
@@ -124,9 +124,7 @@ export class SkyHelpInlineComponent {
readonly #idSvc = inject(SkyIdService);
- protected onClick(evt: Event): void {
- evt.stopPropagation();
-
+ protected onClick(): void {
this.actionClick.emit();
if (this.helpKey) {
From 1c383b1ed27e93695dd957621ac094ee68244c10 Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Wed, 10 Jul 2024 16:39:06 -0400
Subject: [PATCH 21/95] ci: fix cherry-pick title, one line (#2461)
---
.github/workflows/cherry-pick.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml
index 7f45f66797..fb1540a105 100644
--- a/.github/workflows/cherry-pick.yml
+++ b/.github/workflows/cherry-pick.yml
@@ -73,7 +73,7 @@ jobs:
exit 0
fi
- echo "COMMIT_MESSAGE=$(git log -1 --pretty=%B)" >> $GITHUB_ENV
+ echo "COMMIT_MESSAGE='$(git log -1 --pretty=%s | sed -e "s/'/\\\\'/g")'" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
From ec07802786d6ecff7519cf5c2046d7ab375997b6 Mon Sep 17 00:00:00 2001
From: Paul Crowder
Date: Thu, 11 Jul 2024 09:43:47 -0400
Subject: [PATCH 22/95] chore(components/help-inline): split help-inline styles
(#2464)
---
.../help-inline/help-inline.component.html | 2 +-
.../help-inline/help-inline.component.scss | 56 -------------------
.../help-inline/help-inline.component.ts | 5 +-
.../help-inline.default.component.scss | 25 +++++++++
.../help-inline.modern.component.scss | 49 ++++++++++++++++
.../src/lib/styles/_public-api/_mixins.scss | 27 ++++-----
6 files changed, 90 insertions(+), 74 deletions(-)
delete mode 100644 libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.scss
create mode 100644 libs/components/help-inline/src/lib/modules/help-inline/help-inline.default.component.scss
create mode 100644 libs/components/help-inline/src/lib/modules/help-inline/help-inline.modern.component.scss
diff --git a/apps/playground/src/app/components/help-inline/help-inline.component.html b/apps/playground/src/app/components/help-inline/help-inline.component.html
index 19172e39d8..f69c8de6e4 100644
--- a/apps/playground/src/app/components/help-inline/help-inline.component.html
+++ b/apps/playground/src/app/components/help-inline/help-inline.component.html
@@ -3,7 +3,7 @@
Giving
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.scss b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.scss
deleted file mode 100644
index f88ac2be3a..0000000000
--- a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
-@use 'libs/components/theme/src/lib/styles/variables' as *;
-@use 'libs/components/theme/src/lib/styles/_public-api/themes/modern/_compat/mixins'
- as modernMixins;
-
-.sky-help-inline {
- color: $sky-background-color-primary-dark;
- font-size: $sky-font-size-base;
- background-color: transparent;
- border: none;
- display: inline-block;
-
- &:hover {
- color: darken($sky-background-color-primary-dark, 10%);
- transition: color $sky-transition-time-short;
- cursor: pointer;
- }
-
- &-hidden {
- display: none;
- }
-}
-
-::ng-deep .sky-help-inline-popover-text {
- overflow-wrap: break-word;
- margin: 0;
-}
-
-@include mixins.sky-theme-modern {
- sky-icon-stack {
- display: inline-flex;
- }
-
- .sky-help-inline {
- // The button mixin sets the border radius to 6px but we only want 3px on help inline buttons.
- border-radius: 3px;
-
- ::ng-deep .fa-stack-2x {
- font-size: 16px;
- }
-
- ::ng-deep .fa-stack-1x {
- font-size: 10px;
- }
-
- // The 0px and 5px padding is because we want 1px top/bottom and 6px left/right but the mixin
- // adds a pixel to account for standard button drop shadows that do not exist on this button.
- @include modernMixins.sky-theme-modern-button-variant(
- $sky-theme-modern-background-color-primary-dark,
- transparent,
- transparent,
- $sky-theme-modern-background-color-primary-dark,
- 0px 5px
- );
- }
-}
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
index 41b1efe6e7..16b0c1e662 100644
--- a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
+++ b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.component.ts
@@ -25,7 +25,10 @@ import { SkyHelpInlineAriaHaspopupPipe } from './help-inline-aria-haspopup.pipe'
selector: 'sky-help-inline',
standalone: true,
templateUrl: './help-inline.component.html',
- styleUrls: ['./help-inline.component.scss'],
+ styleUrls: [
+ './help-inline.default.component.scss',
+ './help-inline.modern.component.scss',
+ ],
imports: [
CommonModule,
SkyHelpInlineAriaControlsPipe,
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.default.component.scss b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.default.component.scss
new file mode 100644
index 0000000000..53c95314f4
--- /dev/null
+++ b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.default.component.scss
@@ -0,0 +1,25 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+
+@include mixins.sky-component('default', '.sky-help-inline') {
+ color: $sky-background-color-primary-dark;
+ font-size: $sky-font-size-base;
+ background-color: transparent;
+ border: none;
+ display: inline-block;
+
+ &:hover {
+ color: darken($sky-background-color-primary-dark, 10%);
+ transition: color $sky-transition-time-short;
+ cursor: pointer;
+ }
+}
+
+@include mixins.sky-component('default', '.sky-help-inline-hidden') {
+ display: none;
+}
+
+@include mixins.sky-component('default', '.sky-help-inline-popover-text') {
+ overflow-wrap: break-word;
+ margin: 0;
+}
diff --git a/libs/components/help-inline/src/lib/modules/help-inline/help-inline.modern.component.scss b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.modern.component.scss
new file mode 100644
index 0000000000..613f4b3804
--- /dev/null
+++ b/libs/components/help-inline/src/lib/modules/help-inline/help-inline.modern.component.scss
@@ -0,0 +1,49 @@
+@use 'libs/components/theme/src/lib/styles/mixins' as mixins;
+@use 'libs/components/theme/src/lib/styles/variables' as *;
+@use 'libs/components/theme/src/lib/styles/_public-api/themes/modern/_compat/mixins'
+ as modernMixins;
+
+@include mixins.sky-component('modern', '.sky-help-inline') {
+ font-size: $sky-font-size-base;
+ display: inline-block;
+ border-radius: 3px;
+
+ // The 0px and 5px padding is because we want 1px top/bottom and 6px left/right but the mixin
+ // adds a pixel to account for standard button drop shadows that do not exist on this button.
+ @include modernMixins.sky-theme-modern-button-variant(
+ $sky-theme-modern-background-color-primary-dark,
+ transparent,
+ transparent,
+ $sky-theme-modern-background-color-primary-dark,
+ 0px 5px
+ );
+
+ &:hover {
+ color: darken($sky-background-color-primary-dark, 10%);
+ transition: color $sky-transition-time-short;
+ cursor: pointer;
+ }
+
+ ::ng-deep {
+ .fa-stack-2x {
+ font-size: 16px;
+ }
+
+ .fa-stack-1x {
+ font-size: 10px;
+ }
+ }
+}
+
+@include mixins.sky-component('modern', '.sky-help-inline-hidden') {
+ display: none;
+}
+
+@include mixins.sky-component('modern', '.sky-help-inline-popover-text') {
+ overflow-wrap: break-word;
+ margin: 0;
+}
+
+@include mixins.sky-component('modern', 'sky-icon-stack') {
+ display: inline-flex;
+}
diff --git a/libs/components/theme/src/lib/styles/_public-api/_mixins.scss b/libs/components/theme/src/lib/styles/_public-api/_mixins.scss
index 1835acffb1..958ce5edca 100644
--- a/libs/components/theme/src/lib/styles/_public-api/_mixins.scss
+++ b/libs/components/theme/src/lib/styles/_public-api/_mixins.scss
@@ -102,49 +102,44 @@
@mixin sky-component($theme, $selector, $encapsulate: true, $breakpoint: '') {
@if $breakpoint == '' {
- @include sky-component-theme($theme, $selector, $encapsulate) {
+ @include sky-component-theme($theme, $selector) {
@content;
}
} @else if $breakpoint == 'xs' {
@include sky-host-responsive-container-xs-min($encapsulate) {
- @include sky-component-theme($theme, $selector, $encapsulate) {
+ @include sky-component-theme($theme, $selector) {
@content;
}
}
} @else if $breakpoint == 'sm' {
@include sky-host-responsive-container-sm-min($encapsulate) {
- @include sky-component-theme($theme, $selector, $encapsulate) {
+ @include sky-component-theme($theme, $selector) {
@content;
}
}
} @else if $breakpoint == 'md' {
@include sky-host-responsive-container-md-min($encapsulate) {
- @include sky-component-theme($theme, $selector, $encapsulate) {
+ @include sky-component-theme($theme, $selector) {
@content;
}
}
} @else if $breakpoint == 'lg' {
@include sky-host-responsive-container-lg-min($encapsulate) {
- @include sky-component-theme($theme, $selector, $encapsulate) {
+ @include sky-component-theme($theme, $selector) {
@content;
}
}
}
}
-@mixin sky-component-theme($theme, $selector, $encapsulate: true) {
- @if $theme == 'default' {
- #{$selector}:not(.sky-theme-modern *) {
+
+@mixin sky-component-theme($theme, $selector) {
+ @if $theme == 'modern' {
+ :is(.sky-theme-modern #{$selector}) {
@content;
}
} @else {
- @if $encapsulate {
- :host-context(.sky-theme-modern) #{$selector} {
- @content;
- }
- } @else {
- .sky-theme-modern #{$selector} {
- @content;
- }
+ #{$selector}:not(.sky-theme-modern *) {
+ @content;
}
}
}
From bbfb330a8bed125063098ffde6c99c0f1a3deea6 Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Thu, 11 Jul 2024 10:47:33 -0400
Subject: [PATCH 23/95] feat(components/datetime): add support for additional
date formats to `SkyDatePipe` (#2447)
---
.../modules/date-pipe/date.service.spec.ts | 30 +++++++++++++++----
.../src/lib/modules/date-pipe/date.service.ts | 28 +++++++++++++----
2 files changed, 47 insertions(+), 11 deletions(-)
diff --git a/libs/components/datetime/src/lib/modules/date-pipe/date.service.spec.ts b/libs/components/datetime/src/lib/modules/date-pipe/date.service.spec.ts
index 50a79181ee..0d43cfcf3f 100644
--- a/libs/components/datetime/src/lib/modules/date-pipe/date.service.spec.ts
+++ b/libs/components/datetime/src/lib/modules/date-pipe/date.service.spec.ts
@@ -114,12 +114,30 @@ describe('Date service', () => {
});
it('should support Angular DatePipe formats', () => {
- const value = service.format(new Date(2000, 0, 1), undefined, 'fullDate');
- const expectedValues = [
- 'Saturday, January 1, 2000',
- 'Saturday, January 01, 2000', // IE 11
- ];
- expect(expectedValues).toContain(value);
+ const date = new Date();
+ const formatSpy = spyOn(SkyIntlDateFormatter, 'format');
+
+ /* spell-checker:disable */
+ const formats = new Map([
+ ['short', 'yMdjm'],
+ ['medium', 'yMMMdjms'],
+ ['long', 'MMMM d, y, h:mm:ss a Z'],
+ ['full', 'EEEE, MMMM d, y, h:mm:ss a z'],
+ ['shortDate', 'yMd'],
+ ['mediumDate', 'yMMMd'],
+ ['longDate', 'yMMMMd'],
+ ['fullDate', 'yMMMMEEEEd'],
+ ['shortTime', 'jm'],
+ ['mediumTime', 'jms'],
+ ['longTime', 'h:mm:ss a Z'],
+ ['fullTime', 'h:mm:ss a z'],
+ ]);
+ /* spell-checker:enable */
+
+ for (const [formatName, formatExpression] of formats) {
+ service.format(date, undefined, formatName);
+ expect(formatSpy).toHaveBeenCalledWith(date, 'en-US', formatExpression);
+ }
});
it('should default to mediumDate format', () => {
diff --git a/libs/components/datetime/src/lib/modules/date-pipe/date.service.ts b/libs/components/datetime/src/lib/modules/date-pipe/date.service.ts
index 4fe20a80c1..1f8fc913b6 100644
--- a/libs/components/datetime/src/lib/modules/date-pipe/date.service.ts
+++ b/libs/components/datetime/src/lib/modules/date-pipe/date.service.ts
@@ -21,15 +21,33 @@ import { takeUntil } from 'rxjs/operators';
})
export class SkyDateService implements OnDestroy {
/* spell-checker:disable */
+ // See: https://github.com/angular/angular/blob/17.3.x/packages/common/src/pipes/date_pipe.ts
+ // See: https://www.ibm.com/docs/en/app-connect/11.0.0?topic=function-formatting-parsing-datetimes-as-strings
#ALIASES: Record = {
- medium: 'yMMMdjms',
+ // TODO: replace with 'M/d/yy, h:mm a' in a breaking change.
short: 'yMdjm',
- fullDate: 'yMMMMEEEEd',
- longDate: 'yMMMMd',
- mediumDate: 'yMMMd',
+ // TODO: replace with 'MMM d, y, h:mm:ss a' in a breaking change.
+ medium: 'yMMMdjms',
+ // TODO: This format was modified from 'MMMM d, y, h:mm:ss a z' due to the limitations of the Intl API.
+ long: 'MMMM d, y, h:mm:ss a Z',
+ // TODO: This format was modified from 'EEEE, MMMM d, y, h:mm:ss a zzzz' due to the limitations of the Intl API.
+ full: 'EEEE, MMMM d, y, h:mm:ss a z',
+ // TODO: Replace with 'M/d/yy' in a breaking change.
shortDate: 'yMd',
- mediumTime: 'jms',
+ // TODO: Replace with 'MMM d, y' in a breaking change.
+ mediumDate: 'yMMMd',
+ // TODO: Replace with 'MMMM d, y' in a breaking change.
+ longDate: 'yMMMMd',
+ // TODO: Replace with 'EEEE, MMMM d, y' in a breaking change.
+ fullDate: 'yMMMMEEEEd',
+ // TODO: Replace with 'h:mm a' in a breaking change.
shortTime: 'jm',
+ // TODO: Replace with 'h:mm:ss a' in a breaking change.
+ mediumTime: 'jms',
+ // TODO: This format was modified from 'h:mm:ss a z' due to the limitations of the Intl API.
+ longTime: 'h:mm:ss a Z',
+ // TODO: This format was modified from 'h:mm:ss a zzzz' due to the limitations of the Intl API.
+ fullTime: 'h:mm:ss a z',
};
/* spell-checker:enable */
From a715c8a4981267ffc5b8909b225916c6a1cb6977 Mon Sep 17 00:00:00 2001
From: John White <750350+johnhwhite@users.noreply.github.com>
Date: Thu, 11 Jul 2024 13:04:20 -0400
Subject: [PATCH 24/95] ci: fix cherry-pick title (#2470)
---
.github/workflows/cherry-pick.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml
index fb1540a105..ebe0c638c7 100644
--- a/.github/workflows/cherry-pick.yml
+++ b/.github/workflows/cherry-pick.yml
@@ -38,6 +38,7 @@ jobs:
run: npm ci
- name: Cherry pick
+ id: cherry-pick
run: |
# Set the git user to the author of the merge commit.
git config user.name "$(git log -1 --pretty=format:'%an' ${{ github.event.pull_request.merge_commit_sha }})"
@@ -73,7 +74,7 @@ jobs:
exit 0
fi
- echo "COMMIT_MESSAGE='$(git log -1 --pretty=%s | sed -e "s/'/\\\\'/g")'" >> $GITHUB_ENV
+ echo "commit_message=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
@@ -83,6 +84,7 @@ jobs:
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
script: |
const pr = context.payload.pull_request;
+ const title = ${{ toJson(steps.cherry-pick.outputs.commit_message) }};
let body = `:cherries: Cherry picked from #${pr.number} [${pr.title}](${pr.html_url})`
const prAzureBoardLink = pr.body?.match(/(?<=\[)AB#\d+(?=])/g);
if (prAzureBoardLink) {
@@ -93,7 +95,7 @@ jobs:
repo: context.repo.repo,
head: process.env.CHERRY_PICK_BRANCH,
base: process.env.TARGET_BRANCH,
- title: process.env.COMMIT_MESSAGE,
+ title,
body
}).then(result => {
console.log(`Created PR #${result.data.number}: ${result.data.html_url}`);
From 8a173ce0fbe5660806f17ede06323f590f944c1c Mon Sep 17 00:00:00 2001
From: Steve Brush
Date: Thu, 11 Jul 2024 13:33:00 -0400
Subject: [PATCH 25/95] fix(components/tiles): do not expand/collapse content
when help inline clicked (#2468)
---
.../modules/tiles/tile/tile.component.html | 2 +-
.../modules/tiles/tile/tile.component.spec.ts | 19 ++++++++++++++++++-
.../lib/modules/tiles/tile/tile.component.ts | 9 +++++++--
3 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/libs/components/tiles/src/lib/modules/tiles/tile/tile.component.html b/libs/components/tiles/src/lib/modules/tiles/tile/tile.component.html
index 9999043cda..61279e52b8 100644
--- a/libs/components/tiles/src/lib/modules/tiles/tile/tile.component.html
+++ b/libs/components/tiles/src/lib/modules/tiles/tile/tile.component.html
@@ -8,7 +8,7 @@
}"
>