@stacksjs/stx 0.2.119 → 0.2.121

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 (62) hide show
  1. package/dist/builtins/attrs.d.ts +16 -0
  2. package/dist/builtins/attrs.js +16 -0
  3. package/dist/builtins/safe-image.js +2 -9
  4. package/dist/builtins/stx-image.js +2 -9
  5. package/dist/builtins/stx-link.js +2 -9
  6. package/dist/cli.js +1869 -863
  7. package/dist/client-script-bundler.d.ts +9 -7
  8. package/dist/client-script-bundler.js +43 -4
  9. package/dist/client-script.d.ts +24 -0
  10. package/dist/client-script.js +59 -5
  11. package/dist/component-library.js +30 -12
  12. package/dist/component-registry.d.ts +1 -0
  13. package/dist/component-renderer.js +13 -5
  14. package/dist/composables/index.d.ts +1 -0
  15. package/dist/composables/index.js +1 -0
  16. package/dist/composables/use-scroll-lock.d.ts +13 -0
  17. package/dist/composables/use-scroll-lock.js +55 -0
  18. package/dist/composition-api.d.ts +1 -1
  19. package/dist/composition-api.js +13 -4
  20. package/dist/craft-bridge.d.ts +80 -1
  21. package/dist/craft-bridge.js +80 -1
  22. package/dist/craft-components.d.ts +77 -3
  23. package/dist/craft-components.js +138 -9
  24. package/dist/craft.js +12708 -118
  25. package/dist/dev-server/crosswind.d.ts +15 -1
  26. package/dist/dev-server/crosswind.js +12 -3
  27. package/dist/dev-server/serve-app.js +3 -3
  28. package/dist/document-shell.d.ts +17 -0
  29. package/dist/document-shell.js +72 -2
  30. package/dist/expressions.js +14 -7
  31. package/dist/head.d.ts +8 -0
  32. package/dist/head.js +11 -8
  33. package/dist/html-masking.d.ts +12 -0
  34. package/dist/html-masking.js +50 -15
  35. package/dist/index.d.ts +9 -1
  36. package/dist/index.js +9 -1
  37. package/dist/native-build.d.ts +6 -0
  38. package/dist/native-build.js +77 -60
  39. package/dist/process.js +11 -3
  40. package/dist/pwa.js +1232 -508
  41. package/dist/render.js +6 -1
  42. package/dist/runtime-globals.d.ts +7 -4
  43. package/dist/runtime-globals.js +10 -2
  44. package/dist/runtime-injection.js +15 -1
  45. package/dist/signal-processing.js +19 -28
  46. package/dist/signals-api.d.ts +2 -0
  47. package/dist/signals-api.js +9 -0
  48. package/dist/signals.js +785 -62
  49. package/dist/ssg.js +1176 -452
  50. package/dist/state-management.d.ts +1 -1
  51. package/dist/state-management.js +1 -1
  52. package/dist/strict-lint.d.ts +1 -1
  53. package/dist/strict-lint.js +1 -1
  54. package/dist/stx.d.ts +6 -0
  55. package/dist/style-scoping.js +10 -0
  56. package/dist/utils.js +5 -3
  57. package/dist/variable-extractor.js +14 -22
  58. package/dist/vue-template.js +3 -2
  59. package/dist/x-element.d.ts +2 -2
  60. package/dist/x-element.js +9 -8
  61. package/package.json +5 -5
  62. package/stx.d.ts +6 -0
@@ -225,7 +225,7 @@ export declare function transformStoreImports(code: string): string;
225
225
  * }
