@stacksjs/stx 0.2.175 → 0.2.177
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/a11y.d.ts +72 -2
- package/dist/a11y.js +25 -10
- package/dist/builtins/attrs.d.ts +19 -0
- package/dist/builtins/attrs.js +18 -0
- package/dist/builtins/safe-image.js +2 -1
- package/dist/builtins/stx-image.js +2 -1
- package/dist/builtins/stx-link.js +2 -1
- package/dist/cli-flags.d.ts +34 -0
- package/dist/cli-flags.js +20 -0
- package/dist/cli.js +1132 -895
- package/dist/client-script.js +2 -1
- package/dist/component-registry.d.ts +9 -0
- package/dist/component-renderer.js +20 -1
- package/dist/composables/use-fetch.d.ts +6 -1
- package/dist/composables/use-fetch.js +40 -18
- package/dist/composables/use-query.d.ts +7 -2
- package/dist/cookie-serialize.d.ts +40 -0
- package/dist/cookie-serialize.js +18 -0
- package/dist/craft.js +670 -442
- package/dist/crosswind-config.d.ts +13 -0
- package/dist/crosswind-config.js +6 -2
- package/dist/dev-server/crosswind.js +2 -1
- package/dist/edge-runtime.d.ts +2 -9
- package/dist/edge-runtime.js +2 -16
- package/dist/includes.js +8 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/page-action.d.ts +10 -20
- package/dist/page-action.js +33 -9
- package/dist/process.js +2 -1
- package/dist/production-server.js +8 -7
- package/dist/props.d.ts +51 -8
- package/dist/pwa.js +637 -409
- package/dist/reactive.js +2 -2
- package/dist/resolve-stx-targets.d.ts +49 -0
- package/dist/resolve-stx-targets.js +38 -0
- package/dist/runtime-globals.d.ts +24 -0
- package/dist/runtime-globals.js +16 -0
- package/dist/runtime-injection.d.ts +36 -0
- package/dist/runtime-injection.js +14 -0
- package/dist/server-bindings.js +3 -0
- package/dist/signal-processing.js +38 -7
- package/dist/signals.js +157 -35
- package/dist/ssg.d.ts +23 -0
- package/dist/ssg.js +615 -383
- package/dist/ssr.js +2 -16
- package/dist/stx-virtual-ts.d.ts +72 -4
- package/dist/stx-virtual-ts.js +111 -5
- package/dist/stx.d.ts +66 -7
- package/dist/template-hydrator.d.ts +1 -1
- package/dist/template-hydrator.js +6 -6
- package/dist/theme-tokens.d.ts +88 -0
- package/dist/theme-tokens.js +71 -0
- package/dist/typecheck.d.ts +6 -1
- package/dist/typecheck.js +102 -67
- package/dist/types/config-types.d.ts +24 -0
- package/dist/utils.js +5 -4
- package/package.json +5 -5
- package/stx.d.ts +66 -7
|
@@ -46,6 +46,19 @@ export interface MergedCrosswindConfig {
|
|
|
46
46
|
includePreflight: boolean;
|
|
47
47
|
/** Honours the user's `minify` key (default false). */
|
|
48
48
|
minify: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The `:root` / `.dark` declarations backing the role tokens (#1930).
|
|
51
|
+
*
|
|
52
|
+
* Returned rather than folded into `preflights` because a project can set
|
|
53
|
+
* `preflight: false`, and the tokens are not preflight — they are the values
|
|
54
|
+
* the generated utilities resolve against. Callers prepend this to the CSS
|
|
55
|
+
* they emit.
|
|
56
|
+
*
|
|
57
|
+
* Missing it degrades rather than breaks: every token carries its light value
|
|
58
|
+
* as the `var()` fallback, so light mode stays correct and dark mode reverts
|
|
59
|
+
* to the light colour.
|
|
60
|
+
*/
|
|
61
|
+
tokenCSS: string;
|
|
49
62
|
}
|
|
50
63
|
/**
|
|
51
64
|
* Merge a project's crosswind config over the package defaults.
|
package/dist/crosswind-config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { semanticColors, semanticTokenCSS } from "./theme-tokens.js";
|
|
1
2
|
function isPlainObject(value) {
|
|
2
3
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
4
|
}
|
|
@@ -8,7 +9,9 @@ export function deepMergeThemes(target, source) {
|
|
|
8
9
|
return out;
|
|
9
10
|
}
|
|
10
11
|
export function mergeCrosswindConfig(base = {}, user = {}) {
|
|
11
|
-
const baseTheme = base.theme || {}, userTheme = user.theme || {}, { extend: userExtend, ...userThemeKeys } = userTheme, { extend: baseExtend, ...baseThemeKeys } = baseTheme,
|
|
12
|
+
const baseTheme = base.theme || {}, userTheme = user.theme || {}, { extend: userExtend, ...userThemeKeys } = userTheme, { extend: baseExtend, ...baseThemeKeys } = baseTheme, basePalette = baseThemeKeys.colors || {};
|
|
13
|
+
baseThemeKeys.colors = { ...basePalette, ...semanticColors(basePalette) };
|
|
14
|
+
const theme = deepMergeThemes(baseThemeKeys, userThemeKeys), extend = deepMergeThemes(baseExtend || {}, userExtend || {});
|
|
12
15
|
if (Object.keys(extend).length > 0)
|
|
13
16
|
theme.extend = extend;
|
|
14
17
|
const safelist = [
|
|
@@ -35,6 +38,7 @@ export function mergeCrosswindConfig(base = {}, user = {}) {
|
|
|
35
38
|
safelist,
|
|
36
39
|
shortcuts,
|
|
37
40
|
includePreflight: user.preflight !== !1,
|
|
38
|
-
minify: user.minify === !0
|
|
41
|
+
minify: user.minify === !0,
|
|
42
|
+
tokenCSS: semanticTokenCSS(basePalette)
|
|
39
43
|
};
|
|
40
44
|
}
|
|
@@ -324,7 +324,7 @@ export async function generateCrosswindCSS(htmlContent, appDir) {
|
|
|
324
324
|
configFingerprint = "unhashable";
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
const baseConfig = hw.defaultConfig || hw.config, userConfig = cachedConfig || {}, merged = mergeCrosswindConfig(baseConfig, userConfig), { safelist, includePreflight, minify } = merged, crosswindConfig = merged.config, generator = new hw.CSSGenerator(crosswindConfig);
|
|
327
|
+
const baseConfig = hw.defaultConfig || hw.config, userConfig = cachedConfig || {}, merged = mergeCrosswindConfig(baseConfig, userConfig), { safelist, includePreflight, minify, tokenCSS } = merged, crosswindConfig = merged.config, generator = new hw.CSSGenerator(crosswindConfig);
|
|
328
328
|
for (const cls of safelist)
|
|
329
329
|
generator.generate(cls);
|
|
330
330
|
for (const className of classes)
|
|
@@ -374,6 +374,7 @@ export async function generateCrosswindCSS(htmlContent, appDir) {
|
|
|
374
374
|
css += `
|
|
375
375
|
@media (prefers-color-scheme: dark) { .dark .${name} { ${darkDecls.join(" ")} } }`;
|
|
376
376
|
}
|
|
377
|
+
css = tokenCSS + css;
|
|
377
378
|
setLruCache(cacheKey, css);
|
|
378
379
|
writeDiskCache(cacheKey, css);
|
|
379
380
|
return css;
|
package/dist/edge-runtime.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SetCookieOptions } from './cookie-serialize';
|
|
1
2
|
/**
|
|
2
3
|
* Detect the current edge runtime platform.
|
|
3
4
|
*/
|
|
@@ -63,15 +64,7 @@ export declare function parseCookies(request: Request): Record<string, string>;
|
|
|
63
64
|
/**
|
|
64
65
|
* Create a Set-Cookie header value.
|
|
65
66
|
*/
|
|
66
|
-
export declare function createCookie(name: string, value: string, options?:
|
|
67
|
-
maxAge?: number
|
|
68
|
-
expires?: Date
|
|
69
|
-
path?: string
|
|
70
|
-
domain?: string
|
|
71
|
-
secure?: boolean
|
|
72
|
-
httpOnly?: boolean
|
|
73
|
-
sameSite?: 'Strict' | 'Lax' | 'None'
|
|
74
|
-
}): string;
|
|
67
|
+
export declare function createCookie(name: string, value: string, options?: SetCookieOptions): string;
|
|
75
68
|
/**
|
|
76
69
|
* JSON response helper.
|
|
77
70
|
*/
|
package/dist/edge-runtime.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeSetCookie } from "./cookie-serialize.js";
|
|
1
2
|
import nodeProcess from "node:process";
|
|
2
3
|
export function detectRuntime() {
|
|
3
4
|
if (typeof globalThis < "u" && "caches" in globalThis && "CF" in globalThis)
|
|
@@ -500,22 +501,7 @@ export function parseCookies(request) {
|
|
|
500
501
|
return cookies;
|
|
501
502
|
}
|
|
502
503
|
export function createCookie(name, value, options) {
|
|
503
|
-
|
|
504
|
-
if (options?.maxAge !== void 0)
|
|
505
|
-
cookie += `; Max-Age=${options.maxAge}`;
|
|
506
|
-
if (options?.expires)
|
|
507
|
-
cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
508
|
-
if (options?.path)
|
|
509
|
-
cookie += `; Path=${options.path}`;
|
|
510
|
-
if (options?.domain)
|
|
511
|
-
cookie += `; Domain=${options.domain}`;
|
|
512
|
-
if (options?.secure)
|
|
513
|
-
cookie += "; Secure";
|
|
514
|
-
if (options?.httpOnly)
|
|
515
|
-
cookie += "; HttpOnly";
|
|
516
|
-
if (options?.sameSite)
|
|
517
|
-
cookie += `; SameSite=${options.sameSite}`;
|
|
518
|
-
return cookie;
|
|
504
|
+
return serializeSetCookie(name, value, options);
|
|
519
505
|
}
|
|
520
506
|
export function jsonResponse(data, init) {
|
|
521
507
|
const headers = new Headers(init?.headers);
|
package/dist/includes.js
CHANGED
|
@@ -512,14 +512,18 @@ export async function processIncludes(template, context, filePath, options, depe
|
|
|
512
512
|
}
|
|
513
513
|
const currentFilePath = path.resolve(filePath);
|
|
514
514
|
includeStack.add(currentFilePath);
|
|
515
|
+
function includeFailure(includePath, message, templateStr, offsetPos) {
|
|
516
|
+
options.onIncludeError?.({ includePath, templatePath: filePath, message, partialsDir });
|
|
517
|
+
return createDetailedErrorMessage("Include", message, filePath, templateStr, offsetPos);
|
|
518
|
+
}
|
|
515
519
|
async function processIncludeHelper(includePath, localVars = {}, templateStr, offsetPos) {
|
|
516
520
|
const includeFilePath = resolvePath(includePath, partialsDir, filePath);
|
|
517
521
|
if (!includeFilePath)
|
|
518
|
-
return
|
|
522
|
+
return includeFailure(includePath, `Could not resolve path for include: ${includePath}`, templateStr, offsetPos);
|
|
519
523
|
const resolvedIncludePath = path.resolve(includeFilePath);
|
|
520
524
|
if (includeStack.has(resolvedIncludePath)) {
|
|
521
525
|
const chain = [...includeStack, resolvedIncludePath].map((p) => path.basename(p)).join(" -> ");
|
|
522
|
-
return
|
|
526
|
+
return includeFailure(includePath, `Circular include detected: ${chain}`, templateStr, offsetPos);
|
|
523
527
|
}
|
|
524
528
|
try {
|
|
525
529
|
dependencies.add(includeFilePath);
|
|
@@ -529,7 +533,7 @@ export async function processIncludes(template, context, filePath, options, depe
|
|
|
529
533
|
partialContent = await Bun.file(includeFilePath).text();
|
|
530
534
|
} catch (error) {
|
|
531
535
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
532
|
-
return
|
|
536
|
+
return includeFailure(includePath, `Error loading include file ${includePath}: ${errorMessage}`, templateStr, offsetPos);
|
|
533
537
|
}
|
|
534
538
|
if (partialContent)
|
|
535
539
|
partialContent = partialContent.replace(/\{\{--[\s\S]*?--\}\}/g, "");
|
|
@@ -641,7 +645,7 @@ export async function processIncludes(template, context, filePath, options, depe
|
|
|
641
645
|
return processedContent;
|
|
642
646
|
} catch (error) {
|
|
643
647
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
644
|
-
return
|
|
648
|
+
return includeFailure(includePath, `Error processing include ${includePath}: ${errorMessage}`, templateStr, offsetPos);
|
|
645
649
|
}
|
|
646
650
|
}
|
|
647
651
|
const includePattern = /@include\s*\(/g;
|
package/dist/index.d.ts
CHANGED
|
@@ -204,6 +204,9 @@ export * from './reactive-bindings';
|
|
|
204
204
|
export * from './client-helpers';
|
|
205
205
|
export * from './client-script';
|
|
206
206
|
export * from './strip-literals';
|
|
207
|
+
export * from './resolve-stx-targets';
|
|
208
|
+
export * from './cookie-serialize';
|
|
209
|
+
export * from './cli-flags';
|
|
207
210
|
export * from './component-hmr';
|
|
208
211
|
export * from './hydration';
|
|
209
212
|
// ssg moved to stx/ssg
|
package/dist/index.js
CHANGED
|
@@ -113,6 +113,9 @@ export * from "./reactive-bindings.js";
|
|
|
113
113
|
export * from "./client-helpers.js";
|
|
114
114
|
export * from "./client-script.js";
|
|
115
115
|
export * from "./strip-literals.js";
|
|
116
|
+
export * from "./resolve-stx-targets.js";
|
|
117
|
+
export * from "./cookie-serialize.js";
|
|
118
|
+
export * from "./cli-flags.js";
|
|
116
119
|
export * from "./component-hmr.js";
|
|
117
120
|
export * from "./hydration.js";
|
|
118
121
|
export * from "./ssr.js";
|
package/dist/page-action.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SetCookieOptions } from './cookie-serialize';
|
|
1
2
|
/**
|
|
2
3
|
* Parse a form submission body into a plain object.
|
|
3
4
|
*
|
|
@@ -36,26 +37,7 @@ export declare function runPageAction(context: Record<string, any>, options?: Ru
|
|
|
36
37
|
* 303 rather than 302 deliberately: it is the status that makes a browser
|
|
37
38
|
* follow up with a GET, so a reload or a Back does not resubmit the form.
|
|
38
39
|
*/
|
|
39
|
-
export declare function actionRedirectResponse(to: string): Response;
|
|
40
|
-
/**
|
|
41
|
-
* A page handling its own form submission (stacksjs/stx#1847).
|
|
42
|
-
*
|
|
43
|
-
* A page that declares `action` in its `<script server>` block receives its own
|
|
44
|
-
* non-GET request. Whatever the action returns is merged into the render
|
|
45
|
-
* context, so `errors` and `values` repopulate the very same template that drew
|
|
46
|
-
* the form — one handler for the no-JS submit and the enhanced one, which is
|
|
47
|
-
* the half of the Laravel/Remix/SvelteKit contract stx was missing.
|
|
48
|
-
*
|
|
49
|
-
* ## Why this is a module rather than two implementations
|
|
50
|
-
*
|
|
51
|
-
* It shipped in `bun-plugin/src/serve.ts` only, so a form worked under
|
|
52
|
-
* `buddy dev` and silently did nothing in production — the request fell through
|
|
53
|
-
* to the ordinary render and answered 200 with the pre-submit markup, which is
|
|
54
|
-
* indistinguishable from the action having run and found nothing wrong. That is
|
|
55
|
-
* the same dev-only-correctness shape as `stx typecheck` being stricter than
|
|
56
|
-
* the editor, and this repo has been bitten repeatedly by two hand-maintained
|
|
57
|
-
* copies of one rule drifting apart. One module, both servers.
|
|
58
|
-
*/
|
|
40
|
+
export declare function actionRedirectResponse(to: string, cookies?: readonly string[]): Response;
|
|
59
41
|
/**
|
|
60
42
|
* Everything an action is given about the request.
|
|
61
43
|
*
|
|
@@ -72,6 +54,7 @@ export declare interface PageActionContext {
|
|
|
72
54
|
/** What running an action produced. */
|
|
73
55
|
export declare interface PageActionResult {
|
|
74
56
|
redirect?: string
|
|
57
|
+
cookies?: string[]
|
|
75
58
|
ran: boolean
|
|
76
59
|
}
|
|
77
60
|
/** Request data a caller has already parsed, if it has. */
|
|
@@ -81,3 +64,10 @@ export declare interface RunPageActionOptions {
|
|
|
81
64
|
params?: Record<string, string>
|
|
82
65
|
cookies?: Record<string, string>
|
|
83
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* A cookie an action wants set on the response.
|
|
69
|
+
*
|
|
70
|
+
* A bare string is the whole cookie for the common case; the object form adds
|
|
71
|
+
* the attributes a session cookie needs.
|
|
72
|
+
*/
|
|
73
|
+
export type PageActionCookie = string | (SetCookieOptions & { value: string });
|
package/dist/page-action.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeSetCookie } from "./cookie-serialize.js";
|
|
1
2
|
export async function parseFormBody(request) {
|
|
2
3
|
const type = request.headers.get("content-type") || "", form = {}, put = (key, value) => {
|
|
3
4
|
const existing = form[key];
|
|
@@ -46,19 +47,42 @@ export async function runPageAction(context, options = {}) {
|
|
|
46
47
|
cookies: options.cookies ?? {}
|
|
47
48
|
});
|
|
48
49
|
if (result && typeof result === "object") {
|
|
49
|
-
const
|
|
50
|
+
const returned = result, cookies = serializeActionCookies(returned.cookies), to = returned.redirect;
|
|
50
51
|
if (typeof to === "string" && to)
|
|
51
|
-
return { redirect: to, ran: !0 };
|
|
52
|
-
|
|
52
|
+
return { redirect: to, ran: !0, ...cookies.length > 0 ? { cookies } : {} };
|
|
53
|
+
const { cookies: _cookies, redirect: _redirect, ...data } = returned;
|
|
54
|
+
Object.assign(context, data);
|
|
55
|
+
if (cookies.length > 0)
|
|
56
|
+
return { ran: !0, cookies };
|
|
53
57
|
}
|
|
54
58
|
return { ran: !0 };
|
|
55
59
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
function serializeActionCookies(value) {
|
|
61
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
62
|
+
return [];
|
|
63
|
+
const headers = [];
|
|
64
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
65
|
+
if (!name)
|
|
66
|
+
continue;
|
|
67
|
+
if (typeof entry === "string") {
|
|
68
|
+
headers.push(serializeSetCookie(name, entry));
|
|
69
|
+
continue;
|
|
62
70
|
}
|
|
71
|
+
if (entry && typeof entry === "object") {
|
|
72
|
+
const options = entry;
|
|
73
|
+
if (typeof options.value !== "string")
|
|
74
|
+
continue;
|
|
75
|
+
headers.push(serializeSetCookie(name, options.value, options));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return headers;
|
|
79
|
+
}
|
|
80
|
+
export function actionRedirectResponse(to, cookies = []) {
|
|
81
|
+
const headers = new Headers({
|
|
82
|
+
Location: to,
|
|
83
|
+
"Cache-Control": "no-store"
|
|
63
84
|
});
|
|
85
|
+
for (const cookie of cookies)
|
|
86
|
+
headers.append("Set-Cookie", cookie);
|
|
87
|
+
return new Response(null, { status: 303, headers });
|
|
64
88
|
}
|
package/dist/process.js
CHANGED
|
@@ -54,7 +54,7 @@ import { deriveLayoutGroup } from "stx-router/layout-metadata";
|
|
|
54
54
|
import { initializeComponentClientFactories, injectComponentClientFactories } from "./component-client-factories.js";
|
|
55
55
|
import { importOnce } from "./lazy-module.js";
|
|
56
56
|
export { injectRouterScript } from "./runtime-injection.js";
|
|
57
|
-
import { injectSignalsRuntime, outputNeedsSignalsRuntime, pageShipsSignalsRuntime } from "./runtime-injection.js";
|
|
57
|
+
import { injectSignalsRuntime, injectTooltipRuntime, outputNeedsSignalsRuntime, pageShipsSignalsRuntime } from "./runtime-injection.js";
|
|
58
58
|
export { processJsonDirective, processOnceDirective } from "./misc-directives.js";
|
|
59
59
|
export { validateClientScript } from "./script-validation.js";
|
|
60
60
|
const PARENT_PLACEHOLDER = "<!--PARENT_CONTENT_PLACEHOLDER-->";
|
|
@@ -843,6 +843,7 @@ ${runtimeScript}`);
|
|
|
843
843
|
}
|
|
844
844
|
if (!pageShipsSignalsRuntime(output) && outputNeedsSignalsRuntime(output))
|
|
845
845
|
output = await injectSignalsRuntime(output, opts);
|
|
846
|
+
output = injectTooltipRuntime(output);
|
|
846
847
|
if (opts.csp?.enabled && opts.csp.useNonce && typeof context.cspNonce === "string")
|
|
847
848
|
output = addNonceToInlineContent(output, context.cspNonce);
|
|
848
849
|
return output;
|
|
@@ -165,25 +165,26 @@ export async function startProductionServer(options = {}) {
|
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
167
|
try {
|
|
168
|
-
const { html, boundaries, redirect } = await hydrateTemplateStream(compiled, {
|
|
168
|
+
const { html, boundaries, redirect, cookies } = await hydrateTemplateStream(compiled, {
|
|
169
169
|
params,
|
|
170
170
|
request,
|
|
171
171
|
method: request.method
|
|
172
172
|
});
|
|
173
173
|
if (redirect)
|
|
174
|
-
return actionRedirectResponse(redirect);
|
|
174
|
+
return actionRedirectResponse(redirect, cookies);
|
|
175
175
|
if (boundaries && boundaries.length > 0) {
|
|
176
176
|
const { renderStreamingPage, streamToResponse } = await import("./streaming.js");
|
|
177
177
|
return streamToResponse(renderStreamingPage(html, boundaries, { timeoutMs: 30000 }), {
|
|
178
178
|
headers: { "Cache-Control": "no-cache" }
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"Cache-Control": "no-cache"
|
|
185
|
-
}
|
|
181
|
+
const headers = new Headers({
|
|
182
|
+
"Content-Type": "text/html",
|
|
183
|
+
"Cache-Control": "no-cache"
|
|
186
184
|
});
|
|
185
|
+
for (const cookie of cookies ?? [])
|
|
186
|
+
headers.append("Set-Cookie", cookie);
|
|
187
|
+
return new Response(html, { headers });
|
|
187
188
|
} catch (error) {
|
|
188
189
|
console.error(`[stx] Hydration error for ${pathname}:`, error);
|
|
189
190
|
return new Response(compiled.html, {
|
package/dist/props.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Define component props with type safety and validation.
|
|
3
3
|
*
|
|
4
|
+
* Takes an inline object type or a named `interface`/`type` — the named form is
|
|
5
|
+
* shown first because it is the one the framework scaffolds, and because every
|
|
6
|
+
* example here used to be inline. An inline literal has an implicit index
|
|
7
|
+
* signature and a named `interface` does not, so an inline-only docstring
|
|
8
|
+
* demonstrated the one spelling that could not fail (stacksjs/stx#1917).
|
|
9
|
+
*
|
|
4
10
|
* @example
|
|
5
11
|
* ```typescript
|
|
6
|
-
* //
|
|
12
|
+
* // A named type, inline or imported — interface and type alias both work
|
|
13
|
+
* interface CardProps {
|
|
14
|
+
* title: string
|
|
15
|
+
* count: number
|
|
16
|
+
* }
|
|
17
|
+
* const props = defineProps<CardProps>()
|
|
18
|
+
*
|
|
19
|
+
* // Or inline
|
|
7
20
|
* const props = defineProps<{
|
|
8
21
|
* title: string
|
|
9
22
|
* count: number
|
|
@@ -24,12 +37,12 @@
|
|
|
24
37
|
* })
|
|
25
38
|
* ```
|
|
26
39
|
*/
|
|
27
|
-
export declare function defineProps<T extends
|
|
40
|
+
export declare function defineProps<T extends PropsShape>(definitions?: PropsDefinition<T>): T;
|
|
28
41
|
/**
|
|
29
42
|
* Define props with runtime validation.
|
|
30
43
|
* Use this when you need validation errors at runtime.
|
|
31
44
|
*/
|
|
32
|
-
export declare function definePropsWithValidation<T extends
|
|
45
|
+
export declare function definePropsWithValidation<T extends PropsShape>(definitions: PropsDefinition<T>, options?: {
|
|
33
46
|
componentName?: string
|
|
34
47
|
throwOnError?: boolean
|
|
35
48
|
logWarnings?: boolean
|
|
@@ -49,7 +62,7 @@ export declare function definePropsWithValidation<T extends Record<string, unkno
|
|
|
49
62
|
* })
|
|
50
63
|
* ```
|
|
51
64
|
*/
|
|
52
|
-
export declare function withDefaults<T extends
|
|
65
|
+
export declare function withDefaults<T extends PropsShape>(props: T, defaults: Partial<{ [K in keyof T]: T[K] | (() => T[K]) }>): T;
|
|
53
66
|
/**
|
|
54
67
|
* Create a prop type definition for a specific type.
|
|
55
68
|
*/
|
|
@@ -77,7 +90,7 @@ export declare function arrayOf<T>(_itemType: PropType<T>, options?: Omit<PropOp
|
|
|
77
90
|
/**
|
|
78
91
|
* Create a prop for object with specific shape.
|
|
79
92
|
*/
|
|
80
|
-
export declare function shape<T extends
|
|
93
|
+
export declare function shape<T extends PropsShape>(_shape: PropsDefinition<T>, options?: Omit<PropOptions<T>, 'type'>): PropOptions<T>;
|
|
81
94
|
/**
|
|
82
95
|
* Process props passed to a component.
|
|
83
96
|
* Called internally during component rendering.
|
|
@@ -100,7 +113,7 @@ export declare function processComponentProps(rawProps: Record<string, unknown>,
|
|
|
100
113
|
* emit('close', undefined)
|
|
101
114
|
* ```
|
|
102
115
|
*/
|
|
103
|
-
export declare function defineEmits<E extends
|
|
116
|
+
export declare function defineEmits<E extends PropsShape>(): EmitFn<E>;
|
|
104
117
|
/**
|
|
105
118
|
* Expose component methods/properties to parent.
|
|
106
119
|
*
|
|
@@ -113,7 +126,7 @@ export declare function defineEmits<E extends Record<string, unknown>>(): EmitFn
|
|
|
113
126
|
* })
|
|
114
127
|
* ```
|
|
115
128
|
*/
|
|
116
|
-
export declare function defineExpose<T extends
|
|
129
|
+
export declare function defineExpose<T extends PropsShape>(exposed: T): void;
|
|
117
130
|
/**
|
|
118
131
|
* Generate TypeScript interface from prop definitions.
|
|
119
132
|
* Useful for documentation and type generation.
|
|
@@ -151,6 +164,36 @@ export type PropType<T> = | StringConstructor
|
|
|
151
164
|
| SymbolConstructor
|
|
152
165
|
| { new (...args: unknown[]): T }
|
|
153
166
|
| PropType<T>[];
|
|
167
|
+
/**
|
|
168
|
+
* The constraint every props, emits and expose generic is bounded by.
|
|
169
|
+
*
|
|
170
|
+
* `object` rather than `Record<string, unknown>`, and the difference is not
|
|
171
|
+
* cosmetic: **a TypeScript `interface` does not satisfy an index signature.**
|
|
172
|
+
* Only a type alias gets an implicit one, so
|
|
173
|
+
*
|
|
174
|
+
* interface CardProps { title: string }
|
|
175
|
+
* defineProps<CardProps>() // TS2344, TS2345
|
|
176
|
+
* type CardProps = { title: string }
|
|
177
|
+
* defineProps<CardProps>() // fine
|
|
178
|
+
*
|
|
179
|
+
* differ on the keyword alone. `interface` is the form the framework scaffolds
|
|
180
|
+
* — `storage/framework/defaults/` declares props with it 44 times and with a
|
|
181
|
+
* type alias zero times — so the constraint rejected the entire scaffolded
|
|
182
|
+
* corpus (stacksjs/stx#1917).
|
|
183
|
+
*
|
|
184
|
+
* Two things hid it. Every example in the docstrings passes an inline object
|
|
185
|
+
* literal, which HAS an implicit index signature and so always passed; the
|
|
186
|
+
* error appears only once the type is named, which is the moment it is reused
|
|
187
|
+
* or exported. And apps import from `stx`, which is not a package anyone can
|
|
188
|
+
* install — the module resolved to `any`, the constraint was never enforced,
|
|
189
|
+
* and the component type-checked. Correcting the specifier in one app turned 18
|
|
190
|
+
* "Cannot find module" errors into 36 constraint errors.
|
|
191
|
+
*
|
|
192
|
+
* Named rather than written out seven times so the reason lives in one place
|
|
193
|
+
* and the sites cannot drift apart. `object` still rejects `string`, `number`
|
|
194
|
+
* and `undefined`, which is everything this constraint has to do.
|
|
195
|
+
*/
|
|
196
|
+
export type PropsShape = object;
|
|
154
197
|
/** Props definition object */
|
|
155
198
|
export type PropsDefinition<T> = {
|
|
156
199
|
[K in keyof T]?: PropOptions<T[K]> | PropType<T[K]>
|
|
@@ -167,7 +210,7 @@ export type ExtractPropTypes<T extends PropsDefinition<unknown>> = {
|
|
|
167
210
|
// eslint-disable-next-line pickier/no-unused-vars
|
|
168
211
|
export type EventHandler<T = unknown> = (payload: T) => void;
|
|
169
212
|
/** Emit function type */
|
|
170
|
-
export type EmitFn<E extends
|
|
213
|
+
export type EmitFn<E extends PropsShape> = <K extends keyof E>(
|
|
171
214
|
event: K,
|
|
172
215
|
payload: E[K],
|
|
173
216
|
) => void;
|