@sesionario/blocks 1.39.0 → 1.40.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.
@@ -0,0 +1,8 @@
1
+ type ThemeChromeProps = {
2
+ lightIcon: string;
3
+ darkIcon: string;
4
+ };
5
+ /** Pins browser chrome to an explicit light/dark choice. Renders nothing. */
6
+ declare function ThemeChrome({ lightIcon, darkIcon }: ThemeChromeProps): null;
7
+
8
+ export { ThemeChrome, type ThemeChromeProps };
@@ -0,0 +1,125 @@
1
+ "use client";
2
+ import { useTheme } from 'next-themes';
3
+ import { useEffect } from 'react';
4
+
5
+ // src/lib/brand-chrome.ts
6
+ var BRAND_CHROME_LIGHT = "#ffffff";
7
+ var BRAND_CHROME_DARK = "#171717";
8
+ var SNAP = "data-sesionario-chrome-snap";
9
+ var MEDIA = "data-sesionario-chrome-media";
10
+ var HREF = "data-sesionario-chrome-href";
11
+ var CONTENT = "data-sesionario-chrome-content";
12
+ var CREATED = "data-sesionario-chrome-created";
13
+ function chromeColor(theme) {
14
+ switch (theme) {
15
+ case "light":
16
+ return BRAND_CHROME_LIGHT;
17
+ case "dark":
18
+ return BRAND_CHROME_DARK;
19
+ default: {
20
+ const unreachable = theme;
21
+ return unreachable;
22
+ }
23
+ }
24
+ }
25
+ function iconUrl(theme, icons) {
26
+ switch (theme) {
27
+ case "light":
28
+ return icons.light;
29
+ case "dark":
30
+ return icons.dark;
31
+ default: {
32
+ const unreachable = theme;
33
+ return unreachable;
34
+ }
35
+ }
36
+ }
37
+ function snapshot(el, attr, store) {
38
+ if (el.hasAttribute(SNAP)) return;
39
+ if (el.hasAttribute(attr)) {
40
+ el.setAttribute(store, el.getAttribute(attr) ?? "");
41
+ }
42
+ }
43
+ function restoreAttr(el, attr, store) {
44
+ if (el.hasAttribute(store)) {
45
+ el.setAttribute(attr, el.getAttribute(store) ?? "");
46
+ el.removeAttribute(store);
47
+ return;
48
+ }
49
+ el.removeAttribute(attr);
50
+ }
51
+ function restoreSnap(el, pairs) {
52
+ if (!el.hasAttribute(SNAP)) return;
53
+ for (const [attr, store] of pairs) {
54
+ restoreAttr(el, attr, store);
55
+ }
56
+ el.removeAttribute(SNAP);
57
+ }
58
+ function applyThemeChrome(doc, theme, icons) {
59
+ const created = doc.querySelectorAll(`meta[${CREATED}]`);
60
+ if (theme === void 0) {
61
+ created.forEach((node) => node.remove());
62
+ doc.querySelectorAll('meta[name="theme-color"]').forEach(
63
+ (node) => restoreSnap(node, [
64
+ ["content", CONTENT],
65
+ ["media", MEDIA]
66
+ ])
67
+ );
68
+ doc.querySelectorAll('link[rel="icon"]').forEach(
69
+ (node) => restoreSnap(node, [
70
+ ["href", HREF],
71
+ ["media", MEDIA]
72
+ ])
73
+ );
74
+ return;
75
+ }
76
+ const color = chromeColor(theme);
77
+ const href = iconUrl(theme, icons);
78
+ const metas = doc.querySelectorAll('meta[name="theme-color"]');
79
+ if (metas.length === 0) {
80
+ const meta = doc.createElement("meta");
81
+ meta.setAttribute("name", "theme-color");
82
+ meta.setAttribute("content", color);
83
+ meta.setAttribute(CREATED, "1");
84
+ doc.head.append(meta);
85
+ } else {
86
+ metas.forEach((meta) => {
87
+ snapshot(meta, "content", CONTENT);
88
+ snapshot(meta, "media", MEDIA);
89
+ meta.setAttribute(SNAP, "1");
90
+ meta.setAttribute("content", color);
91
+ meta.removeAttribute("media");
92
+ });
93
+ }
94
+ doc.querySelectorAll('link[rel="icon"]').forEach((link, index) => {
95
+ snapshot(link, "href", HREF);
96
+ snapshot(link, "media", MEDIA);
97
+ link.setAttribute(SNAP, "1");
98
+ if (index === 0) {
99
+ link.setAttribute("href", href);
100
+ link.removeAttribute("media");
101
+ return;
102
+ }
103
+ link.setAttribute("media", "not all");
104
+ });
105
+ }
106
+
107
+ // src/components/ThemeChrome.tsx
108
+ function explicitTheme(theme) {
109
+ if (theme === "light" || theme === "dark") return theme;
110
+ return void 0;
111
+ }
112
+ function ThemeChrome({ lightIcon, darkIcon }) {
113
+ const { theme } = useTheme();
114
+ useEffect(() => {
115
+ applyThemeChrome(document, explicitTheme(theme), {
116
+ light: lightIcon,
117
+ dark: darkIcon
118
+ });
119
+ }, [theme, lightIcon, darkIcon]);
120
+ return null;
121
+ }
122
+
123
+ export { ThemeChrome };
124
+ //# sourceMappingURL=theme-chrome.js.map
125
+ //# sourceMappingURL=theme-chrome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/brand-chrome.ts","../src/components/ThemeChrome.tsx"],"names":[],"mappings":"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","file":"theme-chrome.js","sourcesContent":["/** Page background behind the browser chrome. Light and dark match `--background`. */\nexport const BRAND_CHROME_LIGHT = \"#ffffff\";\nexport const BRAND_CHROME_DARK = \"#171717\";\n\nexport const brandViewport = {\n\twidth: \"device-width\",\n\tinitialScale: 1,\n\tviewportFit: \"cover\" as const,\n\tcolorScheme: \"light dark\" as const,\n\tthemeColor: [\n\t\t{ media: \"(prefers-color-scheme: light)\", color: BRAND_CHROME_LIGHT },\n\t\t{ media: \"(prefers-color-scheme: dark)\", color: BRAND_CHROME_DARK },\n\t],\n};\n\nexport type BrandIconInput = {\n\tlight: string;\n\tdark: string;\n\tapple: string;\n\tfallback?: string;\n\tsvg?: string;\n};\n\ntype BrandIcon = { url: string; media?: string; type?: string };\n\n/** OS-sensitive favicon set. Apps pass their own public paths. */\nexport function brandIcons(input: BrandIconInput): {\n\ticon: BrandIcon[];\n\tapple: string;\n} {\n\tconst icon: BrandIcon[] = [\n\t\t{ url: input.light, media: \"(prefers-color-scheme: light)\" },\n\t\t{ url: input.dark, media: \"(prefers-color-scheme: dark)\" },\n\t];\n\tif (input.svg) {\n\t\ticon.push({ url: input.svg, type: \"image/svg+xml\" });\n\t}\n\tif (input.fallback) {\n\t\ticon.push({ url: input.fallback });\n\t}\n\treturn { icon, apple: input.apple };\n}\n\n/** `default` lets iOS paint the status bar from `theme-color`. */\nexport function brandAppleWebApp(title: string): {\n\tcapable: true;\n\tstatusBarStyle: \"default\";\n\ttitle: string;\n} {\n\treturn {\n\t\tcapable: true,\n\t\tstatusBarStyle: \"default\",\n\t\ttitle,\n\t};\n}\n\nexport type ExplicitChromeTheme = \"light\" | \"dark\";\n\nconst SNAP = \"data-sesionario-chrome-snap\";\nconst MEDIA = \"data-sesionario-chrome-media\";\nconst HREF = \"data-sesionario-chrome-href\";\nconst CONTENT = \"data-sesionario-chrome-content\";\nconst CREATED = \"data-sesionario-chrome-created\";\n\nfunction chromeColor(theme: ExplicitChromeTheme): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn BRAND_CHROME_LIGHT;\n\t\tcase \"dark\":\n\t\t\treturn BRAND_CHROME_DARK;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction iconUrl(\n\ttheme: ExplicitChromeTheme,\n\ticons: { light: string; dark: string },\n): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn icons.light;\n\t\tcase \"dark\":\n\t\t\treturn icons.dark;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction snapshot(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(SNAP)) return;\n\tif (el.hasAttribute(attr)) {\n\t\tel.setAttribute(store, el.getAttribute(attr) ?? \"\");\n\t}\n}\n\nfunction restoreAttr(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(store)) {\n\t\tel.setAttribute(attr, el.getAttribute(store) ?? \"\");\n\t\tel.removeAttribute(store);\n\t\treturn;\n\t}\n\tel.removeAttribute(attr);\n}\n\nfunction restoreSnap(el: Element, pairs: Array<[string, string]>) {\n\tif (!el.hasAttribute(SNAP)) return;\n\tfor (const [attr, store] of pairs) {\n\t\trestoreAttr(el, attr, store);\n\t}\n\tel.removeAttribute(SNAP);\n}\n\n/**\n * When the in-app theme is explicit, pin `theme-color` and the tab icon to\n * that choice. `system` and an unresolved theme put the OS media queries back.\n */\nexport function applyThemeChrome(\n\tdoc: Document,\n\ttheme: ExplicitChromeTheme | undefined,\n\ticons: { light: string; dark: string },\n): void {\n\tconst created = doc.querySelectorAll(`meta[${CREATED}]`);\n\tif (theme === undefined) {\n\t\tcreated.forEach((node) => node.remove());\n\t\tdoc.querySelectorAll('meta[name=\"theme-color\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"content\", CONTENT],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"href\", HREF],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\treturn;\n\t}\n\n\tconst color = chromeColor(theme);\n\tconst href = iconUrl(theme, icons);\n\tconst metas = doc.querySelectorAll('meta[name=\"theme-color\"]');\n\tif (metas.length === 0) {\n\t\tconst meta = doc.createElement(\"meta\");\n\t\tmeta.setAttribute(\"name\", \"theme-color\");\n\t\tmeta.setAttribute(\"content\", color);\n\t\tmeta.setAttribute(CREATED, \"1\");\n\t\tdoc.head.append(meta);\n\t} else {\n\t\tmetas.forEach((meta) => {\n\t\t\tsnapshot(meta, \"content\", CONTENT);\n\t\t\tsnapshot(meta, \"media\", MEDIA);\n\t\t\tmeta.setAttribute(SNAP, \"1\");\n\t\t\tmeta.setAttribute(\"content\", color);\n\t\t\tmeta.removeAttribute(\"media\");\n\t\t});\n\t}\n\n\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((link, index) => {\n\t\tsnapshot(link, \"href\", HREF);\n\t\tsnapshot(link, \"media\", MEDIA);\n\t\tlink.setAttribute(SNAP, \"1\");\n\t\tif (index === 0) {\n\t\t\tlink.setAttribute(\"href\", href);\n\t\t\tlink.removeAttribute(\"media\");\n\t\t\treturn;\n\t\t}\n\t\tlink.setAttribute(\"media\", \"not all\");\n\t});\n}\n","\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { useEffect } from \"react\";\nimport {\n\tapplyThemeChrome,\n\ttype ExplicitChromeTheme,\n} from \"../lib/brand-chrome\";\n\nexport type ThemeChromeProps = {\n\tlightIcon: string;\n\tdarkIcon: string;\n};\n\nfunction explicitTheme(\n\ttheme: string | undefined,\n): ExplicitChromeTheme | undefined {\n\tif (theme === \"light\" || theme === \"dark\") return theme;\n\treturn undefined;\n}\n\n/** Pins browser chrome to an explicit light/dark choice. Renders nothing. */\nexport function ThemeChrome({ lightIcon, darkIcon }: ThemeChromeProps) {\n\tconst { theme } = useTheme();\n\n\tuseEffect(() => {\n\t\tapplyThemeChrome(document, explicitTheme(theme), {\n\t\t\tlight: lightIcon,\n\t\t\tdark: darkIcon,\n\t\t});\n\t}, [theme, lightIcon, darkIcon]);\n\n\treturn null;\n}\n"]}
package/dist/theme.cjs CHANGED
@@ -1,5 +1,137 @@
1
1
  'use strict';