226
226
  * ```
227
227
  */
228
- export declare function registerStoresClient(stores: Record<string, DefinedStoreWithGettersAndActions<any, any, any>>): void;
228
+ export declare function registerStoresClient(stores: Record<string, object>): void;
229
229
  /** Store options */
230
230
  export declare interface StoreOptions<T> {
231
231
  name?: string
@@ -495,7 +495,7 @@ export function generateStoreImportRuntime() {
495
495
  resolve(store);
496
496
  }
497
497
  else if (Date.now() - start > timeout) {
498
- reject(new Error('Timeout waiting for store: ' + storeName));
498
+ reject(new Error('Timeout waiting for store: '.concat(storeName)));
499
499
  }
500
500
  else {
501
501
  requestAnimationFrame(check);
@@ -18,7 +18,7 @@ export declare interface StxStrictDiagnostic {
18
18
  }
19
19
  export declare interface StxStrictOptions {
20
20
  filePath?: string
21
- fileKind?: 'view' | 'component'
21
+ fileKind?: 'view' | 'component' | 'layout'
22
22
  rules?: Partial<Record<StxStrictRuleId, boolean>>
23
23
  }
24
24
  /**
@@ -265,7 +265,7 @@ function isRuleOn(rules, id) {
265
265
  return rules?.[id] !== !1;
266
266
  }
267
267
  export function lintStxStrict(source, options = {}) {
268
- const out = [], spans = rawTextSpans(source), filePath = (options.filePath ?? "").replace(/\\/g, "/"), fileKind = options.fileKind ?? (/\/components?\//i.test(filePath) ? "component" : "view");
268
+ const out = [], spans = rawTextSpans(source), filePath = (options.filePath ?? "").replace(/\\/g, "/"), fileKind = options.fileKind ?? (/\/components?\//i.test(filePath) ? "component" : /\/layouts?\//i.test(filePath) ? "layout" : "view");
269
269
  if (isRuleOn(options.rules, "stx/no-bare-function-ref-in-event"))
270
270
  ruleBareFunctionRef(source, spans, out);
271
271
  if (isRuleOn(options.rules, "stx/no-view-level-script-client"))
package/dist/stx.d.ts CHANGED
@@ -38,6 +38,7 @@ declare module '*.md' {
38
38
 
39
39
  interface StxSignal<T> {
40
40
  (): T
41
+ value: T
41
42
  set: (value: T) => void
42
43
  update: (fn: (current: T) => T) => void
43
44
  subscribe: (cb: (value: T) => void) => () => void
@@ -46,6 +47,7 @@ interface StxSignal<T> {
46
47
 
47
48
  interface StxDerivedSignal<T> {
48
49
  (): T
50
+ readonly value: T
49
51
  // Derived signals carry `_isDerived: true` (distinguishes them from state
50
52
  // signals, which carry `_isSignal: true`). The two branches are exposed
51
53
  // separately as `isSignal()` / `isDerived()`. Surfaced by the dual-impl
@@ -155,6 +157,9 @@ declare function useEventListener(
155
157
  _handler: (event: Event) => void,
156
158
  _options?: boolean | AddEventListenerOptions,
157
159
  ): StxCleanup
160
+ declare function useScrollLock(
161
+ _target?: HTMLElement | null | { current?: HTMLElement | null, value?: HTMLElement | null } | (() => HTMLElement | null | undefined),
162
+ ): StxSignal<boolean>
158
163
  declare function useMeta(_meta: Record<string, string>): void
159
164
  declare function useClickOutside(_target: HTMLElement | StxRef<HTMLElement | null> | string | null, _handler: (event: MouseEvent) => void): StxCleanup
160
165
  declare function useFocus(_target: HTMLElement | StxRef<HTMLElement | null> | string | null): { focused: StxSignal<boolean>, focus: () => void, blur: () => void }
@@ -496,6 +501,7 @@ interface StxRuntimeRegistry {
496
501
  useAsync: typeof useAsync
497
502
  useLocalStorage: typeof useLocalStorage
498
503
  useEventListener: typeof useEventListener
504
+ useScrollLock: typeof useScrollLock
499
505
  useWebSocket: typeof useWebSocket
500
506
  useColorMode: typeof useColorMode
501
507
  useDark: typeof useDark
@@ -1,3 +1,4 @@
1
+ import { maskAtElementPosition, matchScriptElement, matchStyleElement } from "./html-masking.js";
1
2
  import { generateComponentId, scopeCss } from "./web-components/css-scoping.js";
2
3
  const SCOPED_STYLE_ATTR = String.raw`(?<![\w-])scoped(?![\w-])`, SCOPED_STYLE_PROBE = new RegExp(`<style\\s+[^>]*${SCOPED_STYLE_ATTR}`, "i");
3
4
  export function processScopedStyles(template, filePath) {
@@ -20,7 +21,16 @@ export function processScopedStyles(template, filePath) {
20
21
  function scopeCssWithAttribute(css, scopeAttr) {
21
22
  return scopeCss(css, "__SCOPE_PLACEHOLDER__", { preserveHost: !1 }).replace(/\.(__SCOPE_PLACEHOLDER__) /g, `[${scopeAttr}] `).replace(/\.(__SCOPE_PLACEHOLDER__)/g, `[${scopeAttr}]`);
22
23
  }
24
+ const MASK_TOKEN = (index) => `\x00stx-scope-mask-${index}\x00`, MASK_TOKEN_PATTERN = /\u0000stx-scope-mask-(\d+)\u0000/g;
23
25
  function addScopeAttribute(html, scopeAttr) {
26
+ const masked = maskAtElementPosition(html, (source, index) => {
27
+ const script = matchScriptElement(source, index);
28
+ return script > index ? script : matchStyleElement(source, index);
29
+ }, (_token, index) => MASK_TOKEN(index)), injected = injectScopeAttribute(masked.output, scopeAttr);
30
+ MASK_TOKEN_PATTERN.lastIndex = 0;
31
+ return injected.replace(MASK_TOKEN_PATTERN, (_match, index) => masked.tokens[Number(index)]);
32
+ }
33
+ function injectScopeAttribute(html, scopeAttr) {
24
34
  const skipTags = new Set([
25
35
  "script",
26
36
  "style",
package/dist/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { extractAndStripCssImports, extractBridgeData, processClientScript, renderVendorStyleTags } from "./client-script.js";
3
+ import { extractAndStripCssImports, extractBridgeData, injectBrowserCoreAutoImports, processClientScript, renderVendorStyleTags } from "./client-script.js";
4
4
  import { interpolateScriptExpressions, processExpressions, unescapeHtml } from "./expressions.js";
5
5
  import { COMPONENT_SCOPE_LOCAL_GLOBALS, buildRuntimeGlobalsDestructure } from "./runtime-globals.js";
6
6
  import { transformStoreImports } from "./store-imports.js";
@@ -629,7 +629,9 @@ ${triedPaths.map((p) => ` - ${p}`).join(`
629
629
  projectRoot: process.cwd()
