@stacksjs/stx 0.2.155 → 0.2.157

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 (241) hide show
  1. package/dist/a11y.d.ts +87 -46
  2. package/dist/animation.d.ts +96 -42
  3. package/dist/appearance-bootstrap.d.ts +49 -15
  4. package/dist/asset-roots.d.ts +32 -0
  5. package/dist/asset-roots.js +7 -0
  6. package/dist/assets.d.ts +9 -7
  7. package/dist/async-components.d.ts +78 -56
  8. package/dist/auth.d.ts +3 -0
  9. package/dist/build-externalize.d.ts +40 -0
  10. package/dist/build-externalize.js +63 -0
  11. package/dist/build-id.d.ts +22 -22
  12. package/dist/build-message.d.ts +73 -2
  13. package/dist/build-message.js +47 -8
  14. package/dist/builtins/attrs.d.ts +14 -0
  15. package/dist/builtins/drawer.d.ts +22 -0
  16. package/dist/builtins/icon.d.ts +16 -2
  17. package/dist/builtins/index.d.ts +5 -15
  18. package/dist/builtins/modal.d.ts +24 -0
  19. package/dist/builtins/safe-image.d.ts +42 -0
  20. package/dist/builtins/stx-image.d.ts +27 -0
  21. package/dist/builtins/stx-link.d.ts +9 -0
  22. package/dist/builtins/stx-loading-indicator.d.ts +11 -0
  23. package/dist/builtins/suspense.d.ts +28 -0
  24. package/dist/builtins/toast.d.ts +17 -0
  25. package/dist/builtins/transition-group.d.ts +25 -0
  26. package/dist/caching.d.ts +12 -15
  27. package/dist/cli.js +1798 -908
  28. package/dist/client/directive.d.ts +7 -0
  29. package/dist/client/index.d.ts +0 -1
  30. package/dist/client-script-bundler.d.ts +63 -5
  31. package/dist/client-script-bundler.js +30 -2
  32. package/dist/client-script.d.ts +102 -47
  33. package/dist/client-script.js +15 -3
  34. package/dist/codemod.d.ts +56 -0
  35. package/dist/codemod.js +192 -0
  36. package/dist/color-mode-boot.d.ts +83 -65
  37. package/dist/component-client-factories.d.ts +1 -6
  38. package/dist/component-processing.d.ts +58 -49
  39. package/dist/component-processing.js +1 -1
  40. package/dist/component-registry.d.ts +94 -31
  41. package/dist/component-renderer.d.ts +18 -0
  42. package/dist/composable-loader.d.ts +24 -0
  43. package/dist/composable-loader.js +12 -9
  44. package/dist/composables/index.d.ts +7 -0
  45. package/dist/composables/index.js +1 -0
  46. package/dist/composables/use-fetch.js +30 -2
  47. package/dist/composables/use-form.d.ts +91 -0
  48. package/dist/composables/use-form.js +112 -0
  49. package/dist/composables.d.ts +64 -60
  50. package/dist/computed.d.ts +86 -92
  51. package/dist/conditionals.d.ts +24 -8
  52. package/dist/conditionals.js +16 -0
  53. package/dist/config.d.ts +34 -21
  54. package/dist/config.js +35 -14
  55. package/dist/craft.js +1476 -768
  56. package/dist/crosswind-config.d.ts +58 -0
  57. package/dist/crosswind-config.js +40 -0
  58. package/dist/csp.d.ts +107 -41
  59. package/dist/defer.d.ts +24 -0
  60. package/dist/dev-server/crosswind.d.ts +35 -26
  61. package/dist/dev-server/crosswind.js +2 -18
  62. package/dist/dev-server/serve-app.js +4 -3
  63. package/dist/dev-server/terminal-colors.d.ts +32 -32
  64. package/dist/directive-api.d.ts +86 -43
  65. package/dist/document-shell.d.ts +54 -41
  66. package/dist/dynamic-components.d.ts +9 -0
  67. package/dist/error-boundaries.d.ts +47 -41
  68. package/dist/error-handling.d.ts +1 -1
  69. package/dist/errors/codes.d.ts +51 -43
  70. package/dist/errors/formatter.d.ts +58 -38
  71. package/dist/errors/index.d.ts +5 -61
  72. package/dist/errors/logger.d.ts +114 -40
  73. package/dist/errors/sanitizer.d.ts +53 -23
  74. package/dist/errors/types.d.ts +44 -31
  75. package/dist/events.d.ts +86 -25
  76. package/dist/expressions.d.ts +50 -42
  77. package/dist/forms-validation.d.ts +138 -123
  78. package/dist/forms-validation.js +1 -90
  79. package/dist/forms.d.ts +120 -66
  80. package/dist/forms.js +31 -11
  81. package/dist/fragment-container.d.ts +57 -0
  82. package/dist/fragment-container.js +109 -0
  83. package/dist/framework-composables.d.ts +32 -0
  84. package/dist/head.d.ts +156 -114
  85. package/dist/heatmap.d.ts +152 -72
  86. package/dist/html-masking.d.ts +40 -34
  87. package/dist/hydration-runtime.d.ts +20 -24
  88. package/dist/i18n.d.ts +93 -32
  89. package/dist/image-optimization/auto-sibling.d.ts +32 -4
  90. package/dist/includes.d.ts +69 -7
  91. package/dist/index.d.ts +66 -0
  92. package/dist/index.js +23 -0
  93. package/dist/internal-markdown.d.ts +12 -8
  94. package/dist/island-chunking.d.ts +30 -13
  95. package/dist/keep-alive.d.ts +72 -52
  96. package/dist/layout-types.d.ts +45 -0
  97. package/dist/layout-types.js +56 -0
  98. package/dist/lazy-module.d.ts +10 -0
  99. package/dist/loops.d.ts +66 -0
  100. package/dist/loops.js +47 -6
  101. package/dist/markdown.d.ts +0 -1
  102. package/dist/media/image/component.d.ts +25 -9
  103. package/dist/media/image/directive.d.ts +15 -1
  104. package/dist/media/image/placeholder.d.ts +20 -9
  105. package/dist/media/image/processor/cache.d.ts +40 -18
  106. package/dist/media/image/processor/index.d.ts +1 -10
  107. package/dist/media/image/processor/optimizer.d.ts +12 -1
  108. package/dist/media/image/processor/responsive.d.ts +16 -6
  109. package/dist/media/image/srcset.d.ts +47 -50
  110. package/dist/media/shared/cache.d.ts +51 -31
  111. package/dist/media/shared/hash.d.ts +7 -0
  112. package/dist/media/types.d.ts +1235 -754
  113. package/dist/media/video/directive.d.ts +15 -1
  114. package/dist/media/video/index.d.ts +39 -17
  115. package/dist/media/video/processor/cache.d.ts +42 -19
  116. package/dist/media/video/processor/index.d.ts +1 -18
  117. package/dist/media/video/processor/streaming.d.ts +17 -10
  118. package/dist/media/video/processor/thumbnail.d.ts +21 -15
  119. package/dist/media/video/processor/transcoder.d.ts +11 -1
  120. package/dist/method-spoofing.d.ts +7 -0
  121. package/dist/middleware.d.ts +60 -0
  122. package/dist/owned-routes.d.ts +42 -0
  123. package/dist/owned-routes.js +26 -0
  124. package/dist/page-meta.d.ts +38 -0
  125. package/dist/page-meta.js +16 -0
  126. package/dist/parser/directive-parser.d.ts +57 -45
  127. package/dist/parser/expression-parser.d.ts +33 -35
  128. package/dist/parser/index.d.ts +4 -39
  129. package/dist/parser/tokenizer.d.ts +61 -49
  130. package/dist/partial-hydration.d.ts +82 -50
  131. package/dist/performance-utils.d.ts +217 -124
  132. package/dist/placeholder.d.ts +18 -9
  133. package/dist/plugin-system.d.ts +264 -115
  134. package/dist/process.d.ts +11 -6
  135. package/dist/process.js +21 -4
  136. package/dist/production-builder.js +7 -0
  137. package/dist/prop-sentinels.d.ts +11 -11
  138. package/dist/public-env.d.ts +14 -0
  139. package/dist/pwa/directives.d.ts +12 -3
  140. package/dist/pwa/inject.d.ts +10 -0
  141. package/dist/pwa/precache.d.ts +22 -16
  142. package/dist/pwa/service-worker.d.ts +12 -0
  143. package/dist/pwa.js +1318 -610
  144. package/dist/reactive-bindings.d.ts +34 -11
  145. package/dist/reactive.d.ts +58 -30
  146. package/dist/reactive.js +56 -31
  147. package/dist/render.d.ts +0 -14
  148. package/dist/render.js +17 -0
  149. package/dist/route-middleware.d.ts +2 -35
  150. package/dist/route-types.d.ts +134 -0
  151. package/dist/route-types.js +24 -0
  152. package/dist/router.d.ts +5 -7
  153. package/dist/routes.d.ts +1 -3
  154. package/dist/runtime-globals.d.ts +147 -24
  155. package/dist/runtime-globals.js +16 -1
  156. package/dist/runtime-injection.d.ts +8 -0
  157. package/dist/runtime-injection.js +33 -24
  158. package/dist/script-emit.d.ts +30 -0
  159. package/dist/script-json.d.ts +36 -0
  160. package/dist/script-json.js +6 -0
  161. package/dist/script-validation.d.ts +21 -4
  162. package/dist/seo.d.ts +118 -47
  163. package/dist/server-bindings.d.ts +21 -0
  164. package/dist/sfc-template.d.ts +8 -8
  165. package/dist/signal-processing.d.ts +35 -15
  166. package/dist/signals-api.d.ts +117 -58
  167. package/dist/signals-api.js +7 -4
  168. package/dist/signals.d.ts +24 -17
  169. package/dist/signals.js +765 -60
  170. package/dist/site-builder/build.js +8 -0
  171. package/dist/site-url.d.ts +46 -0
  172. package/dist/site-url.js +70 -0
  173. package/dist/slots.d.ts +86 -20
  174. package/dist/source-signature.d.ts +45 -0
  175. package/dist/source-signature.js +20 -0
  176. package/dist/ssg.d.ts +285 -142
  177. package/dist/ssg.js +1268 -556
  178. package/dist/state-dir.d.ts +4 -4
  179. package/dist/state-management.d.ts +0 -12
  180. package/dist/state-management.js +8 -25
  181. package/dist/store-imports.d.ts +23 -0
  182. package/dist/store-imports.js +3 -0
  183. package/dist/store-loader.d.ts +10 -0
  184. package/dist/store-loader.js +10 -9
  185. package/dist/story/analytics.d.ts +138 -60
  186. package/dist/story/bookmarks.d.ts +87 -34
  187. package/dist/story/cli.d.ts +123 -69
  188. package/dist/story/collect/scanner.d.ts +4 -0
  189. package/dist/story/collect/tree.d.ts +8 -1
  190. package/dist/story/commands/build.d.ts +11 -6
  191. package/dist/story/commands/dev.d.ts +15 -8
  192. package/dist/story/commands/preview.d.ts +13 -7
  193. package/dist/story/config-watcher.d.ts +20 -13
  194. package/dist/story/config.d.ts +11 -8
  195. package/dist/story/context.d.ts +14 -8
  196. package/dist/story/hmr.d.ts +24 -26
  197. package/dist/story/hot-swap.d.ts +34 -23
  198. package/dist/story/keyboard-shortcuts.d.ts +23 -18
  199. package/dist/story/output.d.ts +46 -44
  200. package/dist/story/performance.d.ts +107 -45
  201. package/dist/story/presets.d.ts +107 -41
  202. package/dist/story/renderer.d.ts +41 -27
  203. package/dist/story/search.d.ts +23 -12
  204. package/dist/story/server.d.ts +21 -11
  205. package/dist/story/setup.d.ts +23 -15
  206. package/dist/story/testing.d.ts +65 -40
  207. package/dist/story/types.d.ts +262 -160
  208. package/dist/streaming.d.ts +119 -57
  209. package/dist/stx-virtual-ts.d.ts +165 -0
  210. package/dist/stx-virtual-ts.js +372 -0
  211. package/dist/stx.d.ts +78 -1
  212. package/dist/style-scoping.d.ts +17 -4
  213. package/dist/suspense.d.ts +67 -52
  214. package/dist/teleport.d.ts +22 -0
  215. package/dist/template-tag-balance.d.ts +22 -6
  216. package/dist/transitions.d.ts +66 -55
  217. package/dist/typecheck.d.ts +77 -0
  218. package/dist/typecheck.js +156 -0
  219. package/dist/types/component-types.d.ts +187 -122
  220. package/dist/types/config-types.d.ts +758 -297
  221. package/dist/types/context-types.d.ts +46 -24
  222. package/dist/types/csp-types.d.ts +70 -66
  223. package/dist/types/directive-types.d.ts +211 -155
  224. package/dist/types/index.d.ts +6 -109
  225. package/dist/types/pwa-types.d.ts +273 -148
  226. package/dist/types.d.ts +0 -1
  227. package/dist/typescript-templates.d.ts +11 -0
  228. package/dist/unresolved-identifiers.d.ts +24 -22
  229. package/dist/unresolved-identifiers.js +2 -0
  230. package/dist/utils.d.ts +24 -21
  231. package/dist/validator.d.ts +28 -26
  232. package/dist/variable-extractor.d.ts +48 -13
  233. package/dist/variable-extractor.js +27 -4
  234. package/dist/view-composers.d.ts +80 -81
  235. package/dist/virtual-scrolling.d.ts +101 -81
  236. package/dist/vue-template.d.ts +23 -0
  237. package/dist/web-components/css-scoping.d.ts +21 -11
  238. package/dist/web-components.d.ts +81 -58
  239. package/dist/x-element.d.ts +0 -1
  240. package/package.json +5 -5
  241. package/stx.d.ts +78 -1