2
2
 
3
+ // src/lib/brand-chrome.ts
4
+ var BRAND_CHROME_LIGHT = "#ffffff";
5
+ var BRAND_CHROME_DARK = "#171717";
6
+ var brandViewport = {
7
+ width: "device-width",
8
+ initialScale: 1,
9
+ viewportFit: "cover",
10
+ colorScheme: "light dark",
11
+ themeColor: [
12
+ { media: "(prefers-color-scheme: light)", color: BRAND_CHROME_LIGHT },
13
+ { media: "(prefers-color-scheme: dark)", color: BRAND_CHROME_DARK }
14
+ ]
15
+ };
16
+ function brandIcons(input) {
17
+ const icon = [
18
+ { url: input.light, media: "(prefers-color-scheme: light)" },
19
+ { url: input.dark, media: "(prefers-color-scheme: dark)" }
20
+ ];
21
+ if (input.svg) {
22
+ icon.push({ url: input.svg, type: "image/svg+xml" });
23
+ }
24
+ if (input.fallback) {
25
+ icon.push({ url: input.fallback });
26
+ }
27
+ return { icon, apple: input.apple };
28
+ }
29
+ function brandAppleWebApp(title) {
30
+ return {
31
+ capable: true,
32
+ statusBarStyle: "default",
33
+ title
34
+ };
35
+ }
36
+ var SNAP = "data-sesionario-chrome-snap";
37
+ var MEDIA = "data-sesionario-chrome-media";
38
+ var HREF = "data-sesionario-chrome-href";
39
+ var CONTENT = "data-sesionario-chrome-content";
40
+ var CREATED = "data-sesionario-chrome-created";
41
+ function chromeColor(theme) {
42
+ switch (theme) {
43
+ case "light":
44
+ return BRAND_CHROME_LIGHT;
45
+ case "dark":
46
+ return BRAND_CHROME_DARK;
47
+ default: {
48
+ const unreachable = theme;
49
+ return unreachable;
50
+ }
51
+ }
52
+ }
53
+ function iconUrl(theme, icons) {
54
+ switch (theme) {
55
+ case "light":
56
+ return icons.light;
57
+ case "dark":
58
+ return icons.dark;
59
+ default: {
60
+ const unreachable = theme;
61
+ return unreachable;
62
+ }
63
+ }
64
+ }
65
+ function snapshot(el, attr, store) {
66
+ if (el.hasAttribute(SNAP)) return;
67
+ if (el.hasAttribute(attr)) {
68
+ el.setAttribute(store, el.getAttribute(attr) ?? "");
69
+ }
70
+ }
71
+ function restoreAttr(el, attr, store) {
72
+ if (el.hasAttribute(store)) {
73
+ el.setAttribute(attr, el.getAttribute(store) ?? "");
74
+ el.removeAttribute(store);
75
+ return;
76
+ }
77
+ el.removeAttribute(attr);
78
+ }
79
+ function restoreSnap(el, pairs) {
80
+ if (!el.hasAttribute(SNAP)) return;
81
+ for (const [attr, store] of pairs) {
82
+ restoreAttr(el, attr, store);
83
+ }
84
+ el.removeAttribute(SNAP);
85
+ }
86
+ function applyThemeChrome(doc, theme, icons) {
87
+ const created = doc.querySelectorAll(`meta[${CREATED}]`);
88
+ if (theme === void 0) {
89
+ created.forEach((node) => node.remove());
90
+ doc.querySelectorAll('meta[name="theme-color"]').forEach(
91
+ (node) => restoreSnap(node, [
92
+ ["content", CONTENT],
93
+ ["media", MEDIA]
94
+ ])
95
+ );
96
+ doc.querySelectorAll('link[rel="icon"]').forEach(
97
+ (node) => restoreSnap(node, [
98
+ ["href", HREF],
99
+ ["media", MEDIA]
100
+ ])
101
+ );
102
+ return;
103
+ }
104
+ const color = chromeColor(theme);
105
+ const href = iconUrl(theme, icons);
106
+ const metas = doc.querySelectorAll('meta[name="theme-color"]');
107
+ if (metas.length === 0) {
108
+ const meta = doc.createElement("meta");
109
+ meta.setAttribute("name", "theme-color");
110
+ meta.setAttribute("content", color);
111
+ meta.setAttribute(CREATED, "1");
112
+ doc.head.append(meta);
113
+ } else {
114
+ metas.forEach((meta) => {
115
+ snapshot(meta, "content", CONTENT);
116
+ snapshot(meta, "media", MEDIA);
117
+ meta.setAttribute(SNAP, "1");
118
+ meta.setAttribute("content", color);
119
+ meta.removeAttribute("media");
120
+ });
121
+ }
122
+ doc.querySelectorAll('link[rel="icon"]').forEach((link, index) => {
123
+ snapshot(link, "href", HREF);
124
+ snapshot(link, "media", MEDIA);
125
+ link.setAttribute(SNAP, "1");
126
+ if (index === 0) {
127
+ link.setAttribute("href", href);
128
+ link.removeAttribute("media");
129
+ return;
130
+ }
131
+ link.setAttribute("media", "not all");
132
+ });
133
+ }
134
+
3
135
  // src/lib/theme-boot.ts
