Skip to content

Commit 91dd304

Browse files
authored
feat: add explicit light/dark theme option to PrintControl (#92)
The panel previously only followed the system prefers-color-scheme. Add a `theme` option and `setTheme()` method so host apps that manage their own light/dark mode can force the panel to match.
1 parent faf8473 commit 91dd304

6 files changed

Lines changed: 121 additions & 24 deletions

File tree

package-lock.json

Lines changed: 2 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "maplibre-gl-components",
3-
"version": "0.19.0",
3+
"version": "0.20.0",
44
"description": "Legend, colorbar, and HTML control components for MapLibre GL JS maps",
55
"type": "module",
66
"main": "./dist/index.cjs",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ export type {
307307
PrintColorbarConfig,
308308
PrintEvent,
309309
PrintEventHandler,
310+
PrintTheme,
310311
MinimapControlOptions,
311312
MinimapControlState,
312313
MinimapEvent,

src/lib/core/PrintControl.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
PrintOrientation,
1616
PrintFitMode,
1717
PrintFormat,
18+
PrintTheme,
1819
ColormapName,
1920
ColorStop,
2021
} from "./types";
@@ -47,6 +48,7 @@ const DEFAULT_OPTIONS: Required<Omit<PrintControlOptions, "colorbar">> & {
4748
className: "",
4849
visible: true,
4950
collapsed: true,
51+
theme: "auto",
5052
format: "png",
5153
quality: 0.92,
5254
filename: "map-export",
@@ -312,6 +314,7 @@ export class PrintControl implements IControl {
312314
private _createContainer(): HTMLElement {
313315
const container = document.createElement("div");
314316
container.className = `maplibregl-ctrl maplibre-gl-print-control ${this._options.className}`;
317+
this._applyThemeClass(container);
315318

316319
if (!this._state.visible) {
317320
container.style.display = "none";
@@ -876,7 +879,10 @@ export class PrintControl implements IControl {
876879
this._marginInput.min = "0";
877880
this._marginInput.value = String(this._state.margin);
878881
this._marginInput.addEventListener("input", () => {
879-
this._state.margin = Math.max(0, parseFloat(this._marginInput!.value) || 0);
882+
this._state.margin = Math.max(
883+
0,
884+
parseFloat(this._marginInput!.value) || 0,
885+
);
880886
});
881887
row2.appendChild(this._makeField("Margin (pt)", this._marginInput));
882888
wrapper.appendChild(row2);
@@ -1037,7 +1043,7 @@ export class PrintControl implements IControl {
10371043
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
10381044
}
10391045
}
1040-
return (~crc) >>> 0;
1046+
return ~crc >>> 0;
10411047
}
10421048

10431049
/**
@@ -1113,10 +1119,7 @@ export class PrintControl implements IControl {
11131119
* @param format - The raster format.
11141120
* @returns A new blob carrying the current DPI, or the original on failure.
11151121
*/
1116-
private async _embedDpi(
1117-
blob: Blob,
1118-
format: "png" | "jpeg",
1119-
): Promise<Blob> {
1122+
private async _embedDpi(blob: Blob, format: "png" | "jpeg"): Promise<Blob> {
11201123
try {
11211124
const bytes = new Uint8Array(await blob.arrayBuffer());
11221125
const dpi = this._effectiveDpi();
@@ -1757,7 +1760,10 @@ export class PrintControl implements IControl {
17571760

17581761
if (content.w >= minCanvasWidthForArrow) {
17591762
const arrowX = content.x + content.w - arrowSize - 18;
1760-
const arrowY = Math.max(content.y + 18, content.y + titleBarHeight + 8);
1763+
const arrowY = Math.max(
1764+
content.y + 18,
1765+
content.y + titleBarHeight + 8,
1766+
);
17611767
const bearing = this._map.getBearing();
17621768
this._drawNorthArrow(ctx, arrowX, arrowY, arrowSize, bearing);
17631769
}
@@ -1951,6 +1957,41 @@ export class PrintControl implements IControl {
19511957
return { ...this._state };
19521958
}
19531959

1960+
/**
1961+
* Set the panel theme.
1962+
*
1963+
* Use `'light'` or `'dark'` to force a theme when the host application
1964+
* manages its own light/dark mode, or `'auto'` to follow the system
1965+
* `prefers-color-scheme`. Updates the live panel immediately.
1966+
*
1967+
* @param theme - The theme to apply.
1968+
* @returns This control instance for chaining.
1969+
*/
1970+
setTheme(theme: PrintTheme): this {
1971+
this._options.theme = theme;
1972+
if (this._container) {
1973+
this._applyThemeClass(this._container);
1974+
}
1975+
return this;
1976+
}
1977+
1978+
/**
1979+
* Apply the theme modifier class to the given element so the panel honors an
1980+
* explicitly forced light/dark theme. `'auto'` removes both modifiers and
1981+
* lets the CSS `prefers-color-scheme` media query drive the theme.
1982+
*/
1983+
private _applyThemeClass(container: HTMLElement): void {
1984+
container.classList.remove(
1985+
"maplibre-gl-print-control--light",
1986+
"maplibre-gl-print-control--dark",
1987+
);
1988+
if (this._options.theme === "light") {
1989+
container.classList.add("maplibre-gl-print-control--light");
1990+
} else if (this._options.theme === "dark") {
1991+
container.classList.add("maplibre-gl-print-control--dark");
1992+
}
1993+
}
1994+
19541995
/**
19551996
* Set the export format.
19561997
*/

src/lib/core/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3146,6 +3146,16 @@ export type PrintFitMode = "contain" | "cover";
31463146
*/
31473147
export type PrintFormat = "png" | "jpeg" | "pdf" | "svg";
31483148

3149+
/**
3150+
* Panel theme for the PrintControl.
3151+
*
3152+
* - `'auto'` (default) follows the system `prefers-color-scheme`.
3153+
* - `'light'` / `'dark'` force the panel into that theme regardless of the
3154+
* system setting. Use this when the host application manages its own theme
3155+
* (e.g. a class toggled on `<html>`) so the panel can be kept in sync.
3156+
*/
3157+
export type PrintTheme = "auto" | "light" | "dark";
3158+
31493159
/**
31503160
* Options for configuring the PrintControl.
31513161
*/
@@ -3158,6 +3168,13 @@ export interface PrintControlOptions {
31583168
visible?: boolean;
31593169
/** Whether to start collapsed. Default: true. */
31603170
collapsed?: boolean;
3171+
/**
3172+
* Panel theme. `'auto'` (default) follows the system `prefers-color-scheme`.
3173+
* Set to `'light'` or `'dark'` to force a theme when the host application
3174+
* manages its own light/dark mode. Can be changed at runtime with
3175+
* {@link PrintControl.setTheme}.
3176+
*/
3177+
theme?: PrintTheme;
31613178
/** Default image format. Default: 'png'. */
31623179
format?: PrintFormat;
31633180
/** JPEG quality (0-1). Default: 0.92. */

src/lib/styles/print-control.css

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
color-scheme: light dark;
1111

1212
/* Theme tokens (light defaults). Overridden by the dark media query below
13-
so the control adapts to the system light/dark theme. */
13+
so the control adapts to the system light/dark theme, or by the explicit
14+
`--light` / `--dark` modifier classes when the host app forces a theme. */
1415
--print-panel-bg: rgba(255, 255, 255, 0.95);
1516
--print-subtle-bg: #f9fafb;
1617
--print-text: #374151;
@@ -27,6 +28,50 @@
2728
--print-feedback: #16a34a;
2829
}
2930

31+
/* Forced light theme. The host app sets the `--light` modifier (via the
32+
PrintControl `theme: 'light'` option / setTheme) so the panel stays light
33+
even when the system prefers dark. Higher specificity than the default rule
34+
above and wins over the dark media query because that query excludes this
35+
modifier. */
36+
.maplibre-gl-print-control.maplibre-gl-print-control--light {
37+
color-scheme: light;
38+
--print-panel-bg: rgba(255, 255, 255, 0.95);
39+
--print-subtle-bg: #f9fafb;
40+
--print-text: #374151;
41+
--print-muted: #6b7280;
42+
--print-subtle: #9ca3af;
43+
--print-border: #e5e7eb;
44+
--print-input-bg: #fff;
45+
--print-input-text: #111;
46+
--print-placeholder: #9ca3af;
47+
--print-hover-bg: #f3f4f6;
48+
--print-focus: #3b82f6;
49+
--print-button-bg: #fff;
50+
--print-button-text: #333;
51+
--print-feedback: #16a34a;
52+
}
53+
54+
/* Forced dark theme. The host app sets the `--dark` modifier (via the
55+
PrintControl `theme: 'dark'` option / setTheme) so the panel stays dark even
56+
when the system prefers light. */
57+
.maplibre-gl-print-control.maplibre-gl-print-control--dark {
58+
color-scheme: dark;
59+
--print-panel-bg: rgba(40, 40, 40, 0.95);
60+
--print-subtle-bg: rgba(255, 255, 255, 0.05);
61+
--print-text: #e0e0e0;
62+
--print-muted: #aaa;
63+
--print-subtle: #999;
64+
--print-border: #555;
65+
--print-input-bg: #2a2a2a;
66+
--print-input-text: #e0e0e0;
67+
--print-placeholder: #888;
68+
--print-hover-bg: rgba(255, 255, 255, 0.08);
69+
--print-focus: #3b82f6;
70+
--print-button-bg: #2a2a2a;
71+
--print-button-text: #e0e0e0;
72+
--print-feedback: #4ade80;
73+
}
74+
3075
.maplibre-gl-print-control .print-button {
3176
display: flex;
3277
align-items: center;
@@ -419,9 +464,13 @@
419464
flex: none;
420465
}
421466

422-
/* Dark mode support */
467+
/* Dark mode support (system preference). Applies only when no explicit theme
468+
is forced via the `--light` / `--dark` modifier classes, so a host app that
469+
manages its own theme is never overridden by the OS setting. */
423470
@media (prefers-color-scheme: dark) {
424-
.maplibre-gl-print-control {
471+
.maplibre-gl-print-control:not(.maplibre-gl-print-control--light):not(
472+
.maplibre-gl-print-control--dark
473+
) {
425474
--print-panel-bg: rgba(40, 40, 40, 0.95);
426475
--print-subtle-bg: rgba(255, 255, 255, 0.05);
427476
--print-text: #e0e0e0;

0 commit comments

Comments
 (0)