@flightlesslabs/dodo-ui 0.8.0 → 0.9.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/dist/index.d.ts +13 -2
- package/dist/index.js +8 -0
- package/dist/stories/components/Form/NumericInput/Events/Events.stories.svelte +126 -0
- package/dist/stories/components/Form/NumericInput/Events/Events.stories.svelte.d.ts +18 -0
- package/dist/stories/components/Form/NumericInput/NumericInput.stories.svelte +79 -0
- package/dist/stories/components/Form/NumericInput/NumericInput.stories.svelte.d.ts +21 -0
- package/dist/stories/components/Form/NumericInput/NumericInput.svelte +161 -0
- package/dist/stories/components/Form/NumericInput/NumericInput.svelte.d.ts +69 -0
- package/dist/stories/components/Form/NumericInput/Validation/Validation.stories.svelte +84 -0
- package/dist/stories/components/Form/NumericInput/Validation/Validation.stories.svelte.d.ts +18 -0
- package/dist/stories/components/Form/PasswordInput/Events/Events.stories.svelte +27 -6
- package/dist/stories/components/Form/PasswordInput/PasswordInput.svelte +5 -3
- package/dist/stories/components/Form/PasswordInput/PasswordInput.svelte.d.ts +7 -1
- package/dist/stories/components/Form/Select/Events/Events.stories.svelte +27 -0
- package/dist/stories/components/Form/Select/Select.svelte +4 -1
- package/dist/stories/components/Form/Select/Select.svelte.d.ts +7 -1
- package/dist/stories/components/Form/TextInput/Events/Events.stories.svelte +27 -0
- package/dist/stories/components/Form/TextInput/TextInput.svelte +5 -3
- package/dist/stories/components/Form/TextInput/TextInput.svelte.d.ts +10 -1
- package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte +1 -1
- package/dist/stories/developer tools/components/DynamicInput/DynamicInput.svelte +23 -5
- package/dist/stories/developer tools/components/DynamicInput/DynamicInput.svelte.d.ts +13 -2
- package/dist/stories/developer tools/components/DynamicInput/Events/Events.stories.svelte +115 -0
- package/dist/stories/developer tools/components/DynamicInput/Events/Events.stories.svelte.d.ts +18 -0
- package/dist/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.d.ts +13 -0
- package/dist/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.js +26 -0
- package/dist/stories/developer tools/helpers/Numbers/cleanNumericString/index.mdx +20 -0
- package/dist/stories/developer tools/helpers/Numbers/isValidNumberValue/index.mdx +71 -0
- package/dist/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.d.ts +51 -0
- package/dist/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.js +96 -0
- package/dist/stories/developer tools/helpers/logger/index.mdx +63 -0
- package/dist/stories/developer tools/helpers/logger/logger.d.ts +24 -0
- package/dist/stories/developer tools/helpers/logger/logger.js +31 -0
- package/package.json +1 -1
- package/src/lib/index.ts +20 -0
- package/src/lib/stories/components/Form/NumericInput/Events/Events.stories.svelte +134 -0
- package/src/lib/stories/components/Form/NumericInput/NumericInput.stories.svelte +84 -0
- package/src/lib/stories/components/Form/NumericInput/NumericInput.svelte +286 -0
- package/src/lib/stories/components/Form/NumericInput/Validation/Validation.stories.svelte +87 -0
- package/src/lib/stories/components/Form/PasswordInput/Events/Events.stories.svelte +28 -6
- package/src/lib/stories/components/Form/PasswordInput/PasswordInput.svelte +15 -3
- package/src/lib/stories/components/Form/Select/Events/Events.stories.svelte +31 -1
- package/src/lib/stories/components/Form/Select/Select.svelte +13 -0
- package/src/lib/stories/components/Form/TextInput/Events/Events.stories.svelte +28 -0
- package/src/lib/stories/components/Form/TextInput/TextInput.svelte +18 -3
- package/src/lib/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte +1 -1
- package/src/lib/stories/developer tools/components/DynamicInput/DynamicInput.svelte +43 -4
- package/src/lib/stories/developer tools/components/DynamicInput/Events/Events.stories.svelte +121 -0
- package/src/lib/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.ts +27 -0
- package/src/lib/stories/developer tools/helpers/Numbers/cleanNumericString/index.mdx +20 -0
- package/src/lib/stories/developer tools/helpers/Numbers/isValidNumberValue/index.mdx +71 -0
- package/src/lib/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.ts +156 -0
- package/src/lib/stories/developer tools/helpers/logger/index.mdx +63 -0
- package/src/lib/stories/developer tools/helpers/logger/logger.ts +46 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
ClipboardEventHandler,
|
|
9
9
|
FocusEventHandler,
|
|
10
10
|
FormEventHandler,
|
|
11
|
+
KeyboardEventHandler,
|
|
11
12
|
} from 'svelte/elements';
|
|
12
13
|
|
|
13
14
|
export type TextInputType = 'text' | 'tel' | 'email' | 'password' | 'url' | 'number';
|
|
@@ -33,6 +34,10 @@
|
|
|
33
34
|
currentTarget: EventTarget & HTMLInputElement;
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
export type TextInputKeyboardEvent = KeyboardEvent & {
|
|
38
|
+
currentTarget: EventTarget & HTMLInputElement;
|
|
39
|
+
};
|
|
40
|
+
|
|
36
41
|
export interface TextInputProps {
|
|
37
42
|
/** Input type? */
|
|
38
43
|
type?: TextInputType;
|
|
@@ -78,6 +83,12 @@
|
|
|
78
83
|
oncopy?: ClipboardEventHandler<HTMLInputElement>;
|
|
79
84
|
/** oncut event handler */
|
|
80
85
|
oncut?: ClipboardEventHandler<HTMLInputElement>;
|
|
86
|
+
/** onkeydown event handler */
|
|
87
|
+
onkeydown?: KeyboardEventHandler<HTMLInputElement>;
|
|
88
|
+
/** onkeypress event handler */
|
|
89
|
+
onkeypress?: KeyboardEventHandler<HTMLInputElement>;
|
|
90
|
+
/** onkeyup event handler */
|
|
91
|
+
onkeyup?: KeyboardEventHandler<HTMLInputElement>;
|
|
81
92
|
}
|
|
82
93
|
</script>
|
|
83
94
|
|
|
@@ -103,6 +114,9 @@
|
|
|
103
114
|
onpaste,
|
|
104
115
|
oncopy,
|
|
105
116
|
oncut,
|
|
117
|
+
onkeydown,
|
|
118
|
+
onkeypress,
|
|
119
|
+
onkeyup,
|
|
106
120
|
before,
|
|
107
121
|
after,
|
|
108
122
|
error = false,
|
|
@@ -116,8 +130,6 @@
|
|
|
116
130
|
|
|
117
131
|
function onfocusMod(e: DynamicInputFocusEvent) {
|
|
118
132
|
const eTyped = e as TextInputFocusEvent;
|
|
119
|
-
focused = true;
|
|
120
|
-
|
|
121
133
|
if (onfocus) {
|
|
122
134
|
onfocus(eTyped);
|
|
123
135
|
}
|
|
@@ -125,7 +137,6 @@
|
|
|
125
137
|
|
|
126
138
|
function onblurMod(e: DynamicInputFocusEvent) {
|
|
127
139
|
const eTyped = e as TextInputFocusEvent;
|
|
128
|
-
focused = false;
|
|
129
140
|
|
|
130
141
|
if (onblur) {
|
|
131
142
|
onblur(eTyped);
|
|
@@ -147,6 +158,7 @@
|
|
|
147
158
|
{id}
|
|
148
159
|
{disabled}
|
|
149
160
|
bind:ref
|
|
161
|
+
bind:focused
|
|
150
162
|
{oninput}
|
|
151
163
|
{onchange}
|
|
152
164
|
onfocus={onfocusMod}
|
|
@@ -154,6 +166,9 @@
|
|
|
154
166
|
{onpaste}
|
|
155
167
|
{oncopy}
|
|
156
168
|
{oncut}
|
|
169
|
+
onkeydown={onkeydown ? (e) => onkeydown(e as TextInputKeyboardEvent) : undefined}
|
|
170
|
+
onkeypress={onkeypress ? (e) => onkeypress(e as TextInputKeyboardEvent) : undefined}
|
|
171
|
+
onkeyup={onkeyup ? (e) => onkeyup(e as TextInputKeyboardEvent) : undefined}
|
|
157
172
|
{placeholder}
|
|
158
173
|
bind:value
|
|
159
174
|
{readonly}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
ClipboardEventHandler,
|
|
7
7
|
FocusEventHandler,
|
|
8
8
|
FormEventHandler,
|
|
9
|
+
KeyboardEventHandler,
|
|
9
10
|
MouseEventHandler,
|
|
10
11
|
} from 'svelte/elements';
|
|
11
12
|
|
|
@@ -21,6 +22,10 @@
|
|
|
21
22
|
currentTarget: EventTarget & (HTMLInputElement | HTMLButtonElement);
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
export type DynamicInputKeyboardEvent = KeyboardEvent & {
|
|
26
|
+
currentTarget: EventTarget & (HTMLInputElement | HTMLButtonElement);
|
|
27
|
+
};
|
|
28
|
+
|
|
24
29
|
export interface DynamicInputProps {
|
|
25
30
|
/** How large should the button be? */
|
|
26
31
|
size?: ComponentSize;
|
|
@@ -30,6 +35,8 @@
|
|
|
30
35
|
ref?: HTMLInputElement | HTMLButtonElement;
|
|
31
36
|
/** Input value */
|
|
32
37
|
value?: string | number;
|
|
38
|
+
/** is focused, set focused */
|
|
39
|
+
focused?: boolean;
|
|
33
40
|
/** variant */
|
|
34
41
|
variant?: DynamicInputVariant;
|
|
35
42
|
/** How round should the border radius be? */
|
|
@@ -64,6 +71,12 @@
|
|
|
64
71
|
oncopy?: ClipboardEventHandler<HTMLInputElement>;
|
|
65
72
|
/** oncut event handler */
|
|
66
73
|
oncut?: ClipboardEventHandler<HTMLInputElement>;
|
|
74
|
+
/** onkeydown event handler */
|
|
75
|
+
onkeydown?: KeyboardEventHandler<HTMLInputElement | HTMLButtonElement>;
|
|
76
|
+
/** onkeypress event handler */
|
|
77
|
+
onkeypress?: KeyboardEventHandler<HTMLInputElement | HTMLButtonElement>;
|
|
78
|
+
/** onkeyup event handler */
|
|
79
|
+
onkeyup?: KeyboardEventHandler<HTMLInputElement | HTMLButtonElement>;
|
|
67
80
|
/** custom Content Formatting for variant button */
|
|
68
81
|
customInputContent?: (value: string | number) => Snippet;
|
|
69
82
|
}
|
|
@@ -83,7 +96,11 @@
|
|
|
83
96
|
onpaste,
|
|
84
97
|
oncopy,
|
|
85
98
|
oncut,
|
|
99
|
+
onkeydown,
|
|
100
|
+
onkeypress,
|
|
101
|
+
onkeyup,
|
|
86
102
|
value = $bindable<string | number>(),
|
|
103
|
+
focused = $bindable<boolean>(),
|
|
87
104
|
placeholder,
|
|
88
105
|
ref = $bindable<HTMLInputElement | HTMLButtonElement>(),
|
|
89
106
|
readonly = false,
|
|
@@ -105,6 +122,22 @@
|
|
|
105
122
|
onclick(e);
|
|
106
123
|
}
|
|
107
124
|
}
|
|
125
|
+
|
|
126
|
+
function onfocusMod(e: DynamicInputFocusEvent) {
|
|
127
|
+
focused = true;
|
|
128
|
+
|
|
129
|
+
if (onfocus) {
|
|
130
|
+
onfocus(e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function onblurMod(e: DynamicInputFocusEvent) {
|
|
135
|
+
focused = false;
|
|
136
|
+
|
|
137
|
+
if (onblur) {
|
|
138
|
+
onblur(e);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
108
141
|
</script>
|
|
109
142
|
|
|
110
143
|
{#if variant === 'button'}
|
|
@@ -119,8 +152,11 @@
|
|
|
119
152
|
].join(' ')}
|
|
120
153
|
bind:this={ref}
|
|
121
154
|
onclick={onclickMod}
|
|
122
|
-
{
|
|
123
|
-
{
|
|
155
|
+
{onkeydown}
|
|
156
|
+
{onkeypress}
|
|
157
|
+
{onkeyup}
|
|
158
|
+
onfocus={onfocusMod}
|
|
159
|
+
onblur={onblurMod}
|
|
124
160
|
{disabled}
|
|
125
161
|
>
|
|
126
162
|
{#if customInputContentTyped}
|
|
@@ -138,11 +174,14 @@
|
|
|
138
174
|
{disabled}
|
|
139
175
|
{oninput}
|
|
140
176
|
{onchange}
|
|
141
|
-
{
|
|
142
|
-
{
|
|
177
|
+
onfocus={onfocusMod}
|
|
178
|
+
onblur={onblurMod}
|
|
143
179
|
{onpaste}
|
|
144
180
|
{oncopy}
|
|
145
181
|
{oncut}
|
|
182
|
+
{onkeydown}
|
|
183
|
+
{onkeypress}
|
|
184
|
+
{onkeyup}
|
|
146
185
|
{placeholder}
|
|
147
186
|
bind:value
|
|
148
187
|
bind:this={ref}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import { storyDynamicInputArgTypes } from '../DynamicInput.stories.svelte';
|
|
4
|
+
import DynamicInput, {
|
|
5
|
+
type DynamicInputFocusEvent,
|
|
6
|
+
type DynamicInputKeyboardEvent,
|
|
7
|
+
} from '../DynamicInput.svelte';
|
|
8
|
+
import type { TextInputClipboardEvent } from '$lib/stories/components/Form/TextInput/TextInput.svelte';
|
|
9
|
+
|
|
10
|
+
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
|
|
11
|
+
const { Story } = defineMeta({
|
|
12
|
+
component: DynamicInput,
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: storyDynamicInputArgTypes,
|
|
15
|
+
args: { value: 'Hello world!' },
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<Story
|
|
20
|
+
name="Input"
|
|
21
|
+
args={{
|
|
22
|
+
oninput: (e: Event) => {
|
|
23
|
+
const target = e.target as HTMLInputElement;
|
|
24
|
+
|
|
25
|
+
console.log('Input Event', target.value);
|
|
26
|
+
},
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<Story
|
|
31
|
+
name="Change"
|
|
32
|
+
args={{
|
|
33
|
+
onchange: (e: Event) => {
|
|
34
|
+
const target = e.target as HTMLInputElement;
|
|
35
|
+
|
|
36
|
+
console.log('onChange Event', target.value);
|
|
37
|
+
},
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<Story
|
|
42
|
+
name="Focus"
|
|
43
|
+
args={{
|
|
44
|
+
onfocus: (e: DynamicInputFocusEvent) => {
|
|
45
|
+
const target = e.target as HTMLInputElement;
|
|
46
|
+
|
|
47
|
+
console.log('onfocus Event', target);
|
|
48
|
+
},
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<Story
|
|
53
|
+
name="Blur"
|
|
54
|
+
args={{
|
|
55
|
+
onblur: (e: DynamicInputFocusEvent) => {
|
|
56
|
+
const target = e.target as HTMLInputElement;
|
|
57
|
+
|
|
58
|
+
console.log('onblur Event', target);
|
|
59
|
+
},
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<Story
|
|
64
|
+
name="Copy"
|
|
65
|
+
args={{
|
|
66
|
+
oncopy: (e: TextInputClipboardEvent) => {
|
|
67
|
+
const target = e.target as HTMLInputElement;
|
|
68
|
+
|
|
69
|
+
console.log('oncopy Event', target);
|
|
70
|
+
},
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
<Story
|
|
75
|
+
name="Cut"
|
|
76
|
+
args={{
|
|
77
|
+
oncut: (e: TextInputClipboardEvent) => {
|
|
78
|
+
const target = e.target as HTMLInputElement;
|
|
79
|
+
|
|
80
|
+
console.log('oncut Event', target);
|
|
81
|
+
},
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<Story
|
|
86
|
+
name="Paste"
|
|
87
|
+
args={{
|
|
88
|
+
onpaste: (e: TextInputClipboardEvent) => {
|
|
89
|
+
const target = e.target as HTMLInputElement;
|
|
90
|
+
|
|
91
|
+
console.log('onpaste Event', target);
|
|
92
|
+
},
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<Story
|
|
97
|
+
name="KeyDown"
|
|
98
|
+
args={{
|
|
99
|
+
onkeydown: (e: DynamicInputKeyboardEvent) => {
|
|
100
|
+
console.log('onkeydown Event', e.key);
|
|
101
|
+
},
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
<Story
|
|
106
|
+
name="KeyPress"
|
|
107
|
+
args={{
|
|
108
|
+
onkeypress: (e: DynamicInputKeyboardEvent) => {
|
|
109
|
+
console.log('onkeypress Event', e.key);
|
|
110
|
+
},
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
<Story
|
|
115
|
+
name="KeyUp"
|
|
116
|
+
args={{
|
|
117
|
+
onkeyup: (e: DynamicInputKeyboardEvent) => {
|
|
118
|
+
console.log('onkeyup Event', e.key);
|
|
119
|
+
},
|
|
120
|
+
}}
|
|
121
|
+
/>
|
package/src/lib/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleans the input string by removing all characters except numeric digits (0-9) and a single decimal point.
|
|
3
|
+
* Only the first decimal point is retained; all subsequent dots are removed.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} input - The input string to be cleaned.
|
|
6
|
+
* @returns {string} - A string containing only digits and at most one decimal point.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* cleanNumericString("abc123.45.67def"); // returns "123.4567"
|
|
10
|
+
* cleanNumericString("a1b2c3"); // returns "123"
|
|
11
|
+
* cleanNumericString("...12.3.4..5"); // returns "12.345"
|
|
12
|
+
*/
|
|
13
|
+
export default function cleanNumericString(input: string): string {
|
|
14
|
+
// Remove all characters except digits and dots
|
|
15
|
+
const filtered = input.replace(/[^0-9.]/g, '');
|
|
16
|
+
|
|
17
|
+
// Split by dot to separate parts
|
|
18
|
+
const parts = filtered.split('.');
|
|
19
|
+
|
|
20
|
+
if (parts.length === 1) {
|
|
21
|
+
// No dot present, just digits
|
|
22
|
+
return parts[0];
|
|
23
|
+
} else {
|
|
24
|
+
// Keep only first dot, join the rest parts without dots
|
|
25
|
+
return parts[0] + '.' + parts.slice(1).join('');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Source } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
# cleanNumericString
|
|
4
|
+
|
|
5
|
+
Cleans the input string by removing all characters except numeric digits (0-9) and a single decimal point.
|
|
6
|
+
Only the first decimal point is retained; all subsequent dots are removed.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Basic Usage
|
|
11
|
+
|
|
12
|
+
<Source
|
|
13
|
+
dark
|
|
14
|
+
language="ts"
|
|
15
|
+
code={`
|
|
16
|
+
import { cleanNumericString } from '@flightlesslabs/dodo-ui';
|
|
17
|
+
|
|
18
|
+
const result = cleanNumericString("abc123.45.67def"); // result === "123.4567"
|
|
19
|
+
`}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Source } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
# isValidNumberValue
|
|
4
|
+
|
|
5
|
+
Utility function to validate whether a string is a valid number based on customizable settings including minimum, maximum, decimal places, and allowance of negative numbers.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Basic Usage
|
|
10
|
+
|
|
11
|
+
Validates a string as a number with default settings (no decimals allowed, no negatives allowed, strict checking):
|
|
12
|
+
|
|
13
|
+
<Source
|
|
14
|
+
dark
|
|
15
|
+
language="ts"
|
|
16
|
+
code={`
|
|
17
|
+
import { isValidNumberValue } from '@flightlesslabs/dodo-ui';
|
|
18
|
+
|
|
19
|
+
const result = isValidNumberValue("123");
|
|
20
|
+
console.log(result); // true
|
|
21
|
+
`}
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## With Settings
|
|
27
|
+
|
|
28
|
+
Customize validation rules:
|
|
29
|
+
|
|
30
|
+
- `min` — minimum allowed value (inclusive)
|
|
31
|
+
- `max` — maximum allowed value (inclusive)
|
|
32
|
+
- `allowNegativeValues` — whether negatives are allowed
|
|
33
|
+
- `decimalPlaces` — max decimal places allowed
|
|
34
|
+
- `log` — enable detailed logging
|
|
35
|
+
- `strict` — Strict checking, Full text - keep it enabled, Partial text - keep it disabled
|
|
36
|
+
|
|
37
|
+
<Source
|
|
38
|
+
dark
|
|
39
|
+
language="ts"
|
|
40
|
+
code={`
|
|
41
|
+
import { isValidNumberValue } from '@flightlesslabs/dodo-ui';
|
|
42
|
+
|
|
43
|
+
const result1 = isValidNumberValue("123.456", { decimalPlaces: 3, log: true });
|
|
44
|
+
const result2 = isValidNumberValue("-50", { allowNegativeValues: true, min: -100 });
|
|
45
|
+
const result3 = isValidNumberValue("200", { max: 150 });
|
|
46
|
+
|
|
47
|
+
const result4 = isValidNumberValue("-", { allowNegativeValues: true });
|
|
48
|
+
const result5 = isValidNumberValue("-", { allowNegativeValues: true, strict: false });
|
|
49
|
+
|
|
50
|
+
console.log(result1); // true
|
|
51
|
+
console.log(result2); // true
|
|
52
|
+
console.log(result3); // false (exceeds max)
|
|
53
|
+
console.log(result4); // false (fails in strict check)
|
|
54
|
+
console.log(result5); // true (pass in strict check)
|
|
55
|
+
`}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Type Definition
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
export type IsValidNumberValueSettings = {
|
|
64
|
+
min?: number;
|
|
65
|
+
max?: number;
|
|
66
|
+
allowNegativeValues?: boolean;
|
|
67
|
+
decimalPlaces?: number;
|
|
68
|
+
log?: boolean;
|
|
69
|
+
strict?: boolean;
|
|
70
|
+
};
|
|
71
|
+
```
|
package/src/lib/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import createLogger from '../../logger/logger.js';
|
|
2
|
+
|
|
3
|
+
export type IsValidNumberValueSettings = {
|
|
4
|
+
/**
|
|
5
|
+
* Minimum allowed numeric value (inclusive).
|
|
6
|
+
*/
|
|
7
|
+
min?: number;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Maximum allowed numeric value (inclusive).
|
|
11
|
+
*/
|
|
12
|
+
max?: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether negative numbers are allowed.
|
|
16
|
+
* Defaults to false.
|
|
17
|
+
*/
|
|
18
|
+
allowNegativeValues?: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maximum allowed decimal places.
|
|
22
|
+
* Defaults to 0 (no decimals allowed).
|
|
23
|
+
*/
|
|
24
|
+
decimalPlaces?: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether to enable logging.
|
|
28
|
+
* Defaults to false.
|
|
29
|
+
*/
|
|
30
|
+
log?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Strict checking, Full text - keep it enabled, Partial text - keep it disabled
|
|
34
|
+
* Defaults to true.
|
|
35
|
+
*/
|
|
36
|
+
strict?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Checks if the input string is a valid numeric string.
|
|
41
|
+
* Allows integers, decimals, negative numbers, and scientific notation.
|
|
42
|
+
*
|
|
43
|
+
* @param str - Input string to validate.
|
|
44
|
+
* @returns True if string is numeric, false otherwise.
|
|
45
|
+
*/
|
|
46
|
+
function isNumericString(str: string): boolean {
|
|
47
|
+
if (typeof str !== 'string') return false;
|
|
48
|
+
const trimmed = str.trim();
|
|
49
|
+
if (trimmed === '') return false;
|
|
50
|
+
const num = Number(trimmed);
|
|
51
|
+
return !isNaN(num) && isFinite(num);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validates whether a string represents a valid number based on provided settings.
|
|
56
|
+
*
|
|
57
|
+
* @param value - The string to validate as a number.
|
|
58
|
+
* @param settings - Optional validation settings.
|
|
59
|
+
* @param settings.min - Minimum allowed value (inclusive).
|
|
60
|
+
* @param settings.max - Maximum allowed value (inclusive).
|
|
61
|
+
* @param settings.allowNegativeValues - Whether negative numbers are allowed.
|
|
62
|
+
* @param settings.decimalPlaces - Maximum allowed decimal places.
|
|
63
|
+
* @param settings.log - Whether to enable detailed logging.
|
|
64
|
+
* @param settings.strict - Whether to enable Strict checking, Full text - keep it enabled, Partial text - keep it disabled
|
|
65
|
+
*
|
|
66
|
+
* @returns True if the value is a valid number according to the settings; otherwise false.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* isValidNumberValue("123.45", { decimalPlaces: 2, log: true });
|
|
71
|
+
* isValidNumberValue("-50", { allowNegativeValues: true, min: -100 });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export default function isValidNumberValue(
|
|
75
|
+
value: string,
|
|
76
|
+
settings?: IsValidNumberValueSettings,
|
|
77
|
+
): boolean {
|
|
78
|
+
const allowNegativeValues = settings?.allowNegativeValues || false;
|
|
79
|
+
const decimalPlaces = settings?.decimalPlaces || 0;
|
|
80
|
+
const valueFormatted = value.trim();
|
|
81
|
+
const log = settings?.log || false;
|
|
82
|
+
const logger = createLogger({
|
|
83
|
+
label: 'isValidNumberValue',
|
|
84
|
+
show: log,
|
|
85
|
+
});
|
|
86
|
+
const min = settings?.min;
|
|
87
|
+
const max = settings?.max;
|
|
88
|
+
const strict = settings?.strict === false ? false : true;
|
|
89
|
+
|
|
90
|
+
logger.info(`Validating value: "${valueFormatted}"`);
|
|
91
|
+
|
|
92
|
+
if (strict && !isNumericString(valueFormatted)) {
|
|
93
|
+
logger.warn('Invalid input: strict checking is enaabled and the text failed the checks');
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (valueFormatted === '') {
|
|
98
|
+
logger.info('Valid input: Blank value');
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (allowNegativeValues && valueFormatted === '-') {
|
|
103
|
+
logger.info(`Valid input: Single '-' sign allowed as a negative placeholder.`);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!decimalPlaces && valueFormatted.includes('.')) {
|
|
108
|
+
logger.warn('Invalid input: Decimals not allowed');
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const dotCount = (valueFormatted.match(/\./g) || []).length;
|
|
113
|
+
|
|
114
|
+
if (dotCount > 1) {
|
|
115
|
+
logger.warn('Invalid input: More than one decimal point detected.');
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const parts = valueFormatted.split('.');
|
|
120
|
+
|
|
121
|
+
if (parts[1]?.length > decimalPlaces) {
|
|
122
|
+
logger.warn(
|
|
123
|
+
`Invalid input: Number has ${parts[1].length} decimal places, exceeding the allowed limit of ${decimalPlaces}.`,
|
|
124
|
+
);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!isNumericString(valueFormatted)) {
|
|
129
|
+
logger.warn('Invalid input: String is not a valid number.');
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const numericValue = Number(valueFormatted);
|
|
134
|
+
|
|
135
|
+
if (!allowNegativeValues && numericValue < 0) {
|
|
136
|
+
logger.warn('Invalid input: Negative numbers are not allowed.');
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (min !== undefined && numericValue < min) {
|
|
141
|
+
logger.warn(
|
|
142
|
+
`Invalid input: Number ${numericValue} is less than the minimum allowed value of ${min}.`,
|
|
143
|
+
);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (max !== undefined && numericValue > max) {
|
|
148
|
+
logger.warn(
|
|
149
|
+
`Invalid input: Number ${numericValue} exceeds the maximum allowed value of ${max}.`,
|
|
150
|
+
);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
logger.info('Validation passed: Input is a valid number according to the provided settings.');
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Source } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
# Logger Utility
|
|
4
|
+
|
|
5
|
+
A customizable logger function that supports:
|
|
6
|
+
|
|
7
|
+
- Log levels (`info`, `warn`, `error`)
|
|
8
|
+
- Optional labels
|
|
9
|
+
- A `show` flag to toggle logging
|
|
10
|
+
- Console output with timestamps
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
Import the logger creation function:
|
|
15
|
+
|
|
16
|
+
<Source dark language="ts" code={`import { createLogger } from '@flightlesslabs/dodo-ui';`} />
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
Create a logger and log different types of messages:
|
|
21
|
+
|
|
22
|
+
<Source
|
|
23
|
+
dark
|
|
24
|
+
language="ts"
|
|
25
|
+
code={`const logger = createLogger({
|
|
26
|
+
label: 'AuthService',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
logger.info('User logged in');
|
|
30
|
+
logger.warn('Token is about to expire');
|
|
31
|
+
logger.error('Login failed', new Error('Invalid credentials'));
|
|
32
|
+
`}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
## Disable Logging
|
|
36
|
+
|
|
37
|
+
You can use the \`show\` flag to completely silence the logger (e.g., in production):
|
|
38
|
+
|
|
39
|
+
<Source
|
|
40
|
+
dark
|
|
41
|
+
language="ts"
|
|
42
|
+
code={`const logger = createLogger({
|
|
43
|
+
show: false,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
logger.info('This will not be logged');
|
|
47
|
+
`}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
## Level Filtering
|
|
51
|
+
|
|
52
|
+
The logger will only show messages **at or above** the configured `level`:
|
|
53
|
+
|
|
54
|
+
<Source
|
|
55
|
+
dark
|
|
56
|
+
language="ts"
|
|
57
|
+
code={`const logger = createLogger();
|
|
58
|
+
|
|
59
|
+
logger.info('This will NOT show');
|
|
60
|
+
logger.warn('This WILL show');
|
|
61
|
+
logger.error('This WILL show');
|
|
62
|
+
`}
|
|
63
|
+
/>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type LogLevel = 'info' | 'warn' | 'error';
|
|
2
|
+
|
|
3
|
+
export interface LoggerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Optional label to prefix log entries.
|
|
6
|
+
*/
|
|
7
|
+
label?: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Whether to actually show logs.
|
|
11
|
+
* Defaults to true.
|
|
12
|
+
*/
|
|
13
|
+
show?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Formats the log message with timestamp, label, and level.
|
|
18
|
+
*/
|
|
19
|
+
export function formatPrefix(level: LogLevel, label?: string): string {
|
|
20
|
+
const timestamp = new Date().toISOString();
|
|
21
|
+
const labelPart = label ? `[${label}]` : '';
|
|
22
|
+
return `[${timestamp}] ${labelPart} [${level.toUpperCase()}]`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a logger that logs every call unless disabled by show=false.
|
|
27
|
+
*/
|
|
28
|
+
export default function createLogger(options: LoggerOptions = {}) {
|
|
29
|
+
const { label, show = true } = options;
|
|
30
|
+
|
|
31
|
+
function shouldLog() {
|
|
32
|
+
return show;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
info: (msg: unknown, ...optionalParams: unknown[]) => {
|
|
37
|
+
if (shouldLog()) console.info(formatPrefix('info', label), msg, ...optionalParams);
|
|
38
|
+
},
|
|
39
|
+
warn: (msg: unknown, ...optionalParams: unknown[]) => {
|
|
40
|
+
if (shouldLog()) console.warn(formatPrefix('warn', label), msg, ...optionalParams);
|
|
41
|
+
},
|
|
42
|
+
error: (msg: unknown, ...optionalParams: unknown[]) => {
|
|
43
|
+
if (shouldLog()) console.error(formatPrefix('error', label), msg, ...optionalParams);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|