@marianmeres/stuic 3.126.1 → 3.128.0

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.
@@ -0,0 +1,117 @@
1
+ <script lang="ts" module>
2
+ import type { HTMLInputAttributes } from "svelte/elements";
3
+
4
+ /** Snapshot returned by `TimeTrap.check()`. */
5
+ export interface TimeTrapSnapshot {
6
+ /** Milliseconds elapsed since the timer armed. */
7
+ elapsedMs: number;
8
+ /** `true` when `elapsedMs < minMs`. */
9
+ isTooFast: boolean;
10
+ /** Epoch ms the timer armed at (client clock), or `undefined` before mount. */
11
+ startedAt: number | undefined;
12
+ }
13
+
14
+ export interface Props extends Omit<HTMLInputAttributes, "value" | "name"> {
15
+ /**
16
+ * Minimum time (ms) a genuine user is expected to need before submitting.
17
+ * Faster submissions are flagged via `isTooFast`. Default: 2000.
18
+ */
19
+ minMs?: number;
20
+ /**
21
+ * Whether the timer is armed. When false, `isTooFast` stays `false` and no
22
+ * timer runs. Default: true.
23
+ */
24
+ enabled?: boolean;
25
+ /**
26
+ * `true` until `minMs` has elapsed since mount, then `false`. Bindable and
27
+ * reactive — safe to read directly at submit time. Defaults to `true`
28
+ * (fail-safe: a submit before the timer arms counts as "too fast").
29
+ */
30
+ isTooFast?: boolean;
31
+ /**
32
+ * Milliseconds elapsed since the timer armed. Updated when the threshold
33
+ * flips and whenever `check()` runs. For an exact submit-time value call
34
+ * `check()`. Bindable. Default: 0.
35
+ */
36
+ elapsedMs?: number;
37
+ /**
38
+ * Epoch ms captured when the timer armed (client clock). Bindable. Also
39
+ * written into the rendered hidden input so it can ride along in a native
40
+ * form POST. NOTE: this is a client-side heuristic and is not tamper-proof;
41
+ * enforce real rate limiting server-side.
42
+ */
43
+ startedAt?: number;
44
+ /** Name of the rendered hidden timestamp input. Default: "_ts". */
45
+ name?: string;
46
+ id?: string;
47
+ /** Underlying hidden `<input>` ref. Bindable. */
48
+ input?: HTMLInputElement;
49
+ }
50
+ </script>
51
+
52
+ <script lang="ts">
53
+ import { getId } from "../../utils/get-id.js";
54
+
55
+ let {
56
+ minMs = 2000,
57
+ enabled = true,
58
+ isTooFast = $bindable(true),
59
+ elapsedMs = $bindable(0),
60
+ startedAt = $bindable(),
61
+ name = "_ts",
62
+ id = getId(),
63
+ input = $bindable(),
64
+ ...rest
65
+ }: Props = $props();
66
+
67
+ /**
68
+ * Recompute `elapsedMs`/`isTooFast` from the current clock, write them back to
69
+ * the bindings, and return a fresh snapshot. Call at submit time for an exact
70
+ * reading (the reactive `isTooFast` binding is already accurate to timer
71
+ * resolution, but this also refreshes `elapsedMs`).
72
+ */
73
+ export function check(): TimeTrapSnapshot {
74
+ if (startedAt != null) {
75
+ elapsedMs = Date.now() - startedAt;
76
+ isTooFast = enabled ? elapsedMs < minMs : false;
77
+ } else {
78
+ isTooFast = enabled;
79
+ }
80
+ return { elapsedMs, isTooFast, startedAt };
81
+ }
82
+
83
+ // Re-arm nonce. Writing `startedAt`/`isTooFast`/`elapsedMs` inside the arming
84
+ // $effect does NOT make the effect depend on them (only reads create deps), so
85
+ // reset() can't re-arm by mutating those. Instead it bumps this tracked nonce,
86
+ // which the $effect reads — re-running it clears the old timeout (via cleanup)
87
+ // and schedules a fresh one from the new arm time.
88
+ let armToken = $state(0);
89
+
90
+ /** Re-arm the timer (e.g. after a "send another" reset). */
91
+ export function reset(): void {
92
+ armToken++;
93
+ }
94
+
95
+ // Arm on mount (client only — avoids SSR `Date.now()` in render and any
96
+ // hydration mismatch). A single timeout flips `isTooFast` once the threshold
97
+ // passes; no polling/ticking. Re-runs (re-arms) when `armToken` (reset),
98
+ // `minMs`, or `enabled` change.
99
+ $effect(() => {
100
+ void armToken; // re-arm on reset()
101
+ if (!enabled) {
102
+ isTooFast = false;
103
+ return;
104
+ }
105
+ const armedAt = Date.now();
106
+ startedAt = armedAt;
107
+ isTooFast = true;
108
+ elapsedMs = 0;
109
+ const t = setTimeout(() => {
110
+ isTooFast = false;
111
+ elapsedMs = Date.now() - armedAt;
112
+ }, minMs);
113
+ return () => clearTimeout(t);
114
+ });
115
+ </script>
116
+
117
+ <input bind:this={input} {id} {name} type="hidden" value={startedAt ?? ""} {...rest} />
@@ -0,0 +1,52 @@
1
+ import type { HTMLInputAttributes } from "svelte/elements";
2
+ /** Snapshot returned by `TimeTrap.check()`. */
3
+ export interface TimeTrapSnapshot {
4
+ /** Milliseconds elapsed since the timer armed. */
5
+ elapsedMs: number;
6
+ /** `true` when `elapsedMs < minMs`. */
7
+ isTooFast: boolean;
8
+ /** Epoch ms the timer armed at (client clock), or `undefined` before mount. */
9
+ startedAt: number | undefined;
10
+ }
11
+ export interface Props extends Omit<HTMLInputAttributes, "value" | "name"> {
12
+ /**
13
+ * Minimum time (ms) a genuine user is expected to need before submitting.
14
+ * Faster submissions are flagged via `isTooFast`. Default: 2000.
15
+ */
16
+ minMs?: number;
17
+ /**
18
+ * Whether the timer is armed. When false, `isTooFast` stays `false` and no
19
+ * timer runs. Default: true.
20
+ */
21
+ enabled?: boolean;
22
+ /**
23
+ * `true` until `minMs` has elapsed since mount, then `false`. Bindable and
24
+ * reactive — safe to read directly at submit time. Defaults to `true`
25
+ * (fail-safe: a submit before the timer arms counts as "too fast").
26
+ */
27
+ isTooFast?: boolean;
28
+ /**
29
+ * Milliseconds elapsed since the timer armed. Updated when the threshold
30
+ * flips and whenever `check()` runs. For an exact submit-time value call
31
+ * `check()`. Bindable. Default: 0.
32
+ */
33
+ elapsedMs?: number;
34
+ /**
35
+ * Epoch ms captured when the timer armed (client clock). Bindable. Also
36
+ * written into the rendered hidden input so it can ride along in a native
37
+ * form POST. NOTE: this is a client-side heuristic and is not tamper-proof;
38
+ * enforce real rate limiting server-side.
39
+ */
40
+ startedAt?: number;
41
+ /** Name of the rendered hidden timestamp input. Default: "_ts". */
42
+ name?: string;
43
+ id?: string;
44
+ /** Underlying hidden `<input>` ref. Bindable. */
45
+ input?: HTMLInputElement;
46
+ }
47
+ declare const TimeTrap: import("svelte").Component<Props, {
48
+ check: () => TimeTrapSnapshot;
49
+ reset: () => void;
50
+ }, "input" | "isTooFast" | "elapsedMs" | "startedAt">;
51
+ type TimeTrap = ReturnType<typeof TimeTrap>;
52
+ export default TimeTrap;
@@ -1,6 +1,8 @@
1
1
  export * from "./types.js";
