@askrjs/themes 0.0.17 → 0.0.19
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/components/_internal/style.js +4 -1
- package/dist/components/_internal/style.js.map +1 -1
- package/dist/components/theme/theme.js +26 -3
- package/dist/components/theme/theme.js.map +1 -1
- package/dist/themes/default/index.css +7 -1
- package/package.json +1 -1
- package/src/components/_internal/style.ts +10 -3
- package/src/components/theme/theme.tsx +42 -2
- package/src/themes/default/styles/display/coverage.css +3 -1
- package/src/themes/default/styles/layout/block.css +4 -0
|
@@ -11,6 +11,8 @@ const CSS_ALLOWED_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
|
11
11
|
"min",
|
|
12
12
|
"max",
|
|
13
13
|
"clamp",
|
|
14
|
+
"minmax",
|
|
15
|
+
"repeat",
|
|
14
16
|
"rgb",
|
|
15
17
|
"rgba",
|
|
16
18
|
"hsl",
|
|
@@ -180,7 +182,8 @@ function ensureStyleRegistry(nonce) {
|
|
|
180
182
|
}
|
|
181
183
|
const current = documentRegistries.get(key);
|
|
182
184
|
if (current?.element.isConnected) return current;
|
|
183
|
-
const
|
|
185
|
+
const existingStyleElements = Array.from(document.querySelectorAll(`style[${STYLE_REGISTRY_ATTR}]`));
|
|
186
|
+
const styleElement = existingStyleElements.find((element) => (element.nonce || void 0) === nonce) ?? (nonce === void 0 && existingStyleElements.length === 1 ? existingStyleElements[0] : void 0) ?? document.createElement("style");
|
|
184
187
|
if (!styleElement.isConnected) {
|
|
185
188
|
styleElement.setAttribute(STYLE_REGISTRY_ATTR, "true");
|
|
186
189
|
if (nonce !== void 0) styleElement.nonce = nonce;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/_internal/style.ts"],"sourcesContent":["import * as Askr from \"@askrjs/askr\";\n\nconst cssPropertyNameCache = new Map<string, string>();\nconst MAX_PROPERTY_CACHE = 256;\n\nconst CSS_UNSAFE_RE = /[{}<>\\\\]/;\nconst CSS_URI_SCHEME_RE = /(?:^|[\\s(,])([a-z][a-z0-9+.-]*):/i;\nconst CSS_FUNCTION_NAME_RE = /([a-z-][a-z0-9-]*)\\s*\\(/gi;\nconst CSS_ALLOWED_FUNCTIONS = new Set([\n \"var\",\n \"calc\",\n \"min\",\n \"max\",\n \"clamp\",\n \"rgb\",\n \"rgba\",\n \"hsl\",\n \"hsla\",\n \"lab\",\n \"lch\",\n \"oklab\",\n \"oklch\",\n \"color\",\n \"color-mix\",\n \"translate\",\n \"translatex\",\n \"translatey\",\n \"translatez\",\n \"scale\",\n \"scalex\",\n \"scaley\",\n \"scalez\",\n \"rotate\",\n \"rotatex\",\n \"rotatey\",\n \"rotatez\",\n \"skew\",\n \"skewx\",\n \"skewy\",\n \"matrix\",\n \"matrix3d\",\n \"linear-gradient\",\n \"radial-gradient\",\n \"conic-gradient\",\n \"repeating-linear-gradient\",\n \"repeating-radial-gradient\",\n \"repeating-conic-gradient\",\n \"cubic-bezier\",\n \"steps\",\n]);\n\nfunction isSafeCssPropertyName(name: string): boolean {\n if (name.startsWith(\"--\")) return /^--[a-zA-Z0-9_-]+$/.test(name);\n return /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name);\n}\n\nfunction isSafeCssValue(value: string): boolean {\n if (CSS_UNSAFE_RE.test(value) || CSS_URI_SCHEME_RE.test(value)) return false;\n\n for (const match of value.matchAll(CSS_FUNCTION_NAME_RE)) {\n if (!CSS_ALLOWED_FUNCTIONS.has(match[1]!.toLowerCase())) return false;\n }\n\n return true;\n}\n\nfunction splitCssDeclarations(value: string): string[] {\n const declarations: string[] = [];\n let start = 0;\n let depth = 0;\n let quote: string | undefined;\n\n for (let index = 0; index < value.length; index += 1) {\n const char = value[index]!;\n if (quote) {\n if (char === quote && value[index - 1] !== \"\\\\\") quote = undefined;\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n continue;\n }\n if (char === \"(\") depth += 1;\n if (char === \")\" && depth > 0) depth -= 1;\n if (char === \";\" && depth === 0) {\n declarations.push(value.slice(start, index));\n start = index + 1;\n }\n }\n\n declarations.push(value.slice(start));\n return declarations;\n}\n\nfunction serializeCssString(value: string): string {\n const entries: Array<[string, string]> = [];\n for (const candidate of splitCssDeclarations(value)) {\n const separator = candidate.indexOf(\":\");\n if (separator <= 0) continue;\n const key = candidate.slice(0, separator).trim();\n const cssValue = candidate.slice(separator + 1).trim();\n if (key && cssValue) entries.push([key, cssValue]);\n }\n return serializeCssDeclarations(Object.fromEntries(entries));\n}\n\nfunction cssPropertyName(name: string): string {\n let cached = cssPropertyNameCache.get(name);\n if (cached !== undefined) {\n return cached;\n }\n\n let result = \"\";\n\n for (let index = 0; index < name.length; index += 1) {\n const code = name.charCodeAt(index);\n\n if (code >= 65 && code <= 90) {\n result += `-${String.fromCharCode(code + 32)}`;\n } else {\n result += name[index];\n }\n }\n\n if (cssPropertyNameCache.size < MAX_PROPERTY_CACHE) {\n cssPropertyNameCache.set(name, result);\n }\n return result;\n}\n\nexport function serializeCssDeclarations(styles: Record<string, unknown>): string {\n const keys = Object.keys(styles);\n let result = \"\";\n\n for (const key of keys) {\n const value = styles[key];\n if (value === undefined || value === null) {\n continue;\n }\n\n const rawProperty = key.trim();\n if (!isSafeCssPropertyName(rawProperty)) {\n continue;\n }\n const property = cssPropertyName(rawProperty).trim();\n const cssValue = String(value).trim();\n if (!cssValue || !isSafeCssValue(cssValue)) {\n continue;\n }\n\n const declaration = `${property}:${cssValue}`;\n result = result ? `${result};${declaration}` : declaration;\n }\n\n return result;\n}\n\nexport function mergeCssVar(style: unknown, name: string, value: string): string {\n const decl = `${name}:${value}`;\n\n if (typeof style === \"string\") {\n const trimmed = style.trim();\n return trimmed ? `${trimmed};${decl}` : decl;\n }\n\n if (style && typeof style === \"object\") {\n const entries = serializeCssDeclarations(style as Record<string, unknown>);\n return entries ? `${entries};${decl}` : decl;\n }\n\n return decl;\n}\n\nconst STYLE_REGISTRY_ATTR = \"data-askr-style-registry\";\nconst STYLE_CLASS_PREFIX = \"ak-style-\";\nconst MAX_STYLE_RULES = 512;\nconst MAX_SSR_STYLE_CACHE = 4096;\n\ntype StyleRule = {\n className: string;\n declarations: string;\n rule: string;\n};\n\nconst styleRulesByClass = new Map<string, StyleRule>();\ntype StyleRegistry = {\n element: HTMLStyleElement;\n ruleCount: number;\n rules: Map<string, StyleRule>;\n};\nconst registries = new WeakMap<Document, Map<string, StyleRegistry>>();\nfunction countRegisteredRules(value: string | null): number {\n return value?.match(/\\.ak-style-[a-z0-9]+\\{/g)?.length ?? 0;\n}\n\nfunction styleClassName(declarations: string): string {\n let first = 0xdeadbeef ^ declarations.length;\n let second = 0x41c6ce57 ^ declarations.length;\n\n for (let index = 0; index < declarations.length; index += 1) {\n const code = declarations.charCodeAt(index);\n first = Math.imul(first ^ code, 2_654_435_761);\n second = Math.imul(second ^ code, 1_597_334_677);\n }\n\n first =\n Math.imul(first ^ (first >>> 16), 2_246_822_507) ^\n Math.imul(second ^ (second >>> 13), 3_266_489_909);\n second =\n Math.imul(second ^ (second >>> 16), 2_246_822_507) ^\n Math.imul(first ^ (first >>> 13), 3_266_489_909);\n\n return `${STYLE_CLASS_PREFIX}${(second >>> 0).toString(36)}${(first >>> 0).toString(36)}`;\n}\n\nfunction escapeStyleRawText(value: string): string {\n return value.replace(/<\\//g, \"<\\\\/\");\n}\n\nfunction styleRuleFor(declarations: string): StyleRule {\n const className = styleClassName(declarations);\n const existing = styleRulesByClass.get(className);\n if (existing) {\n if (existing.declarations !== declarations) {\n throw new RangeError(\"Theme style class collision detected.\");\n }\n return existing;\n }\n\n const entry = {\n className,\n declarations,\n rule: `.${className}{${escapeStyleRawText(declarations)}}`,\n };\n return entry;\n}\n\nfunction rememberStyleRule(entry: StyleRule): void {\n styleRulesByClass.delete(entry.className);\n styleRulesByClass.set(entry.className, entry);\n while (styleRulesByClass.size > MAX_SSR_STYLE_CACHE) {\n const oldest = styleRulesByClass.keys().next().value;\n if (oldest === undefined) break;\n styleRulesByClass.delete(oldest);\n }\n}\n\nfunction registerSSRStyle(entry: StyleRule): void {\n const register = (\n Askr as typeof Askr & {\n registerSSRStyle?: (id: string, cssText: string) => void;\n }\n ).registerSSRStyle;\n register?.(entry.className, entry.rule);\n}\n\nfunction ensureStyleRegistry(nonce: string | undefined): StyleRegistry | null {\n if (typeof document === \"undefined\") return null;\n const key = nonce ?? \"\";\n let documentRegistries = registries.get(document);\n if (!documentRegistries) {\n documentRegistries = new Map();\n registries.set(document, documentRegistries);\n }\n const current = documentRegistries.get(key);\n if (current?.element.isConnected) return current;\n\n const styleElement =\n Array.from(document.querySelectorAll<HTMLStyleElement>(`style[${STYLE_REGISTRY_ATTR}]`)).find(\n (element) => (element.nonce || undefined) === nonce,\n ) ?? document.createElement(\"style\");\n if (!styleElement.isConnected) {\n styleElement.setAttribute(STYLE_REGISTRY_ATTR, \"true\");\n if (nonce !== undefined) styleElement.nonce = nonce;\n (document.head ?? document.documentElement).append(styleElement);\n }\n const registry: StyleRegistry = {\n element: styleElement,\n ruleCount: countRegisteredRules(styleElement.textContent),\n rules: new Map(),\n };\n documentRegistries.set(key, registry);\n return registry;\n}\n\nfunction normalizeDeclarations(declarations: string): string {\n return serializeCssString(declarations);\n}\n\nexport function styleDeclarationsToClass(declarations: string | undefined): string | undefined {\n if (typeof declarations !== \"string\") return undefined;\n\n const normalized = normalizeDeclarations(declarations);\n if (!normalized) return undefined;\n\n const entry = styleRuleFor(normalized);\n const nonce = Askr.cspNonce();\n const registry = ensureStyleRegistry(nonce);\n const registered = registry?.rules.get(normalized);\n if (registered) {\n registerSSRStyle(registered);\n return registered.className;\n }\n\n if (registry) {\n if (!registry.rules.has(normalized) && !registry.element.textContent?.includes(entry.rule)) {\n if (registry.ruleCount >= MAX_STYLE_RULES) {\n throw new RangeError(\"Theme style registry capacity exceeded.\");\n }\n }\n if (!registry.element.textContent?.includes(entry.rule)) {\n registry.element.append(entry.rule, \"\\n\");\n registry.ruleCount += 1;\n }\n registry.rules.set(normalized, entry);\n }\n\n rememberStyleRule(entry);\n registerSSRStyle(entry);\n\n return entry.className;\n}\n\nexport function styleRulesForHtml(html: string): string[] {\n const rules = new Map<string, string>();\n const classAttributePattern = /\\sclass=(?:\"([^\"]*)\"|'([^']*)')/g;\n\n for (const attribute of html.matchAll(classAttributePattern)) {\n const value = attribute[1] ?? attribute[2] ?? \"\";\n for (const className of value.split(/\\s+/)) {\n const entry = styleRulesByClass.get(className);\n if (entry) rules.set(className, entry.rule);\n if (rules.size > MAX_STYLE_RULES) {\n throw new RangeError(\"Theme style registry capacity exceeded.\");\n }\n }\n }\n\n return Array.from(rules.values());\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/_internal/style.ts"],"sourcesContent":["import * as Askr from \"@askrjs/askr\";\n\nconst cssPropertyNameCache = new Map<string, string>();\nconst MAX_PROPERTY_CACHE = 256;\n\nconst CSS_UNSAFE_RE = /[{}<>\\\\]/;\nconst CSS_URI_SCHEME_RE = /(?:^|[\\s(,])([a-z][a-z0-9+.-]*):/i;\nconst CSS_FUNCTION_NAME_RE = /([a-z-][a-z0-9-]*)\\s*\\(/gi;\nconst CSS_ALLOWED_FUNCTIONS = new Set([\n \"var\",\n \"calc\",\n \"min\",\n \"max\",\n \"clamp\",\n \"minmax\",\n \"repeat\",\n \"rgb\",\n \"rgba\",\n \"hsl\",\n \"hsla\",\n \"lab\",\n \"lch\",\n \"oklab\",\n \"oklch\",\n \"color\",\n \"color-mix\",\n \"translate\",\n \"translatex\",\n \"translatey\",\n \"translatez\",\n \"scale\",\n \"scalex\",\n \"scaley\",\n \"scalez\",\n \"rotate\",\n \"rotatex\",\n \"rotatey\",\n \"rotatez\",\n \"skew\",\n \"skewx\",\n \"skewy\",\n \"matrix\",\n \"matrix3d\",\n \"linear-gradient\",\n \"radial-gradient\",\n \"conic-gradient\",\n \"repeating-linear-gradient\",\n \"repeating-radial-gradient\",\n \"repeating-conic-gradient\",\n \"cubic-bezier\",\n \"steps\",\n]);\n\nfunction isSafeCssPropertyName(name: string): boolean {\n if (name.startsWith(\"--\")) return /^--[a-zA-Z0-9_-]+$/.test(name);\n return /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name);\n}\n\nfunction isSafeCssValue(value: string): boolean {\n if (CSS_UNSAFE_RE.test(value) || CSS_URI_SCHEME_RE.test(value)) return false;\n\n for (const match of value.matchAll(CSS_FUNCTION_NAME_RE)) {\n if (!CSS_ALLOWED_FUNCTIONS.has(match[1]!.toLowerCase())) return false;\n }\n\n return true;\n}\n\nfunction splitCssDeclarations(value: string): string[] {\n const declarations: string[] = [];\n let start = 0;\n let depth = 0;\n let quote: string | undefined;\n\n for (let index = 0; index < value.length; index += 1) {\n const char = value[index]!;\n if (quote) {\n if (char === quote && value[index - 1] !== \"\\\\\") quote = undefined;\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n continue;\n }\n if (char === \"(\") depth += 1;\n if (char === \")\" && depth > 0) depth -= 1;\n if (char === \";\" && depth === 0) {\n declarations.push(value.slice(start, index));\n start = index + 1;\n }\n }\n\n declarations.push(value.slice(start));\n return declarations;\n}\n\nfunction serializeCssString(value: string): string {\n const entries: Array<[string, string]> = [];\n for (const candidate of splitCssDeclarations(value)) {\n const separator = candidate.indexOf(\":\");\n if (separator <= 0) continue;\n const key = candidate.slice(0, separator).trim();\n const cssValue = candidate.slice(separator + 1).trim();\n if (key && cssValue) entries.push([key, cssValue]);\n }\n return serializeCssDeclarations(Object.fromEntries(entries));\n}\n\nfunction cssPropertyName(name: string): string {\n let cached = cssPropertyNameCache.get(name);\n if (cached !== undefined) {\n return cached;\n }\n\n let result = \"\";\n\n for (let index = 0; index < name.length; index += 1) {\n const code = name.charCodeAt(index);\n\n if (code >= 65 && code <= 90) {\n result += `-${String.fromCharCode(code + 32)}`;\n } else {\n result += name[index];\n }\n }\n\n if (cssPropertyNameCache.size < MAX_PROPERTY_CACHE) {\n cssPropertyNameCache.set(name, result);\n }\n return result;\n}\n\nexport function serializeCssDeclarations(styles: Record<string, unknown>): string {\n const keys = Object.keys(styles);\n let result = \"\";\n\n for (const key of keys) {\n const value = styles[key];\n if (value === undefined || value === null) {\n continue;\n }\n\n const rawProperty = key.trim();\n if (!isSafeCssPropertyName(rawProperty)) {\n continue;\n }\n const property = cssPropertyName(rawProperty).trim();\n const cssValue = String(value).trim();\n if (!cssValue || !isSafeCssValue(cssValue)) {\n continue;\n }\n\n const declaration = `${property}:${cssValue}`;\n result = result ? `${result};${declaration}` : declaration;\n }\n\n return result;\n}\n\nexport function mergeCssVar(style: unknown, name: string, value: string): string {\n const decl = `${name}:${value}`;\n\n if (typeof style === \"string\") {\n const trimmed = style.trim();\n return trimmed ? `${trimmed};${decl}` : decl;\n }\n\n if (style && typeof style === \"object\") {\n const entries = serializeCssDeclarations(style as Record<string, unknown>);\n return entries ? `${entries};${decl}` : decl;\n }\n\n return decl;\n}\n\nconst STYLE_REGISTRY_ATTR = \"data-askr-style-registry\";\nconst STYLE_CLASS_PREFIX = \"ak-style-\";\nconst MAX_STYLE_RULES = 512;\nconst MAX_SSR_STYLE_CACHE = 4096;\n\ntype StyleRule = {\n className: string;\n declarations: string;\n rule: string;\n};\n\nconst styleRulesByClass = new Map<string, StyleRule>();\ntype StyleRegistry = {\n element: HTMLStyleElement;\n ruleCount: number;\n rules: Map<string, StyleRule>;\n};\nconst registries = new WeakMap<Document, Map<string, StyleRegistry>>();\nfunction countRegisteredRules(value: string | null): number {\n return value?.match(/\\.ak-style-[a-z0-9]+\\{/g)?.length ?? 0;\n}\n\nfunction styleClassName(declarations: string): string {\n let first = 0xdeadbeef ^ declarations.length;\n let second = 0x41c6ce57 ^ declarations.length;\n\n for (let index = 0; index < declarations.length; index += 1) {\n const code = declarations.charCodeAt(index);\n first = Math.imul(first ^ code, 2_654_435_761);\n second = Math.imul(second ^ code, 1_597_334_677);\n }\n\n first =\n Math.imul(first ^ (first >>> 16), 2_246_822_507) ^\n Math.imul(second ^ (second >>> 13), 3_266_489_909);\n second =\n Math.imul(second ^ (second >>> 16), 2_246_822_507) ^\n Math.imul(first ^ (first >>> 13), 3_266_489_909);\n\n return `${STYLE_CLASS_PREFIX}${(second >>> 0).toString(36)}${(first >>> 0).toString(36)}`;\n}\n\nfunction escapeStyleRawText(value: string): string {\n return value.replace(/<\\//g, \"<\\\\/\");\n}\n\nfunction styleRuleFor(declarations: string): StyleRule {\n const className = styleClassName(declarations);\n const existing = styleRulesByClass.get(className);\n if (existing) {\n if (existing.declarations !== declarations) {\n throw new RangeError(\"Theme style class collision detected.\");\n }\n return existing;\n }\n\n const entry = {\n className,\n declarations,\n rule: `.${className}{${escapeStyleRawText(declarations)}}`,\n };\n return entry;\n}\n\nfunction rememberStyleRule(entry: StyleRule): void {\n styleRulesByClass.delete(entry.className);\n styleRulesByClass.set(entry.className, entry);\n while (styleRulesByClass.size > MAX_SSR_STYLE_CACHE) {\n const oldest = styleRulesByClass.keys().next().value;\n if (oldest === undefined) break;\n styleRulesByClass.delete(oldest);\n }\n}\n\nfunction registerSSRStyle(entry: StyleRule): void {\n const register = (\n Askr as typeof Askr & {\n registerSSRStyle?: (id: string, cssText: string) => void;\n }\n ).registerSSRStyle;\n register?.(entry.className, entry.rule);\n}\n\nfunction ensureStyleRegistry(nonce: string | undefined): StyleRegistry | null {\n if (typeof document === \"undefined\") return null;\n const key = nonce ?? \"\";\n let documentRegistries = registries.get(document);\n if (!documentRegistries) {\n documentRegistries = new Map();\n registries.set(document, documentRegistries);\n }\n const current = documentRegistries.get(key);\n if (current?.element.isConnected) return current;\n\n const existingStyleElements = Array.from(\n document.querySelectorAll<HTMLStyleElement>(`style[${STYLE_REGISTRY_ATTR}]`),\n );\n const styleElement =\n existingStyleElements.find((element) => (element.nonce || undefined) === nonce) ??\n (nonce === undefined && existingStyleElements.length === 1\n ? existingStyleElements[0]\n : undefined) ??\n document.createElement(\"style\");\n if (!styleElement.isConnected) {\n styleElement.setAttribute(STYLE_REGISTRY_ATTR, \"true\");\n if (nonce !== undefined) styleElement.nonce = nonce;\n (document.head ?? document.documentElement).append(styleElement);\n }\n const registry: StyleRegistry = {\n element: styleElement,\n ruleCount: countRegisteredRules(styleElement.textContent),\n rules: new Map(),\n };\n documentRegistries.set(key, registry);\n return registry;\n}\n\nfunction normalizeDeclarations(declarations: string): string {\n return serializeCssString(declarations);\n}\n\nexport function styleDeclarationsToClass(declarations: string | undefined): string | undefined {\n if (typeof declarations !== \"string\") return undefined;\n\n const normalized = normalizeDeclarations(declarations);\n if (!normalized) return undefined;\n\n const entry = styleRuleFor(normalized);\n const nonce = Askr.cspNonce();\n const registry = ensureStyleRegistry(nonce);\n const registered = registry?.rules.get(normalized);\n if (registered) {\n registerSSRStyle(registered);\n return registered.className;\n }\n\n if (registry) {\n if (!registry.rules.has(normalized) && !registry.element.textContent?.includes(entry.rule)) {\n if (registry.ruleCount >= MAX_STYLE_RULES) {\n throw new RangeError(\"Theme style registry capacity exceeded.\");\n }\n }\n if (!registry.element.textContent?.includes(entry.rule)) {\n registry.element.append(entry.rule, \"\\n\");\n registry.ruleCount += 1;\n }\n registry.rules.set(normalized, entry);\n }\n\n rememberStyleRule(entry);\n registerSSRStyle(entry);\n\n return entry.className;\n}\n\nexport function styleRulesForHtml(html: string): string[] {\n const rules = new Map<string, string>();\n const classAttributePattern = /\\sclass=(?:\"([^\"]*)\"|'([^']*)')/g;\n\n for (const attribute of html.matchAll(classAttributePattern)) {\n const value = attribute[1] ?? attribute[2] ?? \"\";\n for (const className of value.split(/\\s+/)) {\n const entry = styleRulesByClass.get(className);\n if (entry) rules.set(className, entry.rule);\n if (rules.size > MAX_STYLE_RULES) {\n throw new RangeError(\"Theme style registry capacity exceeded.\");\n }\n }\n }\n\n return Array.from(rules.values());\n}\n"],"mappings":"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"}
|
|
@@ -73,7 +73,9 @@ function ThemeScope(props) {
|
|
|
73
73
|
const ownedCoordinator = state(getDefaultThemeCoordinator())();
|
|
74
74
|
const coordinator = parentScope.coordinator ?? ownedCoordinator;
|
|
75
75
|
const scopeDepth = parentScope.depth + 1;
|
|
76
|
-
coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal)
|
|
76
|
+
coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal, (nextTheme) => {
|
|
77
|
+
if (themeState() !== nextTheme) themeState.set(nextTheme);
|
|
78
|
+
});
|
|
77
79
|
const setTheme = (nextTheme) => {
|
|
78
80
|
themeState.set(nextTheme);
|
|
79
81
|
writeStoredTheme(storageKey, nextTheme);
|
|
@@ -94,6 +96,21 @@ function ThemeScope(props) {
|
|
|
94
96
|
"data-slot": "theme-scope",
|
|
95
97
|
ref: parentScope.coordinator === null ? (element) => {
|
|
96
98
|
coordinator.attach(element, (nextTheme) => localResolvedSystemTheme.set(nextTheme), scopeSignal);
|
|
99
|
+
if (element && typeof window !== "undefined") {
|
|
100
|
+
const onStorage = (event) => {
|
|
101
|
+
let storageMatches = event.storageArea == null;
|
|
102
|
+
try {
|
|
103
|
+
storageMatches ||= event.storageArea === window.localStorage;
|
|
104
|
+
} catch {}
|
|
105
|
+
if (!storageMatches || event.key !== storageKey) return;
|
|
106
|
+
const nextTheme = event.newValue;
|
|
107
|
+
if (!nextTheme) return;
|
|
108
|
+
themeState.set(nextTheme);
|
|
109
|
+
coordinator.activate(scopeId, nextTheme);
|
|
110
|
+
};
|
|
111
|
+
window.addEventListener("storage", onStorage);
|
|
112
|
+
scopeSignal.addEventListener("abort", () => window.removeEventListener("storage", onStorage), { once: true });
|
|
113
|
+
}
|
|
97
114
|
if (!element || persistenceAdoption.complete) return;
|
|
98
115
|
persistenceAdoption.complete = true;
|
|
99
116
|
const storedTheme = readStoredTheme(storageKey);
|
|
@@ -151,13 +168,14 @@ function createThemeCoordinator() {
|
|
|
151
168
|
}
|
|
152
169
|
schedule();
|
|
153
170
|
},
|
|
154
|
-
register(id, depth, themeName, signal) {
|
|
171
|
+
register(id, depth, themeName, signal, onThemeChange) {
|
|
155
172
|
const existing = scopes.get(id);
|
|
156
173
|
scopes.set(id, {
|
|
157
174
|
depth,
|
|
158
175
|
sequence: existing?.sequence ?? nextSequence++,
|
|
159
176
|
theme: themeName,
|
|
160
|
-
signal
|
|
177
|
+
signal,
|
|
178
|
+
onThemeChange
|
|
161
179
|
});
|
|
162
180
|
if (!existing) signal.addEventListener("abort", () => {
|
|
163
181
|
scopes.delete(id);
|
|
@@ -170,6 +188,11 @@ function createThemeCoordinator() {
|
|
|
170
188
|
const scope = scopes.get(id);
|
|
171
189
|
if (scope) scope.theme = themeName;
|
|
172
190
|
explicitOwner = id;
|
|
191
|
+
const ownerDepth = scope?.depth;
|
|
192
|
+
for (const [scopeId, registered] of scopes) if (scopeId !== id && registered.depth === ownerDepth) {
|
|
193
|
+
registered.theme = themeName;
|
|
194
|
+
registered.onThemeChange(themeName);
|
|
195
|
+
}
|
|
173
196
|
syncThemeTarget(target(), themeName);
|
|
174
197
|
}
|
|
175
198
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[],"sources":["../../../src/components/theme/theme.tsx"],"sourcesContent":["import type { JSX } from \"@askrjs/askr/jsx-runtime\";\nimport { defineScope, getSignal, readScope, state } from \"@askrjs/askr\";\nimport type { JSXElement } from \"@askrjs/askr/foundations/structures\";\nimport { Button } from \"@askrjs/ui\";\nimport type { ButtonNativeProps, PressEvent } from \"@askrjs/ui\";\n\nexport const CAT_THEME_NAMES = [\"tabby\", \"ginger\", \"tuxedo\", \"calico\", \"torty\"] as const;\n\nexport type CatThemeName = (typeof CAT_THEME_NAMES)[number];\nexport type ThemeName = \"light\" | \"dark\" | \"system\" | CatThemeName | (string & {});\n\nexport type ThemeOption = {\n value: ThemeName;\n label: string;\n};\n\nexport type ThemeScopeValue = {\n theme: () => ThemeName;\n resolvedSystemTheme: () => \"light\" | \"dark\";\n setTheme: (theme: ThemeName) => void;\n themes: readonly ThemeOption[];\n storageKey: string;\n};\n\nexport type ThemeScopeProps = {\n children?: unknown;\n defaultTheme?: ThemeName;\n themes?: readonly ThemeOption[];\n storageKey?: string;\n};\n\nexport type ThemePickerProps = Omit<\n JSX.IntrinsicElements[\"select\"],\n \"children\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n themes?: readonly ThemeOption[];\n label?: string;\n};\n\nexport type ThemeToggleRenderContext = {\n theme: ThemeName;\n nextTheme: ThemeName;\n};\n\nexport type ThemeToggleProps = Omit<ButtonNativeProps, \"children\" | \"onPress\"> & {\n children?: unknown | ((context: ThemeToggleRenderContext) => unknown);\n lightIcon?: unknown;\n darkIcon?: unknown;\n systemIcon?: unknown;\n themes?: readonly ThemeName[];\n onPress?: (event: PressEvent) => void;\n};\n\nexport const DEFAULT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"system\", label: \"System\" },\n { value: \"light\", label: \"Light\" },\n { value: \"dark\", label: \"Dark\" },\n];\n\nexport const CAT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"tabby\", label: \"Tabby\" },\n { value: \"ginger\", label: \"Ginger\" },\n { value: \"tuxedo\", label: \"Tuxedo\" },\n { value: \"calico\", label: \"Calico\" },\n { value: \"torty\", label: \"Torty\" },\n];\n\nconst DEFAULT_STORAGE_KEY = \"askr-theme\";\nconst STATIC_CHILDREN = Symbol.for(\"askr.static-children\");\nconst STATIC_CHILD_SLOTS_CACHE = Symbol.for(\"__askrStaticChildSlots\");\nconst documentThemeCoordinators = new WeakMap<Document, ThemeCoordinator>();\ntype ThemeCoordinator = ReturnType<typeof createThemeCoordinator>;\ntype InternalThemeScopeValue = ThemeScopeValue & {\n readonly coordinator: ThemeCoordinator | null;\n readonly depth: number;\n};\n\nconst ThemeScopeContext = defineScope<InternalThemeScopeValue>({\n theme: () => \"system\",\n resolvedSystemTheme: () => \"light\",\n setTheme: () => undefined,\n themes: DEFAULT_THEME_OPTIONS,\n storageKey: DEFAULT_STORAGE_KEY,\n coordinator: null,\n depth: -1,\n});\n\nexport function theme(): ThemeScopeValue {\n return readScope(ThemeScopeContext);\n}\n\nexport function ThemeScope(props: ThemeScopeProps): JSX.Element {\n const {\n children,\n defaultTheme = \"system\",\n themes = DEFAULT_THEME_OPTIONS,\n storageKey = DEFAULT_STORAGE_KEY,\n } = props;\n\n const scopeId = state<symbol>(Symbol(\"ThemeScope\"))();\n const scopeSignal = getSignal();\n // The first render must be identical on the server and in the browser.\n // Browser persistence is adopted from the committed root ref, after Askr's\n // hydration verifier has accepted the server markup.\n const themeState = state<ThemeName>(defaultTheme);\n const localResolvedSystemTheme = state<\"light\" | \"dark\">(\"light\");\n const persistenceAdoption = state({ complete: false })();\n const currentTheme = themeState();\n const parentScope = readScope(ThemeScopeContext);\n const ownedCoordinator = state<ThemeCoordinator>(getDefaultThemeCoordinator())();\n const coordinator = parentScope.coordinator ?? ownedCoordinator;\n const scopeDepth = parentScope.depth + 1;\n coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal);\n\n const setTheme = (nextTheme: ThemeName) => {\n themeState.set(nextTheme);\n writeStoredTheme(storageKey, nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n\n const resolvedSystemTheme = parentScope.coordinator\n ? parentScope.resolvedSystemTheme\n : localResolvedSystemTheme;\n const value: InternalThemeScopeValue = {\n theme: themeState,\n resolvedSystemTheme,\n setTheme,\n themes,\n storageKey,\n coordinator,\n depth: scopeDepth,\n };\n\n return (\n <ThemeScopeContext value={value}>\n <div\n data-slot=\"theme-scope\"\n ref={\n parentScope.coordinator === null\n ? (element: HTMLElement | null) => {\n coordinator.attach(\n element,\n (nextTheme) => localResolvedSystemTheme.set(nextTheme),\n scopeSignal,\n );\n if (!element || persistenceAdoption.complete) return;\n persistenceAdoption.complete = true;\n const storedTheme = readStoredTheme(storageKey);\n if (storedTheme && storedTheme !== themeState()) {\n themeState.set(storedTheme);\n coordinator.activate(scopeId, storedTheme);\n }\n }\n : undefined\n }\n >\n {children}\n </div>\n </ThemeScopeContext>\n );\n}\n\nfunction getDefaultThemeCoordinator(): ThemeCoordinator {\n if (typeof document === \"undefined\") {\n return createThemeCoordinator();\n }\n\n const existing = documentThemeCoordinators.get(document);\n if (existing) return existing;\n\n const coordinator = createThemeCoordinator();\n documentThemeCoordinators.set(document, coordinator);\n return coordinator;\n}\n\nfunction createThemeCoordinator() {\n const scopes = new Map<\n symbol,\n {\n depth: number;\n sequence: number;\n theme: ThemeName;\n signal: AbortSignal;\n }\n >();\n let nextSequence = 0;\n let explicitOwner: symbol | undefined;\n let root: Node | null = null;\n let scheduled = false;\n\n const target = (): HTMLElement | null => {\n if (root?.nodeType === 9) return (root as Document).documentElement;\n if (root && \"host\" in root) return (root as ShadowRoot).host as HTMLElement;\n return typeof document === \"undefined\" ? null : document.documentElement;\n };\n const syncActive = (): void => {\n if (explicitOwner !== undefined) return;\n let candidate: { depth: number; sequence: number; theme: ThemeName } | undefined;\n for (const scope of scopes.values()) {\n if (\n !candidate ||\n scope.depth > candidate.depth ||\n (scope.depth === candidate.depth && scope.sequence > candidate.sequence)\n ) {\n candidate = scope;\n }\n }\n if (candidate) syncThemeTarget(target(), candidate.theme);\n };\n const schedule = (): void => {\n if (typeof document === \"undefined\" || scheduled) return;\n scheduled = true;\n setTimeout(() => {\n scheduled = false;\n syncActive();\n }, 0);\n };\n\n return Object.freeze({\n attach(\n element: HTMLElement | null,\n onResolvedSystemTheme: (themeName: \"light\" | \"dark\") => void,\n signal: AbortSignal,\n ) {\n if (element) root = element.getRootNode();\n if (element && typeof window !== \"undefined\" && typeof window.matchMedia === \"function\") {\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const update = () => onResolvedSystemTheme(media.matches ? \"dark\" : \"light\");\n update();\n media.addEventListener?.(\"change\", update);\n signal.addEventListener(\"abort\", () => media.removeEventListener?.(\"change\", update), {\n once: true,\n });\n }\n schedule();\n },\n register(id: symbol, depth: number, themeName: ThemeName, signal: AbortSignal) {\n const existing = scopes.get(id);\n scopes.set(id, {\n depth,\n sequence: existing?.sequence ?? nextSequence++,\n theme: themeName,\n signal,\n });\n if (!existing) {\n signal.addEventListener(\n \"abort\",\n () => {\n scopes.delete(id);\n if (explicitOwner === id) explicitOwner = undefined;\n schedule();\n },\n { once: true },\n );\n }\n schedule();\n },\n activate(id: symbol, themeName: ThemeName) {\n const scope = scopes.get(id);\n if (scope) scope.theme = themeName;\n explicitOwner = id;\n syncThemeTarget(target(), themeName);\n },\n });\n}\n\nexport function ThemePicker(props: ThemePickerProps): JSX.Element {\n const activeTheme = theme();\n const { themes = activeTheme.themes, label = \"Theme\", ...rest } = props;\n const currentTheme = activeTheme.theme();\n\n return (\n <select\n {...rest}\n aria-label={rest[\"aria-label\"] ?? label}\n data-slot=\"theme-picker\"\n value={currentTheme}\n onChange={(event: Event) => {\n const target = getThemePickerTarget(event);\n if (target) {\n activeTheme.setTheme(target.value as ThemeName);\n }\n }}\n >\n {themes.map((option) => (\n <option key={option.value} value={option.value} selected={option.value === currentTheme}>\n {option.label}\n </option>\n ))}\n </select>\n );\n}\n\nfunction getThemePickerTarget(event: Event): HTMLSelectElement | null {\n if (typeof HTMLSelectElement === \"undefined\") {\n return null;\n }\n\n const path = typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const candidates = [event.target, event.currentTarget, ...path];\n\n for (const candidate of candidates) {\n if (candidate instanceof HTMLSelectElement) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function ThemeToggle(props: ThemeToggleProps): JSX.Element {\n const activeTheme = theme();\n const {\n children,\n lightIcon,\n darkIcon,\n systemIcon,\n themes = [\"light\", \"dark\"],\n onPress,\n ...rest\n } = props;\n\n const currentTheme = activeTheme.theme();\n const nextTheme = getNextTheme(currentTheme, themes, activeTheme.resolvedSystemTheme());\n const renderContext = { theme: currentTheme, nextTheme };\n const ariaLabel = (rest as Record<string, unknown>)[\"aria-label\"];\n const themedIcon = resolveThemeToggleIcon(currentTheme, nextTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n });\n const renderedIcon = cloneThemeToggleIcon(themedIcon, currentTheme);\n const renderedIconSlots =\n renderThemeToggleIconSlots(currentTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n }) ?? renderedIcon;\n const content =\n typeof children === \"function\" ? children(renderContext) : (children ?? renderedIconSlots);\n\n return (\n <Button\n {...(rest as ButtonNativeProps)}\n aria-label={typeof ariaLabel === \"string\" ? ariaLabel : `Switch to ${nextTheme} theme`}\n data-theme-control=\"toggle\"\n data-theme-choice={currentTheme}\n data-next-theme={nextTheme}\n onPress={(event) => {\n onPress?.(event);\n if (!event.defaultPrevented && !Object.is(nextTheme, currentTheme)) {\n activeTheme.setTheme(nextTheme);\n }\n }}\n >\n <span data-slot=\"theme-toggle-content\">{content}</span>\n </Button>\n );\n}\n\nfunction getNextTheme(\n currentTheme: ThemeName,\n themes: readonly ThemeName[],\n resolvedSystemTheme: \"light\" | \"dark\" = \"light\",\n): ThemeName {\n if (themes.length === 0) return currentTheme;\n const index = themes.indexOf(currentTheme);\n if (index < 0 && currentTheme === \"system\") {\n if (themes.includes(\"light\") && themes.includes(\"dark\")) {\n return resolvedSystemTheme === \"dark\" ? \"light\" : \"dark\";\n }\n }\n return themes[index >= 0 && index < themes.length - 1 ? index + 1 : 0]!;\n}\n\nfunction getThemeIcon(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (theme === \"light\") return icons.lightIcon;\n if (theme === \"dark\") return icons.darkIcon;\n if (theme === \"system\") return icons.systemIcon;\n return undefined;\n}\n\nexport function resolveThemeToggleIcon(\n theme: ThemeName,\n nextTheme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n return getThemeIcon(theme, icons) ?? getThemeIcon(nextTheme, icons);\n}\n\nfunction renderThemeToggleIconSlots(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (getThemeIcon(theme, icons) === undefined) {\n return undefined;\n }\n\n const slots = [\n [\"light\", icons.lightIcon],\n [\"dark\", icons.darkIcon],\n [\"system\", icons.systemIcon],\n ] as const;\n const availableSlots = slots.filter(([, icon]) => icon !== undefined && icon !== null);\n\n if (availableSlots.length <= 1) {\n return undefined;\n }\n\n return availableSlots.map(([slotTheme, icon]) => (\n <span\n key={slotTheme}\n data-slot=\"theme-toggle-icon\"\n data-theme-toggle-icon={slotTheme}\n hidden={slotTheme === theme ? undefined : true}\n >\n {cloneThemeToggleIcon(icon, slotTheme)}\n </span>\n ));\n}\n\nfunction isJSXElement(value: unknown): value is JSXElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"$$typeof\" in value &&\n \"type\" in value &&\n \"props\" in value\n );\n}\n\nfunction cloneThemeToggleIcon(icon: unknown, key?: string): unknown {\n if (Array.isArray(icon)) {\n const clonedChildren = icon.map((child) => cloneThemeToggleIcon(child));\n if ((icon as unknown as Record<symbol, unknown>)[STATIC_CHILDREN] === true) {\n Object.defineProperty(clonedChildren, STATIC_CHILDREN, {\n value: true,\n configurable: true,\n });\n }\n return clonedChildren;\n }\n\n if (!isJSXElement(icon)) return icon;\n\n const props = icon.props as Record<string, unknown> | undefined;\n const clonedProps = props ? { ...props } : {};\n\n if (\"children\" in clonedProps) {\n clonedProps.children = cloneThemeToggleIcon(clonedProps.children);\n }\n\n const iconKey = (icon.key ?? key ?? null) as string | number | null;\n const clonedIcon = {\n ...icon,\n key: iconKey,\n props: clonedProps,\n };\n\n delete (clonedIcon as Record<symbol, unknown>)[STATIC_CHILD_SLOTS_CACHE];\n return clonedIcon;\n}\n\nfunction syncThemeTarget(\n html: HTMLElement | null,\n themeChoice: ThemeName | null | undefined,\n): void {\n if (!html) return;\n\n if (themeChoice == null) {\n html.removeAttribute(\"data-theme\");\n html.removeAttribute(\"data-theme-choice\");\n return;\n }\n\n html.setAttribute(\"data-theme-choice\", themeChoice);\n\n if (themeChoice === \"system\") {\n html.removeAttribute(\"data-theme\");\n } else {\n html.setAttribute(\"data-theme\", themeChoice);\n }\n}\n\nfunction readStoredTheme(storageKey: string): ThemeName | undefined {\n if (typeof window === \"undefined\") return undefined;\n try {\n const storedTheme = window.localStorage.getItem(storageKey);\n return storedTheme ? (storedTheme as ThemeName) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeStoredTheme(storageKey: string, theme: ThemeName): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey, theme);\n } catch {\n // Storage can be unavailable in private or locked-down browser contexts.\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme.js","names":[],"sources":["../../../src/components/theme/theme.tsx"],"sourcesContent":["import type { JSX } from \"@askrjs/askr/jsx-runtime\";\nimport { defineScope, getSignal, readScope, state } from \"@askrjs/askr\";\nimport type { JSXElement } from \"@askrjs/askr/foundations/structures\";\nimport { Button } from \"@askrjs/ui\";\nimport type { ButtonNativeProps, PressEvent } from \"@askrjs/ui\";\n\nexport const CAT_THEME_NAMES = [\"tabby\", \"ginger\", \"tuxedo\", \"calico\", \"torty\"] as const;\n\nexport type CatThemeName = (typeof CAT_THEME_NAMES)[number];\nexport type ThemeName = \"light\" | \"dark\" | \"system\" | CatThemeName | (string & {});\n\nexport type ThemeOption = {\n value: ThemeName;\n label: string;\n};\n\nexport type ThemeScopeValue = {\n theme: () => ThemeName;\n resolvedSystemTheme: () => \"light\" | \"dark\";\n setTheme: (theme: ThemeName) => void;\n themes: readonly ThemeOption[];\n storageKey: string;\n};\n\nexport type ThemeScopeProps = {\n children?: unknown;\n defaultTheme?: ThemeName;\n themes?: readonly ThemeOption[];\n storageKey?: string;\n};\n\nexport type ThemePickerProps = Omit<\n JSX.IntrinsicElements[\"select\"],\n \"children\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n themes?: readonly ThemeOption[];\n label?: string;\n};\n\nexport type ThemeToggleRenderContext = {\n theme: ThemeName;\n nextTheme: ThemeName;\n};\n\nexport type ThemeToggleProps = Omit<ButtonNativeProps, \"children\" | \"onPress\"> & {\n children?: unknown | ((context: ThemeToggleRenderContext) => unknown);\n lightIcon?: unknown;\n darkIcon?: unknown;\n systemIcon?: unknown;\n themes?: readonly ThemeName[];\n onPress?: (event: PressEvent) => void;\n};\n\nexport const DEFAULT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"system\", label: \"System\" },\n { value: \"light\", label: \"Light\" },\n { value: \"dark\", label: \"Dark\" },\n];\n\nexport const CAT_THEME_OPTIONS: readonly ThemeOption[] = [\n { value: \"tabby\", label: \"Tabby\" },\n { value: \"ginger\", label: \"Ginger\" },\n { value: \"tuxedo\", label: \"Tuxedo\" },\n { value: \"calico\", label: \"Calico\" },\n { value: \"torty\", label: \"Torty\" },\n];\n\nconst DEFAULT_STORAGE_KEY = \"askr-theme\";\nconst STATIC_CHILDREN = Symbol.for(\"askr.static-children\");\nconst STATIC_CHILD_SLOTS_CACHE = Symbol.for(\"__askrStaticChildSlots\");\nconst documentThemeCoordinators = new WeakMap<Document, ThemeCoordinator>();\ntype ThemeCoordinator = ReturnType<typeof createThemeCoordinator>;\ntype InternalThemeScopeValue = ThemeScopeValue & {\n readonly coordinator: ThemeCoordinator | null;\n readonly depth: number;\n};\n\nconst ThemeScopeContext = defineScope<InternalThemeScopeValue>({\n theme: () => \"system\",\n resolvedSystemTheme: () => \"light\",\n setTheme: () => undefined,\n themes: DEFAULT_THEME_OPTIONS,\n storageKey: DEFAULT_STORAGE_KEY,\n coordinator: null,\n depth: -1,\n});\n\nexport function theme(): ThemeScopeValue {\n return readScope(ThemeScopeContext);\n}\n\nexport function ThemeScope(props: ThemeScopeProps): JSX.Element {\n const {\n children,\n defaultTheme = \"system\",\n themes = DEFAULT_THEME_OPTIONS,\n storageKey = DEFAULT_STORAGE_KEY,\n } = props;\n\n const scopeId = state<symbol>(Symbol(\"ThemeScope\"))();\n const scopeSignal = getSignal();\n // The first render must be identical on the server and in the browser.\n // Browser persistence is adopted from the committed root ref, after Askr's\n // hydration verifier has accepted the server markup.\n const themeState = state<ThemeName>(defaultTheme);\n const localResolvedSystemTheme = state<\"light\" | \"dark\">(\"light\");\n const persistenceAdoption = state({ complete: false })();\n const currentTheme = themeState();\n const parentScope = readScope(ThemeScopeContext);\n const ownedCoordinator = state<ThemeCoordinator>(getDefaultThemeCoordinator())();\n const coordinator = parentScope.coordinator ?? ownedCoordinator;\n const scopeDepth = parentScope.depth + 1;\n coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal, (nextTheme) => {\n if (themeState() !== nextTheme) themeState.set(nextTheme);\n });\n\n const setTheme = (nextTheme: ThemeName) => {\n themeState.set(nextTheme);\n writeStoredTheme(storageKey, nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n\n const resolvedSystemTheme = parentScope.coordinator\n ? parentScope.resolvedSystemTheme\n : localResolvedSystemTheme;\n const value: InternalThemeScopeValue = {\n theme: themeState,\n resolvedSystemTheme,\n setTheme,\n themes,\n storageKey,\n coordinator,\n depth: scopeDepth,\n };\n\n return (\n <ThemeScopeContext value={value}>\n <div\n data-slot=\"theme-scope\"\n ref={\n parentScope.coordinator === null\n ? (element: HTMLElement | null) => {\n coordinator.attach(\n element,\n (nextTheme) => localResolvedSystemTheme.set(nextTheme),\n scopeSignal,\n );\n if (element && typeof window !== \"undefined\") {\n const onStorage = (event: StorageEvent) => {\n let storageMatches = event.storageArea == null;\n try {\n storageMatches ||= event.storageArea === window.localStorage;\n } catch {\n // Locked-down/private contexts may deny access to localStorage.\n }\n if (!storageMatches || event.key !== storageKey) {\n return;\n }\n const nextTheme = event.newValue as ThemeName | null;\n if (!nextTheme) return;\n themeState.set(nextTheme);\n coordinator.activate(scopeId, nextTheme);\n };\n window.addEventListener(\"storage\", onStorage);\n scopeSignal.addEventListener(\n \"abort\",\n () => window.removeEventListener(\"storage\", onStorage),\n { once: true },\n );\n }\n if (!element || persistenceAdoption.complete) return;\n persistenceAdoption.complete = true;\n const storedTheme = readStoredTheme(storageKey);\n if (storedTheme && storedTheme !== themeState()) {\n themeState.set(storedTheme);\n coordinator.activate(scopeId, storedTheme);\n }\n }\n : undefined\n }\n >\n {children}\n </div>\n </ThemeScopeContext>\n );\n}\n\nfunction getDefaultThemeCoordinator(): ThemeCoordinator {\n if (typeof document === \"undefined\") {\n return createThemeCoordinator();\n }\n\n const existing = documentThemeCoordinators.get(document);\n if (existing) return existing;\n\n const coordinator = createThemeCoordinator();\n documentThemeCoordinators.set(document, coordinator);\n return coordinator;\n}\n\nfunction createThemeCoordinator() {\n const scopes = new Map<\n symbol,\n {\n depth: number;\n sequence: number;\n theme: ThemeName;\n signal: AbortSignal;\n onThemeChange: (themeName: ThemeName) => void;\n }\n >();\n let nextSequence = 0;\n let explicitOwner: symbol | undefined;\n let root: Node | null = null;\n let scheduled = false;\n\n const target = (): HTMLElement | null => {\n if (root?.nodeType === 9) return (root as Document).documentElement;\n if (root && \"host\" in root) return (root as ShadowRoot).host as HTMLElement;\n return typeof document === \"undefined\" ? null : document.documentElement;\n };\n const syncActive = (): void => {\n if (explicitOwner !== undefined) return;\n let candidate: { depth: number; sequence: number; theme: ThemeName } | undefined;\n for (const scope of scopes.values()) {\n if (\n !candidate ||\n scope.depth > candidate.depth ||\n (scope.depth === candidate.depth && scope.sequence > candidate.sequence)\n ) {\n candidate = scope;\n }\n }\n if (candidate) syncThemeTarget(target(), candidate.theme);\n };\n const schedule = (): void => {\n if (typeof document === \"undefined\" || scheduled) return;\n scheduled = true;\n setTimeout(() => {\n scheduled = false;\n syncActive();\n }, 0);\n };\n\n return Object.freeze({\n attach(\n element: HTMLElement | null,\n onResolvedSystemTheme: (themeName: \"light\" | \"dark\") => void,\n signal: AbortSignal,\n ) {\n if (element) root = element.getRootNode();\n if (element && typeof window !== \"undefined\" && typeof window.matchMedia === \"function\") {\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const update = () => onResolvedSystemTheme(media.matches ? \"dark\" : \"light\");\n update();\n media.addEventListener?.(\"change\", update);\n signal.addEventListener(\"abort\", () => media.removeEventListener?.(\"change\", update), {\n once: true,\n });\n }\n schedule();\n },\n register(\n id: symbol,\n depth: number,\n themeName: ThemeName,\n signal: AbortSignal,\n onThemeChange: (themeName: ThemeName) => void,\n ) {\n const existing = scopes.get(id);\n scopes.set(id, {\n depth,\n sequence: existing?.sequence ?? nextSequence++,\n theme: themeName,\n signal,\n onThemeChange,\n });\n if (!existing) {\n signal.addEventListener(\n \"abort\",\n () => {\n scopes.delete(id);\n if (explicitOwner === id) explicitOwner = undefined;\n schedule();\n },\n { once: true },\n );\n }\n schedule();\n },\n activate(id: symbol, themeName: ThemeName) {\n const scope = scopes.get(id);\n if (scope) scope.theme = themeName;\n explicitOwner = id;\n const ownerDepth = scope?.depth;\n for (const [scopeId, registered] of scopes) {\n if (scopeId !== id && registered.depth === ownerDepth) {\n registered.theme = themeName;\n registered.onThemeChange(themeName);\n }\n }\n syncThemeTarget(target(), themeName);\n },\n });\n}\n\nexport function ThemePicker(props: ThemePickerProps): JSX.Element {\n const activeTheme = theme();\n const { themes = activeTheme.themes, label = \"Theme\", ...rest } = props;\n const currentTheme = activeTheme.theme();\n\n return (\n <select\n {...rest}\n aria-label={rest[\"aria-label\"] ?? label}\n data-slot=\"theme-picker\"\n value={currentTheme}\n onChange={(event: Event) => {\n const target = getThemePickerTarget(event);\n if (target) {\n activeTheme.setTheme(target.value as ThemeName);\n }\n }}\n >\n {themes.map((option) => (\n <option key={option.value} value={option.value} selected={option.value === currentTheme}>\n {option.label}\n </option>\n ))}\n </select>\n );\n}\n\nfunction getThemePickerTarget(event: Event): HTMLSelectElement | null {\n if (typeof HTMLSelectElement === \"undefined\") {\n return null;\n }\n\n const path = typeof event.composedPath === \"function\" ? event.composedPath() : [];\n const candidates = [event.target, event.currentTarget, ...path];\n\n for (const candidate of candidates) {\n if (candidate instanceof HTMLSelectElement) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function ThemeToggle(props: ThemeToggleProps): JSX.Element {\n const activeTheme = theme();\n const {\n children,\n lightIcon,\n darkIcon,\n systemIcon,\n themes = [\"light\", \"dark\"],\n onPress,\n ...rest\n } = props;\n\n const currentTheme = activeTheme.theme();\n const nextTheme = getNextTheme(currentTheme, themes, activeTheme.resolvedSystemTheme());\n const renderContext = { theme: currentTheme, nextTheme };\n const ariaLabel = (rest as Record<string, unknown>)[\"aria-label\"];\n const themedIcon = resolveThemeToggleIcon(currentTheme, nextTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n });\n const renderedIcon = cloneThemeToggleIcon(themedIcon, currentTheme);\n const renderedIconSlots =\n renderThemeToggleIconSlots(currentTheme, {\n lightIcon,\n darkIcon,\n systemIcon,\n }) ?? renderedIcon;\n const content =\n typeof children === \"function\" ? children(renderContext) : (children ?? renderedIconSlots);\n\n return (\n <Button\n {...(rest as ButtonNativeProps)}\n aria-label={typeof ariaLabel === \"string\" ? ariaLabel : `Switch to ${nextTheme} theme`}\n data-theme-control=\"toggle\"\n data-theme-choice={currentTheme}\n data-next-theme={nextTheme}\n onPress={(event) => {\n onPress?.(event);\n if (!event.defaultPrevented && !Object.is(nextTheme, currentTheme)) {\n activeTheme.setTheme(nextTheme);\n }\n }}\n >\n <span data-slot=\"theme-toggle-content\">{content}</span>\n </Button>\n );\n}\n\nfunction getNextTheme(\n currentTheme: ThemeName,\n themes: readonly ThemeName[],\n resolvedSystemTheme: \"light\" | \"dark\" = \"light\",\n): ThemeName {\n if (themes.length === 0) return currentTheme;\n const index = themes.indexOf(currentTheme);\n if (index < 0 && currentTheme === \"system\") {\n if (themes.includes(\"light\") && themes.includes(\"dark\")) {\n return resolvedSystemTheme === \"dark\" ? \"light\" : \"dark\";\n }\n }\n return themes[index >= 0 && index < themes.length - 1 ? index + 1 : 0]!;\n}\n\nfunction getThemeIcon(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (theme === \"light\") return icons.lightIcon;\n if (theme === \"dark\") return icons.darkIcon;\n if (theme === \"system\") return icons.systemIcon;\n return undefined;\n}\n\nexport function resolveThemeToggleIcon(\n theme: ThemeName,\n nextTheme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n return getThemeIcon(theme, icons) ?? getThemeIcon(nextTheme, icons);\n}\n\nfunction renderThemeToggleIconSlots(\n theme: ThemeName,\n icons: Pick<ThemeToggleProps, \"lightIcon\" | \"darkIcon\" | \"systemIcon\">,\n): unknown {\n if (getThemeIcon(theme, icons) === undefined) {\n return undefined;\n }\n\n const slots = [\n [\"light\", icons.lightIcon],\n [\"dark\", icons.darkIcon],\n [\"system\", icons.systemIcon],\n ] as const;\n const availableSlots = slots.filter(([, icon]) => icon !== undefined && icon !== null);\n\n if (availableSlots.length <= 1) {\n return undefined;\n }\n\n return availableSlots.map(([slotTheme, icon]) => (\n <span\n key={slotTheme}\n data-slot=\"theme-toggle-icon\"\n data-theme-toggle-icon={slotTheme}\n hidden={slotTheme === theme ? undefined : true}\n >\n {cloneThemeToggleIcon(icon, slotTheme)}\n </span>\n ));\n}\n\nfunction isJSXElement(value: unknown): value is JSXElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"$$typeof\" in value &&\n \"type\" in value &&\n \"props\" in value\n );\n}\n\nfunction cloneThemeToggleIcon(icon: unknown, key?: string): unknown {\n if (Array.isArray(icon)) {\n const clonedChildren = icon.map((child) => cloneThemeToggleIcon(child));\n if ((icon as unknown as Record<symbol, unknown>)[STATIC_CHILDREN] === true) {\n Object.defineProperty(clonedChildren, STATIC_CHILDREN, {\n value: true,\n configurable: true,\n });\n }\n return clonedChildren;\n }\n\n if (!isJSXElement(icon)) return icon;\n\n const props = icon.props as Record<string, unknown> | undefined;\n const clonedProps = props ? { ...props } : {};\n\n if (\"children\" in clonedProps) {\n clonedProps.children = cloneThemeToggleIcon(clonedProps.children);\n }\n\n const iconKey = (icon.key ?? key ?? null) as string | number | null;\n const clonedIcon = {\n ...icon,\n key: iconKey,\n props: clonedProps,\n };\n\n delete (clonedIcon as Record<symbol, unknown>)[STATIC_CHILD_SLOTS_CACHE];\n return clonedIcon;\n}\n\nfunction syncThemeTarget(\n html: HTMLElement | null,\n themeChoice: ThemeName | null | undefined,\n): void {\n if (!html) return;\n\n if (themeChoice == null) {\n html.removeAttribute(\"data-theme\");\n html.removeAttribute(\"data-theme-choice\");\n return;\n }\n\n html.setAttribute(\"data-theme-choice\", themeChoice);\n\n if (themeChoice === \"system\") {\n html.removeAttribute(\"data-theme\");\n } else {\n html.setAttribute(\"data-theme\", themeChoice);\n }\n}\n\nfunction readStoredTheme(storageKey: string): ThemeName | undefined {\n if (typeof window === \"undefined\") return undefined;\n try {\n const storedTheme = window.localStorage.getItem(storageKey);\n return storedTheme ? (storedTheme as ThemeName) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeStoredTheme(storageKey: string, theme: ThemeName): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey, theme);\n } catch {\n // Storage can be unavailable in private or locked-down browser contexts.\n }\n}\n"],"mappings":"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"}
|
|
@@ -1011,16 +1011,20 @@
|
|
|
1011
1011
|
}
|
|
1012
1012
|
|
|
1013
1013
|
:where([data-slot="nav-group-label"]) {
|
|
1014
|
+
max-inline-size: 100%;
|
|
1014
1015
|
color: var(--ak-color-text-muted);
|
|
1015
1016
|
font-size: var(--ak-font-size-xs);
|
|
1016
1017
|
font-weight: var(--ak-font-weight-medium);
|
|
1017
1018
|
line-height: var(--ak-line-height-tight);
|
|
1019
|
+
overflow-wrap: anywhere;
|
|
1018
1020
|
margin: 0;
|
|
1019
1021
|
}
|
|
1020
1022
|
|
|
1021
1023
|
:where([data-slot="nav-item"]) {
|
|
1022
1024
|
min-block-size: 2rem;
|
|
1025
|
+
max-inline-size: 100%;
|
|
1023
1026
|
color: var(--ak-color-text-muted);
|
|
1027
|
+
white-space: normal;
|
|
1024
1028
|
transition: background var(--ak-duration-fast) var(--ak-ease-standard),
|
|
1025
1029
|
color var(--ak-duration-fast) var(--ak-ease-standard);
|
|
1026
1030
|
text-decoration: none;
|
|
@@ -4330,7 +4334,9 @@
|
|
|
4330
4334
|
}
|
|
4331
4335
|
|
|
4332
4336
|
:where([data-slot="theme-picker"]) {
|
|
4333
|
-
|
|
4337
|
+
appearance: none;
|
|
4338
|
+
block-size: var(--ak-density-control-height-md, 2.25rem);
|
|
4339
|
+
min-block-size: var(--ak-density-control-height-md, 2.25rem);
|
|
4334
4340
|
min-inline-size: min(8rem, 100%);
|
|
4335
4341
|
max-inline-size: 100%;
|
|
4336
4342
|
padding-inline: var(--ak-density-control-padding-x-md) 2.1rem;
|
package/package.json
CHANGED
|
@@ -12,6 +12,8 @@ const CSS_ALLOWED_FUNCTIONS = new Set([
|
|
|
12
12
|
"min",
|
|
13
13
|
"max",
|
|
14
14
|
"clamp",
|
|
15
|
+
"minmax",
|
|
16
|
+
"repeat",
|
|
15
17
|
"rgb",
|
|
16
18
|
"rgba",
|
|
17
19
|
"hsl",
|
|
@@ -265,10 +267,15 @@ function ensureStyleRegistry(nonce: string | undefined): StyleRegistry | null {
|
|
|
265
267
|
const current = documentRegistries.get(key);
|
|
266
268
|
if (current?.element.isConnected) return current;
|
|
267
269
|
|
|
270
|
+
const existingStyleElements = Array.from(
|
|
271
|
+
document.querySelectorAll<HTMLStyleElement>(`style[${STYLE_REGISTRY_ATTR}]`),
|
|
272
|
+
);
|
|
268
273
|
const styleElement =
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
274
|
+
existingStyleElements.find((element) => (element.nonce || undefined) === nonce) ??
|
|
275
|
+
(nonce === undefined && existingStyleElements.length === 1
|
|
276
|
+
? existingStyleElements[0]
|
|
277
|
+
: undefined) ??
|
|
278
|
+
document.createElement("style");
|
|
272
279
|
if (!styleElement.isConnected) {
|
|
273
280
|
styleElement.setAttribute(STYLE_REGISTRY_ATTR, "true");
|
|
274
281
|
if (nonce !== undefined) styleElement.nonce = nonce;
|
|
@@ -110,7 +110,9 @@ export function ThemeScope(props: ThemeScopeProps): JSX.Element {
|
|
|
110
110
|
const ownedCoordinator = state<ThemeCoordinator>(getDefaultThemeCoordinator())();
|
|
111
111
|
const coordinator = parentScope.coordinator ?? ownedCoordinator;
|
|
112
112
|
const scopeDepth = parentScope.depth + 1;
|
|
113
|
-
coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal)
|
|
113
|
+
coordinator.register(scopeId, scopeDepth, currentTheme, scopeSignal, (nextTheme) => {
|
|
114
|
+
if (themeState() !== nextTheme) themeState.set(nextTheme);
|
|
115
|
+
});
|
|
114
116
|
|
|
115
117
|
const setTheme = (nextTheme: ThemeName) => {
|
|
116
118
|
themeState.set(nextTheme);
|
|
@@ -143,6 +145,29 @@ export function ThemeScope(props: ThemeScopeProps): JSX.Element {
|
|
|
143
145
|
(nextTheme) => localResolvedSystemTheme.set(nextTheme),
|
|
144
146
|
scopeSignal,
|
|
145
147
|
);
|
|
148
|
+
if (element && typeof window !== "undefined") {
|
|
149
|
+
const onStorage = (event: StorageEvent) => {
|
|
150
|
+
let storageMatches = event.storageArea == null;
|
|
151
|
+
try {
|
|
152
|
+
storageMatches ||= event.storageArea === window.localStorage;
|
|
153
|
+
} catch {
|
|
154
|
+
// Locked-down/private contexts may deny access to localStorage.
|
|
155
|
+
}
|
|
156
|
+
if (!storageMatches || event.key !== storageKey) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const nextTheme = event.newValue as ThemeName | null;
|
|
160
|
+
if (!nextTheme) return;
|
|
161
|
+
themeState.set(nextTheme);
|
|
162
|
+
coordinator.activate(scopeId, nextTheme);
|
|
163
|
+
};
|
|
164
|
+
window.addEventListener("storage", onStorage);
|
|
165
|
+
scopeSignal.addEventListener(
|
|
166
|
+
"abort",
|
|
167
|
+
() => window.removeEventListener("storage", onStorage),
|
|
168
|
+
{ once: true },
|
|
169
|
+
);
|
|
170
|
+
}
|
|
146
171
|
if (!element || persistenceAdoption.complete) return;
|
|
147
172
|
persistenceAdoption.complete = true;
|
|
148
173
|
const storedTheme = readStoredTheme(storageKey);
|
|
@@ -181,6 +206,7 @@ function createThemeCoordinator() {
|
|
|
181
206
|
sequence: number;
|
|
182
207
|
theme: ThemeName;
|
|
183
208
|
signal: AbortSignal;
|
|
209
|
+
onThemeChange: (themeName: ThemeName) => void;
|
|
184
210
|
}
|
|
185
211
|
>();
|
|
186
212
|
let nextSequence = 0;
|
|
@@ -234,13 +260,20 @@ function createThemeCoordinator() {
|
|
|
234
260
|
}
|
|
235
261
|
schedule();
|
|
236
262
|
},
|
|
237
|
-
register(
|
|
263
|
+
register(
|
|
264
|
+
id: symbol,
|
|
265
|
+
depth: number,
|
|
266
|
+
themeName: ThemeName,
|
|
267
|
+
signal: AbortSignal,
|
|
268
|
+
onThemeChange: (themeName: ThemeName) => void,
|
|
269
|
+
) {
|
|
238
270
|
const existing = scopes.get(id);
|
|
239
271
|
scopes.set(id, {
|
|
240
272
|
depth,
|
|
241
273
|
sequence: existing?.sequence ?? nextSequence++,
|
|
242
274
|
theme: themeName,
|
|
243
275
|
signal,
|
|
276
|
+
onThemeChange,
|
|
244
277
|
});
|
|
245
278
|
if (!existing) {
|
|
246
279
|
signal.addEventListener(
|
|
@@ -259,6 +292,13 @@ function createThemeCoordinator() {
|
|
|
259
292
|
const scope = scopes.get(id);
|
|
260
293
|
if (scope) scope.theme = themeName;
|
|
261
294
|
explicitOwner = id;
|
|
295
|
+
const ownerDepth = scope?.depth;
|
|
296
|
+
for (const [scopeId, registered] of scopes) {
|
|
297
|
+
if (scopeId !== id && registered.depth === ownerDepth) {
|
|
298
|
+
registered.theme = themeName;
|
|
299
|
+
registered.onThemeChange(themeName);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
262
302
|
syncThemeTarget(target(), themeName);
|
|
263
303
|
},
|
|
264
304
|
});
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
:where([data-slot="theme-picker"]) {
|
|
9
|
+
appearance: none;
|
|
9
10
|
display: inline-flex;
|
|
10
|
-
|
|
11
|
+
block-size: var(--ak-density-control-height-md, 2.25rem);
|
|
12
|
+
min-block-size: var(--ak-density-control-height-md, 2.25rem);
|
|
11
13
|
min-inline-size: min(8rem, 100%);
|
|
12
14
|
max-inline-size: 100%;
|
|
13
15
|
padding-inline: var(--ak-density-control-padding-x-md) 2.1rem;
|
|
@@ -24,17 +24,21 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
:where([data-slot="nav-group-label"]) {
|
|
27
|
+
max-inline-size: 100%;
|
|
27
28
|
margin: 0;
|
|
28
29
|
color: var(--ak-color-text-muted);
|
|
29
30
|
font-size: var(--ak-font-size-xs);
|
|
30
31
|
font-weight: var(--ak-font-weight-medium);
|
|
31
32
|
line-height: var(--ak-line-height-tight);
|
|
33
|
+
overflow-wrap: anywhere;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
:where([data-slot="nav-item"]) {
|
|
37
|
+
max-inline-size: 100%;
|
|
35
38
|
min-block-size: 2rem;
|
|
36
39
|
color: var(--ak-color-text-muted);
|
|
37
40
|
text-decoration: none;
|
|
41
|
+
white-space: normal;
|
|
38
42
|
transition:
|
|
39
43
|
background var(--ak-duration-fast) var(--ak-ease-standard),
|
|
40
44
|
color var(--ak-duration-fast) var(--ak-ease-standard);
|