4
136
  var THEME_STORAGE_KEY = "sesionario-theme";
5
137
  var themeColorSchemeCss = "html.light{color-scheme:light}html.dark{color-scheme:dark}";
@@ -17,7 +149,13 @@ function themeBootScript(storageKey = THEME_STORAGE_KEY) {
17
149
  return `(function(){try{var k=${key};function parse(v){return v==="light"||v==="dark"||v==="system"?v:null}function readCookie(){var name=encodeURIComponent(k);var parts=document.cookie.split(";");for(var i=0;i<parts.length;i++){var part=parts[i].trim();var eq=part.indexOf("=");if(eq<0)continue;if(part.slice(0,eq)===name){try{return decodeURIComponent(part.slice(eq+1))}catch(e){return null}}}return null}var cookie=parse(readCookie());var stored=null;try{stored=parse(localStorage.getItem(k))}catch(e){}if(cookie){try{localStorage.setItem(k,cookie)}catch(e){}}var theme=cookie||stored||"system";var dark=theme==="dark"||(theme==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches);var el=document.documentElement;el.classList.remove("light","dark");el.classList.add(dark?"dark":"light");el.style.colorScheme=dark?"dark":"light"}catch(e){}})();`;
18
150
  }
19
151
 
152
+ exports.BRAND_CHROME_DARK = BRAND_CHROME_DARK;
153
+ exports.BRAND_CHROME_LIGHT = BRAND_CHROME_LIGHT;
20
154
  exports.THEME_STORAGE_KEY = THEME_STORAGE_KEY;
155
+ exports.applyThemeChrome = applyThemeChrome;
156
+ exports.brandAppleWebApp = brandAppleWebApp;
157
+ exports.brandIcons = brandIcons;
158
+ exports.brandViewport = brandViewport;
21
159
  exports.htmlThemeClass = htmlThemeClass;
22
160
  exports.parseThemeChoice = parseThemeChoice;