2
2
  export { default as FieldAssets, type Props as FieldAssetsProps, type FieldAsset, type FieldAssetUrlObj, type FieldAssetWithBlobUrl, } from "./FieldAssets.svelte";
3
3
  export { default as FieldCheckbox, type Props as FieldCheckboxProps, } from "./FieldCheckbox.svelte";
4
+ export { default as Honeypot, type Props as HoneypotProps } from "./Honeypot.svelte";
5
+ export { default as TimeTrap, type Props as TimeTrapProps, type TimeTrapSnapshot, } from "./TimeTrap.svelte";
4
6
  export { default as FieldFile, type Props as FieldFileProps } from "./FieldFile.svelte";
5
7
  export { default as FieldInput, type Props as FieldInputProps, } from "./FieldInput.svelte";
6
8
  export { default as FieldMoney, type Props as FieldMoneyProps, } from "./FieldMoney.svelte";
@@ -1,6 +1,8 @@
1
1
  export * from "./types.js";
2
2
  export { default as FieldAssets, } from "./FieldAssets.svelte";
3
3
  export { default as FieldCheckbox, } from "./FieldCheckbox.svelte";
4
+ export { default as Honeypot } from "./Honeypot.svelte";
5
+ export { default as TimeTrap, } from "./TimeTrap.svelte";
4
6
  export { default as FieldFile } from "./FieldFile.svelte";