package/dist/forms.js CHANGED
@@ -20,6 +20,24 @@ function buildClassString(existingClass, defaultClass, hasError, errorClass) {
20
20
  const baseClass = existingClass || defaultClass;
21
21
  return hasError ? `${baseClass} ${errorClass}` : baseClass;
22
22
  }
23
+ function fieldId(name) {
24
+ return String(name).replace(/\[\]$/, "").replace(/[^\w.:-]+/g, "-").replace(/^-+|-+$/g, "");
25
+ }
26
+ function idAttr(attrs, fallbackId) {
27
+ if (/\bid\s*=/i.test(attrs))
28
+ return "";
29
+ return ` id="${escapeAttr(fallbackId)}"`;
30
+ }
31
+ function errorAria(name, hasError, attrs) {
32
+ if (!hasError)
33
+ return "";
34
+ let out = "";
35
+ if (!/\baria-invalid\s*=/i.test(attrs))
36
+ out += ' aria-invalid="true"';
37
+ if (!/\baria-describedby\s*=/i.test(attrs))
38
+ out += ` aria-describedby="${escapeAttr(`${fieldId(name)}-error`)}"`;
39
+ return out;
40
+ }
23
41
  function genId() {
24
42
  return crypto.randomUUID();
25
43
  }
@@ -70,11 +88,11 @@ export function processFormInputDirectives(template, context, classes = defaultF
70
88
  const name = singleQuoteName || doubleQuoteName || "", value = singleQuoteValue || doubleQuoteValue || "", attrs = parseAttributes(attributes), rawOldValue = getOldValue(name, context), oldValue = rawOldValue !== void 0 && rawOldValue !== null ? rawOldValue : value || "", typeMatch = attrs.match(/type=['"]([^'"]+)['"]/i), type = typeMatch ? typeMatch[1] : "text", hasError = hasFieldError(name, context), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = buildClassString(classMatch ? classMatch[1] : "", classes.input, hasError, classes.inputError);
71
89
  let attrsWithoutClassAndType = attrs.replace(/class=['"][^'"]+['"]/i, "");
72
90
  attrsWithoutClassAndType = attrsWithoutClassAndType.replace(/type=['"][^'"]+['"]/i, "");
73
- return `<input type="${escapeAttr(type)}" name="${escapeAttr(name)}" value="${escapeAttr(oldValue)}" class="${className}"${attrsWithoutClassAndType ? ` ${attrsWithoutClassAndType}` : ""}>`;
91
+ return `<input type="${escapeAttr(type)}" name="${escapeAttr(name)}"${idAttr(attrs, fieldId(name))} value="${escapeAttr(oldValue)}" class="${className}"${errorAria(name, hasError, attrs)}${attrsWithoutClassAndType ? ` ${attrsWithoutClassAndType}` : ""}>`;
74
92
  });
