@gjsify/adwaita-web 0.59.1 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/adwaita-web.css +4 -0
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/elements/gtk-button.d.ts +13 -0
- package/lib/types/elements/gtk-drop-down.d.ts +4 -0
- package/lib/types/elements/gtk-label.d.ts +4 -0
- package/lib/types/elements/gtk-text-view.d.ts +15 -0
- package/lib/types/file-chooser-host.d.ts +55 -0
- package/lib/types/file-chooser.spec.d.ts +1 -0
- package/lib/types/gtk-platform.spec.d.ts +1 -0
- package/lib/types/namespace/adw.d.ts +1 -0
- package/lib/types/namespace/gdk.d.ts +2 -0
- package/lib/types/namespace/gio.d.ts +3 -0
- package/lib/types/namespace/glib.d.ts +1 -0
- package/lib/types/namespace/gtk-enums.d.ts +22 -0
- package/lib/types/namespace/gtk-platform.d.ts +13 -0
- package/lib/types/namespace/gtk.d.ts +4 -1
- package/lib/types/platform-host.d.ts +2 -0
- package/lib/types/registered-styles.d.ts +2 -0
- package/lib/types/style-manager.d.ts +7 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/template-classes.d.ts +2 -0
- package/lib/types/widget-members.spec.d.ts +1 -0
- package/package.json +23 -10
- package/scss/_viewport.scss +9 -0
- package/src/capabilities.mjs +1 -4
- package/src/construct-vectors.spec.ts +10 -1
- package/src/elements/gtk-button.ts +34 -0
- package/src/elements/gtk-drop-down.ts +10 -0
- package/src/elements/gtk-label.ts +10 -0
- package/src/elements/gtk-text-view.ts +40 -0
- package/src/file-chooser-host.ts +421 -0
- package/src/file-chooser.spec.ts +376 -0
- package/src/gobject-door.ts +15 -2
- package/src/gobject-elements.ts +4 -0
- package/src/gtk-drop-down.spec.ts +11 -0
- package/src/gtk-label.spec.ts +12 -0
- package/src/gtk-platform.spec.ts +19 -0
- package/src/gtk-text-view.spec.ts +22 -0
- package/src/namespace/adw.ts +1 -0
- package/src/namespace/gdk.ts +18 -1
- package/src/namespace/gio.ts +20 -2
- package/src/namespace/glib.ts +6 -1
- package/src/namespace/gtk-enums.ts +7 -2
- package/src/namespace/gtk-platform.ts +16 -0
- package/src/namespace/gtk.ts +12 -1
- package/src/platform-host.ts +88 -0
- package/src/registered-styles.ts +73 -0
- package/src/shared-tree-builder.ts +46 -2
- package/src/shared-trees.spec.ts +36 -0
- package/src/style-manager.ts +10 -1
- package/src/styles.generated.ts +1 -1
- package/src/template-classes.ts +6 -0
- package/src/test.browser.mts +6 -0
- package/src/widget-members.spec.ts +147 -0
package/src/namespace/gtk.ts
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
// are the same write and nothing in this package had to learn a second input shape.
|
|
22
22
|
|
|
23
23
|
export { GtkAdjustment as Adjustment } from '@gjsify/adwaita-core';
|
|
24
|
-
|
|
24
|
+
// ADR 0100: the file dialog is no element either — `present`-less, it asks the driver
|
|
25
|
+
// `../file-chooser-host.ts` registers (imported by `./gio.ts`) and resolves a `Gio.File`.
|
|
26
|
+
export { GtkFileDialog as FileDialog, GtkFileFilter as FileFilter, DialogError } from '@gjsify/adwaita-core';
|
|
27
|
+
export { License, PolicyType, TextDirection, TextExtendSelection, TextWindowType } from './gtk-enums.js';
|
|
25
28
|
export { GtkAboutDialog as AboutDialog } from '../elements/gtk-about-dialog.js';
|
|
26
29
|
export { GtkActionBar as ActionBar } from '../elements/gtk-action-bar.js';
|
|
27
30
|
export { GtkApplicationWindow as ApplicationWindow } from '../elements/gtk-application-window.js';
|
|
@@ -97,3 +100,11 @@ export { GtkWindowControls as WindowControls } from '../elements/gtk-window-cont
|
|
|
97
100
|
export { GtkViewport as Viewport } from '../elements/gtk-viewport.js';
|
|
98
101
|
export { GtkWindowHandle as WindowHandle } from '../elements/gtk-window-handle.js';
|
|
99
102
|
export { GtkApplication as Application } from '../application.js';
|
|
103
|
+
export {
|
|
104
|
+
STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
105
|
+
STYLE_PROVIDER_PRIORITY_FALLBACK,
|
|
106
|
+
STYLE_PROVIDER_PRIORITY_SETTINGS,
|
|
107
|
+
STYLE_PROVIDER_PRIORITY_THEME,
|
|
108
|
+
STYLE_PROVIDER_PRIORITY_USER,
|
|
109
|
+
} from '@gjsify/adwaita-core';
|
|
110
|
+
export { CssProvider, EventControllerKey, IconTheme, show_uri, StyleContext, Settings } from './gtk-platform.js';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// How the web door reaches the platform for `Gtk.StyleContext`, `Gtk.Settings`, `Gtk.show_uri` and
|
|
2
|
+
// `Gtk.EventControllerKey` (the behaviour is in `@gjsify/adwaita-core`, `gtk-platform.ts`).
|
|
3
|
+
//
|
|
4
|
+
// - styles: one `<style>` element per provider, kept in priority order at the end of `<head>`.
|
|
5
|
+
// - uris: `window.open` with `noopener`; a popup blocker may refuse it outside a user gesture.
|
|
6
|
+
// - keys: `keydown` / `keyup` on the element; a handled key gets `preventDefault()`.
|
|
7
|
+
|
|
8
|
+
import type { HostKeyEvent, PlatformHost } from '@gjsify/adwaita-core';
|
|
9
|
+
|
|
10
|
+
const SHIFT_MASK = 1 << 0;
|
|
11
|
+
const CONTROL_MASK = 1 << 2;
|
|
12
|
+
const ALT_MASK = 1 << 3;
|
|
13
|
+
const SUPER_MASK = 1 << 26;
|
|
14
|
+
|
|
15
|
+
const sheets = new Map<object, HTMLStyleElement>();
|
|
16
|
+
|
|
17
|
+
function modifierState(event: KeyboardEvent): number {
|
|
18
|
+
return (
|
|
19
|
+
(event.shiftKey ? SHIFT_MASK : 0) |
|
|
20
|
+
(event.ctrlKey ? CONTROL_MASK : 0) |
|
|
21
|
+
(event.altKey ? ALT_MASK : 0) |
|
|
22
|
+
(event.metaKey ? SUPER_MASK : 0)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const webPlatformHost: PlatformHost = {
|
|
27
|
+
setStyles(list) {
|
|
28
|
+
if (typeof document === 'undefined') return;
|
|
29
|
+
const keep = new Set(list.map((entry) => entry.owner));
|
|
30
|
+
for (const [owner, element] of sheets) {
|
|
31
|
+
if (keep.has(owner)) continue;
|
|
32
|
+
element.remove();
|
|
33
|
+
sheets.delete(owner);
|
|
34
|
+
}
|
|
35
|
+
// Re-appending in order is what makes the later sheet win at equal specificity.
|
|
36
|
+
for (const { owner, css, priority } of list) {
|
|
37
|
+
let element = sheets.get(owner);
|
|
38
|
+
if (!element) {
|
|
39
|
+
element = document.createElement('style');
|
|
40
|
+
sheets.set(owner, element);
|
|
41
|
+
}
|
|
42
|
+
element.dataset.gtkPriority = String(priority);
|
|
43
|
+
if (element.textContent !== css) element.textContent = css;
|
|
44
|
+
document.head.append(element);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
openUri(uri) {
|
|
49
|
+
if (typeof window === 'undefined') return;
|
|
50
|
+
window.open(uri, '_blank', 'noopener');
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
fontName() {
|
|
54
|
+
if (typeof document === 'undefined' || !document.body) return null;
|
|
55
|
+
const style = getComputedStyle(document.body);
|
|
56
|
+
const family = style.fontFamily
|
|
57
|
+
.split(',')[0]
|
|
58
|
+
?.trim()
|
|
59
|
+
.replace(/^["']|["']$/g, '');
|
|
60
|
+
const px = Number.parseFloat(style.fontSize);
|
|
61
|
+
if (!family || !Number.isFinite(px)) return null;
|
|
62
|
+
return `${family} ${Math.round(px * 0.75 * 10) / 10}`;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
listenKeys(widget, handler) {
|
|
66
|
+
const element = widget as HTMLElement;
|
|
67
|
+
// A bare element takes no keys until it can be focused.
|
|
68
|
+
if (!element.hasAttribute('tabindex')) element.setAttribute('tabindex', '-1');
|
|
69
|
+
const listen = (pressed: boolean) => (event: Event) => {
|
|
70
|
+
const key = event as KeyboardEvent;
|
|
71
|
+
const host: HostKeyEvent = {
|
|
72
|
+
key: key.key,
|
|
73
|
+
keycode: key.keyCode,
|
|
74
|
+
state: modifierState(key),
|
|
75
|
+
pressed,
|
|
76
|
+
};
|
|
77
|
+
if (handler(host)) key.preventDefault();
|
|
78
|
+
};
|
|
79
|
+
const down = listen(true);
|
|
80
|
+
const up = listen(false);
|
|
81
|
+
element.addEventListener('keydown', down);
|
|
82
|
+
element.addEventListener('keyup', up);
|
|
83
|
+
return () => {
|
|
84
|
+
element.removeEventListener('keydown', down);
|
|
85
|
+
element.removeEventListener('keyup', up);
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The stylesheet of a registered class. `<adw-bin>` is styled by a rule on the tag `adw-bin`, and a
|
|
2
|
+
// subclass that `GObject.registerClass` defines as `<gjsify-hexdump>` is another tag, so none of its
|
|
3
|
+
// base's rules (a `display: block`, the box-model reset, the `:only-child` fill) reach it: a Bin
|
|
4
|
+
// subclass collapsed to an inline box of zero width. GTK has no such seam because a subclass keeps
|
|
5
|
+
// its parent's CSS node name.
|
|
6
|
+
//
|
|
7
|
+
// So the rules of the base tag are copied to the subclass tag, once per tag, from the stylesheets
|
|
8
|
+
// the page has when the first instance connects: every selector that names the base tag as a type
|
|
9
|
+
// selector, with that name replaced. The copy is adopted last, so it sits where the base's rules do
|
|
10
|
+
// relative to the author's own.
|
|
11
|
+
|
|
12
|
+
const DONE = new Set<string>();
|
|
13
|
+
|
|
14
|
+
/** Splits a selector list at its top-level commas (`:is(a, b)` stays whole). */
|
|
15
|
+
function splitSelectorList(list: string): string[] {
|
|
16
|
+
const parts: string[] = [];
|
|
17
|
+
let depth = 0;
|
|
18
|
+
let start = 0;
|
|
19
|
+
for (let i = 0; i < list.length; i++) {
|
|
20
|
+
const c = list[i];
|
|
21
|
+
if (c === '(' || c === '[') depth++;
|
|
22
|
+
else if (c === ')' || c === ']') depth--;
|
|
23
|
+
else if (c === ',' && depth === 0) {
|
|
24
|
+
parts.push(list.slice(start, i).trim());
|
|
25
|
+
start = i + 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
parts.push(list.slice(start).trim());
|
|
29
|
+
return parts;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function rulesOf(sheet: CSSStyleSheet): CSSRuleList | null {
|
|
33
|
+
try {
|
|
34
|
+
return sheet.cssRules;
|
|
35
|
+
} catch {
|
|
36
|
+
return null; // a cross-origin sheet is not readable
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function copyRules(rules: CSSRuleList, from: RegExp, to: string, out: string[]): void {
|
|
41
|
+
for (const rule of Array.from(rules)) {
|
|
42
|
+
if (rule instanceof CSSStyleRule) {
|
|
43
|
+
const mine = splitSelectorList(rule.selectorText).filter((selector) => from.test(selector));
|
|
44
|
+
if (mine.length === 0) continue;
|
|
45
|
+
const selector = mine.map((s) => s.replace(new RegExp(from.source, 'g'), to)).join(', ');
|
|
46
|
+
out.push(`${selector} { ${rule.style.cssText} }`);
|
|
47
|
+
} else if ('cssRules' in rule) {
|
|
48
|
+
// @media, @supports, @layer: copy the wrapper around what matches inside it
|
|
49
|
+
const inner: string[] = [];
|
|
50
|
+
copyRules((rule as CSSGroupingRule).cssRules, from, to, inner);
|
|
51
|
+
if (inner.length === 0) continue;
|
|
52
|
+
const head = rule.cssText.slice(0, rule.cssText.indexOf('{')).trim();
|
|
53
|
+
out.push(`${head} { ${inner.join('\n')} }`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Gives `tag` the rules that `base` has in the page's stylesheets. Runs once per tag. */
|
|
59
|
+
export function inheritStyles(tag: string, base: string | null | undefined): void {
|
|
60
|
+
if (!base || DONE.has(tag)) return;
|
|
61
|
+
DONE.add(tag);
|
|
62
|
+
const from = new RegExp(`(?<![\\w-])${base}(?![\\w-])`);
|
|
63
|
+
const css: string[] = [];
|
|
64
|
+
const sheets = [...Array.from(document.styleSheets), ...document.adoptedStyleSheets];
|
|
65
|
+
for (const sheet of sheets) {
|
|
66
|
+
const rules = rulesOf(sheet);
|
|
67
|
+
if (rules) copyRules(rules, from, tag, css);
|
|
68
|
+
}
|
|
69
|
+
if (css.length === 0) return;
|
|
70
|
+
const copy = new CSSStyleSheet();
|
|
71
|
+
copy.replaceSync(css.join('\n'));
|
|
72
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, copy];
|
|
73
|
+
}
|
|
@@ -58,7 +58,7 @@ import { capabilities } from './capabilities.mjs';
|
|
|
58
58
|
import { endpointOf, hasElementApi, installElementApi } from './gobject-elements.js';
|
|
59
59
|
import { dispatchedSignalsOf } from './signals.js';
|
|
60
60
|
import { slottedChildrenOf } from './slotted-children.js';
|
|
61
|
-
import { templateTagFor } from './template-classes.js';
|
|
61
|
+
import { isTemplateTag, templateTagFor } from './template-classes.js';
|
|
62
62
|
import { writeBooleanAttribute } from './attributes.js';
|
|
63
63
|
|
|
64
64
|
/** One authored placement, kept so {@link mountSharedTree} can hold the renderer to it. */
|
|
@@ -344,7 +344,11 @@ export function writeProp(el: HTMLElement, prop: string, value: string | number
|
|
|
344
344
|
const member = propertyOf(prop);
|
|
345
345
|
const expand = GTK_WIDGET_EXPAND.find((name) => name === attr);
|
|
346
346
|
if (expand !== undefined && typeof value === 'boolean') el.setAttribute(expand, value ? '' : 'false');
|
|
347
|
-
else if (
|
|
347
|
+
else if (isTemplateTag(el.localName) && member in el) {
|
|
348
|
+
// An application class has no attributeChangedCallback: its properties are GObject properties,
|
|
349
|
+
// set the way `Gtk.Builder` sets them, after the constructor.
|
|
350
|
+
(el as unknown as Record<string, unknown>)[member] = value;
|
|
351
|
+
} else if (typeof value === 'boolean' && isValueBasedBooleanAttr(el.localName, attr)) {
|
|
348
352
|
writeBooleanAttribute(el, attr, value);
|
|
349
353
|
} else if (value === false && isWritable(el, member)) {
|
|
350
354
|
// Non-value-based, writable property: set the property directly (bypasses attribute).
|
|
@@ -416,11 +420,51 @@ function writeLook(el: HTMLElement, node: SharedTreeNode, menus?: SharedTreeNode
|
|
|
416
420
|
writeExtensions(el, node);
|
|
417
421
|
}
|
|
418
422
|
|
|
423
|
+
/** The `GtkGridLayoutChild` properties `layout { }` may carry, and the CSS Grid line each one is. */
|
|
424
|
+
const GRID_CELL: Readonly<Record<string, { property: 'gridRow' | 'gridColumn'; span: boolean }>> = {
|
|
425
|
+
row: { property: 'gridRow', span: false },
|
|
426
|
+
column: { property: 'gridColumn', span: false },
|
|
427
|
+
'row-span': { property: 'gridRow', span: true },
|
|
428
|
+
'column-span': { property: 'gridColumn', span: true },
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* ADR 0092's `layout { }`: the child's cell in a `Gtk.Grid`. GTK counts from 0 and CSS Grid from 1,
|
|
433
|
+
* and a span is `span N` after the start line. The one layout manager here is the grid, so a
|
|
434
|
+
* layout under any other parent, a key the grid child has no spelling for, or a value that is not
|
|
435
|
+
* a whole number is REFUSED by name: the cell would otherwise be dropped at exit 0.
|
|
436
|
+
*/
|
|
437
|
+
function writeGridCell(parent: HTMLElement, childEl: HTMLElement, child: SharedTreeNode): void {
|
|
438
|
+
const layout = child.layout ?? {};
|
|
439
|
+
if (parent.localName !== 'gtk-grid') {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`layout (${Object.keys(layout).join(', ')}) on \`${child.tag}\` under \`${parent.localName}\`: ` +
|
|
442
|
+
"this renderer's only layout manager is the grid, so the placement would be dropped.",
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const cell = { row: 0, column: 0, 'row-span': 1, 'column-span': 1 } as Record<string, number>;
|
|
446
|
+
for (const [key, value] of Object.entries(layout)) {
|
|
447
|
+
if (GRID_CELL[key] === undefined) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`layout \`${key}\` on \`${child.tag}\` reaches nothing: a grid child takes ` +
|
|
450
|
+
`${Object.keys(GRID_CELL).join(', ')}.`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
454
|
+
throw new Error(`layout \`${key}: ${String(value)}\` on \`${child.tag}\` is not a whole number.`);
|
|
455
|
+
}
|
|
456
|
+
cell[key] = value;
|
|
457
|
+
}
|
|
458
|
+
childEl.style.gridRow = `${cell.row + 1} / span ${cell['row-span']}`;
|
|
459
|
+
childEl.style.gridColumn = `${cell.column + 1} / span ${cell['column-span']}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
419
462
|
/** The built children of `node`, placed (`page`, `slot`) but not yet appended to `el`. */
|
|
420
463
|
function buildChildren(el: HTMLElement, node: SharedTreeNode, record: BuildRecord): HTMLElement[] {
|
|
421
464
|
const built: HTMLElement[] = [];
|
|
422
465
|
for (const child of node.children ?? []) {
|
|
423
466
|
const childEl = buildNode(child, record);
|
|
467
|
+
if (child.layout !== undefined) writeGridCell(el, childEl, child);
|
|
424
468
|
if (child.page !== undefined) writePage(el, childEl, child);
|
|
425
469
|
if (child.slot !== undefined) {
|
|
426
470
|
childEl.setAttribute('slot', child.slot);
|
package/src/shared-trees.spec.ts
CHANGED
|
@@ -364,6 +364,42 @@ export const AdwSharedTreesTest = async () => {
|
|
|
364
364
|
).toThrow('has no slot "middle"');
|
|
365
365
|
});
|
|
366
366
|
|
|
367
|
+
// ADR 0092's `layout { }`: a grid child's cell. GTK counts from 0, CSS Grid from 1.
|
|
368
|
+
await it('`layout { }` places a grid child in its cell, spans defaulting to one', () => {
|
|
369
|
+
mounted(
|
|
370
|
+
{
|
|
371
|
+
tag: 'GtkGrid',
|
|
372
|
+
children: [
|
|
373
|
+
{ tag: 'GtkLabel', id: 'a', layout: { row: 0, column: 0 } },
|
|
374
|
+
{ tag: 'GtkLabel', id: 'b', layout: { row: 1, column: 1, 'column-span': 2 } },
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
(root) => {
|
|
378
|
+
const a = root.querySelector<HTMLElement>('#a')!;
|
|
379
|
+
const b = root.querySelector<HTMLElement>('#b')!;
|
|
380
|
+
expect(a.style.gridRow).toBe('1 / span 1');
|
|
381
|
+
expect(a.style.gridColumn).toBe('1 / span 1');
|
|
382
|
+
expect(b.style.gridRow).toBe('2 / span 1');
|
|
383
|
+
expect(b.style.gridColumn).toBe('2 / span 2');
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await it('`layout { }` under a parent that is not a grid is refused BY NAME', () => {
|
|
389
|
+
expect(() =>
|
|
390
|
+
mountSharedTree({ tag: 'GtkBox', children: [{ tag: 'GtkLabel', layout: { row: 0, column: 0 } }] }),
|
|
391
|
+
).toThrow('only layout manager is the grid');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await it('a `layout { }` key or value the grid child cannot take is refused BY NAME', () => {
|
|
395
|
+
expect(() =>
|
|
396
|
+
mountSharedTree({ tag: 'GtkGrid', children: [{ tag: 'GtkLabel', layout: { colum: 0 } }] }),
|
|
397
|
+
).toThrow('layout `colum`');
|
|
398
|
+
expect(() =>
|
|
399
|
+
mountSharedTree({ tag: 'GtkGrid', children: [{ tag: 'GtkLabel', layout: { row: 1.5 } }] }),
|
|
400
|
+
).toThrow('is not a whole number');
|
|
401
|
+
});
|
|
402
|
+
|
|
367
403
|
// `child: Gtk.Label {…}` in a `.blp` authors `slot: 'child'` — the widget's own
|
|
368
404
|
// GObject property name — for every single-child widget the corpus never places
|
|
369
405
|
// one on. `<adw-clamp>` has no separate box to route into (every child is already
|
package/src/style-manager.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// core's (`style-manager.ts`); this file is the SOURCE: the root's `class` attribute and the media
|
|
4
4
|
// query are the two things that can flip it. Without a `document` (SSR) it is light and never changes.
|
|
5
5
|
|
|
6
|
-
import { StyleManagerBase, type StyleSource } from '@gjsify/adwaita-core';
|
|
6
|
+
import { ColorScheme, StyleManagerBase, type StyleSource } from '@gjsify/adwaita-core';
|
|
7
7
|
|
|
8
8
|
import { isAdwaitaDark } from './accent.js';
|
|
9
9
|
|
|
@@ -22,6 +22,15 @@ export const webStyleSource: StyleSource = {
|
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// FORCE_DARK / FORCE_LIGHT write the same root class an app writes by hand; DEFAULT takes it away.
|
|
26
|
+
webStyleSource.force = (dark) => {
|
|
27
|
+
if (typeof document === 'undefined') return;
|
|
28
|
+
const root = document.documentElement;
|
|
29
|
+
root.classList.remove('theme-dark', 'theme-light');
|
|
30
|
+
if (dark !== null) root.classList.add(dark ? 'theme-dark' : 'theme-light');
|
|
31
|
+
};
|
|
32
|
+
|
|
25
33
|
export class AdwStyleManager extends StyleManagerBase {
|
|
34
|
+
static readonly ColorScheme = ColorScheme;
|
|
26
35
|
static override source = webStyleSource;
|
|
27
36
|
}
|