@samuel-charpentier/sform 0.0.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/LICENSE +21 -0
- package/README.md +405 -0
- package/dist/Sform/Sfield.svelte +164 -0
- package/dist/Sform/Sfield.svelte.d.ts +4 -0
- package/dist/Sform/Sform.svelte +101 -0
- package/dist/Sform/Sform.svelte.d.ts +51 -0
- package/dist/Sform/context.svelte.d.ts +3 -0
- package/dist/Sform/context.svelte.js +65 -0
- package/dist/Sform/index.d.ts +5 -0
- package/dist/Sform/index.js +7 -0
- package/dist/Sform/inputs/ButtonInput.svelte +75 -0
- package/dist/Sform/inputs/ButtonInput.svelte.d.ts +32 -0
- package/dist/Sform/inputs/CheckboxGroupInput.svelte +81 -0
- package/dist/Sform/inputs/CheckboxGroupInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/CheckboxInput.svelte +47 -0
- package/dist/Sform/inputs/CheckboxInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/MaskedInput.svelte +220 -0
- package/dist/Sform/inputs/MaskedInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/NumberInput.svelte +116 -0
- package/dist/Sform/inputs/NumberInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/PasswordInput.svelte +142 -0
- package/dist/Sform/inputs/PasswordInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/RadioInput.svelte +81 -0
- package/dist/Sform/inputs/RadioInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/RangeInput.svelte +67 -0
- package/dist/Sform/inputs/RangeInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/SelectInput.svelte +36 -0
- package/dist/Sform/inputs/SelectInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/TextInput.svelte +61 -0
- package/dist/Sform/inputs/TextInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/TextareaInput.svelte +56 -0
- package/dist/Sform/inputs/TextareaInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/ToggleInput.svelte +101 -0
- package/dist/Sform/inputs/ToggleInput.svelte.d.ts +4 -0
- package/dist/Sform/inputs/ToggleOptionsInput.svelte +118 -0
- package/dist/Sform/inputs/ToggleOptionsInput.svelte.d.ts +4 -0
- package/dist/Sform/sform.css +628 -0
- package/dist/Sform/types.d.ts +610 -0
- package/dist/Sform/types.js +5 -0
- package/dist/Sform/utils/mask.d.ts +68 -0
- package/dist/Sform/utils/mask.js +155 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask Utility for Sform
|
|
3
|
+
* Native implementation for input masking without external dependencies
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_TOKENS = {
|
|
6
|
+
'9': { pattern: /[0-9]/ },
|
|
7
|
+
'#': { pattern: /[0-9]/ },
|
|
8
|
+
a: { pattern: /[a-zA-Z]/ },
|
|
9
|
+
A: { pattern: /[a-zA-Z]/, transform: (c) => c.toUpperCase() },
|
|
10
|
+
'*': { pattern: /[a-zA-Z0-9]/ }
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Apply mask to a value
|
|
14
|
+
*
|
|
15
|
+
* Algorithm:
|
|
16
|
+
* 1. Walk through the mask and input simultaneously
|
|
17
|
+
* 2. For each mask position:
|
|
18
|
+
* - If it's a wildcard, try to find a matching input character
|
|
19
|
+
* - If it's a literal, add it only if we have more input to process
|
|
20
|
+
* 3. Stop when input is exhausted
|
|
21
|
+
*/
|
|
22
|
+
export function applyMask(value, options, cursorPosition = value.length) {
|
|
23
|
+
const { mask, tokens = DEFAULT_TOKENS, placeholder = '_', showPlaceholder = false } = options;
|
|
24
|
+
let masked = '';
|
|
25
|
+
let raw = '';
|
|
26
|
+
let valueIndex = 0;
|
|
27
|
+
for (let maskIndex = 0; maskIndex < mask.length; maskIndex++) {
|
|
28
|
+
const maskChar = mask[maskIndex];
|
|
29
|
+
const token = tokens[maskChar];
|
|
30
|
+
if (token) {
|
|
31
|
+
// This is a wildcard position - find next matching input character
|
|
32
|
+
let found = false;
|
|
33
|
+
while (valueIndex < value.length && !found) {
|
|
34
|
+
const inputChar = value[valueIndex];
|
|
35
|
+
valueIndex++;
|
|
36
|
+
if (token.pattern.test(inputChar)) {
|
|
37
|
+
const transformedChar = token.transform ? token.transform(inputChar) : inputChar;
|
|
38
|
+
masked += transformedChar;
|
|
39
|
+
raw += transformedChar;
|
|
40
|
+
found = true;
|
|
41
|
+
}
|
|
42
|
+
// If char doesn't match, skip it and try next
|
|
43
|
+
}
|
|
44
|
+
if (!found) {
|
|
45
|
+
// No more matching input
|
|
46
|
+
if (showPlaceholder) {
|
|
47
|
+
masked += placeholder;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Stop processing - no more input for wildcards
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// This is a literal character
|
|
57
|
+
if (showPlaceholder) {
|
|
58
|
+
// Always add literals when showing placeholders
|
|
59
|
+
masked += maskChar;
|
|
60
|
+
// Skip if input matches literal
|
|
61
|
+
if (valueIndex < value.length && value[valueIndex] === maskChar) {
|
|
62
|
+
valueIndex++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Only add literals if we have more wildcard matches coming
|
|
67
|
+
// Look ahead to see if there's more input that matches future wildcards
|
|
68
|
+
let hasMoreMatchingInput = false;
|
|
69
|
+
let tempValueIndex = valueIndex;
|
|
70
|
+
// Skip any literal in input that matches
|
|
71
|
+
if (tempValueIndex < value.length && value[tempValueIndex] === maskChar) {
|
|
72
|
+
tempValueIndex++;
|
|
73
|
+
}
|
|
74
|
+
// Look for any remaining input that could match future wildcards
|
|
75
|
+
for (let futureIdx = maskIndex + 1; futureIdx < mask.length; futureIdx++) {
|
|
76
|
+
const futureMaskChar = mask[futureIdx];
|
|
77
|
+
const futureToken = tokens[futureMaskChar];
|
|
78
|
+
if (futureToken) {
|
|
79
|
+
// Check if any remaining input matches this wildcard
|
|
80
|
+
for (let vi = tempValueIndex; vi < value.length; vi++) {
|
|
81
|
+
if (futureToken.pattern.test(value[vi])) {
|
|
82
|
+
hasMoreMatchingInput = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break; // Only check up to the next wildcard
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (hasMoreMatchingInput) {
|
|
90
|
+
masked += maskChar;
|
|
91
|
+
// Skip if input matches literal
|
|
92
|
+
if (valueIndex < value.length && value[valueIndex] === maskChar) {
|
|
93
|
+
valueIndex++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// No more input for future wildcards, stop here
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Calculate cursor position - put cursor at the end of the masked value
|
|
104
|
+
const newCursorPosition = masked.length;
|
|
105
|
+
// Calculate if mask is complete
|
|
106
|
+
const requiredLength = mask.split('').filter((c) => tokens[c]).length;
|
|
107
|
+
const complete = raw.length === requiredLength;
|
|
108
|
+
return {
|
|
109
|
+
masked,
|
|
110
|
+
raw,
|
|
111
|
+
complete,
|
|
112
|
+
cursorPosition: newCursorPosition
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get the raw value from a masked value
|
|
117
|
+
*/
|
|
118
|
+
export function unmask(maskedValue, options) {
|
|
119
|
+
const { mask, tokens = DEFAULT_TOKENS } = options;
|
|
120
|
+
let raw = '';
|
|
121
|
+
let valueIndex = 0;
|
|
122
|
+
for (let maskIndex = 0; maskIndex < mask.length && valueIndex < maskedValue.length; maskIndex++) {
|
|
123
|
+
const maskChar = mask[maskIndex];
|
|
124
|
+
const inputChar = maskedValue[valueIndex];
|
|
125
|
+
if (tokens[maskChar]) {
|
|
126
|
+
// This is a token position
|
|
127
|
+
if (tokens[maskChar].pattern.test(inputChar)) {
|
|
128
|
+
raw += inputChar;
|
|
129
|
+
}
|
|
130
|
+
valueIndex++;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// This is a literal - skip it
|
|
134
|
+
if (inputChar === maskChar) {
|
|
135
|
+
valueIndex++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return raw;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Common mask patterns
|
|
143
|
+
*/
|
|
144
|
+
export const MASK_PATTERNS = {
|
|
145
|
+
phone: '(999) 999-9999',
|
|
146
|
+
phoneIntl: '+9 (999) 999-9999',
|
|
147
|
+
ssn: '999-99-9999',
|
|
148
|
+
zip: '99999',
|
|
149
|
+
zipPlus4: '99999-9999',
|
|
150
|
+
creditCard: '9999 9999 9999 9999',
|
|
151
|
+
date: '99/99/9999',
|
|
152
|
+
time: '99:99',
|
|
153
|
+
time24: '99:99',
|
|
154
|
+
currency: '$9,999.99'
|
|
155
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { Sform, Sfield, Sbutton } from './Sform/index.js';
|
|
2
|
+
export type { ValidateOn, FieldState, SfieldClasses, InputType, SelectOption, SformContext, SformProps, ButtonFormState, ButtonInputProps, InputAffixProps, SfieldTypeMap, AllowedSfieldType, TypedSfieldProps, SfieldBaseProps, TypedBaseSfieldProps, SfieldTextProps, SfieldPasswordProps, SfieldNumberProps, SfieldTextareaProps, SfieldSelectProps, SfieldCheckboxProps, SfieldCheckboxGroupProps, SfieldRadioProps, SfieldRangeProps, SfieldToggleProps, SfieldToggleOptionsProps, SfieldMaskedProps, RemoteForm, RemoteFormField, RemoteFormFields, RemoteFormFieldValue, RemoteFormInput, RemoteFormIssue } from './Sform/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Sform, Sfield, Sbutton } from './Sform/index.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@samuel-charpentier/sform",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A Svelte 5 form library for SvelteKit remote forms",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"svelte",
|
|
7
|
+
"forms",
|
|
8
|
+
"sveltekit"
|
|
9
|
+
],
|
|
10
|
+
"author": "Samuel Charpentier",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/SamuelCharpentier/sform.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/SamuelCharpentier/sform#readme",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite dev",
|
|
19
|
+
"build": "vite build && npm run prepack",
|
|
20
|
+
"preview": "vite preview",
|
|
21
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
22
|
+
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
23
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
24
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
25
|
+
"format": "prettier --write .",
|
|
26
|
+
"lint": "prettier --check . && eslint .",
|
|
27
|
+
"test:unit": "vitest",
|
|
28
|
+
"test": "npm run test:unit -- --run"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"!dist/**/*.test.*",
|
|
33
|
+
"!dist/**/*.spec.*"
|
|
34
|
+
],
|
|
35
|
+
"sideEffects": [
|
|
36
|
+
"**/*.css"
|
|
37
|
+
],
|
|
38
|
+
"svelte": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"type": "module",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"svelte": "./dist/index.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"svelte": "^5.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@eslint/compat": "^1.4.0",
|
|
52
|
+
"@eslint/js": "^9.39.1",
|
|
53
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
54
|
+
"@sveltejs/kit": "^2.49.1",
|
|
55
|
+
"@sveltejs/package": "^2.5.7",
|
|
56
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
57
|
+
"@types/node": "^22",
|
|
58
|
+
"@vitest/browser-playwright": "^4.0.15",
|
|
59
|
+
"eslint": "^9.39.1",
|
|
60
|
+
"eslint-config-prettier": "^10.1.8",
|
|
61
|
+
"eslint-plugin-svelte": "^3.13.1",
|
|
62
|
+
"globals": "^16.5.0",
|
|
63
|
+
"playwright": "^1.57.0",
|
|
64
|
+
"prettier": "^3.7.4",
|
|
65
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
66
|
+
"publint": "^0.3.15",
|
|
67
|
+
"svelte": "^5.45.6",
|
|
68
|
+
"svelte-check": "^4.3.4",
|
|
69
|
+
"typescript": "^5.9.3",
|
|
70
|
+
"typescript-eslint": "^8.48.1",
|
|
71
|
+
"vite": "^7.2.6",
|
|
72
|
+
"vitest": "^4.0.15",
|
|
73
|
+
"vitest-browser-svelte": "^2.0.1"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"valibot": "^1.2.0"
|
|
77
|
+
}
|
|
78
|
+
}
|