@stacksjs/stx 0.2.151 → 0.2.153

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.
Files changed (49) hide show
  1. package/dist/app-shell.js +28 -1
  2. package/dist/browser-composables.d.ts +24 -9
  3. package/dist/build-id.d.ts +49 -0
  4. package/dist/build-id.js +20 -0
  5. package/dist/cli.js +897 -659
  6. package/dist/client-script.js +2 -2
  7. package/dist/color-mode-boot.d.ts +15 -10
  8. package/dist/color-mode-boot.js +12 -8
  9. package/dist/component-client-factories.js +3 -2
  10. package/dist/component-processing.d.ts +16 -0
  11. package/dist/component-processing.js +4 -1
  12. package/dist/component-renderer.js +2 -2
  13. package/dist/composables/index.d.ts +6 -1
  14. package/dist/composables/use-color-mode.d.ts +19 -4
  15. package/dist/composables/use-color-mode.js +37 -24
  16. package/dist/composables/use-device-orientation.d.ts +8 -1
  17. package/dist/composables/use-event-source.d.ts +14 -1
  18. package/dist/composables/use-eye-dropper.d.ts +10 -1
  19. package/dist/composables/use-idle.d.ts +21 -3
  20. package/dist/composables/use-storage.d.ts +15 -10
  21. package/dist/composables/use-storage.js +63 -2
  22. package/dist/composables/use-text-selection.d.ts +27 -3
  23. package/dist/composables/use-wake-lock.d.ts +2 -2
  24. package/dist/craft.js +625 -387
  25. package/dist/dev-server/serve-app.js +9 -5
  26. package/dist/dev-server/types.d.ts +8 -1
  27. package/dist/document-shell.d.ts +55 -0
  28. package/dist/document-shell.js +103 -3
  29. package/dist/edge-runtime.d.ts +1 -1
  30. package/dist/head.js +5 -1
  31. package/dist/includes.js +4 -2
  32. package/dist/index.d.ts +17 -0
  33. package/dist/index.js +9 -0
  34. package/dist/process.js +7 -3
  35. package/dist/production-builder.js +3 -1
  36. package/dist/pwa.js +591 -353
  37. package/dist/render.d.ts +1 -1
  38. package/dist/render.js +2 -2
  39. package/dist/signals.js +280 -44
  40. package/dist/ssg.js +603 -365
  41. package/dist/ssr.d.ts +17 -1
  42. package/dist/stx.d.ts +14 -6
  43. package/dist/types/config-types.d.ts +1 -0
  44. package/dist/utils.js +1 -8
  45. package/dist/variable-extractor.d.ts +8 -0
  46. package/dist/variable-extractor.js +24 -9
  47. package/dist/visual-testing.d.ts +26 -3
  48. package/package.json +5 -5
  49. package/stx.d.ts +14 -6
