@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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +405 -0
  3. package/dist/Sform/Sfield.svelte +164 -0
  4. package/dist/Sform/Sfield.svelte.d.ts +4 -0
  5. package/dist/Sform/Sform.svelte +101 -0
  6. package/dist/Sform/Sform.svelte.d.ts +51 -0
  7. package/dist/Sform/context.svelte.d.ts +3 -0
  8. package/dist/Sform/context.svelte.js +65 -0
  9. package/dist/Sform/index.d.ts +5 -0
  10. package/dist/Sform/index.js +7 -0
  11. package/dist/Sform/inputs/ButtonInput.svelte +75 -0
  12. package/dist/Sform/inputs/ButtonInput.svelte.d.ts +32 -0
  13. package/dist/Sform/inputs/CheckboxGroupInput.svelte +81 -0
  14. package/dist/Sform/inputs/CheckboxGroupInput.svelte.d.ts +4 -0
  15. package/dist/Sform/inputs/CheckboxInput.svelte +47 -0
  16. package/dist/Sform/inputs/CheckboxInput.svelte.d.ts +4 -0
  17. package/dist/Sform/inputs/MaskedInput.svelte +220 -0
  18. package/dist/Sform/inputs/MaskedInput.svelte.d.ts +4 -0
  19. package/dist/Sform/inputs/NumberInput.svelte +116 -0
  20. package/dist/Sform/inputs/NumberInput.svelte.d.ts +4 -0
  21. package/dist/Sform/inputs/PasswordInput.svelte +142 -0
  22. package/dist/Sform/inputs/PasswordInput.svelte.d.ts +4 -0
  23. package/dist/Sform/inputs/RadioInput.svelte +81 -0
  24. package/dist/Sform/inputs/RadioInput.svelte.d.ts +4 -0
  25. package/dist/Sform/inputs/RangeInput.svelte +67 -0
  26. package/dist/Sform/inputs/RangeInput.svelte.d.ts +4 -0
  27. package/dist/Sform/inputs/SelectInput.svelte +36 -0
  28. package/dist/Sform/inputs/SelectInput.svelte.d.ts +4 -0
  29. package/dist/Sform/inputs/TextInput.svelte +61 -0
  30. package/dist/Sform/inputs/TextInput.svelte.d.ts +4 -0
  31. package/dist/Sform/inputs/TextareaInput.svelte +56 -0
  32. package/dist/Sform/inputs/TextareaInput.svelte.d.ts +4 -0
  33. package/dist/Sform/inputs/ToggleInput.svelte +101 -0
  34. package/dist/Sform/inputs/ToggleInput.svelte.d.ts +4 -0
  35. package/dist/Sform/inputs/ToggleOptionsInput.svelte +118 -0
  36. package/dist/Sform/inputs/ToggleOptionsInput.svelte.d.ts +4 -0
  37. package/dist/Sform/sform.css +628 -0
  38. package/dist/Sform/types.d.ts +610 -0
  39. package/dist/Sform/types.js +5 -0
  40. package/dist/Sform/utils/mask.d.ts +68 -0
  41. package/dist/Sform/utils/mask.js +155 -0
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.js +1 -0
  44. 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
+ };
@@ -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
+ }