@djcali570/component-lib 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,412 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+
4
+ /**
5
+ * Input5 v0.0.1
6
+ *
7
+ * Added read only option
8
+ * Allow 4 digits after decimal for floats
9
+ */
10
+
11
+ let {
12
+ colorScheme = {
13
+ mainTextColor: '#D6D6D6',
14
+ mainBgColor: '#121212',
15
+ titleColor: '#989A9A',
16
+ borderColor: '#262626',
17
+ focusedColor: '#4787ac',
18
+ clearColor: '#989A9A',
19
+ clearHoverColor: '#1F2023',
20
+ counterTextColor: '#4787ac',
21
+ counterBgColor: '#1F2023'
22
+ },
23
+ name = 'input',
24
+ title = 'Title',
25
+ value = $bindable(),
26
+ type = 'text',
27
+ disabled = false,
28
+ showCounter = false,
29
+ maxlength,
30
+ uppercase,
31
+ autocomplete = 'off',
32
+ validator = 'none',
33
+ textArea = false,
34
+ textAreaHeight = 80,
35
+ readonly = false,
36
+ onUpdate = (value) => {}
37
+ }: {
38
+ colorScheme?: any;
39
+ name?: string;
40
+ title?: string;
41
+ value?: any;
42
+ type?: 'text' | 'number' | 'password';
43
+ disabled?: boolean;
44
+ showCounter?: boolean;
45
+ maxlength?: number;
46
+ uppercase?: boolean;
47
+ autocomplete?: AutoFill | null | undefined;
48
+ validator?: 'none' | 'number' | 'float' | 'phone' | 'letter';
49
+ textArea?: boolean;
50
+ textAreaHeight?: number;
51
+ readonly?: boolean | null | undefined;
52
+ onUpdate?: (value: string) => void;
53
+ } = $props();
54
+
55
+ const id = generateRandomString();
56
+ let count = $state(0);
57
+ let inputOldValue: string = $state('');
58
+ let inputOldCursor: number | null = $state(null);
59
+ let inputElement: HTMLInputElement | null = $state(null);
60
+ let keydownHandler: (e: Event) => void = $state(() => {});
61
+ let inputHandler: (e: Event) => void = $state(() => {});
62
+
63
+ /**
64
+ * Regex Validators
65
+ */
66
+ const floatPattern: RegExp = /^[0-9]{0,10}(\.[0-9]{0,4})?$/;
67
+ const numberOnlyPattern: RegExp = /^[0-9]*$/;
68
+ const phoneNumberPattern: RegExp = /^[0-9]{0,10}$/;
69
+ const letterOnlyPattern: RegExp = /^[A-Za-z]*$/;
70
+
71
+ $effect(() => {
72
+ if (value) {
73
+ count = value.length;
74
+ if (uppercase) {
75
+ if (typeof value === 'string') {
76
+ value = value.toUpperCase();
77
+ }
78
+ }
79
+ } else {
80
+ count = 0;
81
+ }
82
+ });
83
+
84
+ $effect(() => {
85
+ if (value) {
86
+ onUpdate(value);
87
+ }
88
+ });
89
+
90
+ /**
91
+ * Keydown Handlers
92
+ */
93
+ const mainKeyDownHandler = (e: Event) => {
94
+ const el = e.target as HTMLInputElement;
95
+ inputOldValue = el.value;
96
+ inputOldCursor = el.selectionEnd;
97
+ };
98
+
99
+ /**
100
+ * Input Handlers
101
+ */
102
+ const mainInputInputHandler = (e: Event, exp: RegExp) => {
103
+ let newValue: string;
104
+
105
+ // Validation for phone input
106
+ if (validator === 'phone') {
107
+ if (!value) return value;
108
+
109
+ newValue = value.replace(/[^\d]/g, '');
110
+
111
+ if (newValue === '') {
112
+ value = '';
113
+ }
114
+
115
+ const phoneLength = newValue.length;
116
+
117
+ if (phoneLength < 4) {
118
+ value = `${newValue.slice(0, 3)}`;
119
+ return newValue;
120
+ }
121
+
122
+ if (phoneLength < 7) {
123
+ value = `${newValue.slice(0, 3)}-${newValue.slice(3)}`;
124
+ return newValue;
125
+ }
126
+ if (phoneLength > 7) {
127
+ value = `${newValue.slice(0, 3)}-${newValue.slice(3)}-`;
128
+ }
129
+
130
+ value = `${newValue.slice(0, 3)}-${newValue.slice(3, 6)}-${newValue.slice(6, 10)}`;
131
+ } else {
132
+ // Validation for others
133
+ newValue = value;
134
+ if (newValue.match(exp)) {
135
+ value = newValue;
136
+ } else {
137
+ value = inputOldValue;
138
+ }
139
+ }
140
+ };
141
+
142
+ /**
143
+ * Add the event listeners for validation
144
+ */
145
+ onMount(() => {
146
+ inputElement = document.querySelector(`#input-${id}`);
147
+ if (inputElement && validator !== 'none') {
148
+ inputElement.addEventListener('keydown', keydownHandler);
149
+ inputElement.addEventListener('input', inputHandler);
150
+ }
151
+ });
152
+
153
+ /**
154
+ * Assign function to validator
155
+ */
156
+ if (
157
+ validator === 'float' ||
158
+ validator === 'number' ||
159
+ validator === 'phone' ||
160
+ validator === 'letter'
161
+ ) {
162
+ keydownHandler = mainKeyDownHandler;
163
+ }
164
+
165
+ if (validator === 'float') {
166
+ inputHandler = (e: Event) => mainInputInputHandler(e, floatPattern);
167
+ }
168
+
169
+ if (validator === 'number') {
170
+ inputHandler = (e: Event) => mainInputInputHandler(e, numberOnlyPattern);
171
+ }
172
+
173
+ if (validator === 'phone') {
174
+ inputHandler = (e: Event) => mainInputInputHandler(e, phoneNumberPattern);
175
+ }
176
+
177
+ if (validator === 'phone') {
178
+ inputHandler = (e: Event) => mainInputInputHandler(e, phoneNumberPattern);
179
+ }
180
+
181
+ if (validator === 'letter') {
182
+ inputHandler = (e: Event) => mainInputInputHandler(e, letterOnlyPattern);
183
+ }
184
+
185
+ /**
186
+ * Generate a random string so that each
187
+ * input will have a unique id in the dom
188
+ */
189
+ function generateRandomString() {
190
+ const length = 6;
191
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
192
+
193
+ let result = '';
194
+ for (let i = 0; i < length; i++) {
195
+ const randomIndex = Math.floor(Math.random() * characters.length);
196
+ result += characters.charAt(randomIndex);
197
+ }
198
+
199
+ return result;
200
+ }
201
+
202
+ function clearField() {
203
+ value = '';
204
+ count = 0;
205
+ }
206
+ </script>
207
+
208
+ <div
209
+ class="air5__input__container"
210
+ style="
211
+ --air5__mainTextColor: {colorScheme.mainTextColor};
212
+ --air5__mainBgColor: {colorScheme.mainBgColor};
213
+ --air5__titleColor: {colorScheme.titleColor};
214
+ --air5__borderColor: {colorScheme.borderColor};
215
+ --air5__focusedColor: {colorScheme.focusedColor};
216
+ --air5__clearColor: {colorScheme.clearColor};
217
+ --air5__clearHoverColor: {colorScheme.clearHoverColor};
218
+ --air5__counterTextColor: {colorScheme.counterTextColor};
219
+ --air5__counterBgColor: {colorScheme.counterBgColor};
220
+ --air5__textAreaHeight: {textAreaHeight}px;
221
+ "
222
+ >
223
+ <label for="input-{id}">
224
+ <div>
225
+ <div class="air5__title">{title}</div>
226
+ <div class="air5__input">
227
+ {#if textArea}
228
+ <textarea
229
+ id="input-{id}"
230
+ {name}
231
+ bind:value
232
+ {disabled}
233
+ aria-label={name}
234
+ {maxlength}
235
+ {autocomplete}
236
+ {readonly}
237
+ ></textarea>
238
+ {:else}
239
+ <input
240
+ id="input-{id}"
241
+ {name}
242
+ bind:value
243
+ {disabled}
244
+ aria-label={name}
245
+ {maxlength}
246
+ {autocomplete}
247
+ {type}
248
+ {readonly}
249
+ />
250
+ {/if}
251
+ <div class="air5__options__container">
252
+ <div class="air5__clear__container">
253
+ {#if value && !disabled}
254
+ <button
255
+ aria-label="clear {title}"
256
+ id="air5__clear__btn-{id}"
257
+ type="button"
258
+ tabindex="-1"
259
+ onclick={clearField}
260
+ ><div class="air5__clear__button__svg">
261
+ <svg
262
+ version="1.1"
263
+ id="Layer_1"
264
+ xmlns="http://www.w3.org/2000/svg"
265
+ xmlns:xlink="http://www.w3.org/1999/xlink"
266
+ x="0px"
267
+ y="0px"
268
+ viewBox="0 0 100 100"
269
+ style="enable-background:new 0 0 100 100;"
270
+ xml:space="preserve"
271
+ >
272
+ <path
273
+ fill="currentColor"
274
+ d="M87.8,87.4c-0.7,0.7-1.6,1.1-2.6,1.1c-0.9,0-1.8-0.4-2.5-1.1L50,55.1L17.8,87.8c-0.7,0.7-1.6,1.1-2.6,1.1
275
+ c-0.9,0-1.8-0.3-2.5-1.1c-1.4-1.4-1.4-3.7,0-5.1L44.9,50L12.2,17.8c-1.4-1.4-1.4-3.7,0-5.1c1.4-1.4,3.7-1.4,5.1,0L50,44.9l32.3-32.6
276
+ c1.4-1.4,3.7-1.4,5.1,0c1.4,1.4,1.4,3.7,0,5.1L55.1,50l32.6,32.3C89.2,83.7,89.2,86,87.8,87.4z"
277
+ />
278
+ </svg>
279
+ </div></button
280
+ >
281
+ {/if}
282
+ </div>
283
+ {#if showCounter}
284
+ <div class="air5__counter__container">
285
+ <div class="air5__counter">{count}/{maxlength}</div>
286
+ </div>
287
+ {/if}
288
+ </div>
289
+ </div>
290
+ </div>
291
+ </label>
292
+ </div>
293
+
294
+ <style>
295
+ .air5__input__container {
296
+ display: flex;
297
+ flex-direction: column;
298
+ width: 100%;
299
+ min-height: 64px;
300
+ margin: 0;
301
+ line-height: 1.25rem;
302
+ border-radius: 0.5rem;
303
+ background-color: var(--air5__mainBgColor);
304
+ box-shadow: inset 0 0 0 1px var(--air5__borderColor);
305
+ border: none;
306
+ font-family: Arial, sans-serif;
307
+ }
308
+
309
+ .air5__input__container:focus-within {
310
+ box-shadow: inset 0 0 0 2px var(--air5__focusedColor);
311
+ }
312
+
313
+ .air5__title {
314
+ font-size: 0.75rem;
315
+ padding-inline: 0.75rem;
316
+ padding-top: 0.5rem;
317
+ font-family: inherit;
318
+ color: var(--air5__titleColor);
319
+ }
320
+
321
+ .air5__input {
322
+ display: grid;
323
+ grid-template-columns: 1fr min-content;
324
+ height: 24px;
325
+ padding-inline: 0.75rem;
326
+ color: var(--air5__mainTextColor);
327
+ }
328
+ input {
329
+ width: 100%;
330
+ outline: none;
331
+ font-family: inherit;
332
+ height: 24px;
333
+ color: var(--air5__mainTextColor);
334
+ background-color: var(--air5__mainBgColor);
335
+ border: none;
336
+ padding: 0px;
337
+ }
338
+
339
+ textarea {
340
+ background-color: transparent;
341
+ border: none;
342
+ font-family: inherit;
343
+ padding: 0;
344
+ line-height: inherit;
345
+ width: 100%;
346
+ height: var(--air5__textAreaHeight);
347
+ outline: none;
348
+ color: inherit;
349
+ appearance: none;
350
+ resize: none;
351
+ -webkit-appearance: none;
352
+ }
353
+
354
+ input:autofill {
355
+ box-shadow: 0 0 0 50px var(--air5__mainBgColor) inset;
356
+ -webkit-text-fill-color: var(--air5__mainTextColor);
357
+ }
358
+ .air5__options__container {
359
+ display: flex;
360
+ flex-direction: column;
361
+ min-width: 2rem;
362
+ }
363
+ .air5__clear__container {
364
+ display: flex;
365
+ height: 100%;
366
+ width: 100%;
367
+ justify-content: flex-end;
368
+ align-items: flex-start;
369
+ }
370
+
371
+ .air5__input__container:focus-within .air5__clear__container button {
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: center;
375
+ }
376
+
377
+ .air5__clear__container button {
378
+ display: none;
379
+ height: 24px;
380
+ width: 24px;
381
+ border-radius: calc(infinity * 1px);
382
+ border: none;
383
+ cursor: pointer;
384
+ background-color: transparent;
385
+ }
386
+
387
+ .air5__clear__container button:hover {
388
+ background-color: var(--air5__clearHoverColor);
389
+ }
390
+
391
+ .air5__clear__button__svg {
392
+ height: 14px;
393
+ width: 14px;
394
+ color: var(--air5__clearColor);
395
+ }
396
+
397
+ .air5__counter__container {
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: flex-end;
401
+ padding-bottom: 0.5rem;
402
+ }
403
+
404
+ .air5__counter {
405
+ font-size: 0.65rem;
406
+ padding: 0.25rem;
407
+ border-radius: 0.75rem;
408
+ font-family: inherit;
409
+ color: var(--air5__counterTextColor);
410
+ background-color: var(--air5__counterBgColor);
411
+ }
412
+ </style>
@@ -0,0 +1,20 @@
1
+ type $$ComponentProps = {
2
+ colorScheme?: any;
3
+ name?: string;
4
+ title?: string;
5
+ value?: any;
6
+ type?: 'text' | 'number' | 'password';
7
+ disabled?: boolean;
8
+ showCounter?: boolean;
9
+ maxlength?: number;
10
+ uppercase?: boolean;
11
+ autocomplete?: AutoFill | null | undefined;
12
+ validator?: 'none' | 'number' | 'float' | 'phone' | 'letter';
13
+ textArea?: boolean;
14
+ textAreaHeight?: number;
15
+ readonly?: boolean | null | undefined;
16
+ onUpdate?: (value: string) => void;
17
+ };
18
+ declare const Input5: import("svelte").Component<$$ComponentProps, {}, "value">;
19
+ type Input5 = ReturnType<typeof Input5>;
20
+ export default Input5;