@stonecrop/aform 0.2.5 → 0.2.6
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/package.json +30 -15
- package/src/components/form/ACheckbox.vue +3 -15
- package/src/components/form/ANumericInput.vue +3 -13
- package/src/directives/mask.ts +104 -0
- package/src/histoire.setup.ts +22 -0
- package/src/index.ts +27 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/theme/aform.css +28 -0
- package/src/theme/custom_themes.css +6 -0
- package/src/theme/purple_theme.css +9 -0
- package/dist/aform.js +0 -885
- package/dist/aform.js.map +0 -1
- package/dist/aform.umd.cjs +0 -2
- package/dist/aform.umd.cjs.map +0 -1
- package/dist/style.css +0 -1
package/package.json
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/aform",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Tyler Matteson",
|
|
8
|
+
"email": "tyler@agritheory.com"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/agritheory/stonecrop",
|
|
13
|
+
"directory": "aform"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/agritheory/stonecrop/issues"
|
|
17
|
+
},
|
|
6
18
|
"exports": {
|
|
7
19
|
".": {
|
|
8
20
|
"import": "./dist/aform.js",
|
|
@@ -12,45 +24,48 @@
|
|
|
12
24
|
},
|
|
13
25
|
"main": "dist/aform.js",
|
|
14
26
|
"module": "dist/aform.js",
|
|
27
|
+
"umd": "dist/aform.umd.cjs",
|
|
15
28
|
"types": "src/index",
|
|
16
29
|
"files": [
|
|
17
30
|
"dist/*",
|
|
18
|
-
"src
|
|
31
|
+
"src/*"
|
|
19
32
|
],
|
|
20
33
|
"dependencies": {
|
|
21
34
|
"uuid": "^9.0.0",
|
|
22
|
-
"vue": "^3.
|
|
23
|
-
"@stonecrop/
|
|
24
|
-
"@stonecrop/
|
|
35
|
+
"vue": "^3.4.23",
|
|
36
|
+
"@stonecrop/utilities": "0.2.6",
|
|
37
|
+
"@stonecrop/themes": "0.2.6"
|
|
25
38
|
},
|
|
26
39
|
"devDependencies": {
|
|
27
|
-
"@histoire/plugin-vue": "^0.
|
|
40
|
+
"@histoire/plugin-vue": "^0.17.17",
|
|
28
41
|
"@types/uuid": "^9.0.0",
|
|
29
42
|
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
|
30
43
|
"@typescript-eslint/parser": "^5.59.5",
|
|
31
|
-
"@vitejs/plugin-vue": "^
|
|
32
|
-
"@vitest/coverage-
|
|
33
|
-
"@vitest/ui": "^
|
|
44
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
45
|
+
"@vitest/coverage-v8": "^1.5.0",
|
|
46
|
+
"@vitest/ui": "^1.5.0",
|
|
34
47
|
"@vue/test-utils": "^2.3.2",
|
|
35
48
|
"cypress": "^12.11.0",
|
|
36
49
|
"eslint": "^8.40.0",
|
|
37
50
|
"eslint-config-prettier": "^8.8.0",
|
|
38
51
|
"eslint-plugin-vue": "^9.11.1",
|
|
39
|
-
"histoire": "^0.
|
|
52
|
+
"histoire": "^0.17.17",
|
|
40
53
|
"jsdom": "^22.0.0",
|
|
41
54
|
"typescript": "^5.0.4",
|
|
42
|
-
"vite": "^
|
|
43
|
-
"vitest": "^
|
|
55
|
+
"vite": "^5.2.9",
|
|
56
|
+
"vitest": "^1.5.0",
|
|
44
57
|
"vue-router": "^4",
|
|
45
|
-
"@stonecrop/atable": "0.2.
|
|
58
|
+
"@stonecrop/atable": "0.2.6"
|
|
46
59
|
},
|
|
47
60
|
"peerDependencies": {
|
|
48
|
-
"@stonecrop/atable": "0.2.
|
|
61
|
+
"@stonecrop/atable": "0.2.6"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
49
65
|
},
|
|
50
66
|
"engines": {
|
|
51
67
|
"node": ">=20.11.0"
|
|
52
68
|
},
|
|
53
|
-
"umd": "dist/aform.umd.cjs",
|
|
54
69
|
"scripts": {
|
|
55
70
|
"build": "tsc -b && vite build",
|
|
56
71
|
"dev": "vite",
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
|
-
import {
|
|
13
|
+
import { InputHTMLAttributes } from 'vue'
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
withDefaults(
|
|
16
16
|
defineProps<{
|
|
17
17
|
label?: string
|
|
18
|
-
value?: InputHTMLAttributes['checked']
|
|
19
18
|
required?: boolean
|
|
20
19
|
readOnly?: boolean
|
|
21
20
|
uuid?: string
|
|
@@ -26,18 +25,7 @@ const props = withDefaults(
|
|
|
26
25
|
}
|
|
27
26
|
)
|
|
28
27
|
|
|
29
|
-
const
|
|
30
|
-
(e: 'update:value', value: InputHTMLAttributes['checked']): void
|
|
31
|
-
}>()
|
|
32
|
-
|
|
33
|
-
const checkbox = computed({
|
|
34
|
-
get() {
|
|
35
|
-
return props.value
|
|
36
|
-
},
|
|
37
|
-
set(value) {
|
|
38
|
-
emit('update:value', value)
|
|
39
|
-
},
|
|
40
|
-
})
|
|
28
|
+
const checkbox = defineModel<InputHTMLAttributes['checked']>()
|
|
41
29
|
</script>
|
|
42
30
|
|
|
43
31
|
<style scoped>
|
|
@@ -7,12 +7,9 @@
|
|
|
7
7
|
</template>
|
|
8
8
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const props = withDefaults(
|
|
10
|
+
withDefaults(
|
|
13
11
|
defineProps<{
|
|
14
12
|
label: string
|
|
15
|
-
modelValue: any
|
|
16
13
|
required?: boolean
|
|
17
14
|
readonly?: boolean
|
|
18
15
|
uuid?: string
|
|
@@ -22,15 +19,8 @@ const props = withDefaults(
|
|
|
22
19
|
validation: () => ({ errorMessage: ' ' }),
|
|
23
20
|
}
|
|
24
21
|
)
|
|
25
|
-
|
|
26
|
-
const inputNumber =
|
|
27
|
-
get: () => {
|
|
28
|
-
return props.modelValue
|
|
29
|
-
},
|
|
30
|
-
set: newValue => {
|
|
31
|
-
emit('update:modelValue', newValue)
|
|
32
|
-
},
|
|
33
|
-
})
|
|
22
|
+
|
|
23
|
+
const inputNumber = defineModel<number>()
|
|
34
24
|
</script>
|
|
35
25
|
|
|
36
26
|
<style scoped>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { FormSchema } from 'types'
|
|
2
|
+
import { DirectiveBinding } from 'vue'
|
|
3
|
+
|
|
4
|
+
const NAMED_MASKS = {
|
|
5
|
+
date: '##/##/####',
|
|
6
|
+
datetime: '####/##/## ##:##',
|
|
7
|
+
time: '##:##',
|
|
8
|
+
fulltime: '##:##:##',
|
|
9
|
+
phone: '(###) ### - ####',
|
|
10
|
+
card: '#### #### #### ####',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function extractMaskFn(mask: string): ((args: any) => string) | void {
|
|
14
|
+
try {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
16
|
+
return Function(`"use strict";return (${mask})`)()
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof ReferenceError) {
|
|
19
|
+
// assume mask is a string
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getMask(binding: DirectiveBinding<string>) {
|
|
25
|
+
let mask = binding.value
|
|
26
|
+
|
|
27
|
+
if (mask) {
|
|
28
|
+
const maskFn = extractMaskFn(mask)
|
|
29
|
+
if (maskFn) {
|
|
30
|
+
// TODO: (state) replace with state management;
|
|
31
|
+
// pass the entire form/table data to the function
|
|
32
|
+
const locale = binding.instance['locale']
|
|
33
|
+
mask = maskFn(locale)
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
// TODO: (state) handle using state management
|
|
37
|
+
const schema: FormSchema = binding.instance['schema']
|
|
38
|
+
const fieldType: string | undefined = schema.fieldtype?.toLowerCase()
|
|
39
|
+
if (fieldType && NAMED_MASKS[fieldType]) {
|
|
40
|
+
mask = NAMED_MASKS[fieldType]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return mask
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function unmaskInput(input: string, maskToken?: string) {
|
|
48
|
+
if (!maskToken) {
|
|
49
|
+
maskToken = '#'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let unmaskedInput = input
|
|
53
|
+
const maskChars = [maskToken, '/', '-', '(', ')', ' ']
|
|
54
|
+
|
|
55
|
+
for (const char of maskChars) {
|
|
56
|
+
unmaskedInput = unmaskedInput.replaceAll(char, '')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return unmaskedInput
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function fillMask(input: string, mask: string, maskToken?: string) {
|
|
63
|
+
if (!maskToken) {
|
|
64
|
+
maskToken = '#'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let replacement = mask
|
|
68
|
+
for (const inputChar of input) {
|
|
69
|
+
const replaceIndex = replacement.indexOf(maskToken)
|
|
70
|
+
if (replaceIndex !== -1) {
|
|
71
|
+
const prefix = replacement.substring(0, replaceIndex)
|
|
72
|
+
const suffix = replacement.substring(replaceIndex + 1)
|
|
73
|
+
replacement = prefix + inputChar + suffix
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return replacement.slice(0, mask.length)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function useStringMask(el: HTMLInputElement, binding: DirectiveBinding<string>) {
|
|
81
|
+
const mask = getMask(binding)
|
|
82
|
+
if (!mask) return
|
|
83
|
+
|
|
84
|
+
const maskToken = '#'
|
|
85
|
+
const inputText = el.value
|
|
86
|
+
|
|
87
|
+
// process input value with mask
|
|
88
|
+
const unmaskedInput = unmaskInput(inputText, maskToken)
|
|
89
|
+
if (unmaskedInput) {
|
|
90
|
+
const replacement = fillMask(unmaskedInput, mask, maskToken)
|
|
91
|
+
|
|
92
|
+
// TODO: (state) this is very opinionated;
|
|
93
|
+
// most likely fixed with state management;
|
|
94
|
+
// a better way could be to emit back to instance;
|
|
95
|
+
|
|
96
|
+
if (binding.instance['maskFilled']) {
|
|
97
|
+
binding.instance['maskFilled'] = !replacement.includes(maskToken)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
el.value = replacement
|
|
101
|
+
} else {
|
|
102
|
+
el.value = mask
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineSetupVue3 } from '@histoire/plugin-vue'
|
|
2
|
+
|
|
3
|
+
import ACheckbox from '@/components/form/ACheckbox.vue'
|
|
4
|
+
import AFieldset from '@/components/form/AFieldset.vue'
|
|
5
|
+
import AForm from '@/components/AForm.vue'
|
|
6
|
+
import ANumericInput from '@/components/form/ANumericInput.vue'
|
|
7
|
+
import ATextInput from '@/components/form/ATextInput.vue'
|
|
8
|
+
|
|
9
|
+
import { ATable, ATableHeader, ATableModal } from '@stonecrop/atable'
|
|
10
|
+
import '@stonecrop/atable/styles'
|
|
11
|
+
|
|
12
|
+
export const setupVue3 = defineSetupVue3(({ app }) => {
|
|
13
|
+
// TODO: (typing) add typing for ATable components
|
|
14
|
+
app.component('ACheckbox', ACheckbox)
|
|
15
|
+
app.component('AFieldset', AFieldset)
|
|
16
|
+
app.component('AForm', AForm)
|
|
17
|
+
app.component('ANumericInput', ANumericInput)
|
|
18
|
+
app.component('ATable', ATable)
|
|
19
|
+
app.component('ATableHeader', ATableHeader)
|
|
20
|
+
app.component('ATableModal', ATableModal)
|
|
21
|
+
app.component('ATextInput', ATextInput)
|
|
22
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { App } from 'vue'
|
|
2
|
+
|
|
3
|
+
import ACheckbox from '@/components/form/ACheckbox.vue'
|
|
4
|
+
import AComboBox from '@/components/form/AComboBox.vue'
|
|
5
|
+
import ADate from '@/components/form/ADate.vue'
|
|
6
|
+
import ADropdown from '@/components/form/ADropdown.vue'
|
|
7
|
+
import AFieldset from '@/components/form/AFieldset.vue'
|
|
8
|
+
import AForm from '@/components/AForm.vue'
|
|
9
|
+
import ANumericInput from '@/components/form/ANumericInput.vue'
|
|
10
|
+
import ATextInput from '@/components/form/ATextInput.vue'
|
|
11
|
+
// import { ACurrency } from '@/components/form/ACurrency.vue'
|
|
12
|
+
// import { AQuantity } from '@/components/form/AQuantity.vue'
|
|
13
|
+
|
|
14
|
+
function install(app: App /* options */) {
|
|
15
|
+
app.component('ACheckbox', ACheckbox)
|
|
16
|
+
app.component('ACombobox', AComboBox)
|
|
17
|
+
app.component('ADate', ADate)
|
|
18
|
+
app.component('ADropdown', ADropdown)
|
|
19
|
+
app.component('AFieldset', AFieldset)
|
|
20
|
+
app.component('AForm', AForm)
|
|
21
|
+
app.component('ANumericInput', ANumericInput)
|
|
22
|
+
app.component('ATextInput', ATextInput)
|
|
23
|
+
// app.component('ACurrency', ACurrency)
|
|
24
|
+
// app.component('AQuantity', AQuantity)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { ACheckbox, AComboBox, ADate, ADropdown, AFieldset, AForm, ANumericInput, ATextInput, install }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
|
|
2
|
+
@import url('@stonecrop/themes/default/default.css');
|
|
3
|
+
@import url('custom_themes.css');
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
font-family: var(--font-family);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.aform-primary-action {
|
|
10
|
+
font-size: 100%;
|
|
11
|
+
text-align: center;
|
|
12
|
+
min-height: 2em;
|
|
13
|
+
padding: 0.25rem 1rem 0.25rem 1rem;
|
|
14
|
+
border: 1px solid var(--primary-color);
|
|
15
|
+
color: var(--primary-text-color);
|
|
16
|
+
background-color: var(--primary-color);
|
|
17
|
+
outline: 2px solid var(--primary-text-color);
|
|
18
|
+
transition: outline-offset 200ms ease;
|
|
19
|
+
font-size: var(--font-size);
|
|
20
|
+
margin: 0.5ch;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.aform-primary-action:hover,
|
|
24
|
+
.aform-primary-action:active {
|
|
25
|
+
outline: 2px solid var(--primary-text-color);
|
|
26
|
+
outline-offset: -4px;
|
|
27
|
+
transition: outline-offset 200ms ease;
|
|
28
|
+
}
|