23
161
  exports.themeBootScript = themeBootScript;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/theme-boot.ts"],"names":[],"mappings":";;;AAAO,IAAM,iBAAA,GAAoB;AAK1B,IAAM,mBAAA,GACZ;AAEM,SAAS,iBACf,KAAA,EACqB;AACrB,EAAA,IAAI,UAAU,OAAA,IAAW,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,UAAU,OAAO,KAAA;AACxE,EAAA,OAAO,IAAA;AACR;AAOO,SAAS,eACf,MAAA,EAC+B;AAC/B,EAAA,MAAM,KAAA,GAAQ,iBAAiB,MAAM,CAAA;AACrC,EAAA,IAAI,KAAA,KAAU,OAAA,IAAW,KAAA,KAAU,MAAA,EAAQ,OAAO,KAAA;AAClD,EAAA,OAAO,MAAA;AACR;AAOO,SAAS,eAAA,CACf,aAAqB,iBAAA,EACZ;AACT,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,UAAU,CAAA;AACrC,EAAA,OAAO,yBAAyB,GAAG,CAAA,2zBAAA,CAAA;AACpC","file":"theme.cjs","sourcesContent":["export const THEME_STORAGE_KEY = \"sesionario-theme\";\n\nexport type ThemeChoice = \"light\" | \"dark\" | \"system\";\n\n/** Keeps `color-scheme` aligned with the `light` / `dark` class next-themes toggles. */\nexport const themeColorSchemeCss =\n\t\"html.light{color-scheme:light}html.dark{color-scheme:dark}\";\n\nexport function parseThemeChoice(\n\tvalue: string | null | undefined,\n): ThemeChoice | null {\n\tif (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n\treturn null;\n}\n\n/**\n * Explicit light/dark cookies paint with the server HTML. `system` and a\n * missing cookie stay unset so the blocking script can follow the OS\n * preference before the first paint.\n */\nexport function htmlThemeClass(\n\tcookie: string | null | undefined,\n): \"light\" | \"dark\" | undefined {\n\tconst theme = parseThemeChoice(cookie);\n\tif (theme === \"light\" || theme === \"dark\") return theme;\n\treturn undefined;\n}\n\n/**\n * Blocking script for the start of `<body>`. Reads the cross-subdomain theme\n * cookie first, copies it into this origin's localStorage (next-themes only\n * reads localStorage), then sets `light`/`dark` on `<html>` before paint.\n */\nexport function themeBootScript(\n\tstorageKey: string = THEME_STORAGE_KEY,\n): string {\n\tconst key = JSON.stringify(storageKey);\n\treturn `(function(){try{var k=${key};function parse(v){return v===\"light\"||v===\"dark\"||v===\"system\"?v:null}function readCookie(){var name=encodeURIComponent(k);var parts=document.cookie.split(\";\");for(var i=0;i<parts.length;i++){var part=parts[i].trim();var eq=part.indexOf(\"=\");if(eq<0)continue;if(part.slice(0,eq)===name){try{return decodeURIComponent(part.slice(eq+1))}catch(e){return null}}}return null}var cookie=parse(readCookie());var stored=null;try{stored=parse(localStorage.getItem(k))}catch(e){}if(cookie){try{localStorage.setItem(k,cookie)}catch(e){}}var theme=cookie||stored||\"system\";var dark=theme===\"dark\"||(theme===\"system\"&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var el=document.documentElement;el.classList.remove(\"light\",\"dark\");el.classList.add(dark?\"dark\":\"light\");el.style.colorScheme=dark?\"dark\":\"light\"}catch(e){}})();`;\n}\n"]}
1
+ {"version":3,"sources":["../src/lib/brand-chrome.ts","../src/lib/theme-boot.ts"],"names":[],"mappings":"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","file":"theme.cjs","sourcesContent":["/** Page background behind the browser chrome. Light and dark match `--background`. */\nexport const BRAND_CHROME_LIGHT = \"#ffffff\";\nexport const BRAND_CHROME_DARK = \"#171717\";\n\nexport const brandViewport = {\n\twidth: \"device-width\",\n\tinitialScale: 1,\n\tviewportFit: \"cover\" as const,\n\tcolorScheme: \"light dark\" as const,\n\tthemeColor: [\n\t\t{ media: \"(prefers-color-scheme: light)\", color: BRAND_CHROME_LIGHT },\n\t\t{ media: \"(prefers-color-scheme: dark)\", color: BRAND_CHROME_DARK },\n\t],\n};\n\nexport type BrandIconInput = {\n\tlight: string;\n\tdark: string;\n\tapple: string;\n\tfallback?: string;\n\tsvg?: string;\n};\n\ntype BrandIcon = { url: string; media?: string; type?: string };\n\n/** OS-sensitive favicon set. Apps pass their own public paths. */\nexport function brandIcons(input: BrandIconInput): {\n\ticon: BrandIcon[];\n\tapple: string;\n} {\n\tconst icon: BrandIcon[] = [\n\t\t{ url: input.light, media: \"(prefers-color-scheme: light)\" },\n\t\t{ url: input.dark, media: \"(prefers-color-scheme: dark)\" },\n\t];\n\tif (input.svg) {\n\t\ticon.push({ url: input.svg, type: \"image/svg+xml\" });\n\t}\n\tif (input.fallback) {\n\t\ticon.push({ url: input.fallback });\n\t}\n\treturn { icon, apple: input.apple };\n}\n\n/** `default` lets iOS paint the status bar from `theme-color`. */\nexport function brandAppleWebApp(title: string): {\n\tcapable: true;\n\tstatusBarStyle: \"default\";\n\ttitle: string;\n} {\n\treturn {\n\t\tcapable: true,\n\t\tstatusBarStyle: \"default\",\n\t\ttitle,\n\t};\n}\n\nexport type ExplicitChromeTheme = \"light\" | \"dark\";\n\nconst SNAP = \"data-sesionario-chrome-snap\";\nconst MEDIA = \"data-sesionario-chrome-media\";\nconst HREF = \"data-sesionario-chrome-href\";\nconst CONTENT = \"data-sesionario-chrome-content\";\nconst CREATED = \"data-sesionario-chrome-created\";\n\nfunction chromeColor(theme: ExplicitChromeTheme): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn BRAND_CHROME_LIGHT;\n\t\tcase \"dark\":\n\t\t\treturn BRAND_CHROME_DARK;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction iconUrl(\n\ttheme: ExplicitChromeTheme,\n\ticons: { light: string; dark: string },\n): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn icons.light;\n\t\tcase \"dark\":\n\t\t\treturn icons.dark;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction snapshot(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(SNAP)) return;\n\tif (el.hasAttribute(attr)) {\n\t\tel.setAttribute(store, el.getAttribute(attr) ?? \"\");\n\t}\n}\n\nfunction restoreAttr(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(store)) {\n\t\tel.setAttribute(attr, el.getAttribute(store) ?? \"\");\n\t\tel.removeAttribute(store);\n\t\treturn;\n\t}\n\tel.removeAttribute(attr);\n}\n\nfunction restoreSnap(el: Element, pairs: Array<[string, string]>) {\n\tif (!el.hasAttribute(SNAP)) return;\n\tfor (const [attr, store] of pairs) {\n\t\trestoreAttr(el, attr, store);\n\t}\n\tel.removeAttribute(SNAP);\n}\n\n/**\n * When the in-app theme is explicit, pin `theme-color` and the tab icon to\n * that choice. `system` and an unresolved theme put the OS media queries back.\n */\nexport function applyThemeChrome(\n\tdoc: Document,\n\ttheme: ExplicitChromeTheme | undefined,\n\ticons: { light: string; dark: string },\n): void {\n\tconst created = doc.querySelectorAll(`meta[${CREATED}]`);\n\tif (theme === undefined) {\n\t\tcreated.forEach((node) => node.remove());\n\t\tdoc.querySelectorAll('meta[name=\"theme-color\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"content\", CONTENT],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"href\", HREF],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\treturn;\n\t}\n\n\tconst color = chromeColor(theme);\n\tconst href = iconUrl(theme, icons);\n\tconst metas = doc.querySelectorAll('meta[name=\"theme-color\"]');\n\tif (metas.length === 0) {\n\t\tconst meta = doc.createElement(\"meta\");\n\t\tmeta.setAttribute(\"name\", \"theme-color\");\n\t\tmeta.setAttribute(\"content\", color);\n\t\tmeta.setAttribute(CREATED, \"1\");\n\t\tdoc.head.append(meta);\n\t} else {\n\t\tmetas.forEach((meta) => {\n\t\t\tsnapshot(meta, \"content\", CONTENT);\n\t\t\tsnapshot(meta, \"media\", MEDIA);\n\t\t\tmeta.setAttribute(SNAP, \"1\");\n\t\t\tmeta.setAttribute(\"content\", color);\n\t\t\tmeta.removeAttribute(\"media\");\n\t\t});\n\t}\n\n\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((link, index) => {\n\t\tsnapshot(link, \"href\", HREF);\n\t\tsnapshot(link, \"media\", MEDIA);\n\t\tlink.setAttribute(SNAP, \"1\");\n\t\tif (index === 0) {\n\t\t\tlink.setAttribute(\"href\", href);\n\t\t\tlink.removeAttribute(\"media\");\n\t\t\treturn;\n\t\t}\n\t\tlink.setAttribute(\"media\", \"not all\");\n\t});\n}\n","export const THEME_STORAGE_KEY = \"sesionario-theme\";\n\nexport type ThemeChoice = \"light\" | \"dark\" | \"system\";\n\n/** Keeps `color-scheme` aligned with the `light` / `dark` class next-themes toggles. */\nexport const themeColorSchemeCss =\n\t\"html.light{color-scheme:light}html.dark{color-scheme:dark}\";\n\nexport function parseThemeChoice(\n\tvalue: string | null | undefined,\n): ThemeChoice | null {\n\tif (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n\treturn null;\n}\n\n/**\n * Explicit light/dark cookies paint with the server HTML. `system` and a\n * missing cookie stay unset so the blocking script can follow the OS\n * preference before the first paint.\n */\nexport function htmlThemeClass(\n\tcookie: string | null | undefined,\n): \"light\" | \"dark\" | undefined {\n\tconst theme = parseThemeChoice(cookie);\n\tif (theme === \"light\" || theme === \"dark\") return theme;\n\treturn undefined;\n}\n\n/**\n * Blocking script for the start of `<body>`. Reads the cross-subdomain theme\n * cookie first, copies it into this origin's localStorage (next-themes only\n * reads localStorage), then sets `light`/`dark` on `<html>` before paint.\n */\nexport function themeBootScript(\n\tstorageKey: string = THEME_STORAGE_KEY,\n): string {\n\tconst key = JSON.stringify(storageKey);\n\treturn `(function(){try{var k=${key};function parse(v){return v===\"light\"||v===\"dark\"||v===\"system\"?v:null}function readCookie(){var name=encodeURIComponent(k);var parts=document.cookie.split(\";\");for(var i=0;i<parts.length;i++){var part=parts[i].trim();var eq=part.indexOf(\"=\");if(eq<0)continue;if(part.slice(0,eq)===name){try{return decodeURIComponent(part.slice(eq+1))}catch(e){return null}}}return null}var cookie=parse(readCookie());var stored=null;try{stored=parse(localStorage.getItem(k))}catch(e){}if(cookie){try{localStorage.setItem(k,cookie)}catch(e){}}var theme=cookie||stored||\"system\";var dark=theme===\"dark\"||(theme===\"system\"&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var el=document.documentElement;el.classList.remove(\"light\",\"dark\");el.classList.add(dark?\"dark\":\"light\");el.style.colorScheme=dark?\"dark\":\"light\"}catch(e){}})();`;\n}\n"]}
package/dist/theme.d.cts CHANGED
@@ -1,3 +1,49 @@
1
+ /** Page background behind the browser chrome. Light and dark match `--background`. */
2
+ declare const BRAND_CHROME_LIGHT = "#ffffff";
3
+ declare const BRAND_CHROME_DARK = "#171717";
4
+ declare const brandViewport: {
5
+ width: string;
6
+ initialScale: number;
7
+ viewportFit: "cover";
8
+ colorScheme: "light dark";
9
+ themeColor: {
10
+ media: string;
11
+ color: string;
12
+ }[];
13
+ };
14
+ type BrandIconInput = {
15
+ light: string;
16
+ dark: string;
17
+ apple: string;
18
+ fallback?: string;
19
+ svg?: string;
20
+ };
21
+ type BrandIcon = {
22
+ url: string;
23
+ media?: string;
24
+ type?: string;
25
+ };
26
+ /** OS-sensitive favicon set. Apps pass their own public paths. */
27
+ declare function brandIcons(input: BrandIconInput): {
28
+ icon: BrandIcon[];
29
+ apple: string;
30
+ };
31
+ /** `default` lets iOS paint the status bar from `theme-color`. */
32
+ declare function brandAppleWebApp(title: string): {
33
+ capable: true;
34
+ statusBarStyle: "default";
35
+ title: string;
36
+ };
37
+ type ExplicitChromeTheme = "light" | "dark";
38
+ /**
39
+ * When the in-app theme is explicit, pin `theme-color` and the tab icon to
40
+ * that choice. `system` and an unresolved theme put the OS media queries back.
41
+ */
42
+ declare function applyThemeChrome(doc: Document, theme: ExplicitChromeTheme | undefined, icons: {
43
+ light: string;
44
+ dark: string;
45
+ }): void;
46
+
1
47
  declare const THEME_STORAGE_KEY = "sesionario-theme";
