@stacksjs/stx 0.2.153 → 0.2.155

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 (75) hide show
  1. package/dist/animation.js +6 -6
  2. package/dist/app-shell.js +1 -27
  3. package/dist/build-message.d.ts +10 -0
  4. package/dist/build-message.js +26 -0
  5. package/dist/builtins/attrs.js +6 -1
  6. package/dist/cli.js +1481 -1231
  7. package/dist/client-script-bundler.js +2 -4
  8. package/dist/client-script.d.ts +29 -0
  9. package/dist/client-script.js +140 -95
  10. package/dist/color-mode-boot.d.ts +31 -0
  11. package/dist/color-mode-boot.js +41 -5
  12. package/dist/component-renderer.js +10 -21
  13. package/dist/composables/index.d.ts +2 -0
  14. package/dist/composables/use-clipboard.d.ts +1 -5
  15. package/dist/composables/use-clipboard.js +57 -46
  16. package/dist/composables/use-color-mode.js +13 -5
  17. package/dist/composables/use-router.d.ts +28 -11
  18. package/dist/composables/use-router.js +63 -9
  19. package/dist/config.js +22 -7
  20. package/dist/craft.js +1029 -789
  21. package/dist/dev-server/crosswind.js +8 -6
  22. package/dist/dev-server/serve-app.js +4 -2
  23. package/dist/dev-server/serve-markdown.js +4 -1
  24. package/dist/dev-server/serve-multi.js +32 -20
  25. package/dist/document-shell.d.ts +65 -0
  26. package/dist/document-shell.js +59 -12
  27. package/dist/dynamic-components.js +123 -4
  28. package/dist/events.d.ts +26 -49
  29. package/dist/events.js +23 -9
  30. package/dist/expressions.d.ts +1 -1
  31. package/dist/expressions.js +3 -3
  32. package/dist/framework-composables.d.ts +41 -0
  33. package/dist/framework-composables.js +115 -0
  34. package/dist/hot-reload.js +8 -2
  35. package/dist/includes.js +2 -2
  36. package/dist/isolated-build.d.ts +12 -0
  37. package/dist/isolated-build.js +18 -0
  38. package/dist/plugin-system.d.ts +1 -1
  39. package/dist/process.js +40 -10
  40. package/dist/production-server.js +4 -1
  41. package/dist/prop-sentinels.d.ts +31 -0
  42. package/dist/prop-sentinels.js +7 -0
  43. package/dist/pwa.js +966 -726
  44. package/dist/reactive.js +1 -1
  45. package/dist/reactivity.d.ts +0 -15
  46. package/dist/reactivity.js +15 -6
  47. package/dist/runtime-globals.d.ts +40 -2
  48. package/dist/runtime-globals.js +90 -0
  49. package/dist/runtime-injection.d.ts +51 -0
  50. package/dist/runtime-injection.js +13 -0
  51. package/dist/script-emit.d.ts +20 -0
  52. package/dist/script-emit.js +11 -0
  53. package/dist/script-validation.js +12 -7
  54. package/dist/signal-processing.js +25 -40
  55. package/dist/signals-api.d.ts +30 -0
  56. package/dist/signals-api.js +18 -0
  57. package/dist/signals.js +258 -47
  58. package/dist/site-builder/build.js +21 -7
  59. package/dist/site-builder/seo.js +19 -10
  60. package/dist/site-builder/theme.js +5 -4
  61. package/dist/site-builder/types.d.ts +1 -0
  62. package/dist/ssg.js +986 -746
  63. package/dist/state-management.js +4 -1
  64. package/dist/store-loader.d.ts +0 -11
  65. package/dist/store-loader.js +27 -3
  66. package/dist/strict-lint.d.ts +9 -6
  67. package/dist/strict-lint.js +2 -21
  68. package/dist/stx.d.ts +111 -16
  69. package/dist/unresolved-identifiers.d.ts +50 -0
  70. package/dist/unresolved-identifiers.js +146 -0
  71. package/dist/utils.js +3 -3
  72. package/dist/variable-extractor.js +17 -1
  73. package/dist/x-element.js +1 -1
  74. package/package.json +5 -5
  75. package/stx.d.ts +111 -16
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import fs from "node:fs";
3
3
  import { getPublicEnvDefine } from "./public-env.js";
