@gjsify/adwaita-web 0.37.0 → 0.38.1
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/dist/adwaita-web.css +71 -1
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/accent.d.ts +28 -0
- package/lib/types/adw-accent.spec.d.ts +2 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +18 -7
- package/scss/_theme.scss +20 -2
- package/scss/_variables.scss +9 -1
- package/src/accent.ts +74 -0
- package/src/adw-accent.spec.ts +105 -0
- package/src/index.ts +11 -0
- package/src/styles.generated.ts +1 -1
- package/src/test.browser.mts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/adwaita-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "Adwaita/Libadwaita web components for browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"@codemirror/language": "^6.12.4",
|
|
42
42
|
"@codemirror/state": "^6.7.0",
|
|
43
43
|
"@codemirror/view": "^6.43.4",
|
|
44
|
-
"@gjsify/adwaita-core": "^0.
|
|
45
|
-
"@gjsify/adwaita-fonts": "^0.
|
|
46
|
-
"@gjsify/adwaita-icons": "^0.
|
|
44
|
+
"@gjsify/adwaita-core": "^0.38.1",
|
|
45
|
+
"@gjsify/adwaita-fonts": "^0.38.1",
|
|
46
|
+
"@gjsify/adwaita-icons": "^0.38.1",
|
|
47
47
|
"@lezer/highlight": "^1.2.3"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@gjsify/cli": "^0.
|
|
51
|
-
"@gjsify/unit": "^0.
|
|
50
|
+
"@gjsify/cli": "^0.38.1",
|
|
51
|
+
"@gjsify/unit": "^0.38.1",
|
|
52
52
|
"sass": "^1.101.0",
|
|
53
53
|
"typescript": "^6.0.3"
|
|
54
54
|
},
|
|
@@ -60,7 +60,18 @@
|
|
|
60
60
|
"browser": "polyfill",
|
|
61
61
|
"nativescript": "none"
|
|
62
62
|
},
|
|
63
|
-
"tier": 2
|
|
63
|
+
"tier": 2,
|
|
64
|
+
"nodeScript": {
|
|
65
|
+
"excludeGlobals": [
|
|
66
|
+
"document",
|
|
67
|
+
"HTMLElement",
|
|
68
|
+
"HTMLCanvasElement",
|
|
69
|
+
"Path2D",
|
|
70
|
+
"MutationObserver",
|
|
71
|
+
"navigator",
|
|
72
|
+
"process"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
64
75
|
},
|
|
65
76
|
"license": "MIT",
|
|
66
77
|
"repository": {
|
package/scss/_theme.scss
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
5
5
|
// Modifications: Adapted for @gjsify/adwaita-web Web Components.
|
|
6
6
|
|
|
7
|
+
// `light-theme` lives in _variables.scss; @use makes it reachable (the skin uses
|
|
8
|
+
// the module system, so a mixin there is namespaced rather than global).
|
|
9
|
+
@use 'variables';
|
|
10
|
+
|
|
7
11
|
@mixin dark-theme {
|
|
8
12
|
--window-bg-color: #222226;
|
|
9
13
|
--window-fg-color: #ffffff;
|
|
@@ -64,7 +68,21 @@
|
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
// Manual override
|
|
68
|
-
|
|
71
|
+
// Manual override, on ANY element rather than only on `:root`.
|
|
72
|
+
//
|
|
73
|
+
// It was `:root.theme-dark`, which silently does nothing unless the class lands on
|
|
74
|
+
// `<html>` — so an EMBEDDED consumer could not give its own subtree a scheme without
|
|
75
|
+
// re-theming the host page around it. The storybook is exactly that case: it scopes
|
|
76
|
+
// to its own container, the class was present, the tokens never changed, and nothing
|
|
77
|
+
// reported a problem. Custom properties resolve per element, so an element-level
|
|
78
|
+
// selector overrides the inherited values for its subtree and for nothing else.
|
|
79
|
+
.theme-dark {
|
|
69
80
|
@include dark-theme;
|
|
70
81
|
}
|
|
82
|
+
|
|
83
|
+
// The light counterpart, for the same reason: on a dark system a subtree has to be
|
|
84
|
+
// able to ask for light, which `:root:not(.theme-light)` on the media query above
|
|
85
|
+
// cannot give it once the class sits anywhere but the root.
|
|
86
|
+
.theme-light {
|
|
87
|
+
@include variables.light-theme;
|
|
88
|
+
}
|
package/scss/_variables.scss
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Copyright (c) 2025 csm (adwaita-web). MIT License.
|
|
6
6
|
// Modifications: Subset adapted for @gjsify/adwaita-web Web Components.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
@mixin light-theme {
|
|
9
9
|
// Window
|
|
10
10
|
--window-bg-color: #fafafb;
|
|
11
11
|
--window-fg-color: rgba(0, 0, 6, 0.8);
|
|
@@ -150,6 +150,14 @@
|
|
|
150
150
|
--monospace-font-size: var(--font-size-base);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
// The tokens above are a MIXIN rather than a bare `:root` block so `.theme-light`
|
|
154
|
+
// can include them too (see `_theme.scss`). A light opt-out that exists only as
|
|
155
|
+
// `:not(.theme-light)` on the media query cannot serve a SUBTREE: on a dark system
|
|
156
|
+
// the root stays dark, and a container asking for light matches no rule at all.
|
|
157
|
+
:root {
|
|
158
|
+
@include light-theme;
|
|
159
|
+
}
|
|
160
|
+
|
|
153
161
|
// High contrast moves the three opacity tokens, and only those — every colour
|
|
154
162
|
// that reads them follows without its own query (_colors.scss:325-329).
|
|
155
163
|
@media (prefers-contrast: more) {
|
package/src/accent.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Applying an Adwaita accent in the browser — the renderer half.
|
|
2
|
+
//
|
|
3
|
+
// The palette and the OkLab standalone derivation are `@gjsify/adwaita-core`'s
|
|
4
|
+
// (ADR 0004). This sets the two custom properties every accent-coloured rule in
|
|
5
|
+
// the stylesheet already reads:
|
|
6
|
+
//
|
|
7
|
+
// --accent-bg-color the fill, with white on top
|
|
8
|
+
// --accent-color the standalone colour, for text on the window
|
|
9
|
+
//
|
|
10
|
+
// BOTH ARE SET EXPLICITLY, from core's arithmetic, rather than setting only the
|
|
11
|
+
// background and letting CSS derive the other with
|
|
12
|
+
// `oklab(from var(--accent-bg-color) min(l, 0.5) a b)` — which is what
|
|
13
|
+
// libadwaita's own stylesheet does (`_colors.scss:166-170`). Two reasons: core is
|
|
14
|
+
// then the single source of truth for a value three renderers have to agree on,
|
|
15
|
+
// and the relative-colour form needs channel keywords inside `min()`, which is
|
|
16
|
+
// newer than this package's baseline. The CSS route stays available to a consumer
|
|
17
|
+
// who wants it; nothing here depends on it.
|
|
18
|
+
//
|
|
19
|
+
// NOTE, and it is a pre-existing inconsistency rather than something introduced
|
|
20
|
+
// here: `_variables.scss:55` hardcodes `--accent-color: #1c71d8` for the default
|
|
21
|
+
// blue, while libadwaita derives `#0461be`. The semantic colours in the same file
|
|
22
|
+
// DO use the relative-colour expression. Applying an accent through this module
|
|
23
|
+
// therefore also corrects the standalone colour; leaving it unset keeps the
|
|
24
|
+
// legacy value.
|
|
25
|
+
//
|
|
26
|
+
// Reference: refs/libadwaita/src/stylesheet/_colors.scss:146-170
|
|
27
|
+
// Copyright (c) GNOME contributors (libadwaita). LGPLv2.1+.
|
|
28
|
+
|
|
29
|
+
import { adwaitaAccentBgColor, adwaitaAccentColor, type AdwAccentColorName } from '@gjsify/adwaita-core';
|
|
30
|
+
|
|
31
|
+
/** The custom property carrying the accent fill. */
|
|
32
|
+
export const ACCENT_BG_PROPERTY = '--accent-bg-color';
|
|
33
|
+
/** The custom property carrying the standalone accent, for text on the window. */
|
|
34
|
+
export const ACCENT_PROPERTY = '--accent-color';
|
|
35
|
+
|
|
36
|
+
/** Where and how to apply the accent. */
|
|
37
|
+
export interface ApplyAccentOptions {
|
|
38
|
+
/** The element to set the properties on. Defaults to `document.documentElement`. */
|
|
39
|
+
readonly target?: HTMLElement;
|
|
40
|
+
/**
|
|
41
|
+
* Which standalone variant to use. Defaults to the target's RESOLVED scheme,
|
|
42
|
+
* because the standalone colour is the one thing about an accent that differs
|
|
43
|
+
* between light and dark — reading it beats assuming light.
|
|
44
|
+
*/
|
|
45
|
+
readonly dark?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether `element` is currently rendering dark.
|
|
50
|
+
*
|
|
51
|
+
* `.theme-dark` / `.theme-light` are the stylesheet's manual overrides and win
|
|
52
|
+
* over the media query, so an explicit class is checked first and
|
|
53
|
+
* `prefers-color-scheme` only decides when neither is present.
|
|
54
|
+
*/
|
|
55
|
+
export function isAdwaitaDark(element: HTMLElement): boolean {
|
|
56
|
+
if (element.closest('.theme-dark')) return true;
|
|
57
|
+
if (element.closest('.theme-light')) return false;
|
|
58
|
+
return globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Set the accent custom properties for `name`. */
|
|
62
|
+
export function applyAdwaitaAccent(name: AdwAccentColorName, options: ApplyAccentOptions = {}): void {
|
|
63
|
+
const target = options.target ?? document.documentElement;
|
|
64
|
+
const dark = options.dark ?? isAdwaitaDark(target);
|
|
65
|
+
|
|
66
|
+
target.style.setProperty(ACCENT_BG_PROPERTY, adwaitaAccentBgColor(name));
|
|
67
|
+
target.style.setProperty(ACCENT_PROPERTY, adwaitaAccentColor(name, dark));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Drop the properties, so the stylesheet's own values apply again. */
|
|
71
|
+
export function clearAdwaitaAccent(target: HTMLElement = document.documentElement): void {
|
|
72
|
+
target.style.removeProperty(ACCENT_BG_PROPERTY);
|
|
73
|
+
target.style.removeProperty(ACCENT_PROPERTY);
|
|
74
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Accent application against `ACCENT_COLOR_VECTORS` — the same table the core
|
|
2
|
+
// suite drives, here through the REAL computed style of a real element.
|
|
3
|
+
//
|
|
4
|
+
// This is what makes the table renderer-driven rather than a derivation asserted
|
|
5
|
+
// against itself: the browser resolves the custom properties and the assertions
|
|
6
|
+
// read them back with `getComputedStyle`, so a value that never reaches the
|
|
7
|
+
// cascade fails a test naming the accent.
|
|
8
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
9
|
+
import { ACCENT_COLOR_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
10
|
+
|
|
11
|
+
import '@gjsify/adwaita-web';
|
|
12
|
+
import {
|
|
13
|
+
ACCENT_BG_PROPERTY,
|
|
14
|
+
ACCENT_PROPERTY,
|
|
15
|
+
applyAdwaitaAccent,
|
|
16
|
+
clearAdwaitaAccent,
|
|
17
|
+
isAdwaitaDark,
|
|
18
|
+
} from './accent.js';
|
|
19
|
+
|
|
20
|
+
/** A custom property's resolved value, whitespace trimmed. */
|
|
21
|
+
const property = (element: HTMLElement, name: string): string =>
|
|
22
|
+
getComputedStyle(element).getPropertyValue(name).trim();
|
|
23
|
+
|
|
24
|
+
export const AdwAccentTest = async () => {
|
|
25
|
+
await describe('applyAdwaitaAccent (conformance vectors)', async () => {
|
|
26
|
+
for (const vector of ACCENT_COLOR_VECTORS) {
|
|
27
|
+
await it(`${vector.name} — the fill and the light standalone colour reach the cascade`, async () => {
|
|
28
|
+
const host = document.createElement('div');
|
|
29
|
+
host.className = 'theme-light';
|
|
30
|
+
document.body.append(host);
|
|
31
|
+
|
|
32
|
+
applyAdwaitaAccent(vector.name, { target: host });
|
|
33
|
+
|
|
34
|
+
expect(property(host, ACCENT_BG_PROPERTY)).toBe(vector.background);
|
|
35
|
+
expect(property(host, ACCENT_PROPERTY)).toBe(vector.standaloneLight);
|
|
36
|
+
|
|
37
|
+
host.remove();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await it(`${vector.name} — the DARK standalone colour differs and is the table's`, async () => {
|
|
41
|
+
// Asserted as a pair: a renderer that ignored `dark` would pass
|
|
42
|
+
// every light row above and still be wrong in half its uses.
|
|
43
|
+
const host = document.createElement('div');
|
|
44
|
+
host.className = 'theme-dark';
|
|
45
|
+
document.body.append(host);
|
|
46
|
+
|
|
47
|
+
applyAdwaitaAccent(vector.name, { target: host });
|
|
48
|
+
|
|
49
|
+
expect(property(host, ACCENT_PROPERTY)).toBe(vector.standaloneDark);
|
|
50
|
+
expect(vector.standaloneDark).not.toBe(vector.standaloneLight);
|
|
51
|
+
|
|
52
|
+
host.remove();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await describe('accent application', async () => {
|
|
58
|
+
await it('reads the resolved scheme from the element rather than assuming light', async () => {
|
|
59
|
+
const dark = document.createElement('div');
|
|
60
|
+
dark.className = 'theme-dark';
|
|
61
|
+
const light = document.createElement('div');
|
|
62
|
+
light.className = 'theme-light';
|
|
63
|
+
document.body.append(dark, light);
|
|
64
|
+
|
|
65
|
+
expect(isAdwaitaDark(dark)).toBe(true);
|
|
66
|
+
expect(isAdwaitaDark(light)).toBe(false);
|
|
67
|
+
|
|
68
|
+
// A nested element inherits the ancestor's scheme — `closest`, not a
|
|
69
|
+
// class check on the element itself, which is how the stylesheet's
|
|
70
|
+
// `.theme-dark` scope actually behaves.
|
|
71
|
+
const nested = document.createElement('span');
|
|
72
|
+
dark.append(nested);
|
|
73
|
+
expect(isAdwaitaDark(nested)).toBe(true);
|
|
74
|
+
|
|
75
|
+
dark.remove();
|
|
76
|
+
light.remove();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await it('an explicit dark option overrides what the element resolves to', async () => {
|
|
80
|
+
const host = document.createElement('div');
|
|
81
|
+
host.className = 'theme-light';
|
|
82
|
+
document.body.append(host);
|
|
83
|
+
|
|
84
|
+
applyAdwaitaAccent('blue', { target: host, dark: true });
|
|
85
|
+
expect(property(host, ACCENT_PROPERTY)).toBe(ACCENT_COLOR_VECTORS[0].standaloneDark);
|
|
86
|
+
|
|
87
|
+
host.remove();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await it('clearing restores the stylesheet value instead of leaving the override', async () => {
|
|
91
|
+
const host = document.createElement('div');
|
|
92
|
+
host.className = 'theme-light';
|
|
93
|
+
document.body.append(host);
|
|
94
|
+
|
|
95
|
+
applyAdwaitaAccent('red', { target: host });
|
|
96
|
+
expect(property(host, ACCENT_BG_PROPERTY)).toBe(ACCENT_COLOR_VECTORS[5].background);
|
|
97
|
+
|
|
98
|
+
clearAdwaitaAccent(host);
|
|
99
|
+
// Back to whatever the cascade says — which is NOT red.
|
|
100
|
+
expect(property(host, ACCENT_BG_PROPERTY)).not.toBe(ACCENT_COLOR_VECTORS[5].background);
|
|
101
|
+
|
|
102
|
+
host.remove();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,17 @@ if (typeof document !== 'undefined' && !document.getElementById('adwaita-web-sty
|
|
|
23
23
|
// `AdwBreakpoint`, and the `breakpoint="…"` attribute wiring the split views use.
|
|
24
24
|
export { addBreakpoints, bindBreakpointSetter } from './breakpoints.js';
|
|
25
25
|
|
|
26
|
+
// The accent palette and its OkLab standalone derivation are
|
|
27
|
+
// `@gjsify/adwaita-core`'s; this is the browser half that puts them in the cascade.
|
|
28
|
+
export {
|
|
29
|
+
ACCENT_BG_PROPERTY,
|
|
30
|
+
ACCENT_PROPERTY,
|
|
31
|
+
applyAdwaitaAccent,
|
|
32
|
+
clearAdwaitaAccent,
|
|
33
|
+
isAdwaitaDark,
|
|
34
|
+
} from './accent.js';
|
|
35
|
+
export type { ApplyAccentOptions } from './accent.js';
|
|
36
|
+
|
|
26
37
|
export { AdwAvatar } from './elements/adw-avatar.js';
|
|
27
38
|
export { AdwBanner } from './elements/adw-banner.js';
|
|
28
39
|
export { AdwCard } from './elements/adw-card.js';
|