2
48
  type ThemeChoice = "light" | "dark" | "system";
3
49
  /** Keeps `color-scheme` aligned with the `light` / `dark` class next-themes toggles. */
@@ -16,4 +62,4 @@ declare function htmlThemeClass(cookie: string | null | undefined): "light" | "d
16
62
  */
17
63
  declare function themeBootScript(storageKey?: string): string;
18
64
 
19
- export { THEME_STORAGE_KEY, type ThemeChoice, htmlThemeClass, parseThemeChoice, themeBootScript, themeColorSchemeCss };
65
+ export { BRAND_CHROME_DARK, BRAND_CHROME_LIGHT, type BrandIconInput, type ExplicitChromeTheme, THEME_STORAGE_KEY, type ThemeChoice, applyThemeChrome, brandAppleWebApp, brandIcons, brandViewport, htmlThemeClass, parseThemeChoice, themeBootScript, themeColorSchemeCss };
package/dist/theme.d.ts CHANGED
@@ -1,3 +1,49 @@
1
+ /** Page background behind the browser chrome. Light and dark match `--background`. */
2
+ declare const BRAND_CHROME_LIGHT = "#ffffff";
3
+ declare const BRAND_CHROME_DARK = "#171717";
4
+ declare const brandViewport: {
5
+ width: string;
6
+ initialScale: number;
7
+ viewportFit: "cover";
8
+ colorScheme: "light dark";
9
+ themeColor: {
10
+ media: string;
11
+ color: string;
12
+ }[];
13
+ };
14
+ type BrandIconInput = {
15
+ light: string;
16
+ dark: string;
17
+ apple: string;
18
+ fallback?: string;
19
+ svg?: string;
20
+ };
21
+ type BrandIcon = {
22
+ url: string;
23
+ media?: string;
24
+ type?: string;
25
+ };
26
+ /** OS-sensitive favicon set. Apps pass their own public paths. */
27
+ declare function brandIcons(input: BrandIconInput): {
28
+ icon: BrandIcon[];
29
+ apple: string;
30
+ };
31
+ /** `default` lets iOS paint the status bar from `theme-color`. */
32
+ declare function brandAppleWebApp(title: string): {
33
+ capable: true;
34
+ statusBarStyle: "default";
35
+ title: string;
36
+ };
37
+ type ExplicitChromeTheme = "light" | "dark";
38
+ /**
39
+ * When the in-app theme is explicit, pin `theme-color` and the tab icon to
40
+ * that choice. `system` and an unresolved theme put the OS media queries back.
41
+ */
42
+ declare function applyThemeChrome(doc: Document, theme: ExplicitChromeTheme | undefined, icons: {
43
+ light: string;
44
+ dark: string;
45
+ }): void;
46
+
1
47
  declare const THEME_STORAGE_KEY = "sesionario-theme";
