@waveso/ui 0.15.0 → 0.16.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.
- package/README.md +25 -22
- package/dist/accordion.d.ts +4 -20
- package/dist/accordion.js +2 -2
- package/dist/action-bar.d.ts +1 -10
- package/dist/action-bar.js +6 -5
- package/dist/alert-dialog.d.ts +12 -52
- package/dist/alert-dialog.js +5 -5
- package/dist/alert.d.ts +13 -22
- package/dist/alert.js +13 -4
- package/dist/animate.d.ts +6 -49
- package/dist/animate.js +8 -13
- package/dist/aspect-ratio.d.ts +3 -8
- package/dist/autocomplete.d.ts +21 -88
- package/dist/autocomplete.js +3 -3
- package/dist/avatar.d.ts +7 -28
- package/dist/avatar.js +4 -4
- package/dist/badge.d.ts +3 -11
- package/dist/badge.js +6 -6
- package/dist/breadcrumb.d.ts +7 -31
- package/dist/button-group.d.ts +7 -20
- package/dist/button-group.js +5 -4
- package/dist/button.d.ts +4 -12
- package/dist/button.js +6 -6
- package/dist/card.d.ts +16 -33
- package/dist/card.js +27 -14
- package/dist/checkbox.d.ts +1 -6
- package/dist/checkbox.js +2 -2
- package/dist/collapsible.d.ts +3 -12
- package/dist/combobox.d.ts +18 -81
- package/dist/combobox.js +4 -4
- package/dist/context-menu.d.ts +17 -68
- package/dist/context-menu.js +14 -14
- package/dist/count.d.ts +1 -15
- package/dist/count.js +3 -2
- package/dist/dialog.d.ts +10 -41
- package/dist/dialog.js +5 -5
- package/dist/direction.d.ts +0 -1
- package/dist/drawer.d.ts +11 -44
- package/dist/drawer.js +1 -1
- package/dist/encrypted-text.d.ts +15 -14
- package/dist/encrypted-text.js +205 -66
- package/dist/field.d.ts +27 -30
- package/dist/field.js +36 -8
- package/dist/fieldset.d.ts +14 -0
- package/dist/fieldset.js +26 -0
- package/dist/film-grain-shader.d.ts +3 -4
- package/dist/film-grain-webgl.d.ts +2 -9
- package/dist/film-grain-webgl.js +4 -1
- package/dist/film-grain.d.ts +1 -11
- package/dist/form.d.ts +23 -42
- package/dist/form.js +7 -4
- package/dist/glyph-matrix.d.ts +25 -0
- package/dist/glyph-matrix.js +266 -0
- package/dist/gradient-reveal-text.d.ts +4 -17
- package/dist/gradient-reveal-text.js +8 -8
- package/dist/hooks/use-mobile.d.ts +2 -3
- package/dist/infinite-scroll.d.ts +1 -16
- package/dist/input-group.d.ts +11 -37
- package/dist/input-group.js +1 -1
- package/dist/input-otp.d.ts +4 -20
- package/dist/input-otp.js +2 -2
- package/dist/input.d.ts +3 -11
- package/dist/input.js +1 -1
- package/dist/item.d.ts +14 -51
- package/dist/item.js +17 -11
- package/dist/kbd.d.ts +3 -11
- package/dist/kbd.js +2 -2
- package/dist/label.d.ts +1 -6
- package/dist/label.js +1 -1
- package/dist/lib/focus.d.ts +3 -5
- package/dist/lib/internal-icons.d.ts +19 -22
- package/dist/lib/merge-refs.d.ts +9 -0
- package/dist/lib/merge-refs.js +16 -0
- package/dist/lib/utils.d.ts +3 -5
- package/dist/lib/utils.js +9 -1
- package/dist/masonry.d.ts +2 -16
- package/dist/masonry.js +5 -3
- package/dist/menu.d.ts +17 -65
- package/dist/menu.js +7 -7
- package/dist/menubar.d.ts +16 -55
- package/dist/menubar.js +1 -1
- package/dist/pagination.d.ts +9 -33
- package/dist/pagination.js +1 -1
- package/dist/popover.d.ts +9 -43
- package/dist/popover.js +1 -1
- package/dist/preview-card.d.ts +5 -25
- package/dist/preview-card.js +1 -1
- package/dist/progress.d.ts +5 -24
- package/dist/progress.js +2 -2
- package/dist/radio-group.d.ts +2 -9
- package/dist/radio-group.js +1 -1
- package/dist/radio.d.ts +2 -10
- package/dist/radio.js +2 -2
- package/dist/resizable.d.ts +43 -0
- package/dist/resizable.js +53 -0
- package/dist/scroll-area.d.ts +14 -13
- package/dist/scroll-area.js +26 -7
- package/dist/select.d.ts +15 -64
- package/dist/select.js +4 -4
- package/dist/separator.d.ts +1 -7
- package/dist/separator.js +1 -1
- package/dist/sidebar.d.ts +29 -126
- package/dist/sidebar.js +14 -14
- package/dist/skeleton.d.ts +1 -6
- package/dist/slider.d.ts +1 -14
- package/dist/slider.js +5 -5
- package/dist/spinner.d.ts +8 -6
- package/dist/spinner.js +24 -6
- package/dist/styles.css +575 -182
- package/dist/switch.d.ts +2 -9
- package/dist/switch.js +6 -6
- package/dist/table.d.ts +14 -34
- package/dist/table.js +40 -9
- package/dist/tabs.d.ts +5 -22
- package/dist/tabs.js +3 -3
- package/dist/textarea.d.ts +3 -12
- package/dist/textarea.js +1 -1
- package/dist/toast.d.ts +13 -39
- package/dist/toast.js +4 -4
- package/dist/toggle-group.d.ts +3 -19
- package/dist/toggle-group.js +16 -10
- package/dist/toggle.d.ts +3 -11
- package/dist/toggle.js +11 -6
- package/dist/tooltip.d.ts +6 -24
- package/dist/tooltip.js +10 -6
- package/dist/typewriter.d.ts +1 -14
- package/dist/video-text.d.ts +25 -0
- package/dist/video-text.js +147 -0
- package/package.json +57 -51
package/dist/drawer.js
CHANGED
|
@@ -46,7 +46,7 @@ function DrawerViewport({ className, ...props }) {
|
|
|
46
46
|
function DrawerContent({ className, children, showCloseButton = false, ...props }) {
|
|
47
47
|
return /* @__PURE__ */ jsxs(DrawerPortal, { children: [/* @__PURE__ */ jsx(DrawerOverlay, {}), /* @__PURE__ */ jsx(DrawerViewport, { children: /* @__PURE__ */ jsxs(Drawer$1.Popup, {
|
|
48
48
|
"data-slot": "drawer-content",
|
|
49
|
-
className: cn("group/drawer-content bg-foundation fixed z-50 flex flex-col overflow-y-auto border-
|
|
49
|
+
className: cn("group/drawer-content bg-foundation fixed z-50 flex flex-col overflow-y-auto border-shade text-sm shadow-lg outline-hidden", "transition-[translate] duration-(--duration-lg) ease-(--ease) data-[swiping]:duration-0", "data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:mt-24 data-[swipe-direction=down]:max-h-[80vh] data-[swipe-direction=down]:rounded-t-lg data-[swipe-direction=down]:border-t", "data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:mb-24 data-[swipe-direction=up]:max-h-[80vh] data-[swipe-direction=up]:rounded-b-lg data-[swipe-direction=up]:border-b", "data-[swipe-direction=left]:inset-y-0 data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:w-3/4 data-[swipe-direction=left]:rounded-r-lg data-[swipe-direction=left]:border-r data-[swipe-direction=left]:sm:max-w-sm", "data-[swipe-direction=right]:inset-y-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:w-3/4 data-[swipe-direction=right]:rounded-l-lg data-[swipe-direction=right]:border-l data-[swipe-direction=right]:sm:max-w-sm", "data-[swipe-direction=down]:data-[starting-style]:translate-y-full data-[swipe-direction=down]:data-[ending-style]:translate-y-full", "data-[swipe-direction=up]:data-[starting-style]:-translate-y-full data-[swipe-direction=up]:data-[ending-style]:-translate-y-full", "data-[swipe-direction=left]:data-[starting-style]:-translate-x-full data-[swipe-direction=left]:data-[ending-style]:-translate-x-full", "data-[swipe-direction=right]:data-[starting-style]:translate-x-full data-[swipe-direction=right]:data-[ending-style]:translate-x-full", className),
|
|
50
50
|
...props,
|
|
51
51
|
children: [
|
|
52
52
|
/* @__PURE__ */ jsx("div", {
|
package/dist/encrypted-text.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
1
|
import * as React from "react";
|
|
3
|
-
|
|
4
2
|
//#region src/encrypted-text.d.ts
|
|
5
3
|
type EncryptedTextProps = React.ComponentProps<"span"> & {
|
|
6
4
|
text: string;
|
|
@@ -11,18 +9,21 @@ type EncryptedTextProps = React.ComponentProps<"span"> & {
|
|
|
11
9
|
revealedClassName?: string;
|
|
12
10
|
scrambleOnly?: boolean;
|
|
13
11
|
scrambleOneChar?: boolean;
|
|
12
|
+
/** Where the reveal begins. Default: "start" */
|
|
13
|
+
revealDirection?: "start" | "end" | "center" | "random";
|
|
14
|
+
/** Scramble and reveal again when hovered. Default: false */
|
|
15
|
+
scrambleOnHover?: boolean;
|
|
16
|
+
/** Scramble and reveal again after every reveal. Default: false */
|
|
17
|
+
loop?: boolean;
|
|
18
|
+
/** Pause between a reveal and the next loop, in ms. Default: 2000 */
|
|
19
|
+
loopDelayMs?: number;
|
|
14
20
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
revealedClassName,
|
|
23
|
-
scrambleOnly,
|
|
24
|
-
scrambleOneChar,
|
|
25
|
-
...props
|
|
26
|
-
}: EncryptedTextProps): _$react_jsx_runtime0.JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* Text that starts scrambled and reveals itself, character by character, once
|
|
23
|
+
* it scrolls into view. Each character keeps its real width while scrambled
|
|
24
|
+
* (the stand-in glyph sits over an invisible copy and narrows to fit), so the
|
|
25
|
+
* text never shifts what's around it. Screen readers get the real text.
|
|
26
|
+
*/
|
|
27
|
+
declare function EncryptedText({ text, className, revealDelayMs, charset, flipDelayMs, encryptedClassName, revealedClassName, scrambleOnly, scrambleOneChar, revealDirection, scrambleOnHover, loop, loopDelayMs, onPointerEnter, ...props }: EncryptedTextProps): React.JSX.Element | null;
|
|
27
28
|
//#endregion
|
|
28
29
|
export { EncryptedText, type EncryptedTextProps };
|
package/dist/encrypted-text.js
CHANGED
|
@@ -1,29 +1,95 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "./lib/utils.js";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import * as React from "react";
|
|
5
|
-
import { useReducedMotion } from "motion/react";
|
|
6
5
|
//#region src/encrypted-text.tsx
|
|
7
6
|
const DEFAULT_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-={}[];:,.<>/?";
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const REDUCED_MOTION = "(prefers-reduced-motion: reduce)";
|
|
8
|
+
/** Opacity of a scrambled character far from its turn. */
|
|
9
|
+
const ENCRYPTED_OPACITY = .6;
|
|
10
|
+
/** Characters ahead of the reveal that brighten toward full opacity. */
|
|
11
|
+
const LEAD = 4;
|
|
12
|
+
function isSpace(char) {
|
|
13
|
+
return /\s/.test(char);
|
|
10
14
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
let result = "";
|
|
14
|
-
for (let i = 0; i < original.length; i += 1) result += original[i] === " " ? " " : randomChar(charset);
|
|
15
|
-
return result;
|
|
15
|
+
function randomGlyph(pool) {
|
|
16
|
+
return pool[Math.floor(Math.random() * pool.length)] ?? "";
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A scramble that's the same on the server and in the browser, so the text
|
|
20
|
+
* can start encrypted without a hydration mismatch.
|
|
21
|
+
*/
|
|
22
|
+
function initialFrame(chars, pool) {
|
|
23
|
+
let rank = 0;
|
|
24
|
+
return {
|
|
25
|
+
glyphs: chars.map((char, index) => isSpace(char) ? char : pool[((char.codePointAt(0) ?? 0) + index * 7) % pool.length] ?? char),
|
|
26
|
+
scales: chars.map(() => 1),
|
|
27
|
+
ranks: chars.map((char) => isSpace(char) ? -1 : rank++),
|
|
28
|
+
revealed: 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Each character's place in the reveal order for a direction; -1 for spaces. */
|
|
32
|
+
function rankLetters(chars, direction) {
|
|
33
|
+
const order = chars.flatMap((char, index) => isSpace(char) ? [] : [index]);
|
|
34
|
+
if (direction === "end") order.reverse();
|
|
35
|
+
if (direction === "center") {
|
|
36
|
+
const middle = (chars.length - 1) / 2;
|
|
37
|
+
order.sort((a, b) => Math.abs(a - middle) - Math.abs(b - middle));
|
|
38
|
+
}
|
|
39
|
+
if (direction === "random") for (let i = order.length - 1; i > 0; i--) {
|
|
40
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
41
|
+
[order[i], order[j]] = [order[j] ?? 0, order[i] ?? 0];
|
|
42
|
+
}
|
|
43
|
+
const ranks = chars.map(() => -1);
|
|
44
|
+
order.forEach((index, rank) => {
|
|
45
|
+
ranks[index] = rank;
|
|
46
|
+
});
|
|
47
|
+
return ranks;
|
|
48
|
+
}
|
|
49
|
+
const glyphWidths = /* @__PURE__ */ new Map();
|
|
50
|
+
let measurer;
|
|
51
|
+
/** Measures a glyph in a font, caching it once the font has loaded. */
|
|
52
|
+
function measureGlyph(font, glyph) {
|
|
53
|
+
const key = `${font}|${glyph}`;
|
|
54
|
+
const cached = glyphWidths.get(key);
|
|
55
|
+
if (cached !== void 0) return cached;
|
|
56
|
+
measurer ??= document.createElement("canvas").getContext("2d");
|
|
57
|
+
if (!measurer) return 0;
|
|
58
|
+
measurer.font = font;
|
|
59
|
+
const width = measurer.measureText(glyph).width;
|
|
60
|
+
if (document.fonts.check(font)) glyphWidths.set(key, width);
|
|
61
|
+
return width;
|
|
62
|
+
}
|
|
63
|
+
/** How much to narrow a stand-in glyph so it fits the real one's width. */
|
|
64
|
+
function squeeze(font, char, glyph) {
|
|
65
|
+
const room = measureGlyph(font, char);
|
|
66
|
+
const need = measureGlyph(font, glyph);
|
|
67
|
+
return need > room && need > 0 ? Math.max(.05, room / need) : 1;
|
|
68
|
+
}
|
|
69
|
+
function subscribeReducedMotion(onChange) {
|
|
70
|
+
const query = window.matchMedia(REDUCED_MOTION);
|
|
71
|
+
query.addEventListener("change", onChange);
|
|
72
|
+
return () => query.removeEventListener("change", onChange);
|
|
73
|
+
}
|
|
74
|
+
const getReducedMotion = () => window.matchMedia(REDUCED_MOTION).matches;
|
|
75
|
+
const getServerReducedMotion = () => false;
|
|
76
|
+
/**
|
|
77
|
+
* Text that starts scrambled and reveals itself, character by character, once
|
|
78
|
+
* it scrolls into view. Each character keeps its real width while scrambled
|
|
79
|
+
* (the stand-in glyph sits over an invisible copy and narrows to fit), so the
|
|
80
|
+
* text never shifts what's around it. Screen readers get the real text.
|
|
81
|
+
*/
|
|
82
|
+
function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_CHARSET, flipDelayMs = 50, encryptedClassName, revealedClassName, scrambleOnly = false, scrambleOneChar = false, revealDirection = "start", scrambleOnHover = false, loop = false, loopDelayMs = 2e3, onPointerEnter, ...props }) {
|
|
83
|
+
const prefersReducedMotion = React.useSyncExternalStore(subscribeReducedMotion, getReducedMotion, getServerReducedMotion);
|
|
19
84
|
const ref = React.useRef(null);
|
|
85
|
+
const restartRef = React.useRef(null);
|
|
86
|
+
const isRunningRef = React.useRef(false);
|
|
20
87
|
const [isInView, setIsInView] = React.useState(false);
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const scrambleCharsRef = React.useRef([]);
|
|
88
|
+
const chars = React.useMemo(() => Array.from(text), [text]);
|
|
89
|
+
const pool = React.useMemo(() => Array.from(charset.length > 0 ? charset : DEFAULT_CHARSET), [charset]);
|
|
90
|
+
const initial = React.useMemo(() => initialFrame(chars, pool), [chars, pool]);
|
|
91
|
+
const [live, setLive] = React.useState();
|
|
92
|
+
const frame = live?.chars === chars ? live.frame : initial;
|
|
27
93
|
React.useEffect(() => {
|
|
28
94
|
if (prefersReducedMotion) return;
|
|
29
95
|
const el = ref.current;
|
|
@@ -38,78 +104,151 @@ function EncryptedText({ text, className, revealDelayMs = 50, charset = DEFAULT_
|
|
|
38
104
|
return () => observer.disconnect();
|
|
39
105
|
}, [prefersReducedMotion]);
|
|
40
106
|
React.useEffect(() => {
|
|
41
|
-
|
|
42
|
-
if (!isInView) return;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
107
|
+
const root = ref.current;
|
|
108
|
+
if (prefersReducedMotion || !isInView || !root) return;
|
|
109
|
+
const computed = getComputedStyle(root);
|
|
110
|
+
const font = `${computed.fontStyle} ${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;
|
|
111
|
+
const letters = chars.filter((char) => !isSpace(char)).length;
|
|
112
|
+
let glyphs = [];
|
|
113
|
+
let scales = [];
|
|
114
|
+
let ranks = [];
|
|
115
|
+
let revealed = 0;
|
|
116
|
+
let start = 0;
|
|
117
|
+
let lastFlip = 0;
|
|
118
|
+
let frameId = 0;
|
|
119
|
+
let timer;
|
|
120
|
+
const scramble = (index) => {
|
|
121
|
+
const glyph = randomGlyph(pool);
|
|
122
|
+
glyphs[index] = glyph;
|
|
123
|
+
scales[index] = squeeze(font, chars[index] ?? glyph, glyph);
|
|
124
|
+
};
|
|
125
|
+
const publish = () => {
|
|
126
|
+
setLive({
|
|
127
|
+
chars,
|
|
128
|
+
frame: {
|
|
129
|
+
glyphs: [...glyphs],
|
|
130
|
+
scales: [...scales],
|
|
131
|
+
ranks,
|
|
132
|
+
revealed
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
48
136
|
const update = (now) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
} else for (let index = 0; index < totalLength; index += 1) scrambleCharsRef.current[index] = text[index] === " " ? " " : randomChar(charset);
|
|
61
|
-
lastFlipTimeRef.current = now;
|
|
62
|
-
setFlipTick((t) => t + 1 & 65535);
|
|
137
|
+
let isChanged = false;
|
|
138
|
+
if (!scrambleOnly) {
|
|
139
|
+
const next = Math.min(letters, Math.floor((now - start) / Math.max(1, revealDelayMs)));
|
|
140
|
+
isChanged = next !== revealed;
|
|
141
|
+
revealed = next;
|
|
142
|
+
if (revealed >= letters) {
|
|
143
|
+
publish();
|
|
144
|
+
isRunningRef.current = false;
|
|
145
|
+
if (loop) timer = setTimeout(run, Math.max(0, loopDelayMs));
|
|
146
|
+
return;
|
|
63
147
|
}
|
|
64
|
-
animationFrameRef.current = requestAnimationFrame(update);
|
|
65
|
-
return;
|
|
66
148
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
149
|
+
if (now - lastFlip >= Math.max(0, flipDelayMs)) {
|
|
150
|
+
lastFlip = now;
|
|
151
|
+
isChanged = true;
|
|
152
|
+
if (scrambleOnly && scrambleOneChar) {
|
|
153
|
+
const scrambled = chars.flatMap((char, index) => isSpace(char) ? [] : [index]);
|
|
154
|
+
const index = scrambled[Math.floor(Math.random() * scrambled.length)];
|
|
155
|
+
if (index !== void 0) scramble(index);
|
|
156
|
+
} else chars.forEach((char, index) => {
|
|
157
|
+
if (!isSpace(char) && (ranks[index] ?? 0) >= revealed) scramble(index);
|
|
158
|
+
});
|
|
75
159
|
}
|
|
76
|
-
|
|
160
|
+
if (isChanged) publish();
|
|
161
|
+
frameId = requestAnimationFrame(update);
|
|
162
|
+
};
|
|
163
|
+
const run = () => {
|
|
164
|
+
cancelAnimationFrame(frameId);
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
isRunningRef.current = true;
|
|
167
|
+
ranks = rankLetters(chars, revealDirection);
|
|
168
|
+
glyphs = [...chars];
|
|
169
|
+
scales = chars.map(() => 1);
|
|
170
|
+
chars.forEach((char, index) => {
|
|
171
|
+
if (!isSpace(char)) scramble(index);
|
|
172
|
+
});
|
|
173
|
+
revealed = 0;
|
|
174
|
+
start = performance.now();
|
|
175
|
+
lastFlip = start;
|
|
176
|
+
publish();
|
|
177
|
+
frameId = requestAnimationFrame(update);
|
|
77
178
|
};
|
|
78
|
-
|
|
179
|
+
restartRef.current = run;
|
|
180
|
+
run();
|
|
79
181
|
return () => {
|
|
80
|
-
|
|
81
|
-
|
|
182
|
+
cancelAnimationFrame(frameId);
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
isRunningRef.current = false;
|
|
185
|
+
restartRef.current = null;
|
|
82
186
|
};
|
|
83
187
|
}, [
|
|
84
188
|
prefersReducedMotion,
|
|
85
189
|
isInView,
|
|
86
|
-
|
|
190
|
+
chars,
|
|
191
|
+
pool,
|
|
87
192
|
revealDelayMs,
|
|
88
|
-
charset,
|
|
89
193
|
flipDelayMs,
|
|
90
194
|
scrambleOnly,
|
|
91
|
-
scrambleOneChar
|
|
195
|
+
scrambleOneChar,
|
|
196
|
+
revealDirection,
|
|
197
|
+
loop,
|
|
198
|
+
loopDelayMs
|
|
92
199
|
]);
|
|
200
|
+
const handlePointerEnter = (event) => {
|
|
201
|
+
onPointerEnter?.(event);
|
|
202
|
+
if (scrambleOnHover && !scrambleOnly && !isRunningRef.current) restartRef.current?.();
|
|
203
|
+
};
|
|
93
204
|
if (!text) return null;
|
|
205
|
+
const words = [];
|
|
206
|
+
chars.forEach((char, index) => {
|
|
207
|
+
const word = words.at(-1);
|
|
208
|
+
if (isSpace(char) || !word || isSpace(chars[index - 1] ?? " ")) words.push({
|
|
209
|
+
start: index,
|
|
210
|
+
letters: [char]
|
|
211
|
+
});
|
|
212
|
+
else word.letters.push(char);
|
|
213
|
+
});
|
|
94
214
|
return /* @__PURE__ */ jsxs("span", {
|
|
95
215
|
ref,
|
|
96
216
|
"data-slot": "encrypted-text",
|
|
97
217
|
className,
|
|
218
|
+
onPointerEnter: handlePointerEnter,
|
|
98
219
|
...props,
|
|
99
220
|
children: [/* @__PURE__ */ jsx("span", {
|
|
100
221
|
className: "sr-only",
|
|
101
222
|
children: text
|
|
102
|
-
}),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
223
|
+
}), words.map((word) => isSpace(word.letters[0] ?? "") ? /* @__PURE__ */ jsx(React.Fragment, { children: word.letters[0] }, word.start) : /* @__PURE__ */ jsx("span", {
|
|
224
|
+
"aria-hidden": "true",
|
|
225
|
+
"data-slot": "encrypted-text-word",
|
|
226
|
+
className: "whitespace-nowrap",
|
|
227
|
+
children: word.letters.map((char, offset) => {
|
|
228
|
+
const index = word.start + offset;
|
|
229
|
+
const rank = frame.ranks[index] ?? 0;
|
|
230
|
+
const isRevealed = prefersReducedMotion || !scrambleOnly && rank < frame.revealed;
|
|
231
|
+
const distance = rank - frame.revealed;
|
|
232
|
+
const lift = Math.max(0, 1 - distance / LEAD);
|
|
233
|
+
const scale = frame.scales[index] ?? 1;
|
|
234
|
+
return /* @__PURE__ */ jsx("span", {
|
|
235
|
+
"data-slot": "encrypted-text-char",
|
|
236
|
+
"data-revealed": isRevealed || void 0,
|
|
237
|
+
className: cn("motion-color relative inline-block", isRevealed ? revealedClassName : encryptedClassName),
|
|
238
|
+
style: isRevealed || scrambleOnly ? void 0 : { opacity: ENCRYPTED_OPACITY + .4 * lift },
|
|
239
|
+
children: isRevealed ? char : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
240
|
+
"data-slot": "encrypted-text-ghost",
|
|
241
|
+
className: "invisible",
|
|
242
|
+
children: char
|
|
243
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
244
|
+
"data-slot": "encrypted-text-glyph",
|
|
245
|
+
className: "absolute top-0 left-1/2 -translate-x-1/2",
|
|
246
|
+
style: scale < 1 ? { scale: `${scale} 1` } : void 0,
|
|
247
|
+
children: frame.glyphs[index] ?? char
|
|
248
|
+
})] })
|
|
249
|
+
}, index);
|
|
250
|
+
})
|
|
251
|
+
}, word.start))]
|
|
113
252
|
});
|
|
114
253
|
}
|
|
115
254
|
//#endregion
|
package/dist/field.d.ts
CHANGED
|
@@ -1,40 +1,37 @@
|
|
|
1
|
-
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
1
|
import * as React from "react";
|
|
2
|
+
import { VariantProps } from "class-variance-authority";
|
|
3
3
|
import { Field as FieldPrimitive } from "@base-ui/react/field";
|
|
4
|
-
import * as _$_base_ui_react0 from "@base-ui/react";
|
|
5
|
-
|
|
6
4
|
//#region src/field.d.ts
|
|
7
|
-
|
|
5
|
+
declare const fieldVariants: (props?: ({
|
|
6
|
+
orientation?: "horizontal" | "vertical" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
type FieldProps = React.ComponentProps<typeof FieldPrimitive.Root> & VariantProps<typeof fieldVariants>;
|
|
8
9
|
type FieldLabelProps = React.ComponentProps<typeof FieldPrimitive.Label>;
|
|
10
|
+
type FieldContentProps = React.ComponentProps<"div">;
|
|
9
11
|
type FieldDescriptionProps = React.ComponentProps<typeof FieldPrimitive.Description>;
|
|
10
12
|
type FieldItemProps = React.ComponentProps<typeof FieldPrimitive.Item>;
|
|
11
13
|
type FieldErrorProps = React.ComponentProps<typeof FieldPrimitive.Error>;
|
|
12
14
|
type FieldValidityProps = React.ComponentProps<typeof FieldPrimitive.Validity>;
|
|
13
15
|
type FieldControlProps = React.ComponentProps<typeof FieldPrimitive.Control>;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}:
|
|
22
|
-
declare function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}:
|
|
30
|
-
declare function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
declare function FieldError({
|
|
35
|
-
className,
|
|
36
|
-
...props
|
|
37
|
-
}: FieldErrorProps): _$react_jsx_runtime0.JSX.Element;
|
|
38
|
-
declare const FieldValidity: React.FC<_$_base_ui_react0.FieldValidityProps>;
|
|
16
|
+
/**
|
|
17
|
+
* A labelled control with its messages. Below the control, `FieldError`
|
|
18
|
+
* comes first and `FieldDescription` under it, so the hint stays visible
|
|
19
|
+
* while the error is showing. An invalid field doesn't tint its text: the
|
|
20
|
+
* control's danger ring and the error carry the state, and the label
|
|
21
|
+
* stays neutral.
|
|
22
|
+
*/
|
|
23
|
+
declare function Field({ className, orientation, ...props }: FieldProps): React.JSX.Element;
|
|
24
|
+
declare function FieldLabel({ className, ...props }: FieldLabelProps): React.JSX.Element;
|
|
25
|
+
declare function FieldControl({ className, ...props }: FieldControlProps): React.JSX.Element;
|
|
26
|
+
/**
|
|
27
|
+
* A label and its description stacked together, beside a control. The label
|
|
28
|
+
* row is as tall as the default checkbox, radio and switch (24px), so its
|
|
29
|
+
* text centers on the control.
|
|
30
|
+
*/
|
|
31
|
+
declare function FieldContent({ className, ...props }: FieldContentProps): React.JSX.Element;
|
|
32
|
+
declare function FieldDescription({ className, ...props }: FieldDescriptionProps): React.JSX.Element;
|
|
33
|
+
declare function FieldItem({ className, ...props }: FieldItemProps): React.JSX.Element;
|
|
34
|
+
declare function FieldError({ className, ...props }: FieldErrorProps): React.JSX.Element;
|
|
35
|
+
declare const FieldValidity: React.FC<import("@base-ui/react").FieldValidityProps>;
|
|
39
36
|
//#endregion
|
|
40
|
-
export { Field, FieldControl, type FieldControlProps, FieldDescription, type FieldDescriptionProps, FieldError, type FieldErrorProps, FieldItem, type FieldItemProps, FieldLabel, type FieldLabelProps, FieldPrimitive, type FieldProps, FieldValidity, type FieldValidityProps };
|
|
37
|
+
export { Field, FieldContent, type FieldContentProps, FieldControl, type FieldControlProps, FieldDescription, type FieldDescriptionProps, FieldError, type FieldErrorProps, FieldItem, type FieldItemProps, FieldLabel, type FieldLabelProps, FieldPrimitive, type FieldProps, FieldValidity, type FieldValidityProps, fieldVariants };
|
package/dist/field.js
CHANGED
|
@@ -1,50 +1,78 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "./lib/utils.js";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
4
5
|
import { Field as FieldPrimitive } from "@base-ui/react/field";
|
|
5
6
|
//#region src/field.tsx
|
|
6
|
-
|
|
7
|
+
const fieldVariants = cva("flex w-full gap-3", {
|
|
8
|
+
variants: { orientation: {
|
|
9
|
+
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
|
|
10
|
+
horizontal: "flex flex-row items-center *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
|
|
11
|
+
} },
|
|
12
|
+
defaultVariants: { orientation: "vertical" }
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* A labelled control with its messages. Below the control, `FieldError`
|
|
16
|
+
* comes first and `FieldDescription` under it, so the hint stays visible
|
|
17
|
+
* while the error is showing. An invalid field doesn't tint its text: the
|
|
18
|
+
* control's danger ring and the error carry the state, and the label
|
|
19
|
+
* stays neutral.
|
|
20
|
+
*/
|
|
21
|
+
function Field({ className, orientation, ...props }) {
|
|
7
22
|
return /* @__PURE__ */ jsx(FieldPrimitive.Root, {
|
|
8
23
|
"data-slot": "field",
|
|
9
|
-
|
|
24
|
+
"data-orientation": orientation ?? "vertical",
|
|
25
|
+
className: cn(fieldVariants({ orientation }), className),
|
|
10
26
|
...props
|
|
11
27
|
});
|
|
12
28
|
}
|
|
13
29
|
function FieldLabel({ className, ...props }) {
|
|
14
30
|
return /* @__PURE__ */ jsx(FieldPrimitive.Label, {
|
|
15
31
|
"data-slot": "field-label",
|
|
16
|
-
className: cn("text-sm font-medium
|
|
32
|
+
className: cn("flex w-fit items-center gap-2 text-sm leading-snug font-medium select-none [&[for]]:cursor-clickable data-disabled:pointer-events-none data-disabled:opacity-30", className),
|
|
17
33
|
...props
|
|
18
34
|
});
|
|
19
35
|
}
|
|
20
36
|
function FieldControl({ className, ...props }) {
|
|
21
37
|
return /* @__PURE__ */ jsx(FieldPrimitive.Control, {
|
|
22
38
|
"data-slot": "field-control",
|
|
23
|
-
className
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A label and its description stacked together, beside a control. The label
|
|
45
|
+
* row is as tall as the default checkbox, radio and switch (24px), so its
|
|
46
|
+
* text centers on the control.
|
|
47
|
+
*/
|
|
48
|
+
function FieldContent({ className, ...props }) {
|
|
49
|
+
return /* @__PURE__ */ jsx("div", {
|
|
50
|
+
"data-slot": "field-content",
|
|
51
|
+
className: cn("flex flex-1 flex-col gap-2 leading-snug [&>[data-slot=field-label]]:min-h-6", className),
|
|
24
52
|
...props
|
|
25
53
|
});
|
|
26
54
|
}
|
|
27
55
|
function FieldDescription({ className, ...props }) {
|
|
28
56
|
return /* @__PURE__ */ jsx(FieldPrimitive.Description, {
|
|
29
57
|
"data-slot": "field-description",
|
|
30
|
-
className: cn("text-muted text-
|
|
58
|
+
className: cn("text-muted text-start text-sm leading-normal font-normal", "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", "last:mt-0 nth-last-2:-mt-1 group-has-data-horizontal/field:text-balance", className),
|
|
31
59
|
...props
|
|
32
60
|
});
|
|
33
61
|
}
|
|
34
62
|
function FieldItem({ className, ...props }) {
|
|
35
63
|
return /* @__PURE__ */ jsx(FieldPrimitive.Item, {
|
|
36
64
|
"data-slot": "field-item",
|
|
37
|
-
className: cn("flex items-center gap-2", className),
|
|
65
|
+
className: cn("group/field flex items-center gap-2", className),
|
|
38
66
|
...props
|
|
39
67
|
});
|
|
40
68
|
}
|
|
41
69
|
function FieldError({ className, ...props }) {
|
|
42
70
|
return /* @__PURE__ */ jsx(FieldPrimitive.Error, {
|
|
43
71
|
"data-slot": "field-error",
|
|
44
|
-
className: cn("text-
|
|
72
|
+
className: cn("text-danger text-sm font-normal -mb-3", className),
|
|
45
73
|
...props
|
|
46
74
|
});
|
|
47
75
|
}
|
|
48
76
|
const FieldValidity = FieldPrimitive.Validity;
|
|
49
77
|
//#endregion
|
|
50
|
-
export { Field, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldPrimitive, FieldValidity };
|
|
78
|
+
export { Field, FieldContent, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldPrimitive, FieldValidity, fieldVariants };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Fieldset as Fieldset$1 } from "@base-ui/react/fieldset";
|
|
3
|
+
//#region src/fieldset.d.ts
|
|
4
|
+
type FieldsetProps = React.ComponentProps<typeof Fieldset$1.Root>;
|
|
5
|
+
type FieldsetLegendProps = React.ComponentProps<typeof Fieldset$1.Legend>;
|
|
6
|
+
/**
|
|
7
|
+
* Groups related fields under one legend (an address, a set of choices).
|
|
8
|
+
* `disabled` disables every `Field` inside it.
|
|
9
|
+
*/
|
|
10
|
+
declare function Fieldset({ className, ...props }: FieldsetProps): React.JSX.Element;
|
|
11
|
+
/** The fieldset's title, announced as its accessible name. */
|
|
12
|
+
declare function FieldsetLegend({ className, ...props }: FieldsetLegendProps): React.JSX.Element;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { Fieldset, FieldsetLegend, type FieldsetLegendProps, type FieldsetProps };
|
package/dist/fieldset.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "./lib/utils.js";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { Fieldset as Fieldset$1 } from "@base-ui/react/fieldset";
|
|
5
|
+
//#region src/fieldset.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Groups related fields under one legend (an address, a set of choices).
|
|
8
|
+
* `disabled` disables every `Field` inside it.
|
|
9
|
+
*/
|
|
10
|
+
function Fieldset({ className, ...props }) {
|
|
11
|
+
return /* @__PURE__ */ jsx(Fieldset$1.Root, {
|
|
12
|
+
"data-slot": "fieldset",
|
|
13
|
+
className: cn("flex w-full min-w-0 flex-col gap-7 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 *:data-[slot=field-group]:gap-4", className),
|
|
14
|
+
...props
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The fieldset's title, announced as its accessible name. */
|
|
18
|
+
function FieldsetLegend({ className, ...props }) {
|
|
19
|
+
return /* @__PURE__ */ jsx(Fieldset$1.Legend, {
|
|
20
|
+
"data-slot": "fieldset-legend",
|
|
21
|
+
className: cn("mb-3 text-base font-medium data-disabled:opacity-30", className),
|
|
22
|
+
...props
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { Fieldset, FieldsetLegend };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
//#region src/film-grain-shader.d.ts
|
|
2
|
-
declare const vertexShader = "\nattribute vec2 position;\nvarying vec2 vUv;\n\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n";
|
|
3
|
-
declare const fragmentShader = "\nprecision highp float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uDensity;\nuniform float uOpacity;\nuniform float uFps;\nuniform vec3 uColor;\n\nvarying vec2 vUv;\n\n// Stable hash\nfloat hash(vec2 p, float seed) {\n return fract(sin(dot(p + seed, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\n// Film response curve (soft toe + shoulder rolloff)\nfloat filmCurve(float x) {\n return smoothstep(0.0, 0.2, x) * (1.0 - smoothstep(0.8, 1.0, x));\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy;\n vec2 p = floor(uv);\n\n // Temporal coherence: blend between current and next frame seed\n float frame = floor(uTime * uFps);\n float t = fract(uTime * uFps);\n float seed = frame * 0.1731;\n\n // Multi-scale grain with inter-frame blending on fine layer\n float fineA = hash(p, seed);\n float fineB = hash(p, seed + 0.1731);\n float fine = mix(fineA, fineB, t);\n\n float medium = hash(floor(uv * 0.5), seed + 3.1);\n float coarse = hash(floor(uv * 0.25), seed + 7.93);\n\n float grain = fine * 0.65 + medium * 0.25 + coarse * 0.10;\n\n // Emulsion clumping (organic structure)\n float cluster = hash(floor(uv * 0.18), seed + 9.2);\n grain *= mix(0.75, 1.35, cluster);\n\n // Density threshold\n grain = smoothstep(1.0 - uDensity, 1.0, grain);\n\n // Film response curve (replaces raw pow gamma)\n grain = filmCurve(grain);\n\n // Micro-blur: sample neighbors for optical diffusion\n float n1 = hash(p + vec2(1.0, 0.0), seed);\n float n2 = hash(p - vec2(1.0, 0.0), seed);\n float n3 = hash(p + vec2(0.0, 1.0), seed);\n float n4 = hash(p - vec2(0.0, 1.0), seed);\n float neighbors = (n1 + n2 + n3 + n4) * 0.25;\n grain = mix(grain, neighbors, 0.15);\n\n // Chromatic aberration (reuse neighbor hashes)\n float rShift = n1 * 0.015;\n float bShift = n2 * 0.015;\n\n vec3 color = uColor * vec3(\n grain + rShift,\n grain,\n grain + bShift\n );\n\n color = clamp(color, 0.0, 1.0);\n\n gl_FragColor = vec4(color, grain * uOpacity);\n}\n";
|
|
4
|
-
//#endregion
|
|
5
|
-
export { fragmentShader, vertexShader };
|
|
2
|
+
export declare const vertexShader = "\nattribute vec2 position;\nvarying vec2 vUv;\n\nvoid main() {\n vUv = position * 0.5 + 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n}\n";
|
|
3
|
+
export declare const fragmentShader = "\nprecision highp float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uDensity;\nuniform float uOpacity;\nuniform float uFps;\nuniform vec3 uColor;\n\nvarying vec2 vUv;\n\n// Stable hash\nfloat hash(vec2 p, float seed) {\n return fract(sin(dot(p + seed, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\n// Film response curve (soft toe + shoulder rolloff)\nfloat filmCurve(float x) {\n return smoothstep(0.0, 0.2, x) * (1.0 - smoothstep(0.8, 1.0, x));\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy;\n vec2 p = floor(uv);\n\n // Temporal coherence: blend between current and next frame seed\n float frame = floor(uTime * uFps);\n float t = fract(uTime * uFps);\n float seed = frame * 0.1731;\n\n // Multi-scale grain with inter-frame blending on fine layer\n float fineA = hash(p, seed);\n float fineB = hash(p, seed + 0.1731);\n float fine = mix(fineA, fineB, t);\n\n float medium = hash(floor(uv * 0.5), seed + 3.1);\n float coarse = hash(floor(uv * 0.25), seed + 7.93);\n\n float grain = fine * 0.65 + medium * 0.25 + coarse * 0.10;\n\n // Emulsion clumping (organic structure)\n float cluster = hash(floor(uv * 0.18), seed + 9.2);\n grain *= mix(0.75, 1.35, cluster);\n\n // Density threshold\n grain = smoothstep(1.0 - uDensity, 1.0, grain);\n\n // Film response curve (replaces raw pow gamma)\n grain = filmCurve(grain);\n\n // Micro-blur: sample neighbors for optical diffusion\n float n1 = hash(p + vec2(1.0, 0.0), seed);\n float n2 = hash(p - vec2(1.0, 0.0), seed);\n float n3 = hash(p + vec2(0.0, 1.0), seed);\n float n4 = hash(p - vec2(0.0, 1.0), seed);\n float neighbors = (n1 + n2 + n3 + n4) * 0.25;\n grain = mix(grain, neighbors, 0.15);\n\n // Chromatic aberration (reuse neighbor hashes)\n float rShift = n1 * 0.015;\n float bShift = n2 * 0.015;\n\n vec3 color = uColor * vec3(\n grain + rShift,\n grain,\n grain + bShift\n );\n\n color = clamp(color, 0.0, 1.0);\n\n gl_FragColor = vec4(color, grain * uOpacity);\n}\n";
|
|
4
|
+
//#endregion
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import * as _$react from "react";
|
|
2
|
-
|
|
3
1
|
//#region src/film-grain-webgl.d.ts
|
|
4
2
|
interface UseFilmGrainOptions {
|
|
5
3
|
density: number;
|
|
@@ -8,10 +6,5 @@ interface UseFilmGrainOptions {
|
|
|
8
6
|
/** Hex color for canvas fallback grain. Default '#ffffff' */
|
|
9
7
|
color?: string;
|
|
10
8
|
}
|
|
11
|
-
declare function useFilmGrain({
|
|
12
|
-
|
|
13
|
-
fps,
|
|
14
|
-
color
|
|
15
|
-
}: UseFilmGrainOptions): _$react.RefObject<HTMLCanvasElement | null>;
|
|
16
|
-
//#endregion
|
|
17
|
-
export { useFilmGrain };
|
|
9
|
+
export declare function useFilmGrain({ density, fps, color }: UseFilmGrainOptions): import("react").RefObject<HTMLCanvasElement | null>;
|
|
10
|
+
//#endregion
|
package/dist/film-grain-webgl.js
CHANGED
|
@@ -92,7 +92,10 @@ function generateGrainTexture(width, height, density, color) {
|
|
|
92
92
|
const [r, g, b, a] = parseHex(color);
|
|
93
93
|
const imageData = ctx.createImageData(width, height);
|
|
94
94
|
const pixels = new Uint32Array(imageData.data.buffer);
|
|
95
|
-
for (let i = 0; i < pixels.length; i++) if (Math.random() < density)
|
|
95
|
+
for (let i = 0; i < pixels.length; i++) if (Math.random() < density) {
|
|
96
|
+
const variation = Math.round(a * (.4 + Math.random() * .6));
|
|
97
|
+
pixels[i] = (variation << 24 | b << 16 | g << 8 | r) >>> 0;
|
|
98
|
+
}
|
|
96
99
|
ctx.putImageData(imageData, 0, 0);
|
|
97
100
|
return offscreen;
|
|
98
101
|
}
|
package/dist/film-grain.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
1
|
import * as React from "react";
|
|
3
|
-
|
|
4
2
|
//#region src/film-grain.d.ts
|
|
5
3
|
interface FilmGrainProps {
|
|
6
4
|
density?: number;
|
|
@@ -11,14 +9,6 @@ interface FilmGrainProps {
|
|
|
11
9
|
className?: string;
|
|
12
10
|
style?: React.CSSProperties;
|
|
13
11
|
}
|
|
14
|
-
declare function FilmGrain({
|
|
15
|
-
density,
|
|
16
|
-
opacity,
|
|
17
|
-
blendMode,
|
|
18
|
-
fps,
|
|
19
|
-
color,
|
|
20
|
-
className,
|
|
21
|
-
style
|
|
22
|
-
}: FilmGrainProps): _$react_jsx_runtime0.JSX.Element;
|
|
12
|
+
declare function FilmGrain({ density, opacity, blendMode, fps, color, className, style }: FilmGrainProps): React.JSX.Element;
|
|
23
13
|
//#endregion
|
|
24
14
|
export { FilmGrain, type FilmGrainProps };
|