75
93
  result = result.replace(/@textarea\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)([\s\S]*?)@endtextarea/g, (_match, name, attributes = "", content = "") => {
76
94
  const attrs = parseAttributes(attributes), oldValue = getOldValue(name, context) || content.trim(), hasError = hasFieldError(name, context), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = buildClassString(classMatch ? classMatch[1] : "", classes.input, hasError, classes.inputError), attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, "");
77
- return `<textarea name="${escapeAttr(name)}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${escapeAttr(oldValue)}</textarea>`;
95
+ return `<textarea name="${escapeAttr(name)}"${idAttr(attrs, fieldId(name))} class="${className}"${errorAria(name, hasError, attrs)}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${escapeAttr(oldValue)}</textarea>`;
78
96
  });
79
97
  result = result.replace(/@select\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)([\s\S]*?)@endselect/g, (_match, name, attributes = "", content) => {
80
98
  const attrs = parseAttributes(attributes), oldValue = getOldValue(name, context), hasError = hasFieldError(name, context), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = buildClassString(classMatch ? classMatch[1] : "", classes.input, hasError, classes.inputError), attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, "");
@@ -85,19 +103,19 @@ export function processFormInputDirectives(template, context, classes = defaultF
85
103
  return optionTag.replace(/(<option)/, "$1 selected");
86
104
  return optionMatch;
87
105
  });
