goodchuck-utils 1.2.0 → 1.3.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.
Files changed (81) hide show
  1. package/dist/form/__tests__/formatter.test.d.ts +2 -0
  2. package/dist/form/__tests__/formatter.test.d.ts.map +1 -0
  3. package/dist/form/__tests__/formatter.test.js +74 -0
  4. package/dist/form/__tests__/helpers.test.d.ts +2 -0
  5. package/dist/form/__tests__/helpers.test.d.ts.map +1 -0
  6. package/dist/form/__tests__/helpers.test.js +42 -0
  7. package/dist/form/__tests__/validation.test.d.ts +2 -0
  8. package/dist/form/__tests__/validation.test.d.ts.map +1 -0
  9. package/dist/form/__tests__/validation.test.js +67 -0
  10. package/dist/form/formatter.d.ts +34 -0
  11. package/dist/form/formatter.d.ts.map +1 -0
  12. package/dist/form/formatter.js +76 -0
  13. package/dist/form/helpers.d.ts +16 -0
  14. package/dist/form/helpers.d.ts.map +1 -0
  15. package/dist/form/helpers.js +34 -0
  16. package/dist/form/index.d.ts +9 -0
  17. package/dist/form/index.d.ts.map +1 -0
  18. package/dist/form/index.js +11 -0
  19. package/dist/form/validation.d.ts +33 -0
  20. package/dist/form/validation.d.ts.map +1 -0
  21. package/dist/form/validation.js +56 -0
  22. package/dist/hooks/index.d.ts +11 -0
  23. package/dist/hooks/index.d.ts.map +1 -0
  24. package/dist/hooks/index.js +12 -0
  25. package/dist/hooks/useClickOutside.d.ts +49 -0
  26. package/dist/hooks/useClickOutside.d.ts.map +1 -0
  27. package/dist/hooks/useClickOutside.js +94 -0
  28. package/dist/hooks/useLocalStorage.d.ts +19 -0
  29. package/dist/hooks/useLocalStorage.d.ts.map +1 -0
  30. package/dist/hooks/useLocalStorage.js +91 -0
  31. package/dist/hooks/useMediaQuery.d.ts +56 -0
  32. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  33. package/dist/hooks/useMediaQuery.js +104 -0
  34. package/dist/hooks/useSessionStorage.d.ts +19 -0
  35. package/dist/hooks/useSessionStorage.d.ts.map +1 -0
  36. package/dist/hooks/useSessionStorage.js +85 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +7 -2
  40. package/dist/string/__tests__/case.test.d.ts +2 -0
  41. package/dist/string/__tests__/case.test.d.ts.map +1 -0
  42. package/dist/string/__tests__/case.test.js +61 -0
  43. package/dist/string/__tests__/manipulation.test.d.ts +2 -0
  44. package/dist/string/__tests__/manipulation.test.d.ts.map +1 -0
  45. package/dist/string/__tests__/manipulation.test.js +109 -0
  46. package/dist/string/__tests__/validation.test.d.ts +2 -0
  47. package/dist/string/__tests__/validation.test.d.ts.map +1 -0
  48. package/dist/string/__tests__/validation.test.js +101 -0
  49. package/dist/string/case.d.ts +42 -0
  50. package/dist/string/case.d.ts.map +1 -0
  51. package/dist/string/case.js +71 -0
  52. package/dist/string/index.d.ts +9 -0
  53. package/dist/string/index.d.ts.map +1 -0
  54. package/dist/string/index.js +11 -0
  55. package/dist/string/manipulation.d.ts +61 -0
  56. package/dist/string/manipulation.d.ts.map +1 -0
  57. package/dist/string/manipulation.js +106 -0
  58. package/dist/string/validation.d.ts +79 -0
  59. package/dist/string/validation.d.ts.map +1 -0
  60. package/dist/string/validation.js +115 -0
  61. package/package.json +15 -1
  62. package/src/form/__tests__/formatter.test.ts +97 -0
  63. package/src/form/__tests__/helpers.test.ts +53 -0
  64. package/src/form/__tests__/validation.test.ts +84 -0
  65. package/src/form/formatter.ts +85 -0
  66. package/src/form/helpers.ts +44 -0
  67. package/src/form/index.ts +14 -0
  68. package/src/form/validation.ts +72 -0
  69. package/src/hooks/index.ts +14 -0
  70. package/src/hooks/useClickOutside.ts +114 -0
  71. package/src/hooks/useLocalStorage.ts +112 -0
  72. package/src/hooks/useMediaQuery.ts +116 -0
  73. package/src/hooks/useSessionStorage.ts +106 -0
  74. package/src/index.ts +9 -2
  75. package/src/string/__tests__/case.test.ts +78 -0
  76. package/src/string/__tests__/manipulation.test.ts +142 -0
  77. package/src/string/__tests__/validation.test.ts +128 -0
  78. package/src/string/case.ts +76 -0
  79. package/src/string/index.ts +14 -0
  80. package/src/string/manipulation.ts +124 -0
  81. package/src/string/validation.ts +126 -0
