From 3da68882eefc0944d20cf7bd63deb07ca61df0c2 Mon Sep 17 00:00:00 2001 From: xidedix Date: Thu, 8 Oct 2026 22:01:20 +0200 Subject: [PATCH] fix(ThemeDirective): colorScheme="light" kept on first render - one effect: `dark` wins, else `colorScheme` - before: two effects, `dark` effect removed the attribute set by `colorScheme` on first render; `colorScheme` unset dropped `dark` - `colorScheme` and `dark` JSDoc match the behaviour --- .../src/lib/shared/theme.directive.spec.ts | 49 +++++++++++++++++++ .../src/lib/shared/theme.directive.ts | 17 +++---- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/projects/coreui-angular/src/lib/shared/theme.directive.spec.ts b/projects/coreui-angular/src/lib/shared/theme.directive.spec.ts index eb422ebc..f0396889 100644 --- a/projects/coreui-angular/src/lib/shared/theme.directive.spec.ts +++ b/projects/coreui-angular/src/lib/shared/theme.directive.spec.ts @@ -11,6 +11,23 @@ export class TestComponent { readonly theme = input<'dark' | 'light' | undefined>(); } +@Component({ + imports: [ThemeDirective], + template: '
' +}) +export class TestLightComponent { + readonly dark = input(false); +} + +@Component({ + imports: [ThemeDirective], + template: '
' +}) +export class TestDarkComponent { + readonly dark = input(false); + readonly theme = input<'dark' | 'light' | undefined>(); +} + class MockElementRef extends ElementRef {} describe('ThemeDirective', () => { @@ -46,4 +63,36 @@ describe('ThemeDirective', () => { fixture.detectChanges(); expect(debugElement.nativeElement.getAttribute('data-coreui-theme')).toBeNull(); }); + + it('should keep a static light colorScheme on the first render', () => { + const lightFixture = TestBed.createComponent(TestLightComponent); + const element = lightFixture.debugElement.query(By.css('div')).nativeElement; + lightFixture.detectChanges(); + expect(element.getAttribute('data-coreui-theme')).toBe('light'); + }); + + it('should apply dark over colorScheme and restore colorScheme when dark is unset', () => { + const lightFixture = TestBed.createComponent(TestLightComponent); + const element = lightFixture.debugElement.query(By.css('div')).nativeElement; + lightFixture.componentRef.setInput('dark', true); + lightFixture.detectChanges(); + expect(element.getAttribute('data-coreui-theme')).toBe('dark'); + lightFixture.componentRef.setInput('dark', false); + lightFixture.detectChanges(); + expect(element.getAttribute('data-coreui-theme')).toBe('light'); + }); + + it('should keep dark when colorScheme changes or is unset', () => { + const darkFixture = TestBed.createComponent(TestDarkComponent); + const element = darkFixture.debugElement.query(By.css('div')).nativeElement; + darkFixture.componentRef.setInput('dark', true); + darkFixture.componentRef.setInput('theme', 'dark'); + darkFixture.detectChanges(); + darkFixture.componentRef.setInput('theme', 'light'); + darkFixture.detectChanges(); + expect(element.getAttribute('data-coreui-theme')).toBe('dark'); + darkFixture.componentRef.setInput('theme', undefined); + darkFixture.detectChanges(); + expect(element.getAttribute('data-coreui-theme')).toBe('dark'); + }); }); diff --git a/projects/coreui-angular/src/lib/shared/theme.directive.ts b/projects/coreui-angular/src/lib/shared/theme.directive.ts index e60eaeed..a4e9a055 100644 --- a/projects/coreui-angular/src/lib/shared/theme.directive.ts +++ b/projects/coreui-angular/src/lib/shared/theme.directive.ts @@ -1,4 +1,4 @@ -import { booleanAttribute, Directive, effect, ElementRef, inject, input, Renderer2, untracked } from '@angular/core'; +import { booleanAttribute, Directive, effect, ElementRef, inject, input, Renderer2 } from '@angular/core'; @Directive({ selector: '[cTheme]', @@ -9,26 +9,21 @@ export class ThemeDirective { readonly #renderer = inject(Renderer2); /** - * Sets the dark theme attribute on the host element. + * Sets the `data-coreui-theme` attribute on the host element. Overridden by `dark`. * @returns 'dark' | 'light' */ readonly colorScheme = input<'dark' | 'light'>(); - readonly #colorSchemeChange = effect(() => { - const colorScheme = this.colorScheme(); - colorScheme ? this.setTheme(colorScheme) : this.unsetTheme(); - }); - /** - * Sets a darker color scheme. If the colorScheme is set to 'dark', the dark theme will be applied. + * Applies the dark color scheme, regardless of `colorScheme`. * @returns boolean * @default false */ readonly dark = input(false, { transform: booleanAttribute }); - readonly #darkChange = effect(() => { - const darkTheme = this.dark() || untracked(this.colorScheme) === 'dark'; - darkTheme ? this.setTheme('dark') : this.unsetTheme(); + readonly #themeChange = effect(() => { + const theme = this.dark() ? 'dark' : this.colorScheme(); + theme ? this.setTheme(theme) : this.unsetTheme(); }); setTheme(theme?: string): void {