88
- return `<select name="${escapeAttr(name)}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${processedContent}</select>`;
106
+ return `<select name="${escapeAttr(name)}"${idAttr(attrs, fieldId(name))} class="${className}"${errorAria(name, hasError, attrs)}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${processedContent}</select>`;
89
107
  });
90
- result = result.replace(/@checkbox\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?(?:,\s*\{([^}]+)\})?\)/g, (_match, name, value = "1", attributes = "") => {
91
- const attrs = parseAttributes(attributes), oldValues = getOldValue(name, context), isChecked = oldValues !== void 0 && (Array.isArray(oldValues) ? oldValues.some((v) => String(v) === value) : oldValues === !0 || String(oldValues) === value), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = classMatch ? classMatch[1] : classes.checkInput, attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, "");
92
- return `<input type="checkbox" name="${escapeAttr(name)}" value="${escapeAttr(value)}" class="${className}"${isChecked ? " checked" : ""}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>`;
108
+ result = result.replace(/@checkbox\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"]\s*)?(?:,\s*\{([^}]+)\})?\)/g, (_match, name, rawValue, attributes = "") => {
109
+ const value = rawValue === void 0 ? "1" : rawValue, controlId = rawValue === void 0 ? fieldId(name) : `${fieldId(name)}-${fieldId(value)}`, attrs = parseAttributes(attributes), oldValues = getOldValue(name, context), isChecked = oldValues !== void 0 && (Array.isArray(oldValues) ? oldValues.some((v) => String(v) === value) : oldValues === !0 || String(oldValues) === value), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = classMatch ? classMatch[1] : classes.checkInput, attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, "");
110
+ return `<input type="checkbox" name="${escapeAttr(name)}"${idAttr(attrs, controlId)} value="${escapeAttr(value)}" class="${className}"${isChecked ? " checked" : ""}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>`;
93
111
  });
94
112
  result = result.replace(/@radio\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)/g, (_match, name, value, attributes = "") => {
95
113
  const attrs = parseAttributes(attributes), oldValue = getOldValue(name, context), isChecked = oldValue !== void 0 && oldValue === value, classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = classMatch ? classMatch[1] : classes.checkInput, attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, "");
96
- return `<input type="radio" name="${escapeAttr(name)}" value="${escapeAttr(value)}" class="${className}"${isChecked ? " checked" : ""}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>`;
114
+ return `<input type="radio" name="${escapeAttr(name)}"${idAttr(attrs, `${fieldId(name)}-${fieldId(value)}`)} value="${escapeAttr(value)}" class="${className}"${isChecked ? " checked" : ""}${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>`;
97
115
  });