4
4
  import { stateDir } from "./state-dir.js";
5
+ import { formatBuildFailure } from "./build-message.js";
5
6
  const BUNDLE_CACHE_VERSION = 5, BUNDLE_CACHE_METADATA_VERSION = 1;
6
7
  export function shouldLogBundlerDiagnostics(environment = process.env) {
7
8
  return environment.STX_DEBUG === "true" || environment.STX_DEBUG === "1";
@@ -267,10 +268,7 @@ ${publicAssignments}`.trim();
267
268
  }));
268
269
  return bundled;
269
270
  } catch (error) {
270
- const msg = error instanceof Error ? error.message : String(error), bunErrors = error?.errors, detail = Array.isArray(bunErrors) && bunErrors.length > 0 ? `
271
- ${bunErrors.map((e) => `- ${e?.message ?? e}`).join(`
272
- `)}` : "";
273
- console.warn(`[stx:bundler] error: ${msg}${detail}`);
271
+ console.warn(`[stx:bundler] error: ${formatBuildFailure(error, filePath || void 0)}`);
274
272
  return code;
275
273
  } finally {
276
274
  try {
@@ -46,6 +46,13 @@ export declare function injectBrowserCoreAutoImports(code: string): BrowserCoreA
46
46
  /**
47
47
  * Blank out comments and string literals, preserving length and line structure
48
48
  * so offsets still line up, so that identifier detection only ever sees code.
49
+ *
50
+ * Template-literal INTERPOLATIONS are kept, because `${...}` is code, not text.
51
+ * Blanking them hid every identifier that appears only inside one — a helper
52
+ * called as `${formatDate(d)}` looked unused to the auto-import scanner, and a
53
+ * server value read as `` `/api/users/${userId}` `` looked unreferenced to the
54
+ * data bridge, which would have withheld it and left the client throwing on an
55
+ * undefined name.
49
56
  */
50
57
  export declare function stripCommentsAndLiterals(code: string): string;
51
58
  /**
@@ -91,6 +98,28 @@ export declare function generateServerDataBridge(code: string, serverData?: Reco
91
98
  * @returns A complete `<script>...</script>` tag ready for browser injection
92
99
  */
93
100
  export declare function processClientScript(scriptContent: string, options?: ClientScriptOptions): Promise<string>;
101
+ /**
102
+ * Exports from 'stx' auto-imported into classic `<script>` / `<script client>`
103
+ * blocks.
104
+ *
105
+ * Derived from {@link STX_RUNTIME_GLOBALS} rather than hand-maintained (#1804).
106
+ * This was a separate 82-name literal, and it had drifted: 16 of those names
107
+ * were not on `window.stx` at all. Because the generated prologue destructures
108
+ * (`var { … } = window.stx`), a missing name is not an error — it binds
109
+ * `undefined`, and the author sees "undefined is not a function" at the call
110
+ * site with nothing naming the identifier or saying why. `provide` worked and
111
+ * `inject` did not; `onMounted` worked and `onUpdated` did not.
112
+ *
113
+ * Five of the 16 (`isDerived`, `inject`, `useSlots`, `watchMultiple`,
114
+ * `onBeforeMount`) are now implemented in the runtime and live in the shared
115
+ * list. The remaining 11 are server- or compile-time only — `h` and `Fragment`
116
+ * belong to the JSX runtime, `useMeta`/`getCurrentInstance`/`useAttrs`/
117
+ * `onErrorCaptured`/`onBeforeUpdate`/`onUpdated`/`createStore`/`createSelector`
118
+ * are module exports with no client counterpart, and `action` has no
119
+ * implementation anywhere — so they are no longer offered. A `ReferenceError`
120
+ * naming the identifier beats a silent `undefined`.
121
+ */
122
+ export declare const STX_AUTO_IMPORTS: readonly string[];
94
123
  export declare interface VendorCssImport {
95
124
  source: string
96
125
  resolvedPath: string
@@ -1,8 +1,11 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
+ import { asInvocableStatement } from "./events.js";
3
4
  import { transformStoreImports } from "./store-imports.js";
4
5
  import { shouldTranspileTypeScript, transpileTypeScript } from "./utils.js";
5
6
  import { importOnce } from "./lazy-module.js";
7
+ import { STX_RUNTIME_GLOBALS, usesReactiveRuntime } from "./runtime-globals.js";
8
+ import { escapeScriptBody } from "./script-emit.js";
6
9
  const CSS_SIDE_EFFECT_IMPORT_REGEX = /^[ \t]*import\s+['"]([^'"]+?\.css)(\?[^'"]*)?['"]\s*;?\s*$/gm;
7
10
  function splitBareSpecifier(spec) {
8
11
  if (spec.startsWith(".") || spec.startsWith("/"))
@@ -141,90 +144,8 @@ ${s.contents}
141
144
  `)}
