@stacksjs/stx 0.2.176 → 0.2.177

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/builtins/attrs.d.ts +19 -0
  2. package/dist/builtins/attrs.js +18 -0
  3. package/dist/builtins/safe-image.js +2 -1
  4. package/dist/builtins/stx-image.js +2 -1
  5. package/dist/builtins/stx-link.js +2 -1
  6. package/dist/cli-flags.d.ts +34 -0
  7. package/dist/cli-flags.js +20 -0
  8. package/dist/cli.js +899 -778
  9. package/dist/client-script.js +2 -1
  10. package/dist/component-registry.d.ts +9 -0
  11. package/dist/component-renderer.js +20 -1
  12. package/dist/composables/use-fetch.d.ts +6 -1
  13. package/dist/composables/use-fetch.js +40 -18
  14. package/dist/composables/use-query.d.ts +7 -2
  15. package/dist/cookie-serialize.d.ts +40 -0
  16. package/dist/cookie-serialize.js +18 -0
  17. package/dist/craft.js +500 -381
  18. package/dist/crosswind-config.d.ts +13 -0
  19. package/dist/crosswind-config.js +6 -2
  20. package/dist/dev-server/crosswind.js +2 -1
  21. package/dist/edge-runtime.d.ts +2 -9
  22. package/dist/edge-runtime.js +2 -16
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +2 -0
  25. package/dist/page-action.d.ts +10 -20
  26. package/dist/page-action.js +33 -9
  27. package/dist/production-server.js +8 -7
  28. package/dist/pwa.js +452 -333
  29. package/dist/reactive.js +2 -2
  30. package/dist/signal-processing.js +37 -6
  31. package/dist/signals.js +150 -32
  32. package/dist/ssg.js +465 -346
  33. package/dist/ssr.js +2 -16
  34. package/dist/stx-virtual-ts.d.ts +45 -1
  35. package/dist/stx-virtual-ts.js +75 -5
  36. package/dist/stx.d.ts +66 -7
  37. package/dist/template-hydrator.d.ts +1 -1
  38. package/dist/template-hydrator.js +6 -6
  39. package/dist/theme-tokens.d.ts +88 -0
  40. package/dist/theme-tokens.js +71 -0
  41. package/dist/typecheck.d.ts +6 -1
  42. package/dist/typecheck.js +7 -6
  43. package/package.json +5 -5
  44. package/stx.d.ts +66 -7
@@ -171,7 +171,8 @@ const BROWSER_CORE_IMPORTS = [
171
171
  "readableSize",
172
172
  "useActiveElement",
173
173
  "useDocumentVisibility",
174
- "useIntervalFn"
174
+ "useIntervalFn",
175
+ "useTimeoutFn"
175
176
  ];
176
177
  export function injectBrowserCoreAutoImports(code) {
177
178
  const searchable = stripCommentsAndLiterals(code), explicitlyImported = new Set;
@@ -47,6 +47,15 @@ export interface ResolvedProps {
47
47
  staticNames: Record<string, string>;
48
48
  /** Server-side dynamic bindings — e.g. :count="items.length" (evaluated at compile time) */
49
49
  serverDynamic: Record<string, unknown>;
50
+ /**
51
+ * Original attribute spelling for any `serverDynamic` key that was camelized.
52
+ *
53
+ * Same reason as {@link ResolvedProps.staticNames}: a builtin forwarding an
54
+ * unconsumed binding onto real markup must spell it the way the author wrote
55
+ * it, or `:some-attr="x"` goes out as `someAttr` and the browser sees an
56
+ * attribute it does not know.
57
+ */
58
+ serverDynamicNames?: Record<string, string>;
50
59
  /** Client-side reactive bindings — expression string preserved for signals */
51
60
  clientReactive: Record<string, string>;
52
61
  /** Event handler bindings — e.g. @click="handler()" */
@@ -1,4 +1,5 @@
1
1
  import { registry } from "./component-registry.js";
2
+ import { processConditionals } from "./conditionals.js";
2
3
  import { registerBuiltins } from "./builtins/index.js";
3
4
  import { decodeAttributeEntities, decodeStxProp, findComponentTags, parseMultilineAttributes, pascalToKebab, restoreStashedScripts, stashScriptElements } from "./component-processing.js";
4
5
  import { maskAtElementPosition, matchHtmlComment } from "./html-masking.js";
@@ -182,6 +183,7 @@ function parseComponentProps(rawProps, context, options) {
182
183
  static: {},
183
184
  staticNames: {},
184
185
  serverDynamic: {},
186
+ serverDynamicNames: {},
185
187
  clientReactive: {},
186
188
  events: {}
187
189
  }, clientSignalNames = new Set(Array.isArray(context.__stx_client_signal_names) ? context.__stx_client_signal_names : []), referencesClientSignal = (expression) => {
@@ -245,6 +247,8 @@ function parseComponentProps(rawProps, context, options) {
245
247
  }
246
248
  }
247
249
  resolved.serverDynamic[propName] = evaluated;
250
+ if (propName !== attrName.slice(1) && resolved.serverDynamicNames)
251
+ resolved.serverDynamicNames[propName] = attrName.slice(1);
248
252
  } catch {
249
253
  resolved.clientReactive[propName] = expression;
250
254
  }
@@ -294,6 +298,21 @@ function parseComponentProps(rawProps, context, options) {
294
298
  }
295
299
  return resolved;
296
300
  }