package/dist/ssr.d.ts CHANGED
@@ -16,7 +16,7 @@ export declare function render(template: string, data?: Record<string, unknown>,
16
16
  /**
17
17
  * Create a STX server application
18
18
  */
19
- export declare function createApp(config?: AppConfig): void;
19
+ export declare function createApp(config?: AppConfig): StxServerApp;
20
20
  /** Session data store */
21
21
  export declare interface SessionData {
22
22
  [key: string]: unknown
@@ -87,6 +87,22 @@ export declare interface AppConfig {
87
87
  csrfEnabled?: boolean
88
88
  sessionStore?: SessionStore
89
89
  }
90
+ /**
91
+ * Handle returned by {@link createApp} — the server application surface.
92
+ */
93
+ export declare interface StxServerApp {
94
+ fetch: (request: Request) => Promise<Response>
95
+ get: (pattern: string, handler: RouteHandler, middleware?: Middleware[]) => StxServerApp
96
+ post: (pattern: string, handler: RouteHandler, middleware?: Middleware[]) => StxServerApp
97
+ put: (pattern: string, handler: RouteHandler, middleware?: Middleware[]) => StxServerApp
98
+ delete: (pattern: string, handler: RouteHandler, middleware?: Middleware[]) => StxServerApp
99
+ patch: (pattern: string, handler: RouteHandler, middleware?: Middleware[]) => StxServerApp
100
+ use: (middleware: Middleware) => StxServerApp
101
+ render: (template: string, data?: Record<string, unknown>, ctx?: RequestContext) => Promise<Response>
102
+ redirect: (url: string, status?: 302 | 301 | 303 | 307 | 308) => Response
103
+ back: (ctx: RequestContext, errors?: ValidationErrors, oldInput?: Record<string, string>) => Response
104
+ listen: (port?: number) => ReturnType<typeof Bun.serve>
105
+ }
90
106
  /** Route handler */
91
107
  // eslint-disable-next-line pickier/no-unused-vars
92
108
  export type RouteHandler = (ctx: RequestContext) => Response | Promise<Response>;
package/dist/stx.d.ts CHANGED
@@ -237,11 +237,19 @@ interface StxColorModeOptions {
237
237
  /** localStorage key holding the preference. @default 'stx-color-mode' */
238
238
  storageKey?: string
239
239
  /** Mode used when nothing valid is stored. @default 'auto' */
240
- initialMode?: 'light' | 'dark' | 'auto'
241
- /** Class applied to `<html>` when dark. @default 'dark' */
242
- darkClass?: string
243
- /** Attribute applied to `<html>` INSTEAD of the class, e.g. 'data-theme'. */
244
- attribute?: string
240
+ initialMode?: 'light' | 'dark' | 'auto' | 'system'
241
+ /**
242
+ * Class applied to `<html>` when dark. @default 'dark'
243
+ * Applied alongside `attribute`; pass `null` to opt out of the class.
244
+ */
245
+ darkClass?: string | null
246
+ /** Attribute set to the resolved mode on `<html>`, e.g. 'data-theme'. */
247
+ attribute?: string | null
248
+ /**
249
+ * Spelling written to storage for "follow the system".
250
+ * @default whatever is already stored, else 'auto'
251
+ */
252
+ autoValue?: 'auto' | 'system'
245
253
  /** Suppress CSS transitions across a mode switch. @default true */
246
254
  disableTransitions?: boolean
247
255
  }
@@ -257,7 +265,7 @@ declare function useColorMode(_options?: StxColorModeOptions): {
257
265
  /** User preference, which may be 'auto'. */
258
266
  readonly preference: 'light' | 'dark' | 'auto'
259
267
  readonly isDark: boolean
260
- set: (mode: 'light' | 'dark' | 'auto') => void
268
+ set: (mode: 'light' | 'dark' | 'auto' | 'system') => void
261
269
  toggle: () => void
262
270
  subscribe: (fn: (mode: 'light' | 'dark', preference: 'light' | 'dark' | 'auto') => void) => () => void
263
271
  }
@@ -242,6 +242,7 @@ export declare interface AppHeadConfig {
242
242
  link?: Array<Record<string, string>>
243
243
  script?: Array<{ src?: string, content?: string, [key: string]: any }>
244
244
  headRaw?: string
245
+ htmlAttrs?: Record<string, string>
245
246
  bodyClass?: string
246
247
  bodyAttrs?: Record<string, string>
247
248
  }
package/dist/utils.js CHANGED
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { extractAndStripCssImports, extractBridgeData, injectBrowserCoreAutoImports, processClientScript, renderVendorStyleTags } from "./client-script.js";
4
4
  import { COMPONENT_CLIENT_FACTORIES_CONTEXT_KEY, registerComponentClientFactory } from "./component-client-factories.js";
5
- import { interpolateScriptExpressions, processExpressions, unescapeHtml } from "./expressions.js";
5
+ import { interpolateScriptExpressions, processExpressions } from "./expressions.js";
6
6
  import { COMPONENT_SCOPE_LOCAL_GLOBALS, buildRuntimeGlobalsDestructure } from "./runtime-globals.js";
7
7
  import { transformStoreImports } from "./store-imports.js";
8
8
  import { LRUCache } from "./performance-utils.js";
@@ -648,13 +648,6 @@ ${triedPaths.map((p) => ` - ${p}`).join(`
648
648
  templateContent = templateContent.replace(/\{\{\s*text\s*\|\|\s*slot\s*\}\}/g, () => slotContent);
649
649
  const { applySlots } = await importOnce("stx/slots", () => import("./slots.js")), { defaultSlot, namedSlots } = parsedSlotContent;
650
650
  templateContent = await applySlots(templateContent, defaultSlot, namedSlots, componentContext);
651
- for (const [key, value] of Object.entries(componentContext))
652
- if (typeof value === "string") {
653
- if (value.includes("<") && value.includes(">") || value.includes("&lt;") || value.includes("&quot;")) {
654
- const unescaped = unescapeHtml(value);
655
- componentContext[key] = unescaped;
656
- }
657
- }
658
651
  const hasSignalScripts = clientScripts.some((s) => /\b(?:state|derived|effect|ref|reactive|computed|watch|watchEffect|useReactiveProp|defineProps|withDefaults|defineEmits|defineExpose|defineSlots)\s*(?:<[^<>()]*>)?\s*\(/.test(s)), clientSignalNames = componentClientSignalNames(clientScripts);
659
652
  if (clientSignalNames.length > 0)
660
653
  componentContext.__stx_client_signal_names = clientSignalNames;
@@ -5,6 +5,14 @@
5
5
  * @returns JavaScript code with TypeScript syntax stripped
6
6
  */
7
7
  export declare function stripTypeScript(scriptContent: string): string;
8
+ /**
9
+ * Whether a script failure was a module that could not be found.
10
+ *
11
+ * Matched on the message because the failure arrives as a plain Error from the
12
+ * runtime's loader rather than a typed one, and the wording differs between
13
+ * Bun and Node.
14
+ */
15
+ export declare function isModuleResolutionFailure(message: string): boolean;
8
16
  export declare function extractVariables(scriptContent: string, context: Record<string, unknown>, filePath: string, options?: ExtractVariablesOptions): Promise<void>;
9
17
  /**
10
18
  * Convert ES module syntax to CommonJS
@@ -210,6 +210,9 @@ export function stripTypeScript(scriptContent) {
210
210
  `);
211
211
  return result;
212
212
  }
213
+ export function isModuleResolutionFailure(message) {
214
+ return /cannot find module|could not resolve|module not found|failed to resolve/i.test(message);
215
+ }
213
216
  export async function extractVariables(scriptContent, context, filePath, options = {}) {
214
217
  if (!scriptContent.trim())
215
218
  return;
@@ -470,10 +473,11 @@ export async function extractVariables(scriptContent, context, filePath, options
470
473
  if (!(key in context))
471
474
  context[key] = value;
472
475
  } catch (primaryError) {
473
- if (process.env.STX_DEBUG) {
474
- const msg = primaryError instanceof Error ? primaryError.message : String(primaryError);
476
+ const msg = primaryError instanceof Error ? primaryError.message : String(primaryError);
477
+ if (isModuleResolutionFailure(msg))
478
+ console.warn(`[stx] server <script> in ${filePath ?? "<unknown>"} imports a module that does not resolve, so every variable in that script is undefined. Cause: ${msg}`);
479
+ else if (process.env.STX_DEBUG)
475
480
  console.warn(`[stx] server <script> did not execute in ${filePath ?? "<unknown>"} \u2014 falling back to static extraction. Cause: ${msg}`);
476
- }
477
481
  try {
478
482
  await fallbackVariableExtraction(jsContent, context, filePath);
479
483
  } catch (fallbackError) {
@@ -590,13 +594,24 @@ export function convertToCommonJS(scriptContent, filePath) {
590
594
  convertedLines.push(`const ${name} = await import('${resolved}')`);
591
595
  convertedLines.push(`module.exports.${name} = ${name};`);
592
596
  } else if (namedImportMatch) {
593
- const [, names, source] = namedImportMatch, cleanNames = names.trim(), resolved = resolveSource(source);
594
- convertedLines.push(`const { ${cleanNames} } = await import('${resolved}')`);
595
- for (const n of cleanNames.split(",")) {
596
- const cleanName = n.trim().split(/\s+as\s+/).pop()?.trim();
597
- if (cleanName)
598
- convertedLines.push(`module.exports.${cleanName} = ${cleanName};`);
597
+ const [, names, source] = namedImportMatch, resolved = resolveSource(source), specifiers = [], exported = [];
598
+ for (const raw of names.split(",")) {
599
+ const specifier = raw.trim();
600
+ if (!specifier)
601
+ continue;
602
+ const aliased = specifier.split(/\s+as\s+/);
603
+ if (aliased.length === 2) {
604
+ const [imported, local] = [aliased[0].trim(), aliased[1].trim()];
605
+ specifiers.push(`${imported}: ${local}`);
606
+ exported.push(local);
607
+ } else {
608
+ specifiers.push(specifier);
609
+ exported.push(specifier);
610
+ }
599
611
  }
612
+ convertedLines.push(`const { ${specifiers.join(", ")} } = await import('${resolved}')`);
613
+ for (const local of exported)
614
+ convertedLines.push(`module.exports.${local} = ${local};`);
600
615
  } else if (sideEffectMatch) {
601
616
  const resolved = resolveSource(sideEffectMatch[1]);
602
617
  convertedLines.push(`await import('${resolved}')`);
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Create a snapshot tester
3
3
  */
4
- export declare function createSnapshotTester(config?: VisualTestConfig): void;
4
+ export declare function createSnapshotTester(config?: VisualTestConfig): SnapshotTester;
5
5
  /**
6
6
  * Create a visual regression tester
7
7
  */
8
- export declare function createVisualRegressionTester(config?: VisualTestConfig): void;
8
+ export declare function createVisualRegressionTester(config?: VisualTestConfig): VisualRegressionTester;
9
9
  /**
10
10
  * Create a story-based visual tester
11
11
  */
12
- export declare function createStoryTester(config?: VisualTestConfig): void;
12
+ export declare function createStoryTester(config?: VisualTestConfig): StoryTester;
13
13
  /**
14
14
  * Generate visual test report
15
15
  */
@@ -89,6 +89,29 @@ export declare interface Story {
89
89
  args?: Record<string, unknown>
90
90
  viewports?: Viewport[]
91
91
  }
92
+ /** Handle returned by {@link createSnapshotTester}. */
93
+ export declare interface SnapshotTester {
94
+ snapshot: (html: string, name: string) => Promise<SnapshotResult>
95
+ snapshotJson: (data: unknown, name: string) => Promise<SnapshotResult>
96
+ matchInlineSnapshot: (actual: string, expected?: string) => { passed: boolean, expected: string }
97
+ getConfig: () => Required<VisualTestConfig>
98
+ }
99
+ /** Handle returned by {@link createVisualRegressionTester}. */
100
+ export declare interface VisualRegressionTester {
101
+ compareScreenshots: (name: string, actual: Buffer, viewport?: string) => Promise<ScreenshotResult>
102
+ testViewports: (name: string, captureScreenshot: (viewport: Viewport) => Promise<Buffer>, viewports?: Viewport[]) => Promise<ScreenshotResult[]>
103
+ getSnapshotTester: () => SnapshotTester
104
+ }
105
+ /** Handle returned by {@link createStoryTester}. */
106
+ export declare interface StoryTester {
107
+ addStory: (story: Story) => void
108
+ addStories: (storyList: Story[]) => void
109
+ getStories: () => Story[]
110
+ getStoriesForComponent: (component: string) => Story[]
111
+ testStory: (storyId: string) => Promise<VisualTestResult>
112
+ testAllStories: () => Promise<VisualTestResult[]>
113
+ generateStoryIndex: () => Promise<string>
114
+ }
92
115
  // =============================================================================
93
116
  // Test Reporter
94
117
  // =============================================================================
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.151",
4
+ "version": "0.2.153",
5
5
  "description": "A performant UI Framework. Powered by Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -117,12 +117,12 @@
117
117
  "dependencies": {
118
118
  "@cwcss/crosswind": "^0.2.14",
119
119
  "@stacksjs/clapp": "^0.2.0",
120
- "@stacksjs/desktop": "0.2.151",
121
- "@stacksjs/sanitizer": "0.2.151",
120
+ "@stacksjs/desktop": "0.2.153",
121
+ "@stacksjs/sanitizer": "0.2.153",
122
122
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.151",
123
+ "bun-plugin-stx": "0.2.153",
124
124
  "bunfig": "^0.15.17",
125
- "stx-router": "0.2.151",
125
+ "stx-router": "0.2.153",
126
126
  "ts-broadcasting": "^0.0.9",
127
127
  "ts-images": "^0.1.9",
128
128
  "ts-syntax-highlighter": "^0.2.1"
package/stx.d.ts CHANGED
@@ -237,11 +237,19 @@ interface StxColorModeOptions {
237
237
  /** localStorage key holding the preference. @default 'stx-color-mode' */
238
238
  storageKey?: string
239
239
  /** Mode used when nothing valid is stored. @default 'auto' */
240
- initialMode?: 'light' | 'dark' | 'auto'
241
- /** Class applied to `<html>` when dark. @default 'dark' */
242
- darkClass?: string
243
- /** Attribute applied to `<html>` INSTEAD of the class, e.g. 'data-theme'. */
244
- attribute?: string
240
+ initialMode?: 'light' | 'dark' | 'auto' | 'system'
241
+ /**
242
+ * Class applied to `<html>` when dark. @default 'dark'
243
+ * Applied alongside `attribute`; pass `null` to opt out of the class.
244
+ */
245
+ darkClass?: string | null
246
+ /** Attribute set to the resolved mode on `<html>`, e.g. 'data-theme'. */
247
+ attribute?: string | null
248
+ /**
249
+ * Spelling written to storage for "follow the system".
250
+ * @default whatever is already stored, else 'auto'
251
+ */
252
+ autoValue?: 'auto' | 'system'
245
253
  /** Suppress CSS transitions across a mode switch. @default true */
246
254
  disableTransitions?: boolean
247
255
  }
@@ -257,7 +265,7 @@ declare function useColorMode(_options?: StxColorModeOptions): {
257
265
  /** User preference, which may be 'auto'. */
258
266
  readonly preference: 'light' | 'dark' | 'auto'
259
267
  readonly isDark: boolean
260
- set: (mode: 'light' | 'dark' | 'auto') => void
268
+ set: (mode: 'light' | 'dark' | 'auto' | 'system') => void
261
269
  toggle: () => void
262
270
  subscribe: (fn: (mode: 'light' | 'dark', preference: 'light' | 'dark' | 'auto') => void) => () => void
263
271
  }