@opencosmos/ui 1.3.1
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/.claude/CLAUDE.md +239 -0
- package/README.md +161 -0
- package/dist/cli.mjs +151 -0
- package/dist/dates.d.mts +20 -0
- package/dist/dates.d.ts +20 -0
- package/dist/dates.js +240 -0
- package/dist/dates.js.map +1 -0
- package/dist/dates.mjs +203 -0
- package/dist/dates.mjs.map +1 -0
- package/dist/dnd.d.mts +126 -0
- package/dist/dnd.d.ts +126 -0
- package/dist/dnd.js +274 -0
- package/dist/dnd.js.map +1 -0
- package/dist/dnd.mjs +250 -0
- package/dist/dnd.mjs.map +1 -0
- package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
- package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
- package/dist/forms.d.mts +38 -0
- package/dist/forms.d.ts +38 -0
- package/dist/forms.js +198 -0
- package/dist/forms.js.map +1 -0
- package/dist/forms.mjs +159 -0
- package/dist/forms.mjs.map +1 -0
- package/dist/hooks-1b8WaQf1.d.mts +225 -0
- package/dist/hooks-CKW8vE9H.d.ts +225 -0
- package/dist/hooks.d.mts +3 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +971 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.mjs +943 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index-DscTIrZ2.d.mts +29 -0
- package/dist/index-DscTIrZ2.d.ts +29 -0
- package/dist/index.d.mts +3382 -0
- package/dist/index.d.ts +3382 -0
- package/dist/index.js +15146 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14802 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers-CXPDMsl7.d.mts +30 -0
- package/dist/providers-Dn_Msjvz.d.ts +30 -0
- package/dist/providers.d.mts +3 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +1885 -0
- package/dist/providers.js.map +1 -0
- package/dist/providers.mjs +1859 -0
- package/dist/providers.mjs.map +1 -0
- package/dist/tables.d.mts +10 -0
- package/dist/tables.d.ts +10 -0
- package/dist/tables.js +248 -0
- package/dist/tables.js.map +1 -0
- package/dist/tables.mjs +218 -0
- package/dist/tables.mjs.map +1 -0
- package/dist/tokens.d.mts +1065 -0
- package/dist/tokens.d.ts +1065 -0
- package/dist/tokens.js +2637 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tokens.mjs +2555 -0
- package/dist/tokens.mjs.map +1 -0
- package/dist/utils-CIIM7dAC.d.ts +986 -0
- package/dist/utils-Cs04sxth.d.mts +986 -0
- package/dist/utils.d.mts +4 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +874 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +806 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/validation-Bj1ye-v_.d.mts +114 -0
- package/dist/validation-Bj1ye-v_.d.ts +114 -0
- package/dist/webgl.d.mts +104 -0
- package/dist/webgl.d.ts +104 -0
- package/dist/webgl.js +226 -0
- package/dist/webgl.js.map +1 -0
- package/dist/webgl.mjs +195 -0
- package/dist/webgl.mjs.map +1 -0
- package/package.json +267 -0
- package/src/cli.ts +206 -0
- package/src/component-registry.ts +183 -0
- package/src/components/actions/Button.test.tsx +61 -0
- package/src/components/actions/Button.tsx +70 -0
- package/src/components/actions/Link.tsx +78 -0
- package/src/components/actions/Magnetic.tsx +68 -0
- package/src/components/actions/Toggle.test.tsx +40 -0
- package/src/components/actions/Toggle.tsx +47 -0
- package/src/components/actions/ToggleGroup.tsx +70 -0
- package/src/components/actions/index.ts +5 -0
- package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
- package/src/components/backgrounds/OrbBackground.tsx +424 -0
- package/src/components/backgrounds/WarpBackground.tsx +358 -0
- package/src/components/backgrounds/index.ts +3 -0
- package/src/components/blocks/Hero.tsx +142 -0
- package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
- package/src/components/cursor/SplashCursor.tsx +1315 -0
- package/src/components/cursor/TargetCursor.tsx +187 -0
- package/src/components/cursor/index.ts +2 -0
- package/src/components/data-display/AspectImage.tsx +73 -0
- package/src/components/data-display/Avatar.test.tsx +35 -0
- package/src/components/data-display/Avatar.tsx +55 -0
- package/src/components/data-display/Badge.test.tsx +43 -0
- package/src/components/data-display/Badge.tsx +84 -0
- package/src/components/data-display/Brand.tsx +123 -0
- package/src/components/data-display/Calendar.tsx +70 -0
- package/src/components/data-display/Card.test.tsx +92 -0
- package/src/components/data-display/Card.tsx +115 -0
- package/src/components/data-display/Code.tsx +210 -0
- package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
- package/src/components/data-display/DataTable.tsx +119 -0
- package/src/components/data-display/DescriptionList.tsx +41 -0
- package/src/components/data-display/GitHubIcon.tsx +44 -0
- package/src/components/data-display/Heading.test.tsx +36 -0
- package/src/components/data-display/Heading.tsx +83 -0
- package/src/components/data-display/StatCard.tsx +195 -0
- package/src/components/data-display/Table.tsx +133 -0
- package/src/components/data-display/Text.test.tsx +48 -0
- package/src/components/data-display/Text.tsx +144 -0
- package/src/components/data-display/Timeline.tsx +194 -0
- package/src/components/data-display/TreeView.tsx +226 -0
- package/src/components/data-display/Typewriter.tsx +119 -0
- package/src/components/data-display/VariableWeightText.tsx +130 -0
- package/src/components/data-display/index.ts +19 -0
- package/src/components/feedback/Alert.test.tsx +44 -0
- package/src/components/feedback/Alert.tsx +65 -0
- package/src/components/feedback/EmptyState.tsx +113 -0
- package/src/components/feedback/Progress.test.tsx +60 -0
- package/src/components/feedback/Progress.tsx +30 -0
- package/src/components/feedback/ProgressBar.tsx +158 -0
- package/src/components/feedback/Skeleton.test.tsx +39 -0
- package/src/components/feedback/Skeleton.tsx +45 -0
- package/src/components/feedback/Sonner.tsx +28 -0
- package/src/components/feedback/Spinner.test.tsx +33 -0
- package/src/components/feedback/Spinner.tsx +99 -0
- package/src/components/feedback/Stepper.tsx +307 -0
- package/src/components/feedback/Toast/Toast.tsx +243 -0
- package/src/components/feedback/Toast/index.ts +2 -0
- package/src/components/feedback/index.ts +9 -0
- package/src/components/forms/Checkbox.test.tsx +40 -0
- package/src/components/forms/Checkbox.tsx +31 -0
- package/src/components/forms/ColorPicker.tsx +118 -0
- package/src/components/forms/Combobox.tsx +96 -0
- package/src/components/forms/DragDrop.tsx +440 -0
- package/src/components/forms/FileUpload.tsx +252 -0
- package/src/components/forms/FilterButton.tsx +65 -0
- package/src/components/forms/Form.tsx +197 -0
- package/src/components/forms/Input.test.tsx +46 -0
- package/src/components/forms/Input.tsx +43 -0
- package/src/components/forms/InputOTP.tsx +81 -0
- package/src/components/forms/Label.test.tsx +20 -0
- package/src/components/forms/Label.tsx +25 -0
- package/src/components/forms/RadioGroup.tsx +51 -0
- package/src/components/forms/SearchBar.tsx +215 -0
- package/src/components/forms/Select.test.tsx +118 -0
- package/src/components/forms/Select.tsx +274 -0
- package/src/components/forms/Slider.tsx +29 -0
- package/src/components/forms/Switch.test.tsx +76 -0
- package/src/components/forms/Switch.tsx +30 -0
- package/src/components/forms/TextField.tsx +152 -0
- package/src/components/forms/Textarea.test.tsx +41 -0
- package/src/components/forms/Textarea.tsx +29 -0
- package/src/components/forms/ThemeSwitcher.tsx +290 -0
- package/src/components/forms/ThemeToggle.tsx +151 -0
- package/src/components/forms/index.ts +19 -0
- package/src/components/layout/Accordion.test.tsx +66 -0
- package/src/components/layout/Accordion.tsx +64 -0
- package/src/components/layout/AspectRatio.tsx +7 -0
- package/src/components/layout/Carousel.tsx +277 -0
- package/src/components/layout/Collapsible.test.tsx +40 -0
- package/src/components/layout/Collapsible.tsx +31 -0
- package/src/components/layout/Container.test.tsx +45 -0
- package/src/components/layout/Container.tsx +99 -0
- package/src/components/layout/CustomizerPanel.tsx +400 -0
- package/src/components/layout/DatePicker.tsx +57 -0
- package/src/components/layout/Footer/Footer.tsx +175 -0
- package/src/components/layout/Footer/index.ts +2 -0
- package/src/components/layout/GlassSurface.tsx +82 -0
- package/src/components/layout/Grid.test.tsx +31 -0
- package/src/components/layout/Grid.tsx +130 -0
- package/src/components/layout/Header/Header.tsx +450 -0
- package/src/components/layout/Header/index.ts +2 -0
- package/src/components/layout/PageLayout.tsx +180 -0
- package/src/components/layout/PageTemplate.tsx +158 -0
- package/src/components/layout/Resizable.tsx +48 -0
- package/src/components/layout/ScrollArea.tsx +53 -0
- package/src/components/layout/Separator.test.tsx +28 -0
- package/src/components/layout/Separator.tsx +29 -0
- package/src/components/layout/Sidebar.tsx +171 -0
- package/src/components/layout/Stack.test.tsx +41 -0
- package/src/components/layout/Stack.tsx +89 -0
- package/src/components/layout/glass-surface.css +60 -0
- package/src/components/layout/index.ts +18 -0
- package/src/components/motion/AnimatedBeam.tsx +159 -0
- package/src/components/navigation/Breadcrumb.test.tsx +57 -0
- package/src/components/navigation/Breadcrumb.tsx +119 -0
- package/src/components/navigation/Breadcrumbs.tsx +221 -0
- package/src/components/navigation/Command.tsx +159 -0
- package/src/components/navigation/Menubar.tsx +115 -0
- package/src/components/navigation/NavLink.tsx +55 -0
- package/src/components/navigation/NavigationMenu.tsx +125 -0
- package/src/components/navigation/Pagination.tsx +121 -0
- package/src/components/navigation/SecondaryNav.tsx +100 -0
- package/src/components/navigation/Tabs.test.tsx +47 -0
- package/src/components/navigation/Tabs.tsx +60 -0
- package/src/components/navigation/TertiaryNav.tsx +90 -0
- package/src/components/navigation/index.ts +10 -0
- package/src/components/overlays/AlertDialog.test.tsx +69 -0
- package/src/components/overlays/AlertDialog.tsx +166 -0
- package/src/components/overlays/ContextMenu.tsx +243 -0
- package/src/components/overlays/Dialog.test.tsx +79 -0
- package/src/components/overlays/Dialog.tsx +158 -0
- package/src/components/overlays/Drawer.tsx +128 -0
- package/src/components/overlays/Dropdown.tsx +253 -0
- package/src/components/overlays/DropdownMenu.tsx +242 -0
- package/src/components/overlays/HoverCard.tsx +32 -0
- package/src/components/overlays/Modal.tsx +250 -0
- package/src/components/overlays/NotificationCenter.tsx +364 -0
- package/src/components/overlays/Popover.test.tsx +40 -0
- package/src/components/overlays/Popover.tsx +46 -0
- package/src/components/overlays/Sheet.tsx +163 -0
- package/src/components/overlays/Tooltip.test.tsx +33 -0
- package/src/components/overlays/Tooltip.tsx +32 -0
- package/src/components/overlays/index.ts +12 -0
- package/src/dates.ts +2 -0
- package/src/dnd.ts +1 -0
- package/src/forms.ts +1 -0
- package/src/globals.css +187 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useForm.ts +247 -0
- package/src/hooks/useMotionPreference.test.ts +102 -0
- package/src/hooks/useMotionPreference.ts +78 -0
- package/src/hooks/useTheme.ts +58 -0
- package/src/hooks.ts +9 -0
- package/src/index.ts +168 -0
- package/src/lib/animations.ts +356 -0
- package/src/lib/breadcrumbs.ts +94 -0
- package/src/lib/colors.ts +493 -0
- package/src/lib/store/customizer.ts +482 -0
- package/src/lib/store/index.ts +3 -0
- package/src/lib/store/theme.ts +55 -0
- package/src/lib/syntax-parser/index.ts +50 -0
- package/src/lib/syntax-parser/patterns.ts +64 -0
- package/src/lib/syntax-parser/tokenizer.ts +117 -0
- package/src/lib/syntax-parser/types.ts +27 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +204 -0
- package/src/lib/webgl/Color.ts +11 -0
- package/src/lib/webgl/Mesh.ts +41 -0
- package/src/lib/webgl/Program.ts +118 -0
- package/src/lib/webgl/Renderer.ts +51 -0
- package/src/lib/webgl/Triangle.ts +27 -0
- package/src/lib/webgl/Vec3.ts +18 -0
- package/src/lib/webgl/index.ts +13 -0
- package/src/nativewind-env.d.ts +1 -0
- package/src/providers/ThemeProvider.tsx +461 -0
- package/src/providers/index.ts +1 -0
- package/src/providers.ts +7 -0
- package/src/tables.ts +1 -0
- package/src/test/setup.ts +39 -0
- package/src/theme.css +158 -0
- package/src/tokens.ts +7 -0
- package/src/utils.ts +12 -0
- package/src/webgl.ts +1 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax Tokenizer
|
|
3
|
+
* Core tokenization logic for parsing code into syntax tokens
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SyntaxToken, Language } from './types';
|
|
7
|
+
import { TOKEN_PATTERNS } from './patterns';
|
|
8
|
+
|
|
9
|
+
interface Match {
|
|
10
|
+
text: string;
|
|
11
|
+
type: SyntaxToken['type'];
|
|
12
|
+
index: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tokenizes source code into an array of syntax tokens
|
|
17
|
+
*
|
|
18
|
+
* @param code - The source code to tokenize
|
|
19
|
+
* @param language - The programming language (currently only affects metadata, all use same patterns)
|
|
20
|
+
* @returns Array of syntax tokens with text and type information
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const tokens = tokenize('const greeting = "Hello";');
|
|
25
|
+
* // Returns:
|
|
26
|
+
* // [
|
|
27
|
+
* // { text: 'const', type: 'keyword' },
|
|
28
|
+
* // { text: ' ', type: 'plain' },
|
|
29
|
+
* // { text: 'greeting', type: 'plain' },
|
|
30
|
+
* // { text: ' ', type: 'plain' },
|
|
31
|
+
* // { text: '=', type: 'operator' },
|
|
32
|
+
* // { text: ' ', type: 'plain' },
|
|
33
|
+
* // { text: '"Hello"', type: 'string' },
|
|
34
|
+
* // { text: ';', type: 'punctuation' },
|
|
35
|
+
* // ]
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function tokenize(code: string, language: Language = 'typescript'): SyntaxToken[] {
|
|
39
|
+
// If empty, return plain token
|
|
40
|
+
if (!code || code.trim() === '') {
|
|
41
|
+
return [{ text: code, type: 'plain' }];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Collect all matches from all patterns
|
|
45
|
+
const matches: Match[] = [];
|
|
46
|
+
|
|
47
|
+
for (const { type, pattern } of TOKEN_PATTERNS) {
|
|
48
|
+
// Reset regex lastIndex
|
|
49
|
+
pattern.lastIndex = 0;
|
|
50
|
+
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
53
|
+
matches.push({
|
|
54
|
+
text: match[0],
|
|
55
|
+
type,
|
|
56
|
+
index: match.index,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Sort matches by position
|
|
62
|
+
matches.sort((a, b) => a.index - b.index);
|
|
63
|
+
|
|
64
|
+
// Remove overlapping matches (keep first match at each position)
|
|
65
|
+
const nonOverlapping: Match[] = [];
|
|
66
|
+
let lastEnd = 0;
|
|
67
|
+
|
|
68
|
+
for (const match of matches) {
|
|
69
|
+
if (match.index >= lastEnd) {
|
|
70
|
+
nonOverlapping.push(match);
|
|
71
|
+
lastEnd = match.index + match.text.length;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build tokens array, filling gaps with plain text
|
|
76
|
+
const tokens: SyntaxToken[] = [];
|
|
77
|
+
let position = 0;
|
|
78
|
+
|
|
79
|
+
for (const match of nonOverlapping) {
|
|
80
|
+
// Add plain text before this match
|
|
81
|
+
if (match.index > position) {
|
|
82
|
+
const plainText = code.slice(position, match.index);
|
|
83
|
+
tokens.push({ text: plainText, type: 'plain' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Add the matched token
|
|
87
|
+
tokens.push({ text: match.text, type: match.type });
|
|
88
|
+
position = match.index + match.text.length;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Add any remaining plain text
|
|
92
|
+
if (position < code.length) {
|
|
93
|
+
tokens.push({ text: code.slice(position), type: 'plain' });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return tokens;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Detects the language from code content or file extension
|
|
101
|
+
* Currently returns 'typescript' for all cases as patterns support TS/JS/JSX/TSX
|
|
102
|
+
*
|
|
103
|
+
* @param code - The source code
|
|
104
|
+
* @param filename - Optional filename for extension detection
|
|
105
|
+
* @returns Detected language
|
|
106
|
+
*/
|
|
107
|
+
export function detectLanguage(code: string, filename?: string): Language {
|
|
108
|
+
// For now, treat everything as TypeScript since our patterns support all variants
|
|
109
|
+
// Future: Could detect based on JSX syntax or file extension
|
|
110
|
+
if (filename) {
|
|
111
|
+
if (filename.endsWith('.tsx')) return 'tsx';
|
|
112
|
+
if (filename.endsWith('.jsx')) return 'jsx';
|
|
113
|
+
if (filename.endsWith('.js')) return 'javascript';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return 'typescript';
|
|
117
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax Parser Types
|
|
3
|
+
* Type definitions for the syntax parsing system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type SyntaxType =
|
|
7
|
+
| 'comment'
|
|
8
|
+
| 'keyword'
|
|
9
|
+
| 'function'
|
|
10
|
+
| 'string'
|
|
11
|
+
| 'number'
|
|
12
|
+
| 'boolean'
|
|
13
|
+
| 'operator'
|
|
14
|
+
| 'property'
|
|
15
|
+
| 'className'
|
|
16
|
+
| 'tag'
|
|
17
|
+
| 'attribute'
|
|
18
|
+
| 'variable'
|
|
19
|
+
| 'punctuation'
|
|
20
|
+
| 'plain';
|
|
21
|
+
|
|
22
|
+
export interface SyntaxToken {
|
|
23
|
+
text: string;
|
|
24
|
+
type: SyntaxType;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type Language = 'typescript' | 'javascript' | 'tsx' | 'jsx';
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Simple validation helpers for form fields.
|
|
5
|
+
* Works seamlessly with FormField and other form components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ValidationRule = {
|
|
9
|
+
validate: (value: any) => boolean;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type FieldValidation = {
|
|
14
|
+
required?: boolean | string;
|
|
15
|
+
minLength?: { value: number; message: string };
|
|
16
|
+
maxLength?: { value: number; message: string };
|
|
17
|
+
pattern?: { value: RegExp; message: string };
|
|
18
|
+
custom?: ValidationRule[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type FormErrors = Record<string, string | undefined>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Common validation patterns
|
|
25
|
+
*/
|
|
26
|
+
export const patterns = {
|
|
27
|
+
email: {
|
|
28
|
+
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
|
29
|
+
message: 'Invalid email address',
|
|
30
|
+
},
|
|
31
|
+
url: {
|
|
32
|
+
value: /^https?:\/\/.+\..+/,
|
|
33
|
+
message: 'Invalid URL',
|
|
34
|
+
},
|
|
35
|
+
phone: {
|
|
36
|
+
value: /^[\d\s\-\+\(\)]+$/,
|
|
37
|
+
message: 'Invalid phone number',
|
|
38
|
+
},
|
|
39
|
+
alphanumeric: {
|
|
40
|
+
value: /^[a-zA-Z0-9]+$/,
|
|
41
|
+
message: 'Only letters and numbers allowed',
|
|
42
|
+
},
|
|
43
|
+
noSpaces: {
|
|
44
|
+
value: /^\S+$/,
|
|
45
|
+
message: 'Spaces are not allowed',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate a single field value against validation rules
|
|
51
|
+
*
|
|
52
|
+
* @param value - The field value to validate
|
|
53
|
+
* @param rules - Validation rules to apply
|
|
54
|
+
* @returns Error message if validation fails, undefined if valid
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* const error = validateField(email, {
|
|
59
|
+
* required: true,
|
|
60
|
+
* pattern: patterns.email
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function validateField(
|
|
65
|
+
value: any,
|
|
66
|
+
rules: FieldValidation
|
|
67
|
+
): string | undefined {
|
|
68
|
+
// Required check
|
|
69
|
+
if (rules.required) {
|
|
70
|
+
const isEmpty =
|
|
71
|
+
value === undefined ||
|
|
72
|
+
value === null ||
|
|
73
|
+
value === '' ||
|
|
74
|
+
(Array.isArray(value) && value.length === 0);
|
|
75
|
+
|
|
76
|
+
if (isEmpty) {
|
|
77
|
+
return typeof rules.required === 'string'
|
|
78
|
+
? rules.required
|
|
79
|
+
: 'This field is required';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Skip other validations if value is empty and not required
|
|
84
|
+
if (!value && !rules.required) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Min length check
|
|
89
|
+
if (rules.minLength && value.length < rules.minLength.value) {
|
|
90
|
+
return rules.minLength.message;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Max length check
|
|
94
|
+
if (rules.maxLength && value.length > rules.maxLength.value) {
|
|
95
|
+
return rules.maxLength.message;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Pattern check
|
|
99
|
+
if (rules.pattern && !rules.pattern.value.test(value)) {
|
|
100
|
+
return rules.pattern.message;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Custom validations
|
|
104
|
+
if (rules.custom) {
|
|
105
|
+
for (const rule of rules.custom) {
|
|
106
|
+
if (!rule.validate(value)) {
|
|
107
|
+
return rule.message;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Validate all fields in a form
|
|
117
|
+
*
|
|
118
|
+
* @param values - Object containing all form field values
|
|
119
|
+
* @param validations - Object containing validation rules for each field
|
|
120
|
+
* @returns Object containing errors for each field
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* const errors = validateForm(
|
|
125
|
+
* { email: 'test', password: '123' },
|
|
126
|
+
* {
|
|
127
|
+
* email: { required: true, pattern: patterns.email },
|
|
128
|
+
* password: { required: true, minLength: { value: 8, message: 'Min 8 chars' } }
|
|
129
|
+
* }
|
|
130
|
+
* );
|
|
131
|
+
* // errors = { email: 'Invalid email address', password: 'Min 8 chars' }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export function validateForm(
|
|
135
|
+
values: Record<string, any>,
|
|
136
|
+
validations: Record<string, FieldValidation>
|
|
137
|
+
): FormErrors {
|
|
138
|
+
const errors: FormErrors = {};
|
|
139
|
+
|
|
140
|
+
for (const [field, rules] of Object.entries(validations)) {
|
|
141
|
+
const error = validateField(values[field], rules);
|
|
142
|
+
if (error) {
|
|
143
|
+
errors[field] = error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return errors;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if form has any errors
|
|
152
|
+
*
|
|
153
|
+
* @param errors - Form errors object
|
|
154
|
+
* @returns true if there are any errors
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```tsx
|
|
158
|
+
* if (hasErrors(errors)) {
|
|
159
|
+
* console.log('Form has errors');
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function hasErrors(errors: FormErrors): boolean {
|
|
164
|
+
return Object.values(errors).some((error) => error !== undefined);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Common validation rule builders
|
|
169
|
+
*/
|
|
170
|
+
export const rules = {
|
|
171
|
+
required: (message = 'This field is required'): FieldValidation => ({
|
|
172
|
+
required: message,
|
|
173
|
+
}),
|
|
174
|
+
|
|
175
|
+
email: (message = 'Invalid email address'): FieldValidation => ({
|
|
176
|
+
pattern: { value: patterns.email.value, message },
|
|
177
|
+
}),
|
|
178
|
+
|
|
179
|
+
minLength: (length: number, message?: string): FieldValidation => ({
|
|
180
|
+
minLength: {
|
|
181
|
+
value: length,
|
|
182
|
+
message: message || `Minimum ${length} characters required`,
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
maxLength: (length: number, message?: string): FieldValidation => ({
|
|
187
|
+
maxLength: {
|
|
188
|
+
value: length,
|
|
189
|
+
message: message || `Maximum ${length} characters allowed`,
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
match: (
|
|
194
|
+
otherValue: any,
|
|
195
|
+
message = 'Values do not match'
|
|
196
|
+
): FieldValidation => ({
|
|
197
|
+
custom: [
|
|
198
|
+
{
|
|
199
|
+
validate: (value) => value === otherValue,
|
|
200
|
+
message,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
}),
|
|
204
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Color implementation for WebGL shader uniforms.
|
|
3
|
+
* Replaces OGL's Color — stores three floats (r, g, b).
|
|
4
|
+
*/
|
|
5
|
+
export class Color {
|
|
6
|
+
data: Float32Array;
|
|
7
|
+
|
|
8
|
+
constructor(r = 0, g = 0, b = 0) {
|
|
9
|
+
this.data = new Float32Array([r, g, b]);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Mesh that binds geometry attributes and draws.
|
|
3
|
+
* Replaces OGL's Mesh — combines a Triangle geometry with a Program.
|
|
4
|
+
*/
|
|
5
|
+
import type { Triangle } from './Triangle';
|
|
6
|
+
import type { Program } from './Program';
|
|
7
|
+
|
|
8
|
+
export class Mesh {
|
|
9
|
+
gl: WebGLRenderingContext;
|
|
10
|
+
geometry: Triangle;
|
|
11
|
+
program: Program;
|
|
12
|
+
|
|
13
|
+
constructor(gl: WebGLRenderingContext, { geometry, program }: { geometry: Triangle; program: Program }) {
|
|
14
|
+
this.gl = gl;
|
|
15
|
+
this.geometry = geometry;
|
|
16
|
+
this.program = program;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
draw(): void {
|
|
20
|
+
const { gl, geometry, program } = this;
|
|
21
|
+
|
|
22
|
+
program.use();
|
|
23
|
+
program.uploadUniforms();
|
|
24
|
+
|
|
25
|
+
const posLoc = program.getAttribLocation('position');
|
|
26
|
+
if (posLoc >= 0) {
|
|
27
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.positionBuffer);
|
|
28
|
+
gl.enableVertexAttribArray(posLoc);
|
|
29
|
+
gl.vertexAttribPointer(posLoc, geometry.positionSize, gl.FLOAT, false, 0, 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const uvLoc = program.getAttribLocation('uv');
|
|
33
|
+
if (uvLoc >= 0) {
|
|
34
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.uvBuffer);
|
|
35
|
+
gl.enableVertexAttribArray(uvLoc);
|
|
36
|
+
gl.vertexAttribPointer(uvLoc, geometry.uvSize, gl.FLOAT, false, 0, 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
gl.drawArrays(gl.TRIANGLES, 0, geometry.count);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal shader Program for compiling GLSL and managing uniforms.
|
|
3
|
+
* Replaces OGL's Program — handles compilation, linking, and uniform uploads.
|
|
4
|
+
*
|
|
5
|
+
* Supported uniform value types:
|
|
6
|
+
* number → gl.uniform1f
|
|
7
|
+
* boolean → gl.uniform1i (0 or 1, for GLSL bool)
|
|
8
|
+
* Float32Array → gl.uniform{2,3,4}fv (by length)
|
|
9
|
+
* Vec3 / Color → gl.uniform3fv (they extend Float32Array)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface UniformDef {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
value: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ProgramOptions {
|
|
18
|
+
vertex: string;
|
|
19
|
+
fragment: string;
|
|
20
|
+
uniforms?: Record<string, UniformDef>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function compile(gl: WebGLRenderingContext, type: number, source: string): WebGLShader {
|
|
24
|
+
const shader = gl.createShader(type)!;
|
|
25
|
+
gl.shaderSource(shader, source);
|
|
26
|
+
gl.compileShader(shader);
|
|
27
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
28
|
+
const info = gl.getShaderInfoLog(shader);
|
|
29
|
+
gl.deleteShader(shader);
|
|
30
|
+
throw new Error(`Shader compile error: ${info}`);
|
|
31
|
+
}
|
|
32
|
+
return shader;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Program {
|
|
36
|
+
gl: WebGLRenderingContext;
|
|
37
|
+
program: WebGLProgram;
|
|
38
|
+
uniforms: Record<string, UniformDef>;
|
|
39
|
+
private uniformLocations: Map<string, WebGLUniformLocation>;
|
|
40
|
+
private attributeLocations: Map<string, number>;
|
|
41
|
+
|
|
42
|
+
constructor(gl: WebGLRenderingContext, { vertex, fragment, uniforms = {} }: ProgramOptions) {
|
|
43
|
+
this.gl = gl;
|
|
44
|
+
this.uniforms = uniforms;
|
|
45
|
+
|
|
46
|
+
const vs = compile(gl, gl.VERTEX_SHADER, vertex);
|
|
47
|
+
const fs = compile(gl, gl.FRAGMENT_SHADER, fragment);
|
|
48
|
+
|
|
49
|
+
this.program = gl.createProgram()!;
|
|
50
|
+
gl.attachShader(this.program, vs);
|
|
51
|
+
gl.attachShader(this.program, fs);
|
|
52
|
+
gl.linkProgram(this.program);
|
|
53
|
+
|
|
54
|
+
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
|
|
55
|
+
const info = gl.getProgramInfoLog(this.program);
|
|
56
|
+
throw new Error(`Program link error: ${info}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
gl.deleteShader(vs);
|
|
60
|
+
gl.deleteShader(fs);
|
|
61
|
+
|
|
62
|
+
// Cache uniform locations (only for uniforms the shader actually uses)
|
|
63
|
+
this.uniformLocations = new Map();
|
|
64
|
+
for (const name in uniforms) {
|
|
65
|
+
const loc = gl.getUniformLocation(this.program, name);
|
|
66
|
+
if (loc !== null) {
|
|
67
|
+
this.uniformLocations.set(name, loc);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Cache attribute locations
|
|
72
|
+
this.attributeLocations = new Map();
|
|
73
|
+
const numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
|
|
74
|
+
for (let i = 0; i < numAttribs; i++) {
|
|
75
|
+
const attrib = gl.getActiveAttrib(this.program, i);
|
|
76
|
+
if (attrib) {
|
|
77
|
+
this.attributeLocations.set(attrib.name, gl.getAttribLocation(this.program, attrib.name));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
use(): void {
|
|
83
|
+
this.gl.useProgram(this.program);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getAttribLocation(name: string): number {
|
|
87
|
+
return this.attributeLocations.get(name) ?? -1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
uploadUniforms(): void {
|
|
91
|
+
const gl = this.gl;
|
|
92
|
+
for (const [name, loc] of this.uniformLocations) {
|
|
93
|
+
const value = this.uniforms[name]?.value;
|
|
94
|
+
if (value === undefined || value === null) continue;
|
|
95
|
+
|
|
96
|
+
if (typeof value === 'boolean') {
|
|
97
|
+
gl.uniform1i(loc, value ? 1 : 0);
|
|
98
|
+
} else if (typeof value === 'number') {
|
|
99
|
+
gl.uniform1f(loc, value);
|
|
100
|
+
} else if (value instanceof Float32Array) {
|
|
101
|
+
switch (value.length) {
|
|
102
|
+
case 1: gl.uniform1fv(loc, value); break;
|
|
103
|
+
case 2: gl.uniform2fv(loc, value); break;
|
|
104
|
+
case 3: gl.uniform3fv(loc, value); break;
|
|
105
|
+
case 4: gl.uniform4fv(loc, value); break;
|
|
106
|
+
}
|
|
107
|
+
} else if (value && value.data instanceof Float32Array) {
|
|
108
|
+
// Vec3, Color — classes that wrap a Float32Array in .data
|
|
109
|
+
const arr = value.data as Float32Array;
|
|
110
|
+
switch (arr.length) {
|
|
111
|
+
case 2: gl.uniform2fv(loc, arr); break;
|
|
112
|
+
case 3: gl.uniform3fv(loc, arr); break;
|
|
113
|
+
case 4: gl.uniform4fv(loc, arr); break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal WebGL Renderer that creates a canvas and context.
|
|
3
|
+
* Replaces OGL's Renderer — provides context creation, resize, and render dispatch.
|
|
4
|
+
*/
|
|
5
|
+
import type { Mesh } from './Mesh';
|
|
6
|
+
|
|
7
|
+
interface RendererOptions {
|
|
8
|
+
alpha?: boolean;
|
|
9
|
+
premultipliedAlpha?: boolean;
|
|
10
|
+
dpr?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** WebGLRenderingContext with canvas narrowed to HTMLCanvasElement. */
|
|
14
|
+
export type GL = WebGLRenderingContext & { readonly canvas: HTMLCanvasElement };
|
|
15
|
+
|
|
16
|
+
export class Renderer {
|
|
17
|
+
gl: GL;
|
|
18
|
+
private dpr: number;
|
|
19
|
+
|
|
20
|
+
constructor({ alpha = false, premultipliedAlpha = true, dpr = 1 }: RendererOptions = {}) {
|
|
21
|
+
const canvas = document.createElement('canvas');
|
|
22
|
+
this.dpr = dpr;
|
|
23
|
+
|
|
24
|
+
const attrs: WebGLContextAttributes = {
|
|
25
|
+
alpha,
|
|
26
|
+
premultipliedAlpha,
|
|
27
|
+
antialias: false,
|
|
28
|
+
preserveDrawingBuffer: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const gl =
|
|
32
|
+
(canvas.getContext('webgl2', attrs) as GL | null) ??
|
|
33
|
+
(canvas.getContext('webgl', attrs) as GL | null);
|
|
34
|
+
|
|
35
|
+
if (!gl) throw new Error('WebGL not supported');
|
|
36
|
+
this.gl = gl;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setSize(width: number, height: number): void {
|
|
40
|
+
const canvas = this.gl.canvas;
|
|
41
|
+
canvas.width = width * this.dpr;
|
|
42
|
+
canvas.height = height * this.dpr;
|
|
43
|
+
canvas.style.width = width + 'px';
|
|
44
|
+
canvas.style.height = height + 'px';
|
|
45
|
+
this.gl.viewport(0, 0, canvas.width, canvas.height);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render({ scene }: { scene: Mesh }): void {
|
|
49
|
+
scene.draw();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-screen triangle geometry for fragment-shader-only rendering.
|
|
3
|
+
* Replaces OGL's Triangle — a single oversized triangle that clips to the viewport.
|
|
4
|
+
*/
|
|
5
|
+
export class Triangle {
|
|
6
|
+
gl: WebGLRenderingContext;
|
|
7
|
+
positionBuffer: WebGLBuffer;
|
|
8
|
+
uvBuffer: WebGLBuffer;
|
|
9
|
+
positionSize = 2;
|
|
10
|
+
uvSize = 2;
|
|
11
|
+
count = 3;
|
|
12
|
+
|
|
13
|
+
constructor(gl: WebGLRenderingContext) {
|
|
14
|
+
this.gl = gl;
|
|
15
|
+
|
|
16
|
+
// Oversized triangle covering the full viewport when clipped
|
|
17
|
+
const positions = new Float32Array([-1, -1, 3, -1, -1, 3]);
|
|
18
|
+
this.positionBuffer = gl.createBuffer()!;
|
|
19
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
|
|
20
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
|
21
|
+
|
|
22
|
+
const uvs = new Float32Array([0, 0, 2, 0, 0, 2]);
|
|
23
|
+
this.uvBuffer = gl.createBuffer()!;
|
|
24
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
|
|
25
|
+
gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.STATIC_DRAW);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Vec3 implementation for WebGL shader uniforms.
|
|
3
|
+
* Replaces OGL's Vec3 — only the constructor and .set() are needed.
|
|
4
|
+
*/
|
|
5
|
+
export class Vec3 {
|
|
6
|
+
data: Float32Array;
|
|
7
|
+
|
|
8
|
+
constructor(x = 0, y = 0, z = 0) {
|
|
9
|
+
this.data = new Float32Array([x, y, z]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
set(x: number, y: number, z: number): this {
|
|
13
|
+
this.data[0] = x;
|
|
14
|
+
this.data[1] = y;
|
|
15
|
+
this.data[2] = z;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal WebGL utilities — drop-in replacement for the OGL subset used
|
|
3
|
+
* by the background shader components (OrbBackground, FaultyTerminal, WarpBackground).
|
|
4
|
+
*
|
|
5
|
+
* Only the classes actually consumed are implemented:
|
|
6
|
+
* Renderer, Program, Mesh, Triangle, Vec3, Color
|
|
7
|
+
*/
|
|
8
|
+
export { Renderer, type GL } from './Renderer';
|
|
9
|
+
export { Program } from './Program';
|
|
10
|
+
export { Mesh } from './Mesh';
|
|
11
|
+
export { Triangle } from './Triangle';
|
|
12
|
+
export { Vec3 } from './Vec3';
|
|
13
|
+
export { Color } from './Color';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="nativewind/types" />
|