diff --git a/src/app/alert-form/alert-form.component.scss b/src/app/alert-form/alert-form.component.scss
index fb1d601..9a1b727 100644
--- a/src/app/alert-form/alert-form.component.scss
+++ b/src/app/alert-form/alert-form.component.scss
@@ -1,72 +1,3 @@
-.form-field-group {
- display: flex;
- gap: 1rem;
- flex-wrap: wrap;
-}
-.form-field {
- display: flex;
- flex-direction: column;
- margin-bottom: 1rem;
- flex-grow: 1;
-}
-
-input,
-textarea,
-select,
-ul.list-box {
- background: color-mix(in srgb, var(--pill-accent) 10%, transparent);
- font-size: 1rem;
- padding: 0.75rem 0.5rem;
- border: 2px solid color-mix(in srgb, var(--electric-violet) 30%, transparent);
- border-radius: 0.5rem;
- font-family: var(--font-family);
-
- &::placeholder {
- color: var(--grayr-400);
- }
-}
-
-input,
-textarea,
-select,
-button, ul.list-box li {
- &:focus-within {
- outline-width: 3px;
- outline-style: solid;
- outline-color: lightblue;
- outline-offset: 2px;
- }
-}
-
-label:has(+ input, + textarea, + select, +ul.list-box) {
- color: var(--electric-violet);
- font-weight: bold;
- font-size: 0.875rem;
- margin-bottom: 0.2rem;
- margin-left: 0.5rem;
-}
-
-label:has(+ input[required])::after,
-label:has(+ textarea[required])::after,
-label:has(+ select[required])::after {
- content: " *";
-}
-label:has(+ input[required][aria-invalid="true"])::after,
-label:has(+ textarea[required][aria-invalid="true"])::after,
-label:has(+ select[required][aria-invalid="true"])::after {
- color: var(--hot-red);
-}
-
-p.description {
- color: var(--gray-900);
- font-size: 0.75rem;
- margin-top: 0.2rem;
- margin-left: 0.5rem;
-}
-input[aria-invalid="true"] + p.description {
- color: #ac0000;
-}
-
ul.list-box {
display: flex;
padding: 0;
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 18e3b12..69114b9 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -12,6 +12,11 @@ export const routes: Routes = [
loadComponent: () => import('./deferred-loading-view/deferred-loading-view.component').then(m => m.DeferredLoadingViewComponent),
title: 'Deferred Loading'
},
+ {
+ path: 'track',
+ loadComponent: () => import('./track-view/track-view.component').then(m => m.TrackViewComponent),
+ title: 'Track List Items'
+ },
{
path: 'alerts',
loadComponent: () => import('./alerts-view/alerts-view.component').then(m => m.AlertsViewComponent),
diff --git a/src/app/nav/nav.component.html b/src/app/nav/nav.component.html
index b0b474b..4358cdf 100644
--- a/src/app/nav/nav.component.html
+++ b/src/app/nav/nav.component.html
@@ -16,6 +16,14 @@
>
Deferred Loading
+
+ Track List Items
+
Track Demo
+
+
+ The example below shows the difference of tracking list options with
+ $index
and an actual unique attribute. By tracking a unique
+ attribute, the object wont be completely new initialized. In fact, when the
+ array changes, the focus of elements will be restored/tracked correctly.
+
+
+ Please enter a text in one of the input fields on the left side. After a few
+ seconds, items will be added to the array of the list. Keep an eye on where
+ you entered your text. While entering and concurrently adding new fields, your
+ input moves into another field as only the $index
is tracked. The
+ list on the right tracks a unique attribute (item.id
). This
+ ensures, the focus does not accidentally move to another input event when the
+ list updates concurrently.
+
+
+ Every 5 seconds a new entry is inserted.
+
+
+
diff --git a/src/app/track-view/track-view.component.scss b/src/app/track-view/track-view.component.scss
new file mode 100644
index 0000000..2eb2dd4
--- /dev/null
+++ b/src/app/track-view/track-view.component.scss
@@ -0,0 +1,25 @@
+form {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+fieldset {
+ display: flex;
+ justify-content: space-between;
+ gap: .5rem;
+ flex-direction: column;
+
+ legend {
+ font-weight: bold;
+ }
+}
+
+p {
+ margin-bottom: .5rem;
+}
+
+code {
+ color: var(--bright-blue);
+}
diff --git a/src/app/track-view/track-view.component.spec.ts b/src/app/track-view/track-view.component.spec.ts
new file mode 100644
index 0000000..798af73
--- /dev/null
+++ b/src/app/track-view/track-view.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TrackViewComponent } from './track-view.component';
+
+describe('TrackViewComponent', () => {
+ let component: TrackViewComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TrackViewComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TrackViewComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/track-view/track-view.component.ts b/src/app/track-view/track-view.component.ts
new file mode 100644
index 0000000..282bba7
--- /dev/null
+++ b/src/app/track-view/track-view.component.ts
@@ -0,0 +1,51 @@
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+interface Item {
+ id: number;
+ name: string;
+}
+
+const fullNameItems: Item[] = [
+ { id: 3, name: 'Item #3' },
+ { id: 2, name: 'Item #2' },
+ { id: 1, name: 'Item #1' },
+];
+
+@Component({
+ selector: 'app-track-view',
+ standalone: true,
+ imports: [FormsModule],
+ templateUrl: './track-view.component.html',
+ styleUrl: './track-view.component.scss'
+})
+export class TrackViewComponent {
+ values = ['', '', '']; // Werte der Eingabefelder (initial)
+ items: Item[]; // Array mit Werten für *ngFor
+ toggle: boolean;
+
+ constructor() {
+ this.items = fullNameItems;
+ this.toggle = false;
+ setInterval(() => {
+ if (this.items.length >= 50) {
+ this.items = [];
+ }
+ this.addItem();
+ }, 5000);
+ }
+
+ addItem() {
+ const num = this.items.length + 1;
+ this.toggle = !this.toggle;
+ this.items = [
+ { id: num, name: `Item #${num}` },
+
+ // with the following line will make the third column work as referecnes of the objects are kept
+ // ...this.items,
+
+ // with the follwoing line will make the third column not working as the references are not the same anymore (item === item => false)
+ ...structuredClone(this.items),
+ ];
+ }
+}
diff --git a/src/styles.scss b/src/styles.scss
index fe5d8ef..468e757 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -95,6 +95,75 @@ main {
}
}
+.form-field-group {
+ display: flex;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+.form-field {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 1rem;
+ flex-grow: 1;
+}
+
+input,
+textarea,
+select,
+ul.list-box {
+ background: color-mix(in srgb, var(--pill-accent) 10%, transparent);
+ font-size: 1rem;
+ padding: 0.75rem 0.5rem;
+ border: 2px solid color-mix(in srgb, var(--electric-violet) 30%, transparent);
+ border-radius: 0.5rem;
+ font-family: var(--font-family);
+
+ &::placeholder {
+ color: var(--grayr-400);
+ }
+}
+
+input,
+textarea,
+select,
+button, ul.list-box li {
+ &:focus-within {
+ outline-width: 3px;
+ outline-style: solid;
+ outline-color: lightblue;
+ outline-offset: 2px;
+ }
+}
+
+label:has(+ input, + textarea, + select, +ul.list-box) {
+ color: var(--electric-violet);
+ font-weight: bold;
+ font-size: 0.875rem;
+ margin-bottom: 0.2rem;
+ margin-left: 0.5rem;
+}
+
+label:has(+ input[required])::after,
+label:has(+ textarea[required])::after,
+label:has(+ select[required])::after {
+ content: " *";
+}
+label:has(+ input[required][aria-invalid="true"])::after,
+label:has(+ textarea[required][aria-invalid="true"])::after,
+label:has(+ select[required][aria-invalid="true"])::after {
+ color: var(--hot-red);
+}
+
+p.description {
+ color: var(--gray-900);
+ font-size: 0.75rem;
+ margin-top: 0.2rem;
+ margin-left: 0.5rem;
+}
+input[aria-invalid="true"] + p.description {
+ color: #ac0000;
+}
+
.alerts-overlay {
right: 0;