From 51e1c1e4f62f6d3f3247549f60f1de55fa0b07f3 Mon Sep 17 00:00:00 2001 From: 4eb0da <4eb0da@yandex-team.com> Date: Tue, 5 May 2026 14:32:41 +0300 Subject: [PATCH] Theme variable commit_hash:d0a6ea88e4d213f58f2fc9053a2204df7ff176ae --- client/web/divkit/README.md | 10 ++- client/web/divkit/src/client.ts | 1 + client/web/divkit/src/components/Root.svelte | 71 +++++++++++++------ client/web/divkit/src/server.ts | 4 +- client/web/divkit/typings/client-devtool.d.ts | 2 +- .../web/divkit/typings/client-hydratable.d.ts | 2 +- client/web/divkit/typings/client.d.ts | 2 +- client/web/divkit/typings/server.d.ts | 5 +- test_data/regression_test_data/index.json | 3 +- 9 files changed, 70 insertions(+), 30 deletions(-) diff --git a/client/web/divkit/README.md b/client/web/divkit/README.md index 89264745a..fb2908614 100644 --- a/client/web/divkit/README.md +++ b/client/web/divkit/README.md @@ -415,11 +415,17 @@ Object, optional. A provider for implementing a custom video player. On the server side, the `videoPlayerProvider` must contain the `template` property, which is a string or a markup provider function for the video player. On the client side, the `videoPlayerProvider` must contain an `instance` function that creates an instance of the player. -#### theme (DEPRECATED) +#### theme `system` | `light` | `dark` -The default value is `system`. Affects variables in `palette`. +The default value is `system`. Affects variables in `palette` and the `themeVariableName` variable. + +#### themeVariableName + +String, optional. + +When set, this property creates a variable that stores the current theme (`light` or `dark`). ### Palette support (DEPRECATED) diff --git a/client/web/divkit/src/client.ts b/client/web/divkit/src/client.ts index 3322ff5a5..0fa4a3452 100644 --- a/client/web/divkit/src/client.ts +++ b/client/web/divkit/src/client.ts @@ -37,6 +37,7 @@ export function render(opts: { typefaceProvider?: TypefaceProvider; platform?: Platform; theme?: Theme; + themeVariableName?: string; fetchInit?: FetchInit; tooltipRoot?: HTMLElement; customComponents?: Map | undefined; diff --git a/client/web/divkit/src/components/Root.svelte b/client/web/divkit/src/components/Root.svelte index 8825a9ae0..ce78cf556 100644 --- a/client/web/divkit/src/components/Root.svelte +++ b/client/web/divkit/src/components/Root.svelte @@ -102,6 +102,7 @@ export let json: Partial = {}; export let platform: Platform = 'auto'; export let theme: Theme = 'system'; + export let themeVariableName: string | undefined = undefined; export let globalVariablesController: GlobalVariablesController | undefined = undefined; export let mix = ''; export let customization: Customization = {}; @@ -137,20 +138,25 @@ let currentTheme: 'light' | 'dark' = 'light'; let themeQuery: MediaQueryList | null = null; - $: if (theme === 'light' || theme === 'dark') { - currentTheme = theme; - } else if (theme === 'system') { - if (typeof matchMedia !== 'undefined') { - if (!themeQuery) { - themeQuery = matchMedia('(prefers-color-scheme: dark)'); - themeQuery.addListener(themeQueryListener); + let themeVariable: Variable | undefined; + themeInit(); + + function themeInit(): void { + if (theme === 'light' || theme === 'dark') { + currentTheme = theme; + } else if (theme === 'system') { + if (typeof matchMedia !== 'undefined') { + if (!themeQuery) { + themeQuery = matchMedia('(prefers-color-scheme: dark)'); + themeQuery.addListener(themeQueryListener); + } + currentTheme = themeQuery.matches ? 'dark' : 'light'; + } else { + currentTheme = 'light'; } - currentTheme = themeQuery.matches ? 'dark' : 'light'; } else { - currentTheme = 'light'; + logError(wrapError(new Error('Unsupported theme'))); } - } else { - logError(wrapError(new Error('Unsupported theme'))); } $: if (currentTheme) { @@ -169,6 +175,7 @@ export function setTheme(newTheme: Theme): void { theme = newTheme; + themeInit(); } export function getDebugVariables() { @@ -1894,18 +1901,20 @@ } function updateTheme(): void { - if (!palette) { - return; + if (palette) { + const list = palette[currentTheme]; + list.forEach(item => { + const varInstance = variables.get(item.name); + + if (varInstance) { + varInstance.setValue(item.color); + } + }); } - const list = palette[currentTheme]; - list.forEach(item => { - const varInstance = variables.get(item.name); - - if (varInstance) { - varInstance.setValue(item.color); - } - }); + if (themeVariable) { + themeVariable.setValue(currentTheme); + } } function getBuiltinProtocols(): Set { @@ -2538,13 +2547,15 @@ } } - function declVariable(variable: DivVariable): void { + function declVariable(variable: DivVariable): Variable | undefined { const varInstance = constructVariable(variable); if (varInstance) { localVariables.set(variable.name, varInstance); variables.set(variable.name, varInstance); } + + return varInstance; } for (const [varName, variable] of globalVariables) { @@ -2553,6 +2564,22 @@ } } + if (themeVariableName) { + if (variables.has(themeVariableName)) { + logError(wrapError(new Error('Duplicate variable'), { + additional: { + name: themeVariableName + } + })); + } else { + themeVariable = declVariable({ + name: themeVariableName, + type: 'string', + value: currentTheme + }); + } + } + const startVariables = json?.card?.variables; if (Array.isArray(startVariables)) { startVariables.forEach(variable => { diff --git a/client/web/divkit/src/server.ts b/client/web/divkit/src/server.ts index 78edf4dd6..7877d15a1 100644 --- a/client/web/divkit/src/server.ts +++ b/client/web/divkit/src/server.ts @@ -1,5 +1,5 @@ import Root from './components/Root.svelte'; -import type { Customization, Direction, DivJson, ErrorCallback, Platform, TypefaceProvider, VideoPlayerProviderServer } from '../typings/common'; +import type { Customization, Direction, DivJson, ErrorCallback, Platform, Theme, TypefaceProvider, VideoPlayerProviderServer } from '../typings/common'; import type { GlobalVariablesController } from './expressions/globalVariablesController'; import type { CustomComponentDescription } from '../typings/custom'; import type { Store } from '../typings/store'; @@ -14,6 +14,8 @@ export function render(opts: { onError?: ErrorCallback; typefaceProvider?: TypefaceProvider; platform?: Platform; + theme?: Theme; + themeVariableName?: string; customComponents?: Map | undefined; direction?: Direction; store?: Store; diff --git a/client/web/divkit/typings/client-devtool.d.ts b/client/web/divkit/typings/client-devtool.d.ts index 0cb602b60..690e5c4b7 100644 --- a/client/web/divkit/typings/client-devtool.d.ts +++ b/client/web/divkit/typings/client-devtool.d.ts @@ -40,8 +40,8 @@ export function render(opts: { customization?: Customization; builtinProtocols?: string[]; extensions?: Map; - /** @deprecated */ theme?: Theme; + themeVariableName?: string; fetchInit?: FetchInit; tooltipRoot?: HTMLElement; customComponents?: Map | undefined; diff --git a/client/web/divkit/typings/client-hydratable.d.ts b/client/web/divkit/typings/client-hydratable.d.ts index 1ef7871ac..ca75a2e05 100644 --- a/client/web/divkit/typings/client-hydratable.d.ts +++ b/client/web/divkit/typings/client-hydratable.d.ts @@ -32,8 +32,8 @@ export function render(opts: { customization?: Customization; builtinProtocols?: string[]; extensions?: Map; - /** @deprecated */ theme?: Theme; + themeVariableName?: string; fetchInit?: FetchInit; tooltipRoot?: HTMLElement; customComponents?: Map | undefined; diff --git a/client/web/divkit/typings/client.d.ts b/client/web/divkit/typings/client.d.ts index 0eda6a214..3e79cafc1 100644 --- a/client/web/divkit/typings/client.d.ts +++ b/client/web/divkit/typings/client.d.ts @@ -31,8 +31,8 @@ export function render(opts: { customization?: Customization; builtinProtocols?: string[]; extensions?: Map; - /** @deprecated */ theme?: Theme; + themeVariableName?: string; fetchInit?: FetchInit; tooltipRoot?: HTMLElement; customComponents?: Map | undefined; diff --git a/client/web/divkit/typings/server.d.ts b/client/web/divkit/typings/server.d.ts index b4bc9ba56..8006ade80 100644 --- a/client/web/divkit/typings/server.d.ts +++ b/client/web/divkit/typings/server.d.ts @@ -5,7 +5,8 @@ import type { Customization, TypefaceProvider, Direction, - VideoPlayerProviderServer + VideoPlayerProviderServer, + Theme } from './common'; import type { CustomComponentDescription } from './custom'; import type { Store } from './store'; @@ -14,6 +15,8 @@ export function render(opts: { json: DivJson; id: string; platform?: Platform; + theme?: Theme; + themeVariableName?: string; mix?: string; customization?: Customization; builtinProtocols?: string[]; diff --git a/test_data/regression_test_data/index.json b/test_data/regression_test_data/index.json index 5978723fb..c44149764 100644 --- a/test_data/regression_test_data/index.json +++ b/test_data/regression_test_data/index.json @@ -4210,7 +4210,8 @@ { "title": "Color scheme dependent color animator", "platforms": [ - "ios" + "ios", + "web" ], "tags": [ "Animators"