2
48
  type ThemeChoice = "light" | "dark" | "system";
3
49
  /** Keeps `color-scheme` aligned with the `light` / `dark` class next-themes toggles. */
@@ -16,4 +62,4 @@ declare function htmlThemeClass(cookie: string | null | undefined): "light" | "d
16
62
  */
17
63
  declare function themeBootScript(storageKey?: string): string;
18
64
 
19
- export { THEME_STORAGE_KEY, type ThemeChoice, htmlThemeClass, parseThemeChoice, themeBootScript, themeColorSchemeCss };
65
+ export { BRAND_CHROME_DARK, BRAND_CHROME_LIGHT, type BrandIconInput, type ExplicitChromeTheme, THEME_STORAGE_KEY, type ThemeChoice, applyThemeChrome, brandAppleWebApp, brandIcons, brandViewport, htmlThemeClass, parseThemeChoice, themeBootScript, themeColorSchemeCss };
package/dist/theme.js CHANGED
@@ -1,3 +1,135 @@
1
+ // src/lib/brand-chrome.ts
2
+ var BRAND_CHROME_LIGHT = "#ffffff";
3
+ var BRAND_CHROME_DARK = "#171717";
4
+ var brandViewport = {
5
+ width: "device-width",
6
+ initialScale: 1,
7
+ viewportFit: "cover",
8
+ colorScheme: "light dark",
9
+ themeColor: [
10
+ { media: "(prefers-color-scheme: light)", color: BRAND_CHROME_LIGHT },
11
+ { media: "(prefers-color-scheme: dark)", color: BRAND_CHROME_DARK }
12
+ ]
13
+ };
14
+ function brandIcons(input) {
15
+ const icon = [
16
+ { url: input.light, media: "(prefers-color-scheme: light)" },
17
+ { url: input.dark, media: "(prefers-color-scheme: dark)" }
18
+ ];
19
+ if (input.svg) {
20
+ icon.push({ url: input.svg, type: "image/svg+xml" });
21
+ }
22
+ if (input.fallback) {
23
+ icon.push({ url: input.fallback });
24
+ }
25
+ return { icon, apple: input.apple };
26
+ }
27
+ function brandAppleWebApp(title) {
28
+ return {
29
+ capable: true,
30
+ statusBarStyle: "default",
31
+ title
32
+ };
33
+ }
34
+ var SNAP = "data-sesionario-chrome-snap";
35
+ var MEDIA = "data-sesionario-chrome-media";
36
+ var HREF = "data-sesionario-chrome-href";
37
+ var CONTENT = "data-sesionario-chrome-content";
38
+ var CREATED = "data-sesionario-chrome-created";
39
+ function chromeColor(theme) {
40
+ switch (theme) {
41
+ case "light":
42
+ return BRAND_CHROME_LIGHT;
43
+ case "dark":
44
+ return BRAND_CHROME_DARK;
45
+ default: {
46
+ const unreachable = theme;
47
+ return unreachable;
48
+ }
49
+ }
50
+ }
51
+ function iconUrl(theme, icons) {
52
+ switch (theme) {
53
+ case "light":
54
+ return icons.light;
55
+ case "dark":
56
+ return icons.dark;
57
+ default: {
58
+ const unreachable = theme;
59
+ return unreachable;
60
+ }
61
+ }
62
+ }
63
+ function snapshot(el, attr, store) {
64
+ if (el.hasAttribute(SNAP)) return;
65
+ if (el.hasAttribute(attr)) {
66
+ el.setAttribute(store, el.getAttribute(attr) ?? "");
67
+ }
68
+ }
69
+ function restoreAttr(el, attr, store) {
70
+ if (el.hasAttribute(store)) {
71
+ el.setAttribute(attr, el.getAttribute(store) ?? "");
72
+ el.removeAttribute(store);
73
+ return;
74
+ }
75
+ el.removeAttribute(attr);
76
+ }
77
+ function restoreSnap(el, pairs) {
78
+ if (!el.hasAttribute(SNAP)) return;
79
+ for (const [attr, store] of pairs) {
80
+ restoreAttr(el, attr, store);
81
+ }
82
+ el.removeAttribute(SNAP);
83
+ }
84
+ function applyThemeChrome(doc, theme, icons) {
85
+ const created = doc.querySelectorAll(`meta[${CREATED}]`);
86
+ if (theme === void 0) {
87
+ created.forEach((node) => node.remove());
88
+ doc.querySelectorAll('meta[name="theme-color"]').forEach(
89
+ (node) => restoreSnap(node, [
90
+ ["content", CONTENT],
91
+ ["media", MEDIA]
92
+ ])
93
+ );
94
+ doc.querySelectorAll('link[rel="icon"]').forEach(
95
+ (node) => restoreSnap(node, [
96
+ ["href", HREF],
97
+ ["media", MEDIA]
98
+ ])
99
+ );
100
+ return;
101
+ }
102
+ const color = chromeColor(theme);
103
+ const href = iconUrl(theme, icons);
104
+ const metas = doc.querySelectorAll('meta[name="theme-color"]');
105
+ if (metas.length === 0) {
106
+ const meta = doc.createElement("meta");
107
+ meta.setAttribute("name", "theme-color");
108
+ meta.setAttribute("content", color);
109
+ meta.setAttribute(CREATED, "1");
110
+ doc.head.append(meta);
111
+ } else {
112
+ metas.forEach((meta) => {
113
+ snapshot(meta, "content", CONTENT);
114
+ snapshot(meta, "media", MEDIA);
115
+ meta.setAttribute(SNAP, "1");
116
+ meta.setAttribute("content", color);
117
+ meta.removeAttribute("media");
118
+ });
119
+ }
120
+ doc.querySelectorAll('link[rel="icon"]').forEach((link, index) => {
121
+ snapshot(link, "href", HREF);
122
+ snapshot(link, "media", MEDIA);
123
+ link.setAttribute(SNAP, "1");
124
+ if (index === 0) {
125
+ link.setAttribute("href", href);
126
+ link.removeAttribute("media");
127
+ return;
128
+ }
129
+ link.setAttribute("media", "not all");
130
+ });
131
+ }
132
+
1
133
  // src/lib/theme-boot.ts
2
134
  var THEME_STORAGE_KEY = "sesionario-theme";
3
135
  var themeColorSchemeCss = "html.light{color-scheme:light}html.dark{color-scheme:dark}";