@@ -0,0 +1,128 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ isEmpty,
4
+ startsWith,
5
+ endsWith,
6
+ contains,
7
+ isAlpha,
8
+ isNumeric,
9
+ isAlphanumeric,
10
+ isLowerCase,
11
+ isUpperCase,
12
+ isJSON,
13
+ isHexColor,
14
+ isUUID,
15
+ isBase64,
16
+ } from '../validation';
17
+
18
+ describe('String Validation', () => {
19
+ describe('isEmpty', () => {
20
+ it('should detect empty strings', () => {
21
+ expect(isEmpty('')).toBe(true);
22
+ expect(isEmpty(' ')).toBe(true);
23
+ expect(isEmpty('hello')).toBe(false);
24
+ });
25
+ });
26
+
27
+ describe('startsWith', () => {
28
+ it('should check if string starts with substring', () => {
29
+ expect(startsWith('hello', 'he')).toBe(true);
30
+ expect(startsWith('hello', 'ho')).toBe(false);
31
+ });
32
+ });
33
+
34
+ describe('endsWith', () => {
35
+ it('should check if string ends with substring', () => {
36
+ expect(endsWith('hello', 'lo')).toBe(true);
37
+ expect(endsWith('hello', 'he')).toBe(false);
38
+ });
39
+ });
40
+
41
+ describe('contains', () => {
42
+ it('should check if string contains substring', () => {
43
+ expect(contains('hello world', 'world')).toBe(true);
44
+ expect(contains('hello world', 'foo')).toBe(false);
45
+ });
46
+ });
47
+
48
+ describe('isAlpha', () => {
49
+ it('should validate alphabetic strings', () => {
50
+ expect(isAlpha('hello')).toBe(true);
51
+ expect(isAlpha('Hello')).toBe(true);
52
+ expect(isAlpha('hello123')).toBe(false);
53
+ expect(isAlpha('hello_world')).toBe(false);
54
+ });
55
+ });
56
+
57
+ describe('isNumeric', () => {
58
+ it('should validate numeric strings', () => {
59
+ expect(isNumeric('123')).toBe(true);
60
+ expect(isNumeric('123.45')).toBe(false);
61
+ expect(isNumeric('abc')).toBe(false);
62
+ });
63
+ });
64
+
65
+ describe('isAlphanumeric', () => {
66
+ it('should validate alphanumeric strings', () => {
67
+ expect(isAlphanumeric('hello123')).toBe(true);
68
+ expect(isAlphanumeric('hello')).toBe(true);
69
+ expect(isAlphanumeric('123')).toBe(true);
70
+ expect(isAlphanumeric('hello_123')).toBe(false);
71
+ expect(isAlphanumeric('hello-123')).toBe(false);
72
+ });
73
+ });
74
+
75
+ describe('isLowerCase', () => {
76
+ it('should detect lowercase strings', () => {
77
+ expect(isLowerCase('hello')).toBe(true);
78
+ expect(isLowerCase('Hello')).toBe(false);
79
+ expect(isLowerCase('HELLO')).toBe(false);
80
+ });
81
+ });
82
+
83
+ describe('isUpperCase', () => {
84
+ it('should detect uppercase strings', () => {
85
+ expect(isUpperCase('HELLO')).toBe(true);
86
+ expect(isUpperCase('Hello')).toBe(false);
87
+ expect(isUpperCase('hello')).toBe(false);
88
+ });
89
+ });
90
+
91
+ describe('isJSON', () => {
92
+ it('should validate JSON strings', () => {
93
+ expect(isJSON('{"name":"John"}')).toBe(true);
94
+ expect(isJSON('[1,2,3]')).toBe(true);
95
+ expect(isJSON('"hello"')).toBe(true);
96
+ expect(isJSON('not json')).toBe(false);
97
+ expect(isJSON('{invalid}')).toBe(false);
98
+ });
99
+ });
100
+
101
+ describe('isHexColor', () => {
102
+ it('should validate hex color codes', () => {
103
+ expect(isHexColor('#fff')).toBe(true);
104
+ expect(isHexColor('#ffffff')).toBe(true);
105
+ expect(isHexColor('#FFF')).toBe(true);
106
+ expect(isHexColor('#ABC123')).toBe(true);
107
+ expect(isHexColor('fff')).toBe(false);
108
+ expect(isHexColor('#gggggg')).toBe(false);
109
+ });
110
+ });
111
+
112
+ describe('isUUID', () => {
113
+ it('should validate UUID strings', () => {
114
+ expect(isUUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true);
115
+ expect(isUUID('550e8400-e29b-41d4-a716-446655440000')).toBe(true);
116
+ expect(isUUID('not-a-uuid')).toBe(false);
117
+ expect(isUUID('123e4567-e89b-12d3')).toBe(false);
118
+ });
119
+ });
120
+
121
+ describe('isBase64', () => {
122
+ it('should validate base64 strings', () => {
123
+ expect(isBase64('SGVsbG8gV29ybGQ=')).toBe(true);
124
+ expect(isBase64('dGVzdA==')).toBe(true);
125
+ expect(isBase64('not base64')).toBe(false);
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * String Case Conversion Utilities
3
+ */
4
+
5
+ /**
6
+ * 첫 글자를 대문자로 변환
7
+ * @example capitalize('hello world') // 'Hello world'
8
+ */
9
+ export function capitalize(str: string): string {
10
+ if (!str) return str;
11
+ return str.charAt(0).toUpperCase() + str.slice(1);
12
+ }
13
+
14
+ /**
15
+ * 모든 단어의 첫 글자를 대문자로 변환
16
+ * @example capitalizeWords('hello world') // 'Hello World'
17
+ */
18
+ export function capitalizeWords(str: string): string {
19
+ if (!str) return str;
20
+ return str.replace(/\b\w/g, (char) => char.toUpperCase());
21
+ }
22
+
23
+ /**
24
+ * camelCase로 변환
25
+ * @example camelCase('hello world') // 'helloWorld'
26
+ * @example camelCase('hello-world') // 'helloWorld'
27
+ */
28
+ export function camelCase(str: string): string {
29
+ return str
30
+ .toLowerCase()
31
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, char) => char.toUpperCase());
32
+ }
33
+
34
+ /**
35
+ * PascalCase로 변환
36
+ * @example pascalCase('hello world') // 'HelloWorld'
37
+ */
38
+ export function pascalCase(str: string): string {
39
+ return capitalize(camelCase(str));
40
+ }
41
+
42
+ /**
43
+ * snake_case로 변환
44
+ * @example snakeCase('helloWorld') // 'hello_world'
45
+ * @example snakeCase('Hello World') // 'hello_world'
46
+ */
47
+ export function snakeCase(str: string): string {
48
+ return str
49
+ .replace(/([A-Z])/g, '_$1')
50
+ .replace(/[\s-]+/g, '_')
51
+ .replace(/_+/g, '_')
52
+ .replace(/^_/, '')
53
+ .toLowerCase();
54
+ }
55
+
56
+ /**
57
+ * kebab-case로 변환
58
+ * @example kebabCase('helloWorld') // 'hello-world'
59
+ * @example kebabCase('Hello World') // 'hello-world'
60
+ */
61
+ export function kebabCase(str: string): string {
62
+ return str
63
+ .replace(/([A-Z])/g, '-$1')
64
+ .replace(/[\s_]+/g, '-')
65
+ .replace(/-+/g, '-')
66
+ .replace(/^-/, '')
67
+ .toLowerCase();
68
+ }
69
+
70
+ /**
71
+ * CONSTANT_CASE로 변환
72
+ * @example constantCase('helloWorld') // 'HELLO_WORLD'
73
+ */
74
+ export function constantCase(str: string): string {
75
+ return snakeCase(str).toUpperCase();
76
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * String Utilities
3
+ *
4
+ * This module provides utilities for string case conversion, manipulation, and validation.
5
+ */
6
+
7
+ // Case conversion utilities
8
+ export * from './case';
9
+
10
+ // Manipulation utilities
11
+ export * from './manipulation';
12
+
13
+ // Validation utilities
14
+ export * from './validation';
@@ -0,0 +1,124 @@
1
+ /**
2
+ * String Manipulation Utilities
3
+ */
4
+
5
+ /**
6
+ * 문자열을 지정된 길이로 자르고 말줄임표 추가
7
+ * @example truncate('Hello World', 5) // 'Hello...'
8
+ */
9
+ export function truncate(str: string, length: number, suffix: string = '...'): string {
10
+ if (str.length <= length) return str;
11
+ return str.slice(0, length) + suffix;
12
+ }
13
+
14
+ /**
15
+ * 문자열 양쪽 공백 제거 및 연속된 공백을 하나로
16
+ * @example normalizeWhitespace(' hello world ') // 'hello world'
17
+ */
18
+ export function normalizeWhitespace(str: string): string {
19
+ return str.trim().replace(/\s+/g, ' ');
20
+ }
21
+
22
+ /**
23
+ * URL 친화적인 슬러그 생성
24
+ * @example slugify('Hello World!') // 'hello-world'
25
+ * @example slugify('Test123 World') // 'test123-world'
26
+ */
27
+ export function slugify(str: string): string {
28
+ return str
29
+ .toLowerCase()
30
+ .trim()
31
+ .replace(/[^\w\s-]/g, '')
32
+ .replace(/[\s_]+/g, '-')
33
+ .replace(/-+/g, '-')
34
+ .replace(/^-+|-+$/g, '');
35
+ }
36
+
37
+ /**
38
+ * 랜덤 문자열 생성
39
+ * @param length - 생성할 문자열 길이
40
+ * @param charset - 사용할 문자 집합 ('alphanumeric', 'alpha', 'numeric', 'hex')
41
+ */
42
+ export function randomString(
43
+ length: number,
44
+ charset: 'alphanumeric' | 'alpha' | 'numeric' | 'hex' = 'alphanumeric'
45
+ ): string {
46
+ const charsets = {
47
+ alphanumeric: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
48
+ alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
49
+ numeric: '0123456789',
50
+ hex: '0123456789abcdef',
51
+ };
52
+
53
+ const chars = charsets[charset];
54
+ let result = '';
55
+
56
+ for (let i = 0; i < length; i++) {
57
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ /**
64
+ * 문자열 반복
65
+ * @example repeat('ab', 3) // 'ababab'
66
+ */
67
+ export function repeat(str: string, count: number): string {
68
+ return str.repeat(count);
69
+ }
70
+
71
+ /**
72
+ * 문자열 뒤집기
73
+ * @example reverse('hello') // 'olleh'
74
+ */
75
+ export function reverse(str: string): string {
76
+ return str.split('').reverse().join('');
77
+ }
78
+
79
+ /**
80
+ * 문자열에서 HTML 태그 제거
81
+ * @example stripHtml('<p>Hello <strong>World</strong></p>') // 'Hello World'
82
+ */
83
+ export function stripHtml(str: string): string {
84
+ return str.replace(/<[^>]*>/g, '');
85
+ }
86
+
87
+ /**
88
+ * 문자열을 특정 길이로 패딩
89
+ * @example padStart('5', 3, '0') // '005'
90
+ */
91
+ export function padStart(str: string, length: number, fillString: string = ' '): string {
92
+ return str.padStart(length, fillString);
93
+ }
94
+
95
+ /**
96
+ * 문자열을 특정 길이로 패딩 (오른쪽)
97
+ * @example padEnd('5', 3, '0') // '500'
98
+ */
99
+ export function padEnd(str: string, length: number, fillString: string = ' '): string {
100
+ return str.padEnd(length, fillString);
101
+ }
102
+
103
+ /**
104
+ * 템플릿 문자열 치환
105
+ * @example template('Hello {name}!', { name: 'World' }) // 'Hello World!'
106
+ */
107
+ export function template(str: string, values: Record<string, any>): string {
108
+ return str.replace(/\{(\w+)\}/g, (match, key) => {
109
+ return values[key] !== undefined ? String(values[key]) : match;
110
+ });
111
+ }
112
+
113
+ /**
114
+ * 마스킹 (특정 위치의 문자를 *로 변경)
115
+ * @example mask('1234567890', 3, 7) // '123****890'
116
+ */
117
+ export function mask(str: string, start: number, end?: number, maskChar: string = '*'): string {
118
+ const endPos = end ?? str.length;
119
+ return (
120
+ str.slice(0, start) +
121
+ maskChar.repeat(endPos - start) +
122
+ str.slice(endPos)
123
+ );
124
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * String Validation Utilities
3
+ */
4
+
5
+ /**
6
+ * 빈 문자열 또는 공백만 있는지 확인
7
+ * @example isEmpty('') // true
8
+ * @example isEmpty(' ') // true
9
+ * @example isEmpty('hello') // false
10
+ */
11
+ export function isEmpty(str: string): boolean {
12
+ return !str || str.trim().length === 0;
13
+ }
14
+
15
+ /**
16
+ * 문자열이 특정 문자로 시작하는지 확인
17
+ * @example startsWith('hello', 'he') // true
18
+ */
19
+ export function startsWith(str: string, searchString: string): boolean {
20
+ return str.startsWith(searchString);
21
+ }
22
+
23
+ /**
24
+ * 문자열이 특정 문자로 끝나는지 확인
25
+ * @example endsWith('hello', 'lo') // true
26
+ */
27
+ export function endsWith(str: string, searchString: string): boolean {
28
+ return str.endsWith(searchString);
29
+ }
30
+
31
+ /**
32
+ * 문자열에 특정 문자열이 포함되어 있는지 확인
33
+ * @example contains('hello world', 'world') // true
34
+ */
35
+ export function contains(str: string, searchString: string): boolean {
36
+ return str.includes(searchString);
37
+ }
38
+
39
+ /**
40
+ * 알파벳만 포함하는지 확인
41
+ * @example isAlpha('hello') // true
42
+ * @example isAlpha('hello123') // false
43
+ */
44
+ export function isAlpha(str: string): boolean {
45
+ return /^[a-zA-Z]+$/.test(str);
46
+ }
47
+
48
+ /**
49
+ * 숫자만 포함하는지 확인
50
+ * @example isNumeric('123') // true
51
+ * @example isNumeric('123.45') // false
52
+ */
53
+ export function isNumeric(str: string): boolean {
54
+ return /^[0-9]+$/.test(str);
55
+ }
56
+
57
+ /**
58
+ * 알파벳과 숫자만 포함하는지 확인
59
+ * @example isAlphanumeric('hello123') // true
60
+ * @example isAlphanumeric('hello_123') // false
61
+ */
62
+ export function isAlphanumeric(str: string): boolean {
63
+ return /^[a-zA-Z0-9]+$/.test(str);
64
+ }
65
+
66
+ /**
67
+ * 소문자만 포함하는지 확인
68
+ * @example isLowerCase('hello') // true
69
+ * @example isLowerCase('Hello') // false
70
+ */
71
+ export function isLowerCase(str: string): boolean {
72
+ return str === str.toLowerCase() && str !== str.toUpperCase();
73
+ }
74
+
75
+ /**
76
+ * 대문자만 포함하는지 확인
77
+ * @example isUpperCase('HELLO') // true
78
+ * @example isUpperCase('Hello') // false
79
+ */
80
+ export function isUpperCase(str: string): boolean {
81
+ return str === str.toUpperCase() && str !== str.toLowerCase();
82
+ }
83
+
84
+ /**
85
+ * JSON 형식인지 확인
86
+ * @example isJSON('{"name":"John"}') // true
87
+ * @example isJSON('not json') // false
88
+ */
89
+ export function isJSON(str: string): boolean {
90
+ try {
91
+ JSON.parse(str);
92
+ return true;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 16진수 색상 코드인지 확인
100
+ * @example isHexColor('#fff') // true
101
+ * @example isHexColor('#ffffff') // true
102
+ * @example isHexColor('fff') // false
103
+ */
104
+ export function isHexColor(str: string): boolean {
105
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str);
106
+ }
107
+
108
+ /**
109
+ * UUID 형식인지 확인
110
+ * @example isUUID('123e4567-e89b-12d3-a456-426614174000') // true
111
+ */
112
+ export function isUUID(str: string): boolean {
113
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
114
+ }
115
+
116
+ /**
117
+ * Base64 인코딩된 문자열인지 확인
118
+ * @example isBase64('SGVsbG8gV29ybGQ=') // true
119
+ */
120
+ export function isBase64(str: string): boolean {
121
+ try {
122
+ return btoa(atob(str)) === str;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }