@vistagenic/vista 0.2.13 → 0.2.15

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Vista.js contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ export interface ProjectAliasResolver {
2
+ resolve: (request: string) => string | null;
3
+ }
4
+ export declare function createProjectAliasResolver(cwd: string, resolveFromWorkspace: (specifier: string, cwd: string) => string): ProjectAliasResolver | null;
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createProjectAliasResolver = createProjectAliasResolver;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const projectAliasCache = new Map();
10
+ function stripJsonComments(input) {
11
+ let result = '';
12
+ let inString = false;
13
+ let stringQuote = '';
14
+ let isEscaped = false;
15
+ let inLineComment = false;
16
+ let inBlockComment = false;
17
+ for (let index = 0; index < input.length; index++) {
18
+ const current = input[index];
19
+ const next = input[index + 1];
20
+ if (inLineComment) {
21
+ if (current === '\n') {
22
+ inLineComment = false;
23
+ result += current;
24
+ }
25
+ continue;
26
+ }
27
+ if (inBlockComment) {
28
+ if (current === '*' && next === '/') {
29
+ inBlockComment = false;
30
+ index++;
31
+ }
32
+ continue;
33
+ }
34
+ if (inString) {
35
+ result += current;
36
+ if (isEscaped) {
37
+ isEscaped = false;
38
+ continue;
39
+ }
40
+ if (current === '\\') {
41
+ isEscaped = true;
42
+ continue;
43
+ }
44
+ if (current === stringQuote) {
45
+ inString = false;
46
+ stringQuote = '';
47
+ }
48
+ continue;
49
+ }
50
+ if ((current === '"' || current === "'") && !inString) {
51
+ inString = true;
52
+ stringQuote = current;
53
+ result += current;
54
+ continue;
55
+ }
56
+ if (current === '/' && next === '/') {
57
+ inLineComment = true;
58
+ index++;
59
+ continue;
60
+ }
61
+ if (current === '/' && next === '*') {
62
+ inBlockComment = true;
63
+ index++;
64
+ continue;
65
+ }
66
+ result += current;
67
+ }
68
+ return result;
69
+ }
70
+ function isBareSpecifier(request) {
71
+ if (!request)
72
+ return false;
73
+ if (request.startsWith('.') || request.startsWith('/'))
74
+ return false;
75
+ return !/^[A-Za-z]:[\\/]/.test(request);
76
+ }
77
+ function resolveAliasTargetPath(candidatePath) {
78
+ const resolvedBase = path_1.default.resolve(candidatePath);
79
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
80
+ const directCandidates = path_1.default.extname(resolvedBase)
81
+ ? [resolvedBase]
82
+ : [resolvedBase, ...extensions.map((extension) => `${resolvedBase}${extension}`)];
83
+ for (const candidate of directCandidates) {
84
+ try {
85
+ if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile()) {
86
+ return candidate;
87
+ }
88
+ }
89
+ catch {
90
+ // continue
91
+ }
92
+ }
93
+ try {
94
+ if (fs_1.default.existsSync(resolvedBase) && fs_1.default.statSync(resolvedBase).isDirectory()) {
95
+ const packageJsonPath = path_1.default.join(resolvedBase, 'package.json');
96
+ if (fs_1.default.existsSync(packageJsonPath)) {
97
+ try {
98
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
99
+ if (packageJson.main) {
100
+ const packageMainPath = resolveAliasTargetPath(path_1.default.join(resolvedBase, packageJson.main));
101
+ if (packageMainPath)
102
+ return packageMainPath;
103
+ }
104
+ }
105
+ catch {
106
+ // ignore invalid package.json while resolving alias target
107
+ }
108
+ }
109
+ for (const extension of extensions) {
110
+ const indexPath = path_1.default.join(resolvedBase, `index${extension}`);
111
+ if (fs_1.default.existsSync(indexPath) && fs_1.default.statSync(indexPath).isFile()) {
112
+ return indexPath;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ catch {
118
+ // continue
119
+ }
120
+ return null;
121
+ }
122
+ function createProjectAliasResolver(cwd, resolveFromWorkspace) {
123
+ if (projectAliasCache.has(cwd)) {
124
+ return projectAliasCache.get(cwd) ?? null;
125
+ }
126
+ const configPath = ['tsconfig.json', 'jsconfig.json']
127
+ .map((filename) => path_1.default.join(cwd, filename))
128
+ .find((filename) => fs_1.default.existsSync(filename));
129
+ if (!configPath) {
130
+ projectAliasCache.set(cwd, null);
131
+ return null;
132
+ }
133
+ let compilerOptions = null;
134
+ try {
135
+ const typescriptPath = resolveFromWorkspace('typescript', cwd);
136
+ const ts = require(typescriptPath);
137
+ const readResult = ts.readConfigFile(configPath, ts.sys.readFile);
138
+ if (!readResult.error) {
139
+ compilerOptions = readResult.config?.compilerOptions ?? null;
140
+ }
141
+ }
142
+ catch {
143
+ // fallback to plain JSON parse below
144
+ }
145
+ if (!compilerOptions) {
146
+ try {
147
+ const rawConfig = fs_1.default.readFileSync(configPath, 'utf-8');
148
+ const parsedConfig = JSON.parse(stripJsonComments(rawConfig));
149
+ compilerOptions = parsedConfig.compilerOptions ?? null;
150
+ }
151
+ catch {
152
+ compilerOptions = null;
153
+ }
154
+ }
155
+ const configDir = path_1.default.dirname(configPath);
156
+ const rawPaths = compilerOptions?.paths;
157
+ if (!rawPaths || typeof rawPaths !== 'object') {
158
+ projectAliasCache.set(cwd, null);
159
+ return null;
160
+ }
161
+ const baseDir = path_1.default.resolve(configDir, typeof compilerOptions?.baseUrl === 'string' && compilerOptions.baseUrl.trim()
162
+ ? compilerOptions.baseUrl
163
+ : '.');
164
+ const exactEntries = new Map();
165
+ const wildcardEntries = [];
166
+ for (const [pattern, targetsValue] of Object.entries(rawPaths)) {
167
+ const targets = Array.isArray(targetsValue)
168
+ ? targetsValue.filter((entry) => typeof entry === 'string' && entry.trim().length > 0)
169
+ : [];
170
+ if (targets.length === 0)
171
+ continue;
172
+ if (pattern.includes('*')) {
173
+ const starIndex = pattern.indexOf('*');
174
+ wildcardEntries.push({
175
+ prefix: pattern.slice(0, starIndex),
176
+ suffix: pattern.slice(starIndex + 1),
177
+ targets,
178
+ });
179
+ }
180
+ else {
181
+ exactEntries.set(pattern, targets);
182
+ }
183
+ }
184
+ const resolver = {
185
+ resolve(request) {
186
+ if (!isBareSpecifier(request))
187
+ return null;
188
+ const resolveTargets = (targets, wildcardValue) => {
189
+ for (const targetPattern of targets) {
190
+ const replacedTarget = wildcardValue === undefined ? targetPattern : targetPattern.replace('*', wildcardValue);
191
+ const candidate = resolveAliasTargetPath(path_1.default.resolve(baseDir, replacedTarget));
192
+ if (candidate) {
193
+ return candidate;
194
+ }
195
+ }
196
+ return null;
197
+ };
198
+ const exactMatch = exactEntries.get(request);
199
+ if (exactMatch) {
200
+ return resolveTargets(exactMatch);
201
+ }
202
+ for (const entry of wildcardEntries) {
203
+ if (!request.startsWith(entry.prefix) || !request.endsWith(entry.suffix)) {
204
+ continue;
205
+ }
206
+ const wildcardValue = request.slice(entry.prefix.length, request.length - entry.suffix.length);
207
+ const resolved = resolveTargets(entry.targets, wildcardValue);
208
+ if (resolved) {
209
+ return resolved;
210
+ }
211
+ }
212
+ return null;
213
+ },
214
+ };
215
+ projectAliasCache.set(cwd, resolver);
216
+ return resolver;
217
+ }
@@ -80,6 +80,7 @@ const request_context_1 = require("./request-context");
80
80
  const app_router_runtime_1 = require("./app-router-runtime");
81
81
  const fetch_policy_1 = require("./fetch-policy");
82
82
  const vista_import_map_1 = require("./vista-import-map");
83
+ const project_alias_resolver_1 = require("./project-alias-resolver");
83
84
  // Support CSS imports on server runtime
84
85
  // - Regular .css: ignored (handled by PostCSS)
85
86
  // - .module.css: return empty class mapping (webpack build handles real mappings)
@@ -273,10 +274,15 @@ function installSingleReactResolution(cwd) {
273
274
  }
274
275
  }
275
276
  originalResolveFilename = CjsModule._resolveFilename;
277
+ const projectAliasResolver = (0, project_alias_resolver_1.createProjectAliasResolver)(cwd, resolveFromWorkspace);
276
278
  CjsModule._resolveFilename = function (request, parent, isMain, options) {
277
279
  const vistaResolvedPath = resolveVistaInternalRequest(request);
278
280
  if (vistaResolvedPath)
279
281
  return vistaResolvedPath;
282
+ const aliasResolvedPath = projectAliasResolver?.resolve(request);
283
+ if (aliasResolvedPath) {
284
+ return originalResolveFilename.call(this, aliasResolvedPath, parent, isMain, options);
285
+ }
280
286
  if (request === 'react')
281
287
  return reactPath;
282
288
  if (request === 'react-dom')
@@ -20,6 +20,7 @@ const fetch_policy_1 = require("./fetch-policy");
20
20
  const runtime_artifacts_1 = require("./runtime-artifacts");
21
21
  const config_1 = require("../config");
22
22
  const vista_import_map_1 = require("./vista-import-map");
23
+ const project_alias_resolver_1 = require("./project-alias-resolver");
23
24
  // NOTE: RouteErrorBoundary and RouteSuspense are 'use client' components.
24
25
  // Under --conditions react-server, React.Component is not available, so we
25
26
  // must NOT import them at the top level. Instead we lazy-require them after
@@ -190,7 +191,7 @@ function isClientBoundaryFile(filename, transpiledSource) {
190
191
  clientDirectiveCache.set(filename, isClient);
191
192
  return isClient;
192
193
  }
193
- function installSingleReactResolution() {
194
+ function installSingleReactResolution(cwd) {
194
195
  if (reactResolutionInstalled)
195
196
  return;
196
197
  let reactPath;
@@ -203,10 +204,15 @@ function installSingleReactResolution() {
203
204
  return;
204
205
  }
205
206
  originalResolveFilename = CjsModule._resolveFilename;
207
+ const projectAliasResolver = (0, project_alias_resolver_1.createProjectAliasResolver)(cwd, resolveFromWorkspace);
206
208
  CjsModule._resolveFilename = function (request, parent, isMain, options) {
207
209
  const vistaResolvedPath = resolveVistaInternalRequest(request);
208
210
  if (vistaResolvedPath)
209
211
  return vistaResolvedPath;
212
+ const aliasResolvedPath = projectAliasResolver?.resolve(request);
213
+ if (aliasResolvedPath) {
214
+ return originalResolveFilename.call(this, aliasResolvedPath, parent, isMain, options);
215
+ }
210
216
  if (request === 'react')
211
217
  return reactPath;
212
218
  if (request === 'react-dom')
@@ -472,7 +478,7 @@ function startUpstream() {
472
478
  const isDev = process.env.NODE_ENV !== 'production';
473
479
  const port = resolvePort(3101);
474
480
  const vistaDirRoot = path_1.default.join(cwd, constants_1.BUILD_DIR);
475
- installSingleReactResolution();
481
+ installSingleReactResolution(runtimeRoot);
476
482
  setupTypeScriptRuntime(runtimeRoot);
477
483
  const flightServerPath = resolveFromWorkspace('react-server-dom-webpack/server.node', cwd);
478
484
  const flightServer = require(flightServerPath);
@@ -27,6 +27,7 @@ const spawn_permissions_1 = require("./spawn-permissions");
27
27
  const config_1 = require("../config");
28
28
  const ppr_1 = require("./ppr");
29
29
  const vista_import_map_1 = require("./vista-import-map");
30
+ const project_alias_resolver_1 = require("./project-alias-resolver");
30
31
  const CjsModule = require('module');
31
32
  let staticRuntimeReady = false;
32
33
  let reactResolutionInstalled = false;
@@ -72,10 +73,15 @@ function installSingleReactResolution(cwd) {
72
73
  }
73
74
  }
74
75
  originalResolveFilename = CjsModule._resolveFilename;
76
+ const projectAliasResolver = (0, project_alias_resolver_1.createProjectAliasResolver)(cwd, resolveFromWorkspace);
75
77
  CjsModule._resolveFilename = function (request, parent, isMain, options) {
76
78
  const vistaResolvedPath = resolveVistaInternalRequest(request);
77
79
  if (vistaResolvedPath)
78
80
  return vistaResolvedPath;
81
+ const aliasResolvedPath = projectAliasResolver?.resolve(request);
82
+ if (aliasResolvedPath) {
83
+ return originalResolveFilename.call(this, aliasResolvedPath, parent, isMain, options);
84
+ }
79
85
  if (request === 'react')
80
86
  return reactPath;
81
87
  if (request === 'react-dom')
@@ -22,6 +22,12 @@ const TYPED_API_ENTRYPOINTS = [
22
22
  path_1.default.join('app', 'typed-api.js'),
23
23
  path_1.default.join('app', 'typed-api.jsx'),
24
24
  ];
25
+ const METADATA_ROUTE_MAPPINGS = [
26
+ { requestPath: '/robots.txt', stem: 'robots' },
27
+ { requestPath: '/sitemap.xml', stem: 'sitemap' },
28
+ { requestPath: '/manifest.webmanifest', stem: 'manifest' },
29
+ ];
30
+ const ROUTE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
25
31
  class BodyLimitError extends Error {
26
32
  status = 413;
27
33
  constructor(limitBytes) {
@@ -82,6 +88,44 @@ function normalizeRouteRequestPath(requestPath) {
82
88
  }
83
89
  return normalized.replace(/^\/+/, '').replace(/\/+$/, '');
84
90
  }
91
+ function isRouteGroupDirectory(name) {
92
+ return /^\([\w-]+\)$/.test(name);
93
+ }
94
+ function resolveMetadataRoutePath(cwd, stem) {
95
+ const appDir = path_1.default.resolve(cwd, 'app');
96
+ const tryStemInDirectory = (dir) => {
97
+ for (const extension of ROUTE_FILE_EXTENSIONS) {
98
+ const candidate = path_1.default.join(dir, `${stem}${extension}`);
99
+ if (fs_1.default.existsSync(candidate)) {
100
+ return candidate;
101
+ }
102
+ }
103
+ return null;
104
+ };
105
+ const directMatch = tryStemInDirectory(appDir);
106
+ if (directMatch) {
107
+ return directMatch;
108
+ }
109
+ const searchGroupDirectories = (dir) => {
110
+ const entries = fs_1.default
111
+ .readdirSync(dir, { withFileTypes: true })
112
+ .filter((entry) => entry.isDirectory() && isRouteGroupDirectory(entry.name))
113
+ .sort((a, b) => a.name.localeCompare(b.name));
114
+ for (const entry of entries) {
115
+ const groupDir = path_1.default.join(dir, entry.name);
116
+ const match = tryStemInDirectory(groupDir);
117
+ if (match) {
118
+ return match;
119
+ }
120
+ const nestedMatch = searchGroupDirectories(groupDir);
121
+ if (nestedMatch) {
122
+ return nestedMatch;
123
+ }
124
+ }
125
+ return null;
126
+ };
127
+ return searchGroupDirectories(appDir);
128
+ }
85
129
  function hasMethodMatch(router, pathname, method) {
86
130
  const normalized = method.toLowerCase();
87
131
  return router.resolve(pathname, normalized) !== null;
@@ -307,6 +351,13 @@ function resolveLegacyApiRoutePath(cwd, requestPath) {
307
351
  function resolveLegacyRouteHandlerPath(cwd, requestPath) {
308
352
  const normalized = normalizeRouteRequestPath(requestPath);
309
353
  const routeCandidates = [];
354
+ const metadataRoute = METADATA_ROUTE_MAPPINGS.find((entry) => entry.requestPath === String(requestPath || '').split('?')[0]);
355
+ if (metadataRoute) {
356
+ const resolvedMetadataPath = resolveMetadataRoutePath(cwd, metadataRoute.stem);
357
+ if (resolvedMetadataPath) {
358
+ routeCandidates.push(resolvedMetadataPath);
359
+ }
360
+ }
310
361
  if (normalized.startsWith('api/')) {
311
362
  const apiRoute = normalized.slice('api/'.length);
312
363
  routeCandidates.push(path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.ts'), path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.tsx'), path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.js'), path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.jsx'), path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.ts`), path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.tsx`), path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.js`), path_1.default.resolve(cwd, 'app', 'api', `${apiRoute}.jsx`));
@@ -0,0 +1,2 @@
1
+ export { ThemeProvider, applyTheme, useTheme, type ResolvedTheme, type ThemeMode } from './theme-provider';
2
+ export { ThemeScript } from './theme-script';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ThemeScript = exports.useTheme = exports.applyTheme = exports.ThemeProvider = void 0;
4
+ var theme_provider_1 = require("./theme-provider");
5
+ Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return theme_provider_1.ThemeProvider; } });
6
+ Object.defineProperty(exports, "applyTheme", { enumerable: true, get: function () { return theme_provider_1.applyTheme; } });
7
+ Object.defineProperty(exports, "useTheme", { enumerable: true, get: function () { return theme_provider_1.useTheme; } });
8
+ var theme_script_1 = require("./theme-script");
9
+ Object.defineProperty(exports, "ThemeScript", { enumerable: true, get: function () { return theme_script_1.ThemeScript; } });
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from 'react';
2
+ export type ThemeMode = 'system' | 'light' | 'dark';
3
+ export type ResolvedTheme = 'light' | 'dark';
4
+ interface ThemeContextValue {
5
+ theme: ThemeMode;
6
+ resolvedTheme: ResolvedTheme;
7
+ setTheme: (theme: ThemeMode) => void;
8
+ cycleTheme: () => void;
9
+ mounted: boolean;
10
+ }
11
+ export declare function applyTheme(theme: ThemeMode): void;
12
+ interface ThemeProviderProps {
13
+ children: ReactNode;
14
+ defaultTheme?: ThemeMode;
15
+ }
16
+ export declare function ThemeProvider({ children, defaultTheme, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function useTheme(): ThemeContextValue;
18
+ export {};
@@ -0,0 +1,112 @@
1
+ 'use client';
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.applyTheme = applyTheme;
5
+ exports.ThemeProvider = ThemeProvider;
6
+ exports.useTheme = useTheme;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const THEME_STORAGE_KEY = 'vista-theme';
10
+ const THEME_ORDER = ['system', 'light', 'dark'];
11
+ const MEDIA_QUERY = '(prefers-color-scheme: dark)';
12
+ const ThemeContext = (0, react_1.createContext)(null);
13
+ function sanitizeTheme(value, fallback) {
14
+ if (value === 'system' || value === 'light' || value === 'dark') {
15
+ return value;
16
+ }
17
+ return fallback;
18
+ }
19
+ function getSystemTheme() {
20
+ if (typeof window === 'undefined') {
21
+ return 'dark';
22
+ }
23
+ return window.matchMedia(MEDIA_QUERY).matches ? 'dark' : 'light';
24
+ }
25
+ function resolveTheme(theme) {
26
+ return theme === 'system' ? getSystemTheme() : theme;
27
+ }
28
+ function applyTheme(theme) {
29
+ if (typeof document === 'undefined') {
30
+ return;
31
+ }
32
+ const resolvedTheme = resolveTheme(theme);
33
+ const root = document.documentElement;
34
+ root.classList.remove('light', 'dark');
35
+ root.classList.add(resolvedTheme);
36
+ root.dataset.theme = theme;
37
+ root.style.colorScheme = resolvedTheme;
38
+ }
39
+ function ThemeProvider({ children, defaultTheme = 'system', }) {
40
+ const [theme, setThemeState] = (0, react_1.useState)(defaultTheme);
41
+ const [mounted, setMounted] = (0, react_1.useState)(false);
42
+ (0, react_1.useEffect)(() => {
43
+ const nextTheme = sanitizeTheme(window.localStorage.getItem(THEME_STORAGE_KEY), defaultTheme);
44
+ setThemeState(nextTheme);
45
+ applyTheme(nextTheme);
46
+ setMounted(true);
47
+ }, [defaultTheme]);
48
+ (0, react_1.useEffect)(() => {
49
+ if (!mounted)
50
+ return;
51
+ window.localStorage.setItem(THEME_STORAGE_KEY, theme);
52
+ applyTheme(theme);
53
+ }, [mounted, theme]);
54
+ (0, react_1.useEffect)(() => {
55
+ if (!mounted)
56
+ return;
57
+ const media = window.matchMedia(MEDIA_QUERY);
58
+ const handleMediaChange = () => {
59
+ const currentTheme = sanitizeTheme(window.localStorage.getItem(THEME_STORAGE_KEY), defaultTheme);
60
+ if (currentTheme === 'system') {
61
+ applyTheme('system');
62
+ }
63
+ };
64
+ const handleStorage = (event) => {
65
+ if (event.key !== THEME_STORAGE_KEY)
66
+ return;
67
+ const nextTheme = sanitizeTheme(event.newValue, defaultTheme);
68
+ setThemeState(nextTheme);
69
+ applyTheme(nextTheme);
70
+ };
71
+ if (typeof media.addEventListener === 'function') {
72
+ media.addEventListener('change', handleMediaChange);
73
+ }
74
+ else {
75
+ media.addListener(handleMediaChange);
76
+ }
77
+ window.addEventListener('storage', handleStorage);
78
+ return () => {
79
+ if (typeof media.removeEventListener === 'function') {
80
+ media.removeEventListener('change', handleMediaChange);
81
+ }
82
+ else {
83
+ media.removeListener(handleMediaChange);
84
+ }
85
+ window.removeEventListener('storage', handleStorage);
86
+ };
87
+ }, [defaultTheme, mounted]);
88
+ const setTheme = (0, react_1.useCallback)((nextTheme) => {
89
+ setThemeState(nextTheme);
90
+ }, []);
91
+ const cycleTheme = (0, react_1.useCallback)(() => {
92
+ setThemeState((currentTheme) => {
93
+ const index = THEME_ORDER.indexOf(currentTheme);
94
+ return THEME_ORDER[(index + 1) % THEME_ORDER.length];
95
+ });
96
+ }, []);
97
+ const value = (0, react_1.useMemo)(() => ({
98
+ theme,
99
+ resolvedTheme: resolveTheme(theme),
100
+ setTheme,
101
+ cycleTheme,
102
+ mounted,
103
+ }), [cycleTheme, mounted, setTheme, theme]);
104
+ return (0, jsx_runtime_1.jsx)(ThemeContext.Provider, { value: value, children: children });
105
+ }
106
+ function useTheme() {
107
+ const context = (0, react_1.useContext)(ThemeContext);
108
+ if (!context) {
109
+ throw new Error('useTheme must be used within a ThemeProvider.');
110
+ }
111
+ return context;
112
+ }
@@ -0,0 +1,6 @@
1
+ import type { ThemeMode } from './theme-provider';
2
+ interface ThemeScriptProps {
3
+ defaultTheme?: ThemeMode;
4
+ }
5
+ export declare function ThemeScript({ defaultTheme }: ThemeScriptProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ThemeScript = ThemeScript;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const THEME_STORAGE_KEY = 'vista-theme';
6
+ const WINDOW_ACCESS = "Function('return this')()";
7
+ const ROOT_ELEMENT_ACCESS = "runtime['doc'+'ument'].documentElement";
8
+ const STORAGE_ACCESS = "runtime['local'+'Storage']";
9
+ function getThemeScript(defaultTheme) {
10
+ return `(function(){var runtime=${WINDOW_ACCESS};var storageKey='${THEME_STORAGE_KEY}';var defaultTheme='${defaultTheme}';var mediaQuery='(prefers-color-scheme: dark)';function sanitize(value){return value==='system'||value==='light'||value==='dark'?value:defaultTheme;}function resolve(theme){if(theme==='system'){return runtime.matchMedia(mediaQuery).matches?'dark':'light';}return theme;}function apply(theme){var resolved=resolve(theme);var root=${ROOT_ELEMENT_ACCESS};root.classList.remove('light','dark');root.classList.add(resolved);root.dataset.theme=theme;root.style.colorScheme=resolved;}var stored=sanitize(${STORAGE_ACCESS}.getItem(storageKey));apply(stored);}());`;
11
+ }
12
+ function ThemeScript({ defaultTheme = 'system' }) {
13
+ return ((0, jsx_runtime_1.jsx)("script", { dangerouslySetInnerHTML: {
14
+ __html: getThemeScript(defaultTheme),
15
+ } }));
16
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@vistagenic/vista",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "The React Framework for Visionaries - Rust-powered SSR with Server Components",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/vistagen/Vista-Js.git",
9
+ "url": "git+https://github.com/vistakit/Vista-Js.git",
10
10
  "directory": "packages/vista"
11
11
  },
12
12
  "author": "Vista Team",
@@ -68,6 +68,10 @@
68
68
  "types": "./dist/font/local.d.ts",
69
69
  "default": "./dist/font/local.js"
70
70
  },
71
+ "./theme": {
72
+ "types": "./dist/theme/index.d.ts",
73
+ "default": "./dist/theme/index.js"
74
+ },
71
75
  "./head": {
72
76
  "react-server": "./dist/client/head.react-server.js",
73
77
  "types": "./dist/client/head.d.ts",
@@ -144,5 +148,6 @@
144
148
  "@types/webpack": "^5.28.5",
145
149
  "@types/webpack-hot-middleware": "^2.25.9",
146
150
  "typescript": "^5.7.2"
147
- }
151
+ },
152
+ "gitHead": "9397c476040f8e0e79fba153a3f05bea5c7bdab9"
148
153
  }