142
145
  `;
143
146
  }
144
- const STX_AUTO_IMPORTS = [
145
- "state",
146
- "derived",
147
- "effect",
148
- "batch",
149
- "untrack",
150
- "peek",
151
- "isSignal",
152
- "isDerived",
153
- "onMount",
154
- "onDestroy",
155
- "useRef",
156
- "navigate",
157
- "goBack",
158
- "goForward",
159
- "useRoute",
160
- "setRouteParams",
161
- "useSearchParams",
162
- "useFetch",
163
- "useQuery",
164
- "useMutation",
165
- "useOptimistic",
166
- "useId",
167
- "useReactiveProp",
168
- "useLocalStorage",
169
- "useSessionStorage",
170
- "useCookie",
171
- "useWebSocket",
172
- "useHead",
173
- "useSeoMeta",
174
- "definePageMeta",
175
- "useEventListener",
176
- "useMeta",
177
- "useDebounce",
178
- "useDebouncedValue",
179
- "useThrottle",
180
- "useInterval",
181
- "useTimeout",
182
- "useToggle",
183
- "useCounter",
184
- "useClickOutside",
185
- "useFocus",
186
- "useAsync",
187
- "useColorMode",
188
- "useDark",
189
- "useMediaQuery",
190
- "useScrollLock",
191
- "usePreferredDark",
192
- "usePreferredLight",
193
- "usePreferredReducedMotion",
194
- "usePreferredContrast",
195
- "ref",
196
- "reactive",
197
- "computed",
198
- "watch",
199
- "watchEffect",
200
- "watchMultiple",
201
- "onBeforeMount",
202
- "onMounted",
203
- "onBeforeUpdate",
204
- "onUpdated",
205
- "onBeforeUnmount",
206
- "onUnmounted",
207
- "defineProps",
208
- "withDefaults",
209
- "defineEmits",
210
- "defineExpose",
211
- "defineSlots",
212
- "provide",
213
- "inject",
214
- "nextTick",
215
- "getCurrentInstance",
216
- "onErrorCaptured",
217
- "useSlots",
218
- "useAttrs",
219
- "createStore",
220
- "defineStore",
221
- "registerStoresClient",
222
- "useStore",
223
- "action",
224
- "createSelector",
225
- "h",
226
- "Fragment"
227
- ], BROWSER_CORE_IMPORTS = [
147
+ export const STX_AUTO_IMPORTS = STX_RUNTIME_GLOBALS;
148
+ const BROWSER_CORE_IMPORTS = [
228
149
  "browserQuery",
229
150
  "BrowserQueryBuilder",
230
151
  "BrowserQueryError",
@@ -342,6 +263,84 @@ const JS_BUILTINS = new Set([
342
263
  "Modal",
343
264
  "Intl"
344
265
  ]);
266
+ function skipQuoted(code, start) {
267
+ const quote = code[start];
268
+ let i = start + 1;
269
+ while (i < code.length) {
270
+ if (code[i] === "\\") {
271
+ i += 2;
272
+ continue;
273
+ }
274
+ if (code[i] === quote)
275
+ return i + 1;
276
+ i++;
277
+ }
278
+ return code.length;
279
+ }
280
+ function skipTemplate(code, start) {
281
+ let i = start + 1;
282
+ while (i < code.length) {
283
+ if (code[i] === "\\") {
284
+ i += 2;
285
+ continue;
286
+ }
287
+ if (code[i] === "`")
288
+ return i + 1;
289
+ if (code[i] === "$" && code[i + 1] === "{") {
290
+ i = findInterpolationEnd(code, i + 2);
291
+ if (i < code.length)
292
+ i++;
293
+ continue;
294
+ }
295
+ i++;
296
+ }
297
+ return code.length;
298
+ }
299
+ function findInterpolationEnd(code, start) {
300
+ let depth = 1, i = start;
301
+ while (i < code.length) {
302
+ const ch = code[i];
303
+ if (ch === "\\") {
304
+ i += 2;
305
+ continue;
306
+ }
307
+ if (ch === '"' || ch === "'") {
308
+ i = skipQuoted(code, i);
309
+ continue;
310
+ }
311
+ if (ch === "`") {
312
+ i = skipTemplate(code, i);
313
+ continue;
314
+ }
315
+ if (ch === "/" && code[i + 1] === "/") {
316
+ while (i < code.length && code[i] !== `
317
+ `)
318
+ i++;
319
+ continue;
320
+ }
321
+ if (ch === "/" && code[i + 1] === "*") {
322
+ i += 2;
323
+ while (i < code.length && code.slice(i, i + 2) !== "*/")
324
+ i++;
325
+ i += 2;
326
+ continue;
327
+ }
328
+ if (ch === "{") {
329
+ depth++;
330
+ i++;
331
+ continue;
332
+ }
333
+ if (ch === "}") {
334
+ depth--;
335
+ if (depth === 0)
336
+ return i;
337
+ i++;
338
+ continue;
339
+ }
340
+ i++;
341
+ }
342
+ return code.length;
343
+ }
345
344
  export function stripCommentsAndLiterals(code) {
346
345
  let out = "", i = 0;
347
346
  while (i < code.length) {
@@ -362,7 +361,7 @@ export function stripCommentsAndLiterals(code) {
362
361
  continue;
363
362
  }
364
363
  const ch = code[i];
365
- if (ch === '"' || ch === "'" || ch === "`") {
364
+ if (ch === '"' || ch === "'") {
366
365
  const quote = ch;
367
366
  out += " ";
368
367
  i++;
@@ -374,6 +373,33 @@ export function stripCommentsAndLiterals(code) {
374
373
  }
375
374
  out += code[i] === `
376
375
  ` ? `
376
+ ` : " ";
377
+ i++;
378
+ }
379
+ out += " ";
380
+ i++;
381
+ continue;
382
+ }
383
+ if (ch === "`") {
384
+ out += " ";
385
+ i++;
386
+ while (i < code.length && code[i] !== "`") {
387
+ if (code[i] === "\\") {
388
+ out += " ";
389
+ i += 2;
390
+ continue;
391
+ }
392
+ if (code[i] === "$" && code[i + 1] === "{") {
393
+ const bodyStart = i + 2, bodyEnd = findInterpolationEnd(code, bodyStart);
394
+ out += " ";
395
+ out += stripCommentsAndLiterals(code.slice(bodyStart, bodyEnd));
396
+ if (bodyEnd < code.length)
397
+ out += " ";
398
+ i = bodyEnd + 1;
399
+ continue;
400
+ }
401
+ out += code[i] === `
402
+ ` ? `
377
403
  ` : " ";
378
404
  i++;
379
405
  }
@@ -535,14 +561,32 @@ function declaresClientIdentifier(code, name) {
535
561
  return !0;
536
562
  return !1;
537
563
  }
564
+ function referencesIdentifier(searchable, name) {
565
+ return new RegExp(`(?<![.#\\w$])${name}(?![\\w$])`).test(searchable);
566
+ }
567
+ const CREDENTIAL_NAME = /token|secret|passw|apikey|api_key|privatekey|private_key|credential|webhook|bearer|jwt|signature/i, warnedBridgeKeys = new Set;
568
+ function warnAboutBridgedValue(name, json) {
569
+ const credential = CREDENTIAL_NAME.test(name), oversized = json.length > 32768;
570
+ if (!credential && !oversized)
571
+ return;
572
+ if (warnedBridgeKeys.has(name))
573
+ return;
574
+ warnedBridgeKeys.add(name);
575
+ if (credential)
576
+ console.warn(`[stx] server\u2192client bridge is publishing "${name}" into the page body. If it holds a credential, rename it with a "__" prefix (never bridged) and pass only what the template needs. See stacksjs/stx#1831.`);
577
+ if (oversized)
578
+ console.warn(`[stx] server\u2192client bridge is publishing ${(json.length / 1024).toFixed(1)}KB under "${name}". Every byte ships in the HTML on each request; prefix the binding with "__" and project just the fields the client reads.`);
579
+ }
538
580
  export function generateServerDataBridge(code, serverData) {
539
581
  if (!serverData)
540
582
  return "";
541
- const lines = [];
583
+ const searchable = stripCommentsAndLiterals(code), lines = [];
542
584
  for (const [name, value] of Object.entries(serverData)) {
543
585
  if (!/^[A-Za-z_$][\w$]*$/.test(name))
544
586
  continue;
545
- if (!new RegExp(`\\b${name}\\b`).test(code))
587
+ if (name.startsWith("__") || name.startsWith("$"))
588
+ continue;
589
+ if (!referencesIdentifier(searchable, name))
546
590
  continue;
547
591
  if (declaresClientIdentifier(code, name))
548
592
  continue;
@@ -554,6 +598,7 @@ export function generateServerDataBridge(code, serverData) {
554
598
  }
555
599
  if (json === void 0)
556
600
  continue;
601
+ warnAboutBridgedValue(name, json);
557
602
  lines.push(` var ${name} = ${json.replace(/</g, "\\u003c")};`);
558
603
  }
559
604
  if (lines.length === 0)
@@ -589,7 +634,7 @@ function generateModifierChecks(modifiers) {
589
634
  return checks.join("; ");
590
635
  }
591
636
  function generateSingleEventBinding(binding, index) {
592
- const { elementId, event, handler, modifiers } = binding, checks = generateModifierChecks(modifiers), options = [];
637
+ const { elementId, event, modifiers } = binding, handler = asInvocableStatement(binding.handler), checks = generateModifierChecks(modifiers), options = [];
593
638
  if (modifiers.once)
594
639
  options.push("once: true");
595
640
  if (modifiers.capture)
@@ -910,19 +955,19 @@ export async function processClientScript(scriptContent, options = {}) {
910
955
  if (/\btype\s*=\s*["']module["']/i.test(attrs)) {
911
956
  const extraAttrs = attrs.replace(/\btype\s*=\s*["']module["']/i, "").trim(), attrStr = `type="module" data-stx-scoped${extraAttrs ? ` ${extraAttrs}` : ""}`;
912
957
  return `${vendorStyleTags}<script ${attrStr}>
913
- ${autoImportCode}${code}
914
- ${eventCode}
958
+ ${escapeScriptBody(`${autoImportCode}${code}
959
+ ${eventCode}`)}
915
960
  </script>`;
916
961
  }
917
- const usesSignals = /\b(state|derived|effect|ref|reactive|computed|watch|watchEffect|useLocalStorage|useSessionStorage|useRef|useEventListener|useFetch|useDebounce|useDebouncedValue|useThrottle|useInterval|useTimeout|useToggle|useCounter|useClickOutside|useFocus|useAsync|useColorMode|useDark|useWebSocket|useRoute|useSearchParams|onMount|onDestroy|onMounted|onBeforeUnmount|onUnmounted|batch|untrack|peek)\s*(?:<[^>]*>)?\s*\(/.test(scriptContent), isSfcWrapped = /function __stx_setup_/.test(code), alreadyMounts = /\bstx\.mount\s*\(|\bstx\.mountEl\s*\(/.test(scriptContent);
962
+ const usesSignals = usesReactiveRuntime(scriptContent), isSfcWrapped = /function __stx_setup_/.test(code), alreadyMounts = /\bstx\.mount\s*\(|\bstx\.mountEl\s*\(/.test(scriptContent);
918
963
  function wrapInMount(declarations) {
919
964
  const returnStmt = declarations.length > 0 ? `
920
965
  return { ${declarations.join(", ")} };` : "";
921
966
  return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always">
922
967
  window.stx.mount(function() {
923
968
  'use strict';
924
- ${autoImportCode}${code}
925
- ${eventCode}${returnStmt}
969
+ ${escapeScriptBody(`${autoImportCode}${code}
970
+ ${eventCode}${returnStmt}`)}
926
971
  })</script>`;
927
972
  }
928
973
  if (!alreadyMounts && !isSfcWrapped) {
@@ -940,7 +985,7 @@ ${eventCode}${returnStmt}
940
985
  return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always">
941
986
  ;(function() {
942
987
  'use strict';
943
- ${autoImportCode}${code}
944
- ${eventCode}
988
+ ${escapeScriptBody(`${autoImportCode}${code}
989
+ ${eventCode}`)}
945
990
  })()</script>`;
946
991
  }
@@ -21,6 +21,37 @@ export declare function generateColorModeBootScript(options?: ColorModeBootConfi
21
21
  * unchanged, so this is safe on paths that run it more than once.
22
22
  */
23
23
  export declare function injectColorModeBootScript(html: string, options?: ColorModeBootConfig): string;
24
+ /**
25
+ * Re-assert the ordering of the two head tags whose position is load-bearing:
26
+ * `<meta charset>` first, then the color-mode boot script, then everything else.
27
+ *
28
+ * Run this LAST, after every other head injection. Ordering the injectors
29
+ * against each other would hold only until the next one is added; re-asserting
30
+ * the result once at the end holds regardless of what ran before.
31
+ */
32
+ export declare function normalizeCriticalHeadOrder(html: string): string;
33
+ /**
34
+ * Move an already-injected boot script back to the top of `<head>`.
35
+ *
36
+ * `injectColorModeBootScript` puts it first, but later stages of the pipeline
37
+ * then insert *ahead* of it and push it down (#1803): the signals runtime is
38
+ * relocated before the first `<script>` on the page — which is this one — and
39
+ * the store and composable bundles anchor themselves to the runtime tag. The
40
+ * boot script ended up fifth, behind all three.
41
+ *
42
+ * That is not merely a paint-timing problem. The bundles that overtook it read
43
+ * `window.__STX_COLOR_MODE__`, which this script publishes, so a `useColorMode()`
44
+ * inside a store saw `undefined` and silently fell back to the default storage
45
+ * key and a null attribute — writing the user's preference somewhere the boot
46
+ * script will never read it back, and never applying the configured attribute.
47
+ *
48
+ * Run this last, after every other head injection. Ordering the injectors
49
+ * against each other would work only until the next one is added; re-asserting
50
+ * the position once at the end holds regardless of what ran before.
51
+ *
52
+ * A no-op when the document has no boot script or it is already in place.
53
+ */
54
+ export declare function hoistColorModeBootScript(html: string): string;
24
55
  /**
25
56
  * Pre-paint color-mode bootstrap (stacksjs/stx#1794).
26
57
  *
@@ -26,14 +26,50 @@ export function generateColorModeBootScript(options = {}) {
26
26
  export function injectColorModeBootScript(html, options = {}) {
27
27
  if (html.includes(COLOR_MODE_BOOT_MARKER))
28
28
  return html;
29
+ const insertAt = bootInsertionPoint(html);
30
+ if (insertAt === null)
31
+ return html;
32
+ const script = generateColorModeBootScript(options);
33
+ return `${html.slice(0, insertAt)}
34
+ ${script}${html.slice(insertAt)}`;
35
+ }
36
+ function bootInsertionPoint(html) {
29
37
  const headOpen = /<head\b[^>]*>/i.exec(html);
30
38
  if (!headOpen)
31
- return html;
39
+ return null;
32
40
  let insertAt = headOpen.index + headOpen[0].length;
33
- const afterHead = html.slice(insertAt), charset = /^\s*<meta\b[^>]*\bcharset\b[^>]*>/i.exec(afterHead);
41
+ const charset = /^\s*<meta\b[^>]*\bcharset\b[^>]*>/i.exec(html.slice(insertAt));
34
42
  if (charset)
35
43
  insertAt += charset[0].length;
36
- const script = generateColorModeBootScript(options);
37
- return `${html.slice(0, insertAt)}
38
- ${script}${html.slice(insertAt)}`;
44
+ return insertAt;
45
+ }
46
+ const BOOT_SCRIPT_TAG = new RegExp(`<script\\b[^>]*\\b${COLOR_MODE_BOOT_MARKER}\\b[^>]*>[\\s\\S]*?<\\/script>`, "i"), CHARSET_META = /<meta\b[^>]*\bcharset\b[^>]*>/i;
47
+ function hoistCharsetMeta(html) {
48
+ const headOpen = /<head\b[^>]*>/i.exec(html);
49
+ if (!headOpen)
50
+ return html;
51
+ const headStart = headOpen.index + headOpen[0].length, headEnd = html.toLowerCase().indexOf("</head>", headStart), head = html.slice(headStart, headEnd === -1 ? html.length : headEnd), meta = CHARSET_META.exec(head);
52
+ if (!meta || head.slice(0, meta.index).trim() === "")
53
+ return html;
54
+ const withoutMeta = head.slice(0, meta.index) + head.slice(meta.index + meta[0].length);
55
+ return html.slice(0, headStart) + `
56
+ ${meta[0]}` + withoutMeta + html.slice(headStart + head.length);
57
+ }
58
+ export function normalizeCriticalHeadOrder(html) {
59
+ return hoistColorModeBootScript(hoistCharsetMeta(html));
60
+ }
61
+ export function hoistColorModeBootScript(html) {
62
+ const tag = BOOT_SCRIPT_TAG.exec(html);
63
+ if (!tag)
64
+ return html;
65
+ const target = bootInsertionPoint(html);
66
+ if (target === null)
67
+ return html;
68
+ if (tag.index <= target || html.slice(target, tag.index).trim() === "")
69
+ return html;
70
+ const withoutTag = html.slice(0, tag.index) + html.slice(tag.index + tag[0].length), insertAt = bootInsertionPoint(withoutTag);
71
+ if (insertAt === null)
72
+ return html;
73
+ return `${withoutTag.slice(0, insertAt)}
74
+ ${tag[0]}${withoutTag.slice(insertAt)}`;
39
75
  }
@@ -4,10 +4,12 @@ import { decodeAttributeEntities, decodeStxProp, findComponentTags, parseMultili
4
4
  import { maskAtElementPosition, matchHtmlComment } from "./html-masking.js";
5
5
  import { renderComponentWithSlot, userComponentFileExists } from "./utils.js";
6
6
  import { createSafeFunction, isExpressionSafe, safeEvaluateObject } from "./safe-evaluator.js";
7
+ import { BOOLEAN_ATTRIBUTE_SENTINEL } from "./prop-sentinels.js";
8
+ import { COMPONENT_PASSTHROUGH_X_ATTRS } from "./runtime-globals.js";
7
9
  import fs from "node:fs";
8
10
  import path from "node:path";
9
11
  let builtinsRegistered = !1;
10
- const BOOLEAN_ATTRIBUTE_SENTINEL = "\x00stx-boolean-attribute", htmlTags = new Set([
12
+ const htmlTags = new Set([
11
13
  "a",
12
14
  "abbr",
13
15
  "address",
@@ -29,6 +31,7 @@ const BOOLEAN_ATTRIBUTE_SENTINEL = "\x00stx-boolean-attribute", htmlTags = new S
29
31
  "code",
30
32
  "col",
31
33
  "colgroup",
34
+ "component",
32
35
  "data",
33
36
  "datalist",
34
37
  "dd",
@@ -180,24 +183,7 @@ function parseComponentProps(rawProps, context, options) {
180
183
  serverDynamic: {},
181
184
  clientReactive: {},
182
185
  events: {}
183
- }, ALPINE_PASSTHROUGH = new Set([
184
- "x-cloak",
185
- "x-show",
186
- "x-if",
187
- "x-for",
188
- "x-data",
189
- "x-init",
190
- "x-transition",
191
- "x-ref",
192
- "x-effect",
193
- "x-model",
194
- "x-text",
195
- "x-html",
196
- "x-class",
197
- "x-style",
198
- "x-on",
199
- "x-bind"
200
- ]), kebabToCamel = (key) => {
186
+ }, ALPINE_PASSTHROUGH = new Set(COMPONENT_PASSTHROUGH_X_ATTRS), kebabToCamel = (key) => {
201
187
  if (!key.includes("-") || key.startsWith("@") || key.startsWith(":") || key.startsWith("x-") || key.startsWith("__"))
202
188
  return key;
203
189
  if (key.startsWith("aria-") || key.startsWith("data-"))
@@ -343,6 +329,9 @@ function parseAllAttributes(attributesStr) {
343
329
  }
344
330
  return props;
345
331
  }
332
+ function serialiseForwardedEvents(events) {
333
+ return Object.entries(events).map(([event, handler]) => handler === BOOLEAN_ATTRIBUTE_SENTINEL ? event : `${event}="${escapeAttribute(handler)}"`).join(" ");
334
+ }
346
335
  function escapeAttribute(value) {
347
336
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
348
337
  }
@@ -617,7 +606,7 @@ async function processCustomElementTags(template, context, filePath, options, de
617
606
  if (Object.keys(resolvedProps.clientReactive).length > 0)
618
607
  rendered = emitClientReactiveAttrs(rendered, resolvedProps.clientReactive);
619
608
  if (Object.keys(resolvedProps.events).length > 0) {
620
- const eventAttrs = Object.entries(resolvedProps.events).map(([event, handler]) => `${event}="${escapeAttribute(handler)}"`).join(" ");
609
+ const eventAttrs = serialiseForwardedEvents(resolvedProps.events);
621
610
  rendered = rendered.replace(/^(\s*<[a-zA-Z][a-zA-Z0-9-]*)/, `$1 ${eventAttrs}`);
622
611
  }
623
612
  result = result.substring(0, tag.startIndex) + rendered + result.substring(tag.endIndex);
@@ -636,7 +625,7 @@ async function processCustomElementTags(template, context, filePath, options, de
636
625
  if (Object.keys(resolvedProps.events).length > 0) {
637
626
  const root = findRenderedComponentRoot(finalContent);
638
627
  if (root) {
639
- const eventAttrs = Object.entries(resolvedProps.events).map(([event, handler]) => `${event}="${escapeAttribute(handler)}"`).join(" "), parentEvents = Object.keys(resolvedProps.events).map((event) => event.replace(/^@/, "").split(".")[0]).join(" ");
628
+ const eventAttrs = serialiseForwardedEvents(resolvedProps.events), parentEvents = Object.keys(resolvedProps.events).map((event) => event.replace(/^@/, "").split(".")[0]).join(" ");
640
629
  finalContent = finalContent.substring(0, root.insertPos) + ` data-stx-parent-events="${parentEvents}" ${eventAttrs}` + finalContent.substring(root.insertPos);
641
630
  }
642
631
  }
@@ -345,7 +345,9 @@ export {
345
345
  goForward,
346
346
  useRoute,
347
347
  useSearchParams,
348
+ type NavigateOptions,
348
349
  type RouteInfo,
350
+ type SearchParamsCommitOptions,
349
351
  type SearchParamsRef,
350
352
  } from './use-router';
351
353
  // Data Fetching (Query / Mutation)
@@ -19,11 +19,6 @@ export declare function useClipboard(options?: { timeout?: number }): ClipboardR
19
19
  * Copy text to clipboard (one-shot helper)
20
20
  */
21
21
  export declare function copyToClipboard(text: string): Promise<boolean>;
22
- /**
23
- * useClipboard - Reactive clipboard composable
24
- *
25
- * Provides easy access to the Clipboard API with fallbacks.
26
- */
27
22
  export declare interface ClipboardRef {
28
23
  text: string
29
24
  isSupported: boolean
@@ -31,4 +26,5 @@ export declare interface ClipboardRef {
31
26
  copy: (text: string) => Promise<boolean>
32
27
  read: () => Promise<string>
33
28
  subscribe: (callback: (text: string) => void) => () => void
29
+ stop: () => void
34
30
  }