@@ -15,6 +147,6 @@ function themeBootScript(storageKey = THEME_STORAGE_KEY) {
15
147
  return `(function(){try{var k=${key};function parse(v){return v==="light"||v==="dark"||v==="system"?v:null}function readCookie(){var name=encodeURIComponent(k);var parts=document.cookie.split(";");for(var i=0;i<parts.length;i++){var part=parts[i].trim();var eq=part.indexOf("=");if(eq<0)continue;if(part.slice(0,eq)===name){try{return decodeURIComponent(part.slice(eq+1))}catch(e){return null}}}return null}var cookie=parse(readCookie());var stored=null;try{stored=parse(localStorage.getItem(k))}catch(e){}if(cookie){try{localStorage.setItem(k,cookie)}catch(e){}}var theme=cookie||stored||"system";var dark=theme==="dark"||(theme==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches);var el=document.documentElement;el.classList.remove("light","dark");el.classList.add(dark?"dark":"light");el.style.colorScheme=dark?"dark":"light"}catch(e){}})();`;
16
148
  }
17
149
 
18
- export { THEME_STORAGE_KEY, htmlThemeClass, parseThemeChoice, themeBootScript, themeColorSchemeCss };
150
+ export { BRAND_CHROME_DARK, BRAND_CHROME_LIGHT, THEME_STORAGE_KEY, applyThemeChrome, brandAppleWebApp, brandIcons, brandViewport, htmlThemeClass, parseThemeChoice, themeBootScript, themeColorSchemeCss };
19
151
  //# sourceMappingURL=theme.js.map
20
152
  //# sourceMappingURL=theme.js.map