630
630
  });
631
631
  let bundledContent = cssExtraction.code;
632
- const vendorStyleTags = renderVendorStyleTags(cssExtraction.styles), { hasUserImports, bundleClientScript } = await import("./client-script-bundler.js");
632
+ const vendorStyleTags = renderVendorStyleTags(cssExtraction.styles);
633
+ bundledContent = injectBrowserCoreAutoImports(bundledContent).code;
634
+ const { hasUserImports, bundleClientScript } = await import("./client-script-bundler.js");
633
635
  if (hasUserImports(bundledContent))
634
636
  bundledContent = await bundleClientScript(bundledContent, componentFilePath, {
635
637
  projectRoot: process.cwd(),
@@ -639,7 +641,7 @@ ${triedPaths.map((p) => ` - ${p}`).join(`
639
641
  bundledContent = bundledContent.replace(/^\s*import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"](?:stx|@stacksjs\/stx|@stacksjs\/browser)['"]\s*;?\s*$/gm, (_match, specifiers) => rewriteStxImportSpecifiers(specifiers));
640
642
  const content = transformStoreImports(bundledContent), componentLocalNames = extractVariableNames(content), wrappedContent = `
641
643
  (function() {
642
- ${buildRuntimeGlobalsDestructure("const", [...COMPONENT_SCOPE_LOCAL_GLOBALS, ...componentLocalNames])}
644
+ ${buildRuntimeGlobalsDestructure("const", [...COMPONENT_SCOPE_LOCAL_GLOBALS, ...componentLocalNames], content)}
643
645
  const __scope = window.stx._scopes = window.stx._scopes || {};
644
646
  const __scopeVars = __scope['${scopeId}'] = __scope['${scopeId}'] || {};
645
647
  const __previousCurrentElement = window.__STX_CURRENT_ELEMENT__;
@@ -1,7 +1,7 @@
1
1
  var {require}=import.meta;import path from "node:path";
2
2
  import process from "node:process";
3
3
  import { findMatchingDelimiter } from "./parser/tokenizer.js";
4
- import { useHead as headUseHead, useSeoMeta as headUseSeoMeta, getHead as headGetHead } from "./head.js";
4
+ import { mergeHeadConfigs, seoMetaToHeadConfig } from "./head.js";
5
5
  import { getPublicEnvDefine } from "./public-env.js";
6
6
  import { safeEvaluate } from "./safe-evaluator.js";
7
7
  function extractDeclaredVariableNames(script) {
@@ -261,6 +261,13 @@ export async function extractVariables(scriptContent, context, filePath, options
261
261
  };
262
262
  for (const [key, value] of Object.entries(propsObj))
263
263
  Object.defineProperty($props, key, { value, enumerable: !0, configurable: !0, writable: !0 });
264
+ for (const shadowed of ["name", "length", "caller", "arguments"]) {
265
+ if (Object.prototype.hasOwnProperty.call(propsObj, shadowed))
266
+ continue;
267
+ try {
268
+ Object.defineProperty($props, shadowed, { value: void 0, enumerable: !1, configurable: !0, writable: !0 });
269
+ } catch {}
270
+ }
264
271
  const defineProps = (definitions) => {
265
272
  if (!definitions)
266
273
  return propsObj;
@@ -309,26 +316,11 @@ export async function extractVariables(scriptContent, context, filePath, options
309
316
  matched: []
310
317
  };
311
318
  }, useRouter = () => ({ push: (_to) => {}, replace: (_to) => {}, back: () => {}, forward: () => {}, go: (_n) => {} }), useHead = (head) => {
312
- headUseHead(head);
313
- const existing = context.__stx_runtime_head || {}, incoming = head || {};
314
- context.__stx_runtime_head = {
315
- ...existing,
316
- ...incoming.title && { title: incoming.title },
317
- meta: [...existing.meta || [], ...incoming.meta || []],
318
- link: [...existing.link || [], ...incoming.link || []],
319
- script: [...existing.script || [], ...incoming.script || []],
320
- htmlAttrs: { ...existing.htmlAttrs || {}, ...incoming.htmlAttrs || {} },
321
- bodyAttrs: { ...existing.bodyAttrs || {}, ...incoming.bodyAttrs || {} }
322
- };
319
+ const existing = context.__stx_runtime_head || {};
320
+ context.__stx_runtime_head = mergeHeadConfigs(existing, head || {});
323
321
  }, useSeoMeta = (meta) => {
324
- headUseSeoMeta(meta);
325
- const head = headGetHead();
326
- context.__stx_runtime_head = {
327
- ...context.__stx_runtime_head || {},
328
- ...head.title && { title: head.title },
329
- meta: head.meta ? [...head.meta] : []
330
- };
331
- }, ref = (val) => ({ value: val }), reactive = (obj) => obj, computed = (fn) => ({ value: typeof fn === "function" ? fn() : fn }), watch = (_source, _cb) => {}, onMounted = (_fn) => {}, onUnmounted = (_fn) => {}, nextTick = (fn) => fn ? fn() : Promise.resolve(), defineEmits = (_events) => (_event, ..._args) => {}, defineExpose = (_exposed) => {}, provide = (_key, _value) => {}, inject = (_key, defaultValue) => defaultValue, useColorMode = () => ({ value: "light" }), useDark = () => state(!1), useMediaQuery = (_query) => state(!1), usePreferredDark = () => useMediaQuery("(prefers-color-scheme: dark)"), usePreferredLight = () => useMediaQuery("(prefers-color-scheme: light)"), usePreferredReducedMotion = () => useMediaQuery("(prefers-reduced-motion: reduce)"), usePreferredContrast = () => useMediaQuery("(prefers-contrast: more)"), mockWindow = {
322
+ useHead(seoMetaToHeadConfig(meta || {}));
323
+ }, ref = (val) => ({ value: val }), reactive = (obj) => obj, computed = (fn) => ({ value: typeof fn === "function" ? fn() : fn }), watch = (_source, _cb) => {}, onMounted = (_fn) => {}, onUnmounted = (_fn) => {}, nextTick = (fn) => fn ? fn() : Promise.resolve(), defineEmits = (_events) => (_event, ..._args) => {}, defineExpose = (_exposed) => {}, provide = (_key, _value) => {}, inject = (_key, defaultValue) => defaultValue, useColorMode = () => ({ value: "light" }), useDark = () => state(!1), useMediaQuery = (_query) => state(!1), useScrollLock = () => state(!1), usePreferredDark = () => useMediaQuery("(prefers-color-scheme: dark)"), usePreferredLight = () => useMediaQuery("(prefers-color-scheme: light)"), usePreferredReducedMotion = () => useMediaQuery("(prefers-reduced-motion: reduce)"), usePreferredContrast = () => useMediaQuery("(prefers-contrast: more)"), mockWindow = {
332
324
  siteId: void 0,
333
325
  API_ENDPOINT: "",
334
326
  ANALYTICS_SITE_ID: void 0,
@@ -463,11 +455,11 @@ export async function extractVariables(scriptContent, context, filePath, options
463
455
  scriptContextKeys.push(filteredContextKeys[i]);
464
456
  scriptContextValues.push(filteredContextValues[i]);
465
457
  }
466
- const result = await Function("module", "exports", "require", "props", "$props", "defineProps", "withDefaults", "state", "derived", "effect", "batch", "onMount", "onDestroy", "definePageMeta", "useRoute", "useRouter", "useHead", "useSeoMeta", "ref", "reactive", "computed", "watch", "onMounted", "onUnmounted", "nextTick", "defineEmits", "defineExpose", "defineSlots", "provide", "inject", "useColorMode", "useDark", "useMediaQuery", "usePreferredDark", "usePreferredLight", "usePreferredReducedMotion", "usePreferredContrast", "window", "document", "console", "confirm", "alert", "fetch", "params", ...propArgNames, ...scriptContextKeys, `return (async () => {
458
+ const result = await Function("module", "exports", "require", "props", "$props", "defineProps", "withDefaults", "state", "derived", "effect", "batch", "onMount", "onDestroy", "definePageMeta", "useRoute", "useRouter", "useHead", "useSeoMeta", "ref", "reactive", "computed", "watch", "onMounted", "onUnmounted", "nextTick", "defineEmits", "defineExpose", "defineSlots", "provide", "inject", "useColorMode", "useDark", "useMediaQuery", "useScrollLock", "usePreferredDark", "usePreferredLight", "usePreferredReducedMotion", "usePreferredContrast", "window", "document", "console", "confirm", "alert", "fetch", "params", ...propArgNames, ...scriptContextKeys, `return (async () => {
467
459
  ${convertedScript}
468
460
  ${reSyncCode}
469
461
  return module.exports
470
- })()`)(module, exports, requireFn, propsObj, $props, defineProps, withDefaults, state, derived, effect, batch, onMount, onDestroy, definePageMeta, useRoute, useRouter, useHead, useSeoMeta, ref, reactive, computed, watch, onMounted, onUnmounted, nextTick, defineEmits, defineExpose, defineSlots, provide, inject, useColorMode, useDark, useMediaQuery, usePreferredDark, usePreferredLight, usePreferredReducedMotion, usePreferredContrast, windowProxy, mockDocument, mockConsole, mockWindow.confirm, mockWindow.alert, mockWindow.fetch, paramsObj, ...propArgValues, ...scriptContextValues);
462
+ })()`)(module, exports, requireFn, propsObj, $props, defineProps, withDefaults, state, derived, effect, batch, onMount, onDestroy, definePageMeta, useRoute, useRouter, useHead, useSeoMeta, ref, reactive, computed, watch, onMounted, onUnmounted, nextTick, defineEmits, defineExpose, defineSlots, provide, inject, useColorMode, useDark, useMediaQuery, useScrollLock, usePreferredDark, usePreferredLight, usePreferredReducedMotion, usePreferredContrast, windowProxy, mockDocument, mockConsole, mockWindow.confirm, mockWindow.alert, mockWindow.fetch, paramsObj, ...propArgValues, ...scriptContextValues);
471
463
  if (options.preserveExisting) {
472
464
  for (const [key, value] of Object.entries(result))
473
465
  if (!(key in context) || context[key] === void 0)
@@ -134,11 +134,12 @@ function findNextOpenTag(html, startFrom) {
134
134
  const match = tagRegex.exec(html);
135
135
  if (!match)
136
136
  return null;
137
+ const selfClosing = /\/\s*>$/.test(match[0]) || VOID_ELEMENTS.has(match[1].toLowerCase()), attrs = selfClosing ? match[2].replace(/\/\s*$/, "") : match[2];
137
138
  return {
138
139
  full: match[0],
139
140
  tag: match[1],
140
- attrs: match[2],
141
- selfClosing: match[3].includes("/") || VOID_ELEMENTS.has(match[1].toLowerCase()),
141
+ attrs,
142
+ selfClosing,
142
143
  start: match.index,
143
144
  end: match.index + match[0].length
144
145
  };
@@ -29,7 +29,7 @@
29
29
  */
30
30
  /**
31
31
  * Generate the x-element runtime script
32
- * This is injected once per page that uses x-* directives
32
+ * This is injected once per page that declares an x-data scope
33
33
  */
34
34
  export declare function generateXElementRuntime(): string;
35
35
  /**
@@ -38,6 +38,6 @@ export declare function generateXElementRuntime(): string;
38
38
  export declare function hasXElementDirectives(template: string): boolean;
39
39
  /**
40
40
  * Process x-element directives in template
41
- * Injects the runtime if x-* directives are found
41
+ * Injects one runtime if an x-data scope is found
42
42
  */
43
43
  export declare function processXElementDirectives(template: string): string;
package/dist/x-element.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export function generateXElementRuntime() {
2
- return `<script data-stx-scoped>
2
+ return `<script data-stx-scoped data-stx-x-element-runtime>
3
3
  (function() {
4
4
  'use strict';
5
5
 
@@ -286,15 +286,16 @@ else {
286
286
  </script>`;
287
287
  }
288
288
  export function hasXElementDirectives(template) {
289
- return /\bx-(data|model|text|show|init|html)\b/.test(template) || /\s:[a-z]/.test(template);
289
+ return /\bx-data(?:\s|=)/.test(template);
290
290
  }
291
291
  export function processXElementDirectives(template) {
292
- if (!hasXElementDirectives(template))
293
- return template;
294
- const runtime = generateXElementRuntime(), bodyCloseIdx = template.lastIndexOf("</body>");
292
+ const runtimePattern = /<script\b(?=[^>]*\bdata-stx-x-element-runtime\b)[^>]*>[\s\S]*?<\/script>\s*/gi, existingRuntimes = template.match(runtimePattern) || [], withoutRuntimes = existingRuntimes.length > 0 ? template.replace(runtimePattern, "") : template;
293
+ if (!hasXElementDirectives(withoutRuntimes))
294
+ return withoutRuntimes;
295
+ const runtime = existingRuntimes[0]?.trim() || generateXElementRuntime(), bodyCloseIdx = withoutRuntimes.lastIndexOf("</body>");
295
296
  if (bodyCloseIdx !== -1)
296
- return template.slice(0, bodyCloseIdx) + runtime + `
297
- ` + template.slice(bodyCloseIdx);
298
- return template + `
297
+ return withoutRuntimes.slice(0, bodyCloseIdx) + runtime + `
298
+ ` + withoutRuntimes.slice(bodyCloseIdx);
299
+ return withoutRuntimes + `
299
300
  ` + runtime;
300
301
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.119",
4
+ "version": "0.2.121",
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.12",
119
119
  "@stacksjs/clapp": "^0.2.0",
120
- "@stacksjs/desktop": "0.2.119",
121
- "@stacksjs/sanitizer": "0.2.119",
120
+ "@stacksjs/desktop": "0.2.121",
121
+ "@stacksjs/sanitizer": "0.2.121",
122
122
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.119",
123
+ "bun-plugin-stx": "0.2.121",
124
124
  "bunfig": "^0.15.15",
125
- "stx-router": "0.2.119",
125
+ "stx-router": "0.2.121",
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
@@ -38,6 +38,7 @@ declare module '*.md' {
38
38
 
39
39
  interface StxSignal<T> {
40
40
  (): T
41
+ value: T
41
42
  set: (value: T) => void
42
43
  update: (fn: (current: T) => T) => void
43
44
  subscribe: (cb: (value: T) => void) => () => void
@@ -46,6 +47,7 @@ interface StxSignal<T> {
46
47
 
47
48
  interface StxDerivedSignal<T> {
48
49
  (): T
50
+ readonly value: T
49
51
  // Derived signals carry `_isDerived: true` (distinguishes them from state
50
52
  // signals, which carry `_isSignal: true`). The two branches are exposed
51
53
  // separately as `isSignal()` / `isDerived()`. Surfaced by the dual-impl
@@ -155,6 +157,9 @@ declare function useEventListener(
155
157
  _handler: (event: Event) => void,
156
158
  _options?: boolean | AddEventListenerOptions,
157
159
  ): StxCleanup
160
+ declare function useScrollLock(
161
+ _target?: HTMLElement | null | { current?: HTMLElement | null, value?: HTMLElement | null } | (() => HTMLElement | null | undefined),
162
+ ): StxSignal<boolean>
158
163
  declare function useMeta(_meta: Record<string, string>): void
159
164
  declare function useClickOutside(_target: HTMLElement | StxRef<HTMLElement | null> | string | null, _handler: (event: MouseEvent) => void): StxCleanup
160
165
  declare function useFocus(_target: HTMLElement | StxRef<HTMLElement | null> | string | null): { focused: StxSignal<boolean>, focus: () => void, blur: () => void }
@@ -496,6 +501,7 @@ interface StxRuntimeRegistry {
496
501
  useAsync: typeof useAsync
497
502
  useLocalStorage: typeof useLocalStorage
498
503
  useEventListener: typeof useEventListener
504
+ useScrollLock: typeof useScrollLock
499
505
  useWebSocket: typeof useWebSocket
500
506
  useColorMode: typeof useColorMode
501
507
  useDark: typeof useDark