98
116
  result = result.replace(/@file\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)/g, (_match, name, attributes = "") => {
99
117
  const attrs = parseAttributes(attributes), acceptMatch = attrs.match(/accept=['"]([^'"]+)['"]/i), accept = acceptMatch ? acceptMatch[1] : "", isMultiple = attrs.includes("multiple"), hasError = hasFieldError(name, context), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = buildClassString(classMatch ? classMatch[1] : "", classes.input, hasError, classes.inputError), attrsWithoutHandled = attrs.replace(/class=['"][^'"]+['"]/gi, "").replace(/accept=['"][^'"]+['"]/gi, "").replace(/\bmultiple\b/gi, "").trim(), parts = [
100
- `<input type="file" name="${escapeAttr(name)}"`,
118
+ `<input type="file" name="${escapeAttr(name)}"${idAttr(attrs, fieldId(name))}`,
101
119
  `class="${className}"`
102
120
  ];
103
121
  if (accept)
@@ -110,15 +128,15 @@ export function processFormInputDirectives(template, context, classes = defaultF
110
128
  });
111
129
  result = result.replace(/@label\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]+)\})?\)([\s\S]*?)@endlabel/g, (_match, forAttr, attributes = "", content) => {
112
130
  const attrs = parseAttributes(attributes), classMatch = attrs.match(/class=['"]([^'"]+)['"]/i), className = classMatch ? classMatch[1] : classes.label, attrsWithoutClass = attrs.replace(/class=['"][^'"]+['"]/i, "");
113
- return `<label for="${escapeAttr(forAttr)}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${content}</label>`;
131
+ return `<label for="${escapeAttr(fieldId(forAttr))}" class="${className}"${attrsWithoutClass ? ` ${attrsWithoutClass}` : ""}>${content}</label>`;
114
132
  });
115
133
  return result;
116
134
  }