package/dist/theme.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/theme-boot.ts"],"names":[],"mappings":";AAAO,IAAM,iBAAA,GAAoB;AAK1B,IAAM,mBAAA,GACZ;AAEM,SAAS,iBACf,KAAA,EACqB;AACrB,EAAA,IAAI,UAAU,OAAA,IAAW,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,UAAU,OAAO,KAAA;AACxE,EAAA,OAAO,IAAA;AACR;AAOO,SAAS,eACf,MAAA,EAC+B;AAC/B,EAAA,MAAM,KAAA,GAAQ,iBAAiB,MAAM,CAAA;AACrC,EAAA,IAAI,KAAA,KAAU,OAAA,IAAW,KAAA,KAAU,MAAA,EAAQ,OAAO,KAAA;AAClD,EAAA,OAAO,MAAA;AACR;AAOO,SAAS,eAAA,CACf,aAAqB,iBAAA,EACZ;AACT,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,UAAU,CAAA;AACrC,EAAA,OAAO,yBAAyB,GAAG,CAAA,2zBAAA,CAAA;AACpC","file":"theme.js","sourcesContent":["export const THEME_STORAGE_KEY = \"sesionario-theme\";\n\nexport type ThemeChoice = \"light\" | \"dark\" | \"system\";\n\n/** Keeps `color-scheme` aligned with the `light` / `dark` class next-themes toggles. */\nexport const themeColorSchemeCss =\n\t\"html.light{color-scheme:light}html.dark{color-scheme:dark}\";\n\nexport function parseThemeChoice(\n\tvalue: string | null | undefined,\n): ThemeChoice | null {\n\tif (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n\treturn null;\n}\n\n/**\n * Explicit light/dark cookies paint with the server HTML. `system` and a\n * missing cookie stay unset so the blocking script can follow the OS\n * preference before the first paint.\n */\nexport function htmlThemeClass(\n\tcookie: string | null | undefined,\n): \"light\" | \"dark\" | undefined {\n\tconst theme = parseThemeChoice(cookie);\n\tif (theme === \"light\" || theme === \"dark\") return theme;\n\treturn undefined;\n}\n\n/**\n * Blocking script for the start of `<body>`. Reads the cross-subdomain theme\n * cookie first, copies it into this origin's localStorage (next-themes only\n * reads localStorage), then sets `light`/`dark` on `<html>` before paint.\n */\nexport function themeBootScript(\n\tstorageKey: string = THEME_STORAGE_KEY,\n): string {\n\tconst key = JSON.stringify(storageKey);\n\treturn `(function(){try{var k=${key};function parse(v){return v===\"light\"||v===\"dark\"||v===\"system\"?v:null}function readCookie(){var name=encodeURIComponent(k);var parts=document.cookie.split(\";\");for(var i=0;i<parts.length;i++){var part=parts[i].trim();var eq=part.indexOf(\"=\");if(eq<0)continue;if(part.slice(0,eq)===name){try{return decodeURIComponent(part.slice(eq+1))}catch(e){return null}}}return null}var cookie=parse(readCookie());var stored=null;try{stored=parse(localStorage.getItem(k))}catch(e){}if(cookie){try{localStorage.setItem(k,cookie)}catch(e){}}var theme=cookie||stored||\"system\";var dark=theme===\"dark\"||(theme===\"system\"&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var el=document.documentElement;el.classList.remove(\"light\",\"dark\");el.classList.add(dark?\"dark\":\"light\");el.style.colorScheme=dark?\"dark\":\"light\"}catch(e){}})();`;\n}\n"]}
1
+ {"version":3,"sources":["../src/lib/brand-chrome.ts","../src/lib/theme-boot.ts"],"names":[],"mappings":"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","file":"theme.js","sourcesContent":["/** Page background behind the browser chrome. Light and dark match `--background`. */\nexport const BRAND_CHROME_LIGHT = \"#ffffff\";\nexport const BRAND_CHROME_DARK = \"#171717\";\n\nexport const brandViewport = {\n\twidth: \"device-width\",\n\tinitialScale: 1,\n\tviewportFit: \"cover\" as const,\n\tcolorScheme: \"light dark\" as const,\n\tthemeColor: [\n\t\t{ media: \"(prefers-color-scheme: light)\", color: BRAND_CHROME_LIGHT },\n\t\t{ media: \"(prefers-color-scheme: dark)\", color: BRAND_CHROME_DARK },\n\t],\n};\n\nexport type BrandIconInput = {\n\tlight: string;\n\tdark: string;\n\tapple: string;\n\tfallback?: string;\n\tsvg?: string;\n};\n\ntype BrandIcon = { url: string; media?: string; type?: string };\n\n/** OS-sensitive favicon set. Apps pass their own public paths. */\nexport function brandIcons(input: BrandIconInput): {\n\ticon: BrandIcon[];\n\tapple: string;\n} {\n\tconst icon: BrandIcon[] = [\n\t\t{ url: input.light, media: \"(prefers-color-scheme: light)\" },\n\t\t{ url: input.dark, media: \"(prefers-color-scheme: dark)\" },\n\t];\n\tif (input.svg) {\n\t\ticon.push({ url: input.svg, type: \"image/svg+xml\" });\n\t}\n\tif (input.fallback) {\n\t\ticon.push({ url: input.fallback });\n\t}\n\treturn { icon, apple: input.apple };\n}\n\n/** `default` lets iOS paint the status bar from `theme-color`. */\nexport function brandAppleWebApp(title: string): {\n\tcapable: true;\n\tstatusBarStyle: \"default\";\n\ttitle: string;\n} {\n\treturn {\n\t\tcapable: true,\n\t\tstatusBarStyle: \"default\",\n\t\ttitle,\n\t};\n}\n\nexport type ExplicitChromeTheme = \"light\" | \"dark\";\n\nconst SNAP = \"data-sesionario-chrome-snap\";\nconst MEDIA = \"data-sesionario-chrome-media\";\nconst HREF = \"data-sesionario-chrome-href\";\nconst CONTENT = \"data-sesionario-chrome-content\";\nconst CREATED = \"data-sesionario-chrome-created\";\n\nfunction chromeColor(theme: ExplicitChromeTheme): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn BRAND_CHROME_LIGHT;\n\t\tcase \"dark\":\n\t\t\treturn BRAND_CHROME_DARK;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction iconUrl(\n\ttheme: ExplicitChromeTheme,\n\ticons: { light: string; dark: string },\n): string {\n\tswitch (theme) {\n\t\tcase \"light\":\n\t\t\treturn icons.light;\n\t\tcase \"dark\":\n\t\t\treturn icons.dark;\n\t\tdefault: {\n\t\t\tconst unreachable: never = theme;\n\t\t\treturn unreachable;\n\t\t}\n\t}\n}\n\nfunction snapshot(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(SNAP)) return;\n\tif (el.hasAttribute(attr)) {\n\t\tel.setAttribute(store, el.getAttribute(attr) ?? \"\");\n\t}\n}\n\nfunction restoreAttr(el: Element, attr: string, store: string) {\n\tif (el.hasAttribute(store)) {\n\t\tel.setAttribute(attr, el.getAttribute(store) ?? \"\");\n\t\tel.removeAttribute(store);\n\t\treturn;\n\t}\n\tel.removeAttribute(attr);\n}\n\nfunction restoreSnap(el: Element, pairs: Array<[string, string]>) {\n\tif (!el.hasAttribute(SNAP)) return;\n\tfor (const [attr, store] of pairs) {\n\t\trestoreAttr(el, attr, store);\n\t}\n\tel.removeAttribute(SNAP);\n}\n\n/**\n * When the in-app theme is explicit, pin `theme-color` and the tab icon to\n * that choice. `system` and an unresolved theme put the OS media queries back.\n */\nexport function applyThemeChrome(\n\tdoc: Document,\n\ttheme: ExplicitChromeTheme | undefined,\n\ticons: { light: string; dark: string },\n): void {\n\tconst created = doc.querySelectorAll(`meta[${CREATED}]`);\n\tif (theme === undefined) {\n\t\tcreated.forEach((node) => node.remove());\n\t\tdoc.querySelectorAll('meta[name=\"theme-color\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"content\", CONTENT],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((node) =>\n\t\t\trestoreSnap(node, [\n\t\t\t\t[\"href\", HREF],\n\t\t\t\t[\"media\", MEDIA],\n\t\t\t]),\n\t\t);\n\t\treturn;\n\t}\n\n\tconst color = chromeColor(theme);\n\tconst href = iconUrl(theme, icons);\n\tconst metas = doc.querySelectorAll('meta[name=\"theme-color\"]');\n\tif (metas.length === 0) {\n\t\tconst meta = doc.createElement(\"meta\");\n\t\tmeta.setAttribute(\"name\", \"theme-color\");\n\t\tmeta.setAttribute(\"content\", color);\n\t\tmeta.setAttribute(CREATED, \"1\");\n\t\tdoc.head.append(meta);\n\t} else {\n\t\tmetas.forEach((meta) => {\n\t\t\tsnapshot(meta, \"content\", CONTENT);\n\t\t\tsnapshot(meta, \"media\", MEDIA);\n\t\t\tmeta.setAttribute(SNAP, \"1\");\n\t\t\tmeta.setAttribute(\"content\", color);\n\t\t\tmeta.removeAttribute(\"media\");\n\t\t});\n\t}\n\n\tdoc.querySelectorAll('link[rel=\"icon\"]').forEach((link, index) => {\n\t\tsnapshot(link, \"href\", HREF);\n\t\tsnapshot(link, \"media\", MEDIA);\n\t\tlink.setAttribute(SNAP, \"1\");\n\t\tif (index === 0) {\n\t\t\tlink.setAttribute(\"href\", href);\n\t\t\tlink.removeAttribute(\"media\");\n\t\t\treturn;\n\t\t}\n\t\tlink.setAttribute(\"media\", \"not all\");\n\t});\n}\n","export const THEME_STORAGE_KEY = \"sesionario-theme\";\n\nexport type ThemeChoice = \"light\" | \"dark\" | \"system\";\n\n/** Keeps `color-scheme` aligned with the `light` / `dark` class next-themes toggles. */\nexport const themeColorSchemeCss =\n\t\"html.light{color-scheme:light}html.dark{color-scheme:dark}\";\n\nexport function parseThemeChoice(\n\tvalue: string | null | undefined,\n): ThemeChoice | null {\n\tif (value === \"light\" || value === \"dark\" || value === \"system\") return value;\n\treturn null;\n}\n\n/**\n * Explicit light/dark cookies paint with the server HTML. `system` and a\n * missing cookie stay unset so the blocking script can follow the OS\n * preference before the first paint.\n */\nexport function htmlThemeClass(\n\tcookie: string | null | undefined,\n): \"light\" | \"dark\" | undefined {\n\tconst theme = parseThemeChoice(cookie);\n\tif (theme === \"light\" || theme === \"dark\") return theme;\n\treturn undefined;\n}\n\n/**\n * Blocking script for the start of `<body>`. Reads the cross-subdomain theme\n * cookie first, copies it into this origin's localStorage (next-themes only\n * reads localStorage), then sets `light`/`dark` on `<html>` before paint.\n */\nexport function themeBootScript(\n\tstorageKey: string = THEME_STORAGE_KEY,\n): string {\n\tconst key = JSON.stringify(storageKey);\n\treturn `(function(){try{var k=${key};function parse(v){return v===\"light\"||v===\"dark\"||v===\"system\"?v:null}function readCookie(){var name=encodeURIComponent(k);var parts=document.cookie.split(\";\");for(var i=0;i<parts.length;i++){var part=parts[i].trim();var eq=part.indexOf(\"=\");if(eq<0)continue;if(part.slice(0,eq)===name){try{return decodeURIComponent(part.slice(eq+1))}catch(e){return null}}}return null}var cookie=parse(readCookie());var stored=null;try{stored=parse(localStorage.getItem(k))}catch(e){}if(cookie){try{localStorage.setItem(k,cookie)}catch(e){}}var theme=cookie||stored||\"system\";var dark=theme===\"dark\"||(theme===\"system\"&&window.matchMedia(\"(prefers-color-scheme: dark)\").matches);var el=document.documentElement;el.classList.remove(\"light\",\"dark\");el.classList.add(dark?\"dark\":\"light\");el.style.colorScheme=dark?\"dark\":\"light\"}catch(e){}})();`;\n}\n"]}