@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
package/dist/a11y.d.ts
CHANGED
|
@@ -36,6 +36,29 @@ export interface A11yViolation {
|
|
|
36
36
|
/** URL to more information */
|
|
37
37
|
helpUrl?: string;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Has this element declared itself decorative rather than semantic?
|
|
41
|
+
*
|
|
42
|
+
* `role="presentation"` and its synonym `role="none"` remove an element's
|
|
43
|
+
* implicit semantics from the accessibility tree. On a table that is exactly the
|
|
44
|
+
* declaration that it carries layout, not data — so demanding a `<th>` or a
|
|
45
|
+
* `<caption>` asks the author to reintroduce the semantics the role exists to
|
|
46
|
+
* remove (stacksjs/stx#1916).
|
|
47
|
+
*
|
|
48
|
+
* This is not a corner case. Every HTML email is nested layout tables: mail
|
|
49
|
+
* clients strip `<style>` and have no reliable box model, so `role="presentation"`
|
|
50
|
+
* is the standard, correct pattern there. One email template accounted for every
|
|
51
|
+
* remaining finding in an otherwise-clean app, and none of them was fixable —
|
|
52
|
+
* the correct markup was what triggered them. An advisory checker with a
|
|
53
|
+
* permanent finding nobody can close is one people learn to ignore, which costs
|
|
54
|
+
* more than the rule is worth.
|
|
55
|
+
*
|
|
56
|
+
* The rule itself is right to exist: a DATA table with no headers is a real
|
|
57
|
+
* problem. It just has to take the author's word for which kind it is.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isPresentationalRole(role: string | null | undefined): boolean;
|
|
60
|
+
/** Read one attribute out of a raw tag's attribute text. */
|
|
61
|
+
export declare function attributeValue(attrs: string, name: string): string | null;
|
|
39
62
|
/**
|
|
40
63
|
* Process accessibility directives.
|
|
41
64
|
* Handles @a11y hints, @screenReader content, and @ariaDescribe.
|
|
@@ -63,6 +86,23 @@ else {
|
|
|
63
86
|
* ```
|
|
64
87
|
*/
|
|
65
88
|
export declare function hasDOMSupport(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Regex-based accessibility checker for non-DOM environments.
|
|
91
|
+
* Provides a subset of checks that can be performed with regex parsing.
|
|
92
|
+
*
|
|
93
|
+
* @param html - HTML content to check
|
|
94
|
+
* @param _filePath - File path for error reporting
|
|
95
|
+
* @returns Array of accessibility violations found
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Regex-based checking, used when no DOM is available — which is the CLI.
|
|
99
|
+
*
|
|
100
|
+
* Exported so tests can exercise it directly. It and the DOM path implement the
|
|
101
|
+
* same rules twice, and the way two implementations of one rule go wrong is by
|
|
102
|
+
* disagreeing; pinning only whichever one the test environment happens to pick
|
|
103
|
+
* leaves the other free to drift (#1916).
|
|
104
|
+
*/
|
|
105
|
+
export declare function checkA11yWithRegex(html: string, _filePath: string): A11yViolation[];
|
|
66
106
|
/**
|
|
67
107
|
* Automatically check template for accessibility issues.
|
|
68
108
|
*
|
|
@@ -81,9 +121,39 @@ export declare function hasDOMSupport(): boolean;
|
|
|
81
121
|
* ```
|
|
82
122
|
*/
|
|
83
123
|
export declare function checkA11y(html: string, filePath: string): Promise<A11yViolation[]>;
|
|
124
|
+
/** What a scan actually read, alongside what it found. */
|
|
125
|
+
export interface A11yScanResult {
|
|
126
|
+
/** Files that had at least one violation, mapped to their violations. */
|
|
127
|
+
results: Record<string, A11yViolation[]>;
|
|
128
|
+
/**
|
|
129
|
+
* Every file the scan READ.
|
|
130
|
+
*
|
|
131
|
+
* Reported separately because "no violations" and "no files" are different
|
|
132
|
+
* answers that used to be the same one: `results` only ever contains files
|
|
133
|
+
* WITH violations, so an empty map means both "everything is clean" and
|
|
134
|
+
* "nothing was scanned", and the success banner was printed for both
|
|
135
|
+
* (stacksjs/stx#1918).
|
|
136
|
+
*/
|
|
137
|
+
scannedFiles: string[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Scan a directory, a file, or a glob for accessibility issues.
|
|
141
|
+
*
|
|
142
|
+
* The argument used to be joined into a scan pattern, so only a directory
|
|
143
|
+
* worked: a file path became `<file>/**` + `/*.stx` and a glob became a doubled
|
|
144
|
+
* pattern, both matching nothing and both reported as a clean pass. See
|
|
145
|
+
* `resolve-stx-targets.ts` for what that cost.
|
|
146
|
+
*/
|
|
147
|
+
export declare function scanA11yTargets(target: string | readonly string[], options?: {
|
|
148
|
+
recursive?: boolean;
|
|
149
|
+
ignorePaths?: string[];
|
|
150
|
+
}): Promise<A11yScanResult>;
|
|
84
151
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
152
|
+
* Back-compatible wrapper: violations only.
|
|
153
|
+
*
|
|
154
|
+
* Kept so existing callers keep working, but it cannot say whether the scan read
|
|
155
|
+
* anything — which is the distinction #1918 turned on. Prefer
|
|
156
|
+
* {@link scanA11yTargets}.
|
|
87
157
|
*/
|
|
88
158
|
export declare function scanA11yIssues(directory: string, options?: {
|
|
89
159
|
recursive?: boolean;
|
package/dist/a11y.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { resolveStxTargets } from "./resolve-stx-targets.js";
|
|
3
|
+
export function isPresentationalRole(role) {
|
|
4
|
+
if (!role)
|
|
5
|
+
return !1;
|
|
6
|
+
return role.trim().split(/\s+/)[0]?.toLowerCase() === "presentation" || role.trim().split(/\s+/)[0]?.toLowerCase() === "none";
|
|
7
|
+
}
|
|
8
|
+
export function attributeValue(attrs, name) {
|
|
9
|
+
const match = new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i").exec(attrs);
|
|
10
|
+
if (!match)
|
|
11
|
+
return null;
|
|
12
|
+
return match[2] ?? match[3] ?? match[4] ?? null;
|
|
13
|
+
}
|
|
2
14
|
export function processA11yDirectives(template, _context, _filePath, _options) {
|
|
3
15
|
let output = template;
|
|
4
16
|
output = output.replace(/@a11y\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?\)/g, (_, hintType, customMessage) => {
|
|
@@ -58,7 +70,7 @@ function protectStxComponentTags(source) {
|
|
|
58
70
|
})
|
|
59
71
|
};
|
|
60
72
|
}
|
|
61
|
-
function checkA11yWithRegex(html, _filePath) {
|
|
73
|
+
export function checkA11yWithRegex(html, _filePath) {
|
|
62
74
|
const violations = [], imgRegex = /<img\s+([^>]*)>/gi;
|
|
63
75
|
let match;
|
|
64
76
|
match = imgRegex.exec(html);
|
|
@@ -156,11 +168,11 @@ function checkA11yWithRegex(html, _filePath) {
|
|
|
156
168
|
help: "Add lang attribute to the html element",
|
|
157
169
|
helpUrl: "https://web.dev/learn/accessibility/more-html/#language"
|
|
158
170
|
});
|
|
159
|
-
const tableRegex = /<table[^>]
|
|
171
|
+
const tableRegex = /<table([^>]*)>([\s\S]*?)<\/table>/gi;
|
|
160
172
|
match = tableRegex.exec(html);
|
|
161
173
|
while (match !== null) {
|
|
162
|
-
const tableContent = match[
|
|
163
|
-
if (!/<th\b/i.test(tableContent))
|
|
174
|
+
const tableContent = match[2];
|
|
175
|
+
if (!isPresentationalRole(attributeValue(match[1], "role")) && !/<th\b/i.test(tableContent))
|
|
164
176
|
violations.push({
|
|
165
177
|
type: "table-missing-headers",
|
|
166
178
|
element: "<table>",
|
|
@@ -321,6 +333,8 @@ export async function checkA11y(html, filePath) {
|
|
|
321
333
|
});
|
|
322
334
|
}
|
|
323
335
|
container.querySelectorAll("table").forEach((table) => {
|
|
336
|
+
if (isPresentationalRole(table.getAttribute("role")))
|
|
337
|
+
return;
|
|
324
338
|
const hasCaption = table.querySelector("caption");
|
|
325
339
|
if (!table.querySelector("th"))
|
|
326
340
|
violations.push({
|
|
@@ -411,23 +425,24 @@ export async function checkA11y(html, filePath) {
|
|
|
411
425
|
}
|
|
412
426
|
return violations;
|
|
413
427
|
}
|
|
414
|
-
export async function
|
|
415
|
-
const results = {}, { recursive = !0, ignorePaths = [] } = options,
|
|
416
|
-
for await (const file of glob.scan())
|
|
417
|
-
if (!ignorePaths.some((ignorePath) => file.includes(path.normalize(ignorePath))))
|
|
418
|
-
files.push(file);
|
|
428
|
+
export async function scanA11yTargets(target, options = {}) {
|
|
429
|
+
const results = {}, { recursive = !0, ignorePaths = [] } = options, targets = Array.isArray(target) ? target : [target], files = await resolveStxTargets(targets, { recursive, ignorePaths }), scannedFiles = [];
|
|
419
430
|
for (const file of files)
|
|
420
431
|
try {
|
|
421
432
|
const content = await Bun.file(file).text();
|
|
422
433
|
if (file.endsWith("good-a11y.stx"))
|
|
423
434
|
continue;
|
|
424
435
|
const violations = await checkA11y(content, file);
|
|
436
|
+
scannedFiles.push(file);
|
|
425
437
|
if (violations.length > 0)
|
|
426
438
|
results[file] = violations;
|
|
427
439
|
} catch (error) {
|
|
428
440
|
console.error(`Error scanning ${file}:`, error);
|
|
429
441
|
}
|
|
430
|
-
return results;
|
|
442
|
+
return { results, scannedFiles };
|
|
443
|
+
}
|
|
444
|
+
export async function scanA11yIssues(directory, options = {}) {
|
|
445
|
+
return (await scanA11yTargets(directory, options)).results;
|
|
431
446
|
}
|
|
432
447
|
const defaultAutoFixConfig = {
|
|
433
448
|
fixMissingAlt: !0,
|
package/dist/builtins/attrs.d.ts
CHANGED
|
@@ -28,3 +28,22 @@ import type { ResolvedProps } from '../component-registry';
|
|
|
28
28
|
* @param consumed - Prop names the builtin has already rendered itself.
|
|
29
29
|
*/
|
|
30
30
|
export declare function forwardStaticAttrs(props: ResolvedProps, consumed: Set<string>): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Render the server-evaluated `:prop` bindings a builtin did not consume.
|
|
33
|
+
*
|
|
34
|
+
* Only `static` was ever forwarded, so `<StxLink to="/x" :title="pageTitle">`
|
|
35
|
+
* emitted an `<a>` with no title and no warning — the binding was resolved,
|
|
36
|
+
* stored, and thrown away (stacksjs/stx#1930). Client-reactive `:prop` and
|
|
37
|
+
* `@event` bindings already reach the element by another route (the component
|
|
38
|
+
* renderer re-applies them to the root), which is what made the gap specific to
|
|
39
|
+
* the server-evaluated ones and easy to miss.
|
|
40
|
+
*
|
|
41
|
+
* `null`, `undefined` and `false` DROP the attribute rather than rendering the
|
|
42
|
+
* word — that is what makes `:aria-current="active ? 'page' : null"` express
|
|
43
|
+
* "this attribute is conditional", which is the whole reason to reach for a
|
|
44
|
+
* binding instead of a literal. `true` renders bare, as HTML booleans do.
|
|
45
|
+
*
|
|
46
|
+
* @param props - The component's resolved props.
|
|
47
|
+
* @param consumed - Prop names the builtin has already rendered itself.
|
|
48
|
+
*/
|
|
49
|
+
export declare function forwardResolvedAttrs(props: ResolvedProps, consumed: Set<string>): string[];
|
package/dist/builtins/attrs.js
CHANGED
|
@@ -19,3 +19,21 @@ export function forwardStaticAttrs(props, consumed) {
|
|
|
19
19
|
}
|
|
20
20
|
return attrs;
|
|
21
21
|
}
|
|
22
|
+
export function forwardResolvedAttrs(props, consumed) {
|
|
23
|
+
const attrs = [];
|
|
24
|
+
for (const [key, value] of Object.entries(props.serverDynamic ?? {})) {
|
|
25
|
+
if (consumed.has(key))
|
|
26
|
+
continue;
|
|
27
|
+
if (value === null || value === void 0 || value === !1)
|
|
28
|
+
continue;
|
|
29
|
+
const name = props.serverDynamicNames?.[key] ?? key;
|
|
30
|
+
if (value === !0) {
|
|
31
|
+
attrs.push(escapeAttr(name));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "object")
|
|
35
|
+
continue;
|
|
36
|
+
attrs.push(`${escapeAttr(name)}="${escapeAttr(stripControlChars(String(value)))}"`);
|
|
37
|
+
}
|
|
38
|
+
return attrs;
|
|
39
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardStaticAttrs } from "./attrs.js";
|
|
1
|
+
import { forwardResolvedAttrs, forwardStaticAttrs } from "./attrs.js";
|
|
2
2
|
const DEFAULT_FALLBACK = "/images/safe-image-default.svg";
|
|
3
3
|
function escapeAttr(value) {
|
|
4
4
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -34,6 +34,7 @@ export const SafeImageBuiltin = {
|
|
|
34
34
|
"fallbackClassName"
|
|
35
35
|
]);
|
|
36
36
|
imgAttrs.push(...forwardStaticAttrs(props, consumed));
|
|
37
|
+
imgAttrs.push(...forwardResolvedAttrs(props, consumed));
|
|
37
38
|
const imgTag = `<img ${imgAttrs.join(" ")} />`, renderCtx = ctx.context;
|
|
38
39
|
if (!renderCtx.__safeImageHandlerEmitted) {
|
|
39
40
|
renderCtx.__safeImageHandlerEmitted = !0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardStaticAttrs } from "./attrs.js";
|
|
1
|
+
import { forwardResolvedAttrs, forwardStaticAttrs } from "./attrs.js";
|
|
2
2
|
const DEFAULT_BREAKPOINTS = {
|
|
3
3
|
sm: 640,
|
|
4
4
|
md: 768,
|
|
@@ -165,6 +165,7 @@ export const StxImageBuiltin = {
|
|
|
165
165
|
"picture"
|
|
166
166
|
]);
|
|
167
167
|
imgAttrs.push(...forwardStaticAttrs(props, consumedStatic));
|
|
168
|
+
imgAttrs.push(...forwardResolvedAttrs(props, consumedStatic));
|
|
168
169
|
const imgTag = `<img ${imgAttrs.join(" ")} />`;
|
|
169
170
|
let preloadTag = "";
|
|
170
171
|
if (preload) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardStaticAttrs } from "./attrs.js";
|
|
1
|
+
import { forwardResolvedAttrs, forwardStaticAttrs } from "./attrs.js";
|
|
2
2
|
function escapeAttr(value) {
|
|
3
3
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
4
4
|
}
|
|
@@ -29,6 +29,7 @@ export const StxLinkBuiltin = {
|
|
|
29
29
|
attrs.push("data-stx-prefetch");
|
|
30
30
|
const consumedStatic = new Set(["to", "class", "className", "activeClass", "exactActiveClass", "prefetch"]);
|
|
31
31
|
attrs.push(...forwardStaticAttrs(props, consumedStatic));
|
|
32
|
+
attrs.push(...forwardResolvedAttrs(props, consumedStatic));
|
|
32
33
|
return `<a ${attrs.join(" ")}>${slotContent}</a>`;
|
|
33
34
|
}
|
|
34
35
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a flag that may legitimately appear more than once.
|
|
3
|
+
*
|
|
4
|
+
* The CLI parser collapses repeats to the LAST value — `--lib a --lib b` arrives
|
|
5
|
+
* as the string `"b"`, and the same is true of `<path...>` and `[path...]`, so
|
|
6
|
+
* there is no option spec that expresses "repeatable". The typecheck command
|
|
7
|
+
* documented `--lib` as repeatable and had an `Array.isArray(options.lib)`
|
|
8
|
+
* branch that could therefore never run: a project splitting its ambient
|
|
9
|
+
* declarations across two files silently got whichever one it listed last
|
|
10
|
+
* (stacksjs/stx#1925, stacksjs/stx#1926).
|
|
11
|
+
*
|
|
12
|
+
* The failure points the wrong way, which is what made it expensive. Listing
|
|
13
|
+
* every declaration file — the obvious move, and what "repeatable" invites —
|
|
14
|
+
* lands on the UNHELPED baseline, because the one file that was helping got
|
|
15
|
+
* replaced by one that does not cover those names. That reads as "the flag does
|
|
16
|
+
* nothing" rather than "the flag is dropping all but one of my files", and it
|
|
17
|
+
* changes which errors are reported rather than merely how many: names that
|
|
18
|
+
* would resolve come back as `Cannot find name`, hiding the real diagnostics
|
|
19
|
+
* underneath them.
|
|
20
|
+
*
|
|
21
|
+
* So the values are read from argv, where all of them still are.
|
|
22
|
+
*
|
|
23
|
+
* @module cli-flags
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Every value given for `flag`, in the order written.
|
|
27
|
+
*
|
|
28
|
+
* Accepts both spellings — `--lib a` and `--lib=a` — because a user who has
|
|
29
|
+
* been told a flag repeats will reasonably try either.
|
|
30
|
+
*
|
|
31
|
+
* Values are returned verbatim; resolving them against a directory is the
|
|
32
|
+
* caller's business, since it is the caller that knows what they name.
|
|
33
|
+
*/
|
|
34
|
+
export declare function collectRepeatedFlag(argv: readonly string[], flag: string): string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function collectRepeatedFlag(argv, flag) {
|
|
2
|
+
const values = [], inline = `${flag}=`;
|
|
3
|
+
for (let i = 0;i < argv.length; i++) {
|
|
4
|
+
const arg = argv[i];
|
|
5
|
+
if (arg.startsWith(inline)) {
|
|
6
|
+
const value = arg.slice(inline.length);
|
|
7
|
+
if (value)
|
|
8
|
+
values.push(value);
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (arg === flag) {
|
|
12
|
+
const next = argv[i + 1];
|
|
13
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
14
|
+
values.push(next);
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return values;
|
|
20
|
+
}
|