5
7
  export { default as FieldInput, } from "./FieldInput.svelte";
6
8
  export { default as FieldMoney, } from "./FieldMoney.svelte";
@@ -396,6 +396,14 @@
396
396
  flex-shrink: 0;
397
397
  }
398
398
 
399
+ /* No-intent (neutral) dot: --_color resolves to the muted token, which is also
400
+ the soft variant's background — so the dot would be invisible. Use the muted
401
+ foreground tone instead. Placed before the solid rule so solid keeps --_text
402
+ (equal specificity, later rule wins). */
403
+ .stuic-pill:not([data-intent]) .stuic-pill-dot {
404
+ background: var(--stuic-color-muted-foreground);
405
+ }
406
+
399
407
  /* On solid variants the bg already IS the intent color — use foreground instead. */
400
408
  .stuic-pill[data-variant="solid"] .stuic-pill-dot {
401
409
  background: var(--_text);
package/dist/index.css CHANGED
@@ -71,6 +71,7 @@ In practice:
71
71
  @import "./components/LoginOrRegisterForm/index.css";
72
72
  @import "./components/Checkout/index.css";
73
73
  @import "./components/CommandMenu/index.css";
74
+ @import "./components/ContactUsForm/index.css";
74
75
  @import "./components/CronInput/index.css";
75
76
  @import "./components/DataTable/index.css";
76
77
  @import "./components/DismissibleMessage/index.css";
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ export * from "./components/Checkout/index.js";
38
38
  export * from "./components/Collapsible/index.js";
39
39
  export * from "./components/ColorScheme/index.js";
40
40
  export * from "./components/CommandMenu/index.js";
41
+ export * from "./components/ContactUsForm/index.js";
41
42
  export * from "./components/CronInput/index.js";
42
43
  export * from "./components/DataTable/index.js";
43
44
  export * from "./components/DismissibleMessage/index.js";
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ export * from "./components/Checkout/index.js";
39
39
  export * from "./components/Collapsible/index.js";
40
40
  export * from "./components/ColorScheme/index.js";
41
41
  export * from "./components/CommandMenu/index.js";
42
+ export * from "./components/ContactUsForm/index.js";
42
43
  export * from "./components/CronInput/index.js";
43
44
  export * from "./components/DataTable/index.js";
44
45
  export * from "./components/DismissibleMessage/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.126.1",
3
+ "version": "3.128.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",