301
+ const TAG_CONDITIONAL_PAIRS = [
302
+ [/@if\s*\(/, "@endif"],
303
+ [/@unless\s*\(/, "@endunless"]
304
+ ];
305
+ function resolveTagConditionals(attributesStr, context, filePath) {
306
+ if (!attributesStr.includes("@"))
307
+ return attributesStr;
308
+ if (!TAG_CONDITIONAL_PAIRS.some(([open, close]) => open.test(attributesStr) && attributesStr.includes(close)))
309
+ return attributesStr;
310
+ try {
311
+ return processConditionals(attributesStr, context, filePath);
312
+ } catch {
313
+ return attributesStr;
314
+ }
315
+ }
297
316
  function parseAllAttributes(attributesStr) {
298
317
  const props = {};
299
318
  let pos = 0;
@@ -611,7 +630,7 @@ async function processCustomElementTags(template, context, filePath, options, de
611
630
  let result = html;
612
631
  const tags = findComponentTags(result, tagPattern, skipTags);
613
632
  for (let i = tags.length - 1;i >= 0; i--) {
614
- const tag = tags[i], componentPath = isPascalCase ? pascalToKebab(tag.tagName) : tag.tagName, rawProps = parseAllAttributes(tag.attributes), resolvedProps = parseComponentProps(rawProps, context, options), builtinDef = registry.getBuiltin(tag.tagName), overriddenByUser = builtinDef ? await userComponentFileExists(componentPath, componentsDir, context, filePath, options) : !1;
633
+ const tag = tags[i], componentPath = isPascalCase ? pascalToKebab(tag.tagName) : tag.tagName, rawProps = parseAllAttributes(resolveTagConditionals(tag.attributes, context, filePath)), resolvedProps = parseComponentProps(rawProps, context, options), builtinDef = registry.getBuiltin(tag.tagName), overriddenByUser = builtinDef ? await userComponentFileExists(componentPath, componentsDir, context, filePath, options) : !1;
615
634
  if (builtinDef && !overriddenByUser) {
616
635
  const renderCtx = {
617
636
  context,
@@ -41,14 +41,19 @@ export declare interface FetchOptions<T = unknown> extends Omit<RequestInit, 'bo
41
41
  refetchInterval?: number
42
42
  initialData?: T | null
43
43
  }
44
+ /** How one run of the request should behave. */
45
+ export declare interface RefetchOptions {
46
+ background?: boolean
47
+ }
44
48
  /** Signal-shaped fetch result. Matches the runtime's `useFetch` return. */
45
49
  export declare interface FetchRef<T> {
46
50
  data: Signal<T | null>
47
51
  loading: Signal<boolean>
52
+ isFetching: Signal<boolean>
48
53
  error: Signal<Error | null>
49
54
  status: Signal<number | null>
50
55
  statusText: Signal<string | null>
51
- refetch: () => Promise<void>
56
+ refetch: (_options?: RefetchOptions) => Promise<void>
52
57
  execute: () => Promise<void>
53
58
  abort: () => void
54
59
  }
@@ -30,7 +30,7 @@ export function useFetch(url, options = {}) {
30
30
  query,
31
31
  body,
32
32
  ...fetchOptions
33
- } = options, data = state(initialData), loading = state(!1), error = state(null), status = state(null), statusText = state(null);
33
+ } = options, data = state(initialData), loading = state(!1), isFetching = state(!1), error = state(null), status = state(null), statusText = state(null);
34
34
  let abortController = null, focusHandler = null, intervalId = null;
35
35
  const getURL = () => {
36
36
  const rawURL = typeof url === "function" ? url() : url;
@@ -45,19 +45,23 @@ export function useFetch(url, options = {}) {
45
45
  }, setCache = (value) => {
46
46
  if (cacheTime > 0)
47
47
  fetchCache.set(getCacheKey(), { data: value, timestamp: Date.now() });
48
- }, doFetch = async (attempt = 0) => {
48
+ }, doFetch = async (attempt = 0, background = !1) => {
49
49
  const cached = getFromCache();
50
50
  if (cached !== null) {
51
51
  data.set(cached);
52
52
  loading.set(!1);
53
+ isFetching.set(!1);
53
54
  error.set(null);
54
55
  return;
55
56
  }
56
57
  if (abortController)
57
58
  abortController.abort();
58
59
  abortController = new AbortController;
59
- loading.set(!0);
60
- error.set(null);
60
+ if (!background)
61
+ loading.set(!0);
62
+ isFetching.set(!0);
63
+ if (!background)
64
+ error.set(null);
61
65
  const timeoutId = setTimeout(() => abortController?.abort(), timeout);
62
66
  try {
63
67
  let requestBody;
@@ -113,30 +117,36 @@ export function useFetch(url, options = {}) {
113
117
  payload = transform(payload);
114
118
  setCache(payload);
115
119
  data.set(payload);
116
- loading.set(!1);
120
+ if (background)
121
+ error.set(null);
122
+ if (!background)
123
+ loading.set(!1);
124
+ isFetching.set(!1);
117
125
  } catch (err) {
118
126
  clearTimeout(timeoutId);
119
127
  if (err instanceof Error && err.name === "AbortError")
120
128
  return;
121
129
  if (attempt < retry) {
122
130
  await new Promise((resolve) => setTimeout(resolve, retryDelay));
123
- return doFetch(attempt + 1);
131
+ return doFetch(attempt + 1, background);
124
132
  }
125
133
  error.set(err instanceof Error ? err : Error(String(err)));
126
- loading.set(!1);
134
+ if (!background)
135
+ loading.set(!1);
136
+ isFetching.set(!1);
127
137
  }
128
138
  }, setupRefetchOnFocus = () => {
129
139
  if (!refetchOnFocus || typeof window > "u")
130
140
  return;
131
141
  focusHandler = () => {
132
142
  if (document.visibilityState === "visible")
133
- doFetch();
143
+ doFetch(0, !0);
134
144
  };
135
145
  document.addEventListener("visibilitychange", focusHandler);
136
146
  }, setupRefetchInterval = () => {
137
147
  if (!refetchInterval || typeof window > "u")
138
148
  return;
139
- intervalId = setInterval(() => void doFetch(), refetchInterval);
149
+ intervalId = setInterval(() => void doFetch(0, !0), refetchInterval);
140
150
  };
141
151
  if (immediate) {
142
152
  doFetch();
@@ -146,10 +156,11 @@ export function useFetch(url, options = {}) {
146
156
  return {
147
157
  data,
148
158
  loading,
159
+ isFetching,
149
160
  error,
150
161
  status,
151
162
  statusText,
152
- refetch: () => doFetch(),
163
+ refetch: (refetchOptions) => doFetch(0, !!refetchOptions?.background),
153
164
  execute: async () => {
154
165
  await doFetch();
155
166
  setupRefetchOnFocus();
@@ -176,7 +187,7 @@ export function useAsyncData(fetcher, options = {}) {
176
187
  initialData = null,
177
188
  key,
178
189
  cacheTime = 0
179
- } = options, data = state(initialData), loading = state(!1), error = state(null), status = state(null), statusText = state(null), getCacheKey = () => key || fetcher.toString(), getFromCache = () => {
190
+ } = options, data = state(initialData), loading = state(!1), isFetching = state(!1), error = state(null), status = state(null), statusText = state(null), getCacheKey = () => key || fetcher.toString(), getFromCache = () => {
180
191
  if (cacheTime <= 0)
181
192
  return null;
182
193
  const cached = fetchCache.get(getCacheKey());
@@ -186,26 +197,36 @@ export function useAsyncData(fetcher, options = {}) {
186
197
  }, setCache = (value) => {
187
198
  if (cacheTime > 0)
188
199
  fetchCache.set(getCacheKey(), { data: value, timestamp: Date.now() });
189
- }, doFetch = async () => {
200
+ }, doFetch = async (background = !1) => {
190
201
  const cached = getFromCache();
191
202
  if (cached !== null) {
192
203
  data.set(cached);
193
204
  loading.set(!1);
205
+ isFetching.set(!1);
194
206
  error.set(null);
195
207
  return;
196
208
  }
197
- loading.set(!0);
198
- error.set(null);
209
+ if (!background)
210
+ loading.set(!0);
211
+ isFetching.set(!0);
212
+ if (!background)
213
+ error.set(null);
199
214
  try {
200
215
  let payload = await fetcher();
201
216
  if (transform)
202
217
  payload = transform(payload);
203
218
  setCache(payload);
204
219
  data.set(payload);
205
- loading.set(!1);
220
+ if (background)
221
+ error.set(null);
222
+ if (!background)
223
+ loading.set(!1);
224
+ isFetching.set(!1);
206
225
  } catch (err) {
207
226
  error.set(err instanceof Error ? err : Error(String(err)));
208
- loading.set(!1);
227
+ if (!background)
228
+ loading.set(!1);
229
+ isFetching.set(!1);
209
230
  }
210
231
  };
211
232
  if (immediate)
@@ -213,11 +234,12 @@ export function useAsyncData(fetcher, options = {}) {
213
234
  return {
214
235
  data,
215
236
  loading,
237
+ isFetching,
216
238
  error,
217
239
  status,
218
240
  statusText,
219
- refetch: doFetch,
220
- execute: doFetch,
241
+ refetch: (refetchOptions) => doFetch(!!refetchOptions?.background),
242
+ execute: () => doFetch(),
221
243
  abort: () => {}
222
244
  };
223
245
  }
@@ -17,13 +17,18 @@ export declare interface UseQueryOptions<T = unknown> {
17
17
  onSuccess?: (data: T) => void
18
18
  onError?: (error: Error) => void
19
19
  }
20
+ /** How one run of a request should behave. */
21
+ export declare interface QueryRefetchOptions {
22
+ background?: boolean
23
+ }
20
24
  export declare interface UseQueryResult<T = unknown> {
21
25
  data: { (): T | null, set: (v: T | null) => void }
22
26
  loading: { (): boolean, set: (v: boolean) => void }
27
+ isFetching: { (): boolean, set: (v: boolean) => void }
23
28
  error: { (): string | null, set: (v: string | null) => void }
24
29
  isStale: { (): boolean, set: (v: boolean) => void }
25
- refetch: () => Promise<void>
26
- invalidate: () => Promise<void>
30
+ refetch: (options?: QueryRefetchOptions) => Promise<void>
31
+ invalidate: (options?: QueryRefetchOptions) => Promise<void>
27
32
  }
28
33
  export declare interface UseMutationOptions<T = unknown> {
29
34
  method?: 'POST' | 'PUT' | 'PATCH' | 'DELETE'
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Build one `Set-Cookie` header value.
3
+ *
4
+ * Name and value are percent-encoded, so a session token containing `;` or `=`
5
+ * cannot terminate the cookie early or invent an attribute.
6
+ *
7
+ * `maxAge` is compared against `undefined` rather than tested for truthiness:
8
+ * `Max-Age=0` is how a cookie is DELETED, and a truthiness check drops exactly
9
+ * that case — leaving the cookie in place while the caller believes it cleared
10
+ * it.
11
+ */
12
+ export declare function serializeSetCookie(name: string, value: string, options?: SetCookieOptions): string;
13
+ /**
14
+ * One `Set-Cookie` serializer.
15
+ *
16
+ * There were three, byte-identical in behaviour and separately maintained:
17
+ * `ssr.ts` (private), `edge-runtime.ts` (`createCookie`, exported) and
18
+ * `composables/use-cookie.ts` (client-side, writes `document.cookie`). Page
19
+ * actions needed a fourth (stacksjs/stx#1927), which is the point at which
20
+ * copying it again stops being cheaper than sharing it — the way a set of
21
+ * duplicated implementations goes wrong is by drifting, and an attribute that
22
+ * one of them emits and another does not is a cookie that works on one code
23
+ * path and silently does not on the next.
24
+ *
25
+ * The client-side one in `use-cookie.ts` is deliberately left alone: it runs in
26
+ * a browser against `document.cookie`, where `HttpOnly` is meaningless and the
27
+ * encoding rules differ.
28
+ *
29
+ * @module cookie-serialize
30
+ */
31
+ /** Attributes of a `Set-Cookie` header. */
32
+ export declare interface SetCookieOptions {
33
+ maxAge?: number
34
+ expires?: Date
35
+ path?: string
36
+ domain?: string
37
+ secure?: boolean
38
+ httpOnly?: boolean
39
+ sameSite?: 'Strict' | 'Lax' | 'None'
40
+ }
@@ -0,0 +1,18 @@
1
+ export function serializeSetCookie(name, value, options = {}) {
2
+ let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
3
+ if (options.maxAge !== void 0)
4
+ cookie += `; Max-Age=${options.maxAge}`;
5
+ if (options.expires)
6
+ cookie += `; Expires=${options.expires.toUTCString()}`;
7
+ if (options.path)
8
+ cookie += `; Path=${options.path}`;
9
+ if (options.domain)
10
+ cookie += `; Domain=${options.domain}`;
11
+ if (options.secure)
12
+ cookie += "; Secure";
13
+ if (options.httpOnly)
14
+ cookie += "; HttpOnly";
15
+ if (options.sameSite)
16
+ cookie += `; SameSite=${options.sameSite}`;
17
+ return cookie;
18
+ }