@@ -2,6 +2,8 @@ import { action } from '@ember/object';
22import Service from '@ember/service' ;
33import { tracked } from '@glimmer/tracking' ;
44
5+ import { restartableTask , waitForEvent } from 'ember-concurrency' ;
6+
57import * as localStorage from '../utils/local-storage' ;
68
79const DEFAULT_SCHEME = 'light' ;
@@ -10,6 +12,16 @@ const LS_KEY = 'color-scheme';
1012
1113export default class DesignService extends Service {
1214 @tracked _scheme = localStorage . getItem ( LS_KEY ) ;
15+ @tracked resolvedScheme ;
16+
17+ constructor ( ) {
18+ super ( ...arguments ) ;
19+ this . restartWatcherTask ( ) ;
20+ }
21+
22+ get isDark ( ) {
23+ return this . resolvedScheme === 'dark' ;
24+ }
1325
1426 get scheme ( ) {
1527 return VALID_SCHEMES . has ( this . _scheme ) ? this . _scheme : DEFAULT_SCHEME ;
@@ -18,5 +30,32 @@ export default class DesignService extends Service {
1830 @action set ( scheme ) {
1931 this . _scheme = scheme ;
2032 localStorage . setItem ( LS_KEY , scheme ) ;
33+ this . restartWatcherTask ( ) ;
2134 }
35+
36+ restartWatcherTask ( ) {
37+ this . watcherTask . perform ( ) . catch ( ( ) => {
38+ // Ignore Promise rejections. This shouldn't be able to fail, and task cancellations are expected.
39+ } ) ;
40+ }
41+
42+ /**
43+ * This task watches for changes in the system color scheme and updates the `resolvedScheme` property accordingly.
44+ */
45+ watcherTask = restartableTask ( async ( ) => {
46+ let mediaQueryList = window . matchMedia ( '(prefers-color-scheme: dark)' ) ;
47+ // eslint-disable-next-line no-constant-condition
48+ while ( true ) {
49+ let scheme = this . scheme ;
50+ if ( scheme === 'system' ) {
51+ scheme = mediaQueryList . matches ? 'dark' : 'light' ;
52+ }
53+
54+ if ( this . resolvedScheme !== scheme ) {
55+ this . resolvedScheme = scheme ;
56+ }
57+
58+ await waitForEvent ( mediaQueryList , 'change' ) ;
59+ }
60+ } ) ;
2261}
0 commit comments