Capture UI Theme change events

I am trying to build a UI for a panel using spectrum-web-components. If using this, the parts must be placed under the sp-theme component or they will not be displayed.

The problem is that sp-theme needs to specify the brightness as an attribute, such as light or dark.

I want to choose the brightness according to the user’s InDesign preferences, but I don’t know how to track changes in the target item app.generalPreferences.uiBrightnessPreference.

I tried monitoring MutationEvent.AFTER_ATTRIBUTE_CHANGED, but there was no response. What is the way to capture changes in uiBrightnessPreference without using IdleTask or setInterval?

Environment:

  • InDesign 2023 (18.5.0.57)
  • macOS 12.6.7

Sample code of sp-theme:

export const App = () => {
  const [brightness, setBrightness] = useState('light') ;

  return (
    <sp-theme
      theme='spectrum'
      color={brightness}
      scale='medium'
    >
      <sp-action-button>
        <sp-icon-abc slot='icon'></sp-icon-abc>
      </sp-action-button>
    </sp-theme>
  ) ;
} ;
2 Likes

As far as I can see, there doesn’t seem to be any direct event that can catch app.generalPreferences.uiBrightnessPreference changes.
As an alternative, I thought of the following: That is, the user must open the “User Interface” in the “Preferences” window to change the app.generalPreferences.uiBrightnessPreference. After changing uiBrightnessPreference, the next operation cannot be performed unless the window is closed. By observing this behavior, you can catch uiBrightnessPreference changes, if not in real time.
The following code is what I checked with ExtendScript. Since it is not UXP, $.sleep() etc. need to be rewritten.
The problem with this approach is that the afterInvoke event fires multiple times between opening and closing the window (without switching the list on the left). I don’t know why.
All but one of them, “flag = app.generalPreferences.uiBrightnessPreference;” fails. I don’t know why either.

#targetengine "getEv"
if (app.eventListeners.itemByName("ev08").isValid) {
    app.eventListeners.itemByName("ev08").remove();
}

app.menuActions.itemByID(91232).addEventListener("afterInvoke", evtCheck).name = "ev08";
function evtCheck(ev) {
    while (app.modalState) {
        $.sleep(200);
    }
    var flag = -1;
    flag = app.generalPreferences.uiBrightnessPreference;
    if (flag == -1) return;
    alert(flag);
}

I hope it’s a useful reference.

2 Likes

ありがとうございます。確かに,menuのinvokeからダイアログが閉じられるまでの間だけならポーリングも許容範囲です。

Thank you very much. Certainly, I can tolerate polling if it is only between the invoke of the menu and the dialog being closed.

1 Like

I’m interested in this, as well. But I think that Adobe should provide a good way of dealing with this out-of-the-box (in a UXP-cross-app-way) given that UXP now has “official” support for SWC.

The thing is that in cross-app plugins, the UI should be re-usable (for the most part). And with that, <sp-theme> is an integral part of the UI (which really shouldn’t be host-app-specific, although different host apps might “only” offer specific themes – like XD only offering a light theme).

Perhaps UXP could offer a <uxp-sp-theme> that handles this automatically? :thinking: (definitely not the best idea how this could be implemented, but there should, in my opinion, be some easy solution, whatever that looks like)

This was also an (unsolved) challenge for a sample I created: Add a minimal React + SWC sample by pklaschka · Pull Request #4 · AdobeDocs/uxp-indesign-samples · GitHub. It uses SWC, but won’t reflect host app theme changes until restarted.

CC @pkrishna @Sujai

2 Likes

The Theme switching process is usually needed even though it is not an essential plugin feature. You are right, it would be nice if special components or custom hooks or something could be provided that feature automatically in any app, if possible.

Thanks for the great sample plugins. It’s simple and easy to understand; the writing of package.json, webpack.config.js, and manifest.json is complex enough, so I like that the other code is minimal.

1 Like

試したところ思ったより単純なコードになりました。afterInvokeがきっちり環境設定を閉じた後実行されるためです。

次のコードのgetColorThemeを,環境設定メニューのすべてのMenuItemのafterInvokeイベントで実行することで実現できました。

The code became simpler than I expected when I tried it. Because afterInvoke is executed exactly after closing the preferences.

I was able to achieve by executing getColorTheme on the afterInvoke event of all menu items in the preferences menu.

/**
  * Returns color theme name from brightness number
  * @param {Number} brightness 0.0-1.0
  * @returns { 'darkest'|'dark'|'light'|'lightest' } 
*/
export const brightnessToColorTheme = (brightness) => {
  let res = 'light' ;

  if(brightness <= 0) {
    res = 'darkest' ;
  } else if( (0 < brightness) && (brightness <= 0.5) ) {
    res = 'dark' ;
  } else if( (0.5 < brightness) && (brightness < 1.0) ) {
    res = 'light' ;
  } else if(brightness === 1.0) {
    res = 'lightest' ;
  }
  
  return res ;
} ;

/**
  * Get uiBrightnessPreference and return color theme name 
  * @return { 'darkest'|'dark'|'light'|'lightest' } 
*/
export const getColorTheme = () => {
  const res = brightnessToColorTheme(app.generalPreferences.uiBrightnessPreference) ;
  return res ;
} ;
3 Likes

I have also been looking for a solution to this problem. »document« has an additional property theme:

document.theme.onUpdated.addListener((theme) => {
	console.log(theme);
	const themeElemList = document.querySelectorAll("sp-theme");
	themeElemList.forEach((elem) => {
		elem.setAttribute("color", theme);
	});
  });

Must test it in more detail but so far it seems to work.

3 Likes

Thank you @Roland_Dreger, it works fine.

Which document did you find this in? Is there any way to find out how to use it correctly?

I think this is not documented (yet). I found it via UXP Developer Tool.

Unfortunately, window.matchMedia("(prefers-color-scheme: light)")
doesn’t work either.

1 Like

That’s great. I am amazed you found your way to addListener from there.