117
135
  export function processErrorDirective(template, context, _classes = defaultFormClasses) {
118
136
  return template.replace(/@error\(['"]([^'"]+)['"]\)([\s\S]*?)@enderror/g, (match, field, content) => {
119
137
  try {
120
- if (hasFieldError(field, context))
121
- return content.replace(/\{\{([^}]+)\}\}/g, (_, expr) => {
138
+ if (hasFieldError(field, context)) {
139
+ const rendered = content.replace(/\{\{([^}]+)\}\}/g, (_, expr) => {
122
140
  try {
123
141
  if (expr.trim() === "$message" || expr.trim() === "message")
124
142
  return getErrorMessage(field, context);
@@ -137,6 +155,8 @@ export function processErrorDirective(template, context, _classes = defaultFormC
137
155
  return expr;
138
156
  }
139
157
  });
158
+ return `<div id="${escapeAttr(`${fieldId(field)}-error`)}" role="alert">${rendered}</div>`;
159
+ }
140
160
  return "";
141
161
  } catch (error) {
142
162
  console.error("Error processing @error directive:", error);
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Parse the supported subset of CSS selector syntax.
3
+ *
4
+ * Returns null for anything unsupported (descendant combinators, `:has()`,
5
+ * comma lists) rather than guessing — a wrong container is worse than a
6
+ * reported one.
7
+ */
8
+ export declare function parseContainerSelector(selector: string): ParsedSelector | null;
9
+ /**
10
+ * Find the region of `html` occupied by the first element matching `selector`.
11
+ *
12
+ * The closing tag is found by depth counting over same-named tags, so a
13
+ * container that contains another element of the same name — or a page that has
14
+ * two of them — resolves to the container's OWN close rather than the last one
15
+ * in the document.
16
+ */
17
+ export declare function findContainerRegion(html: string, selector?: string): ContainerRegion | null;
18
+ /**
19
+ * Locating the SPA swap container in rendered HTML.
20
+ *
21
+ * The client resolves the container with a CSS selector (`router.container`,
22
+ * falling back to `[data-stx-content]` then `main`). The server used to ignore
23
+ * that entirely and string-slice from the first `<main …>` to the LAST
24
+ * `</main>`, which produced three separate failures:
25
+ *
26
+ * - configuring `router: { container: '[data-stx-content]' }` still produced a
27
+ * `<main>`-shaped fragment, which the client then injected into a different
28
+ * element, duplicating the page chrome;
29
+ * - a page with two `<main>` elements swapped everything between the first open
30
+ * and the last close, i.e. the wrong span;
31
+ * - a page with no `<main>` shipped its whole body as the "fragment", silently
32
+ * disabling SPA navigation for every link on it, at HTTP 200.
33
+ *
34
+ * This module resolves the same selector the client uses and finds the container's
35
+ * OWN matching close tag by depth counting, so nesting and repetition behave.
36
+ *
37
+ * It is deliberately a small matcher, not a CSS engine: it covers the selector
38
+ * shapes a swap container realistically uses — a tag name, an attribute, an id,
39
+ * a class, and a tag qualified by one of those. Anything else returns null, and
40
+ * the caller is expected to say so rather than silently ship the whole body.
41
+ *
42
+ * @module fragment-container
43
+ */
44
+ export declare interface ContainerRegion {
45
+ openTag: string
46
+ openIndex: number
47
+ start: number
48
+ end: number
49
+ tagName: string
50
+ }
51
+ declare interface ParsedSelector {
52
+ tag?: string
53
+ attr?: string
54
+ attrValue?: string
55
+ id?: string
56
+ className?: string
57
+ }
@@ -0,0 +1,109 @@
1
+ const VOID_ELEMENTS = new Set([
2
+ "area",
3
+ "base",
4
+ "br",
5
+ "col",
6
+ "embed",
7
+ "hr",
8
+ "img",
9
+ "input",
10
+ "link",
11
+ "meta",
12
+ "param",
13
+ "source",
14
+ "track",
15
+ "wbr"
16
+ ]);
17
+ export function parseContainerSelector(selector) {
18
+ const trimmed = (selector || "").trim();
19
+ if (!trimmed)
20
+ return null;
21
+ if (/[\s>+~,:]/.test(trimmed))
22
+ return null;
23
+ const parsed = {};
24
+ let rest = trimmed;
25
+ const tagMatch = rest.match(/^[a-z][\w-]*/i);
26
+ if (tagMatch) {
27
+ parsed.tag = tagMatch[0].toLowerCase();
28
+ rest = rest.slice(tagMatch[0].length);
29
+ }
30
+ while (rest.length > 0) {
31
+ const attr = rest.match(/^\[([\w-]+)(?:=(["']?)([^\]"']*)\2)?\]/);
32
+ if (attr) {
33
+ parsed.attr = attr[1];
34
+ if (attr[3] !== void 0 && attr[3] !== "")
35
+ parsed.attrValue = attr[3];
36
+ rest = rest.slice(attr[0].length);
37
+ continue;
38
+ }
39
+ const id = rest.match(/^#([\w-]+)/);
40
+ if (id) {
41
+ parsed.id = id[1];
42
+ rest = rest.slice(id[0].length);
43
+ continue;
44
+ }
45
+ const cls = rest.match(/^\.([\w-]+)/);
46
+ if (cls) {
47
+ parsed.className = cls[1];
48
+ rest = rest.slice(cls[0].length);
49
+ continue;
50
+ }
51
+ return null;
52
+ }
53
+ if (!parsed.tag && !parsed.attr && !parsed.id && !parsed.className)
54
+ return null;
55
+ return parsed;
56
+ }
57
+ function attributesMatch(attrs, parsed) {
58
+ if (parsed.attr) {
59
+ if (!(parsed.attrValue === void 0 ? new RegExp(`\\b${parsed.attr}(?=[\\s=>/]|$)`, "i") : new RegExp(`\\b${parsed.attr}\\s*=\\s*(["'])\\s*${parsed.attrValue}\\s*\\1`, "i")).test(attrs))
60
+ return !1;
61
+ }
62
+ if (parsed.id) {
63
+ if (!new RegExp(`\\bid\\s*=\\s*(["'])\\s*${parsed.id}\\s*\\1`, "i").test(attrs))
64
+ return !1;
65
+ }
66
+ if (parsed.className) {
67
+ const classMatch = attrs.match(/\bclass\s*=\s*(["'])([\s\S]*?)\1/i);
68
+ if (!classMatch)
69
+ return !1;
70
+ if (!classMatch[2].split(/\s+/).includes(parsed.className))
71
+ return !1;
72
+ }
73
+ return !0;
74
+ }
75
+ export function findContainerRegion(html, selector = "main") {
76
+ const parsed = parseContainerSelector(selector);
77
+ if (!parsed)
78
+ return null;
79
+ const openRe = /<([a-z][\w-]*)\b([^>]*)>/gi;
80
+ let match;
81
+ while ((match = openRe.exec(html)) !== null) {
82
+ const tagName = match[1].toLowerCase(), attrs = match[2] || "";
83
+ if (attrs.trimEnd().endsWith("/") || VOID_ELEMENTS.has(tagName))
84
+ continue;
85
+ if (parsed.tag && parsed.tag !== tagName)
86
+ continue;
87
+ if (!attributesMatch(attrs, parsed))
88
+ continue;
89
+ const openTag = match[0], contentStart = match.index + openTag.length, pairRe = new RegExp(`<(/?)${tagName}\\b([^>]*)>`, "gi");
90
+ pairRe.lastIndex = contentStart;
91
+ let depth = 1, pair;
92
+ while ((pair = pairRe.exec(html)) !== null) {
93
+ const isClose = pair[1] === "/";
94
+ if (!isClose && (pair[2] || "").trimEnd().endsWith("/"))
95
+ continue;
96
+ depth += isClose ? -1 : 1;
97
+ if (depth === 0)
98
+ return {
99
+ openTag,
100
+ openIndex: match.index,
101
+ start: contentStart,
102
+ end: pair.index,
103
+ tagName
104
+ };
105
+ }
106
+ return null;
107
+ }
108
+ return null;
109
+ }
@@ -1,3 +1,35 @@
1
+ /**
2
+ * Ship the framework's own browser-only composables to the client
3
+ * (stacksjs/stx#1805).
4
+ *
5
+ * `src/composables/` exports 149 functions; 120 of them have no counterpart in
6
+ * the client runtime. They are exported from the package, they type-check, they
7
+ * autocomplete — and they are unreachable from the only environment where their
8
+ * underlying API exists. `useClipboard`, `useGeolocation`, `useFullscreen`,
9
+ * `useBattery` and the observer family all wrap browser APIs and all threw
10
+ * `ReferenceError` when called from a `<script client>` block.
11
+ *
12
+ * There are two ways to close that gap. Hand-porting each one into the runtime's
13
+ * template literal duplicates 120 implementations that already exist and would
14
+ * drift the same way the auto-import list did (#1804). Instead this bundles the
15
+ * real modules — one source of truth — and merges them onto `window.stx`.
16
+ *
17
+ * DEMAND-DRIVEN: only composables the page actually references are emitted, so
18
+ * a page calling `useClipboard` ships a couple of KB rather than the whole tree.
19
+ *
20
+ * SKIP, NEVER CLOBBER: a name the runtime already provides wins. The runtime
21
+ * versions are the signal-based ones that #1710/#1726/#1797 unified; replacing
22
+ * them with the module versions would be a regression, not a fix.
23
+ *
24
+ * KNOWN LIMITATION, and it is why this is a bridge rather than the end state:
25
+ * these are pre-unification shapes — a getter plus `subscribe()` plus a manual
26
+ * `stop()`, not signals. So `useClipboard().copy(text)` works, `subscribe()`
27
+ * works, and imperative use is correct; but reading `.copied` in a template does
28
+ * not re-render on its own. Converting them to the Signal contract is the real
29
+ * fix and is tracked separately.
30
+ *
31
+ * @module framework-composables
32
+ */
1
33
  /** Clear the cache. Dev HMR calls this when composables change on disk. */
2
34
  export declare function clearFrameworkComposableCache(): void;
3
35
  /**
package/dist/head.d.ts CHANGED
@@ -1,3 +1,154 @@
1
+ /**
2
+ * STX Head Management
3
+ *
4
+ * Manage document head (title, meta, links, scripts) with a simple API.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { useHead, useSeoMeta, definePageMeta } from 'stx'
9
+ *
10
+ * // Full control
11
+ * useHead({
12
+ * title: 'My Page',
13
+ * meta: [{ name: 'description', content: '...' }]
14
+ * })
15
+ *
16
+ * // SEO shorthand
17
+ * useSeoMeta({
18
+ * title: 'My Page',
19
+ * description: 'Page description',
20
+ * ogImage: '/og.png'
21
+ * })
22
+ *
23
+ * // Page configuration
24
+ * definePageMeta({
25
+ * title: 'My Page',
26
+ * layout: 'default'
27
+ * })
28
+ * ```
29
+ */
30
+ export interface MetaTag {
31
+ name?: string;
32
+ property?: string;
33
+ content: string;
34
+ httpEquiv?: string;
35
+ charset?: string;
36
+ }
37
+ export interface LinkTag {
38
+ rel: string;
39
+ href: string;
40
+ type?: string;
41
+ sizes?: string;
42
+ crossorigin?: string;
43
+ as?: string;
44
+ media?: string;
45
+ }
46
+ export interface ScriptTag {
47
+ src?: string;
48
+ type?: string;
49
+ async?: boolean;
50
+ defer?: boolean;
51
+ content?: string;
52
+ id?: string;
53
+ }
54
+ export interface HeadConfig {
55
+ title?: string;
56
+ titleTemplate?: string | ((title: string) => string);
57
+ base?: {
58
+ href?: string;
59
+ target?: string;
60
+ };
61
+ meta?: MetaTag[];
62
+ link?: LinkTag[];
63
+ script?: ScriptTag[];
64
+ style?: {
65
+ content: string;
66
+ type?: string;
67
+ }[];
68
+ htmlAttrs?: Record<string, string>;
69
+ bodyAttrs?: Record<string, string>;
70
+ }
71
+ export interface SeoMeta {
72
+ title?: string;
73
+ titleTemplate?: string;
74
+ description?: string;
75
+ keywords?: string | string[];
76
+ author?: string;
77
+ robots?: string;
78
+ canonical?: string;
79
+ ogTitle?: string;
80
+ ogDescription?: string;
81
+ ogImage?: string;
82
+ ogUrl?: string;
83
+ ogType?: string;
84
+ ogSiteName?: string;
85
+ ogLocale?: string;
86
+ twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player';
87
+ twitterTitle?: string;
88
+ twitterDescription?: string;
89
+ twitterImage?: string;
90
+ twitterSite?: string;
91
+ twitterCreator?: string;
92
+ articleAuthor?: string;
93
+ articlePublishedTime?: string;
94
+ articleModifiedTime?: string;
95
+ articleSection?: string;
96
+ articleTags?: string[];
97
+ }
98
+ /**
99
+ * Layouts that exist on disk, augmented by the generated
100
+ * `<state-dir>/layout-types.d.ts` (stacksjs/stx#1879).
101
+ *
102
+ * Empty here on purpose. A project that has not generated the declaration —
103
+ * or has not added the state directory to its tsconfig — keeps `layout` typed
104
+ * as `string`, so nothing breaks; a project that has gets a type error for a
105
+ * name with no layout behind it.
106
+ */
107
+ export interface KnownLayouts {
108
+ }
109
+ /**
110
+ * A layout name: the generated union when one is available, otherwise any
111
+ * string.
112
+ *
113
+ * `keyof KnownLayouts` is `never` while the interface is empty, which is what
114
+ * makes the fallback work.
115
+ */
116
+ export type LayoutName = keyof KnownLayouts extends never ? string : keyof KnownLayouts;
117
+ export interface PageMeta {
118
+ title?: string;
119
+ description?: string;
120
+ /** Static HTTP response status for this page. */
121
+ status?: number;
122
+ /**
123
+ * Layout for this page, by name (`'app'` → `<layoutsDir>/app.stx`), or
124
+ * `false` for none — the same as `@nolayout`.
125
+ *
126
+ * Precedence: `@extends`/`@layout` in the template wins, then this, then an
127
+ * auto-discovered `_layout.stx`, then `defaultLayout`.
128
+ *
129
+ * Do NOT hand-write `<meta name="stx-layout" content="…">` instead. That tag
130
+ * is stx *output*: the router reads it to decide between a fragment swap and
131
+ * a full-document swap, and a page asserting a group it does not belong to
132
+ * mis-routes rather than mis-styles. This field is the authoring surface; the
133
+ * meta tag is derived from the layout that actually resolved (#1879).
134
+ *
135
+ * Typed as the union of layouts on disk when the generated
136
+ * `<state-dir>/layout-types.d.ts` is in scope, so a typo is a type error
137
+ * rather than an unlayouted page.
138
+ */
139
+ layout?: LayoutName | false;
140
+ middleware?: string | string[];
141
+ transition?: string | {
142
+ name: string;
143
+ mode?: string;
144
+ };
145
+ keepAlive?: boolean;
146
+ key?: string | ((route: unknown) => string);
147
+ validate?: (route: {
148
+ params: Record<string, string>;
149
+ }) => boolean | Promise<boolean>;
150
+ [key: string]: unknown;
151
+ }
1
152
  /**
2
153
  * Get the current accumulated head config.
3
154
  */
@@ -104,7 +255,10 @@ export declare function applyHead(config?: HeadConfig): void;
104
255
  * @endhead
105
256
  * ```
106
257
  */
107
- export declare function processHeadDirective(content: string): { content: string; headContent: string };
258
+ export declare function processHeadDirective(content: string): {
259
+ content: string;
260
+ headContent: string;
261
+ };
108
262
  /**
109
263
  * Process @title directive.
110
264
  *
@@ -125,116 +279,4 @@ export declare function processTitleDirective(content: string, context: Record<s
125
279
  * ```
126
280
  */
127
281
  export declare function processMetaDirective(content: string, context: Record<string, unknown>): string;
128
- /* eslint-disable prefer-const, style/max-statements-per-line, no-super-linear-backtracking, regexp/no-unused-capturing-group */
129
- /**
130
- * STX Head Management
131
- *
132
- * Manage document head (title, meta, links, scripts) with a simple API.
133
- *
134
- * @example
135
- * ```typescript
136
- * import { useHead, useSeoMeta, definePageMeta } from 'stx'
137
- *
138
- * // Full control
139
- * useHead({
140
- * title: 'My Page',
141
- * meta: [{ name: 'description', content: '...' }]
142
- * })
143
- *
144
- * // SEO shorthand
145
- * useSeoMeta({
146
- * title: 'My Page',
147
- * description: 'Page description',
148
- * ogImage: '/og.png'
149
- * })
150
- *
151
- * // Page configuration
152
- * definePageMeta({
153
- * title: 'My Page',
154
- * layout: 'default'
155
- * })
156
- * ```
157
- */
158
- // =============================================================================
159
- // Types
160
- // =============================================================================
161
- export declare interface MetaTag {
162
- name?: string
163
- property?: string
164
- content: string
165
- httpEquiv?: string
166
- charset?: string
167
- }
168
- export declare interface LinkTag {
169
- rel: string
170
- href: string
171
- type?: string
172
- sizes?: string
173
- crossorigin?: string
174
- as?: string
175
- media?: string
176
- }
177
- export declare interface ScriptTag {
178
- src?: string
179
- type?: string
180
- async?: boolean
181
- defer?: boolean
182
- content?: string
183
- id?: string
184
- }
185
- export declare interface HeadConfig {
186
- title?: string
187
- titleTemplate?: string | ((title: string) => string)
188
- base?: { href?: string; target?: string }
189
- meta?: MetaTag[]
190
- link?: LinkTag[]
191
- script?: ScriptTag[]
192
- style?: { content: string; type?: string }[]
193
- htmlAttrs?: Record<string, string>
194
- bodyAttrs?: Record<string, string>
195
- }
196
- export declare interface SeoMeta {
197
- title?: string
198
- titleTemplate?: string
199
- description?: string
200
- keywords?: string | string[]
201
- author?: string
202
- robots?: string
203
- canonical?: string
204
- ogTitle?: string
205
- ogDescription?: string
206
- ogImage?: string
207
- ogUrl?: string
208
- ogType?: string
209
- ogSiteName?: string
210
- ogLocale?: string
211
- twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player'
212
- twitterTitle?: string
213
- twitterDescription?: string
214
- twitterImage?: string
215
- twitterSite?: string
216
- twitterCreator?: string
217
- articleAuthor?: string
218
- articlePublishedTime?: string
219
- articleModifiedTime?: string
220
- articleSection?: string
221
- articleTags?: string[]
222
- }
223
- export declare interface PageMeta {
224
- title?: string
225
- description?: string
226
- status?: number
227
- layout?: string | false
228
- middleware?: string | string[]
229
- transition?: string | { name: string; mode?: string }
230
- keepAlive?: boolean
231
- key?: string | ((route: unknown) => string)
232
- validate?: (route: { params: Record<string, string> }) => boolean | Promise<boolean>
233
- [key: string]: unknown
234
- }
235
- // =============================================================================
236
- // Exports
237
- // =============================================================================
238
- export {
239
- usePageTitle as useTitle
240
- };
282
+ export { usePageTitle as useTitle };