@stonecrop/aform 0.31.0 → 0.32.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.
Files changed (92) hide show
  1. package/dist/aform.js +3024 -2256
  2. package/dist/aform.js.map +1 -1
  3. package/dist/assets/index.css +1170 -1
  4. package/dist/src/components/AForm.vue.d.ts +20 -0
  5. package/dist/src/components/AForm.vue.d.ts.map +1 -0
  6. package/dist/src/components/AFormLoading.vue.d.ts +14 -0
  7. package/dist/src/components/AFormLoading.vue.d.ts.map +1 -0
  8. package/dist/src/components/base/CollapseButton.vue.d.ts +7 -0
  9. package/dist/src/components/base/CollapseButton.vue.d.ts.map +1 -0
  10. package/dist/src/components/base/ExpandButton.vue.d.ts +7 -0
  11. package/dist/src/components/base/ExpandButton.vue.d.ts.map +1 -0
  12. package/dist/src/components/form/ABadge.vue.d.ts +14 -0
  13. package/dist/src/components/form/ABadge.vue.d.ts.map +1 -0
  14. package/dist/src/components/form/ACheckbox.vue.d.ts +15 -0
  15. package/dist/src/components/form/ACheckbox.vue.d.ts.map +1 -0
  16. package/dist/src/components/form/ACurrencyInput.vue.d.ts +20 -0
  17. package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -0
  18. package/dist/src/components/form/ADate.vue.d.ts +14 -0
  19. package/dist/src/components/form/ADate.vue.d.ts.map +1 -0
  20. package/dist/src/components/form/ADatePicker.vue.d.ts +28 -0
  21. package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -0
  22. package/dist/src/components/form/ADateRange.vue.d.ts +18 -0
  23. package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -0
  24. package/dist/src/components/form/ADateSelection.vue.d.ts +52 -0
  25. package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -0
  26. package/dist/src/components/form/ADateTime.vue.d.ts +17 -0
  27. package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -0
  28. package/dist/src/components/form/ADateTimeInput.vue.d.ts +30 -0
  29. package/dist/src/components/form/ADateTimeInput.vue.d.ts.map +1 -0
  30. package/dist/src/components/form/ADropdown.vue.d.ts +20 -0
  31. package/dist/src/components/form/ADropdown.vue.d.ts.map +1 -0
  32. package/dist/src/components/form/ADuration.vue.d.ts +18 -0
  33. package/dist/src/components/form/ADuration.vue.d.ts.map +1 -0
  34. package/dist/src/components/form/AFieldset.vue.d.ts +28 -0
  35. package/dist/src/components/form/AFieldset.vue.d.ts.map +1 -0
  36. package/dist/src/components/form/AFileAttach.vue.d.ts +5 -0
  37. package/dist/src/components/form/AFileAttach.vue.d.ts.map +1 -0
  38. package/dist/src/components/form/AFormLink.vue.d.ts +40 -0
  39. package/dist/src/components/form/AFormLink.vue.d.ts.map +1 -0
  40. package/dist/src/components/form/ANumericInput.vue.d.ts +14 -0
  41. package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -0
  42. package/dist/src/components/form/AQuantityInput.vue.d.ts +20 -0
  43. package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -0
  44. package/dist/src/components/form/ASegmentedControl.vue.d.ts +23 -0
  45. package/dist/src/components/form/ASegmentedControl.vue.d.ts.map +1 -0
  46. package/dist/src/components/form/ATextInput.vue.d.ts +14 -0
  47. package/dist/src/components/form/ATextInput.vue.d.ts.map +1 -0
  48. package/dist/src/components/form/ATextboxInput.vue.d.ts +21 -0
  49. package/dist/src/components/form/ATextboxInput.vue.d.ts.map +1 -0
  50. package/dist/src/components/utilities/Login.vue.d.ts +14 -0
  51. package/dist/src/components/utilities/Login.vue.d.ts.map +1 -0
  52. package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
  53. package/package.json +35 -30
  54. package/dist/aform.d.ts +0 -441
  55. package/dist/aform.tsbuildinfo +0 -1
  56. package/dist/src/directives/mask.js +0 -107
  57. package/dist/src/index.js +0 -58
  58. package/dist/src/types/index.js +0 -0
  59. package/dist/src/utils/badge.js +0 -71
  60. package/dist/src/utils/columns.js +0 -33
  61. package/dist/src/utils/deserialize.js +0 -20
  62. package/src/components/AForm.vue +0 -314
  63. package/src/components/AFormLoading.vue +0 -76
  64. package/src/components/base/CollapseButton.vue +0 -36
  65. package/src/components/base/ExpandButton.vue +0 -34
  66. package/src/components/form/ABadge.vue +0 -135
  67. package/src/components/form/ACheckbox.vue +0 -62
  68. package/src/components/form/ACurrencyInput.vue +0 -357
  69. package/src/components/form/ADate.vue +0 -93
  70. package/src/components/form/ADatePicker.vue +0 -470
  71. package/src/components/form/ADateRange.vue +0 -133
  72. package/src/components/form/ADateSelection.vue +0 -174
  73. package/src/components/form/ADateTime.vue +0 -149
  74. package/src/components/form/ADateTimeInput.vue +0 -299
  75. package/src/components/form/ADropdown.vue +0 -222
  76. package/src/components/form/ADuration.vue +0 -124
  77. package/src/components/form/AFieldset.vue +0 -72
  78. package/src/components/form/AFileAttach.vue +0 -97
  79. package/src/components/form/AFormLink.vue +0 -368
  80. package/src/components/form/ANumericInput.vue +0 -32
  81. package/src/components/form/AQuantityInput.vue +0 -349
  82. package/src/components/form/ASegmentedControl.vue +0 -342
  83. package/src/components/form/ATextInput.vue +0 -40
  84. package/src/components/form/ATextboxInput.vue +0 -66
  85. package/src/components/utilities/Login.vue +0 -189
  86. package/src/directives/mask.ts +0 -122
  87. package/src/index.ts +0 -92
  88. package/src/shims-vue.d.ts +0 -5
  89. package/src/types/index.ts +0 -333
  90. package/src/utils/badge.ts +0 -94
  91. package/src/utils/columns.ts +0 -38
  92. package/src/utils/deserialize.ts +0 -20
@@ -1,222 +0,0 @@
1
- <template>
2
- <div class="aform_form-element">
3
- <template v-if="mode === 'display'">
4
- <span v-if="badgeDescriptor" class="aform_display-value" :style="displayAccentStyle">{{
5
- badgeDescriptor.label
6
- }}</span>
7
- <span v-else class="aform_display-value">{{ search ?? '' }}</span>
8
- <label class="aform_field-label">{{ label }}</label>
9
- </template>
10
- <template v-else>
11
- <div v-on-click-outside="onClickOutside" class="autocomplete" :class="{ isOpen: dropdown.open }">
12
- <input
13
- v-model="search"
14
- type="text"
15
- class="aform_input-field"
16
- :disabled="mode === 'read'"
17
- :style="inputAccentStyle"
18
- @input="filter"
19
- @focus="openDropdown"
20
- @keydown.down="selectNextResult"
21
- @keydown.up="selectPrevResult"
22
- @keydown.enter="setCurrentResult"
23
- @keydown.esc="onClickOutside"
24
- @keydown.tab="onClickOutside" />
25
-
26
- <ul v-show="dropdown.open" id="autocomplete-results" class="autocomplete-results">
27
- <li v-if="dropdown.loading" class="loading autocomplete-result">Loading results...</li>
28
- <li
29
- v-for="(result, i) in dropdown.results"
30
- v-else
31
- :key="result"
32
- class="autocomplete-result"
33
- :class="{ 'is-active': i === dropdown.activeItemIndex }"
34
- @click.stop="setResult(result)">
35
- {{ result }}
36
- </li>
37
- </ul>
38
- <label class="aform_field-label">{{ label }}</label>
39
- </div>
40
- <p v-show="errorText" class="aform_error" v-html="errorText"></p>
41
- </template>
42
- </div>
43
- </template>
44
-
45
- <script setup lang="ts">
46
- import type { FieldOptions } from '@stonecrop/schema'
47
- import { selectChoices } from '@stonecrop/schema'
48
- import { vOnClickOutside } from '@vueuse/components'
49
- import { computed, reactive, ref, watch } from 'vue'
50
-
51
- import type { ComponentProps } from '../../types'
52
- import type { BadgeFormatFn } from '../../utils/badge'
53
- import { badgeInputAccentStyle, resolveFieldBadge } from '../../utils/badge'
54
- import { deserializeFunction } from '../../utils/deserialize'
55
-
56
- const {
57
- label,
58
- options = [],
59
- format,
60
- isAsync = false,
61
- filterFunction = undefined,
62
- mode,
63
- errors,
64
- validation = { errorMessage: '' },
65
- } = defineProps<
66
- ComponentProps & {
67
- options?: FieldOptions
68
- format?: string
69
- isAsync?: boolean
70
- filterFunction?: (search: string) => string[] | Promise<string[]>
71
- }
72
- >()
73
-
74
- const choiceList = computed(() => selectChoices(options))
75
-
76
- // Compile the serialized formatter once per `format` string, not once per keystroke:
77
- // badgeDescriptor re-evaluates on every input event and deserializeFunction runs the
78
- // Function constructor.
79
- const formatFn = computed(() => (format ? deserializeFunction<BadgeFormatFn>(format) : undefined))
80
-
81
- const badgeDescriptor = computed(() => resolveFieldBadge(search.value, options, formatFn.value))
82
-
83
- const inputAccentStyle = computed(() => badgeInputAccentStyle(badgeDescriptor.value))
84
-
85
- const displayAccentStyle = computed(() => badgeInputAccentStyle(badgeDescriptor.value))
86
-
87
- const errorText = computed(() => (errors?.length ? errors.join('; ') : (validation.errorMessage ?? '')))
88
- const search = defineModel<string>()
89
-
90
- const committedValue = ref(search.value ?? '')
91
-
92
- const dropdown = reactive({
93
- activeItemIndex: null as number | null,
94
- open: false,
95
- loading: false,
96
- results: [] as string[],
97
- })
98
-
99
- watch(
100
- choiceList,
101
- choices => {
102
- dropdown.results = choices
103
- },
104
- { immediate: true }
105
- )
106
-
107
- const onClickOutside = () => closeDropdown()
108
-
109
- const filter = async () => {
110
- dropdown.open = true
111
- dropdown.activeItemIndex = null
112
- if (filterFunction) {
113
- if (isAsync) dropdown.loading = true
114
- try {
115
- const filteredResults = await filterFunction(search.value || '')
116
- dropdown.results = filteredResults || []
117
- } catch {
118
- dropdown.results = []
119
- } finally {
120
- if (isAsync) dropdown.loading = false
121
- }
122
- } else {
123
- filterResults()
124
- }
125
- }
126
-
127
- const setResult = (result: string) => {
128
- search.value = result
129
- committedValue.value = result
130
- closeDropdown(result)
131
- }
132
-
133
- const openDropdown = () => {
134
- const idx = choiceList.value.indexOf(search.value ?? '')
135
- dropdown.activeItemIndex = isAsync ? null : idx >= 0 ? idx : null
136
- dropdown.open = true
137
- dropdown.results = isAsync ? [] : choiceList.value
138
- }
139
-
140
- const closeDropdown = (result?: string) => {
141
- dropdown.activeItemIndex = null
142
- dropdown.open = false
143
- if (!choiceList.value.includes(result || search.value || '')) {
144
- search.value = committedValue.value
145
- }
146
- }
147
-
148
- const filterResults = () => {
149
- if (!search.value) {
150
- dropdown.results = choiceList.value
151
- } else {
152
- dropdown.results = choiceList.value.filter(item => item.toLowerCase().includes((search.value ?? '').toLowerCase()))
153
- }
154
- }
155
-
156
- const selectNextResult = () => {
157
- const resultsLength = dropdown.results?.length || 0
158
- if (dropdown.activeItemIndex != null) {
159
- const currentIndex = isNaN(dropdown.activeItemIndex) ? 0 : dropdown.activeItemIndex
160
- dropdown.activeItemIndex = (currentIndex + 1) % resultsLength
161
- } else {
162
- dropdown.activeItemIndex = 0
163
- }
164
- }
165
-
166
- const selectPrevResult = () => {
167
- const resultsLength = dropdown.results?.length || 0
168
- if (dropdown.activeItemIndex != null) {
169
- const currentIndex = isNaN(dropdown.activeItemIndex) ? 0 : dropdown.activeItemIndex
170
- if (currentIndex === 0) {
171
- dropdown.activeItemIndex = null
172
- } else {
173
- dropdown.activeItemIndex = currentIndex - 1
174
- }
175
- } else {
176
- dropdown.activeItemIndex = resultsLength - 1
177
- }
178
- }
179
-
180
- const setCurrentResult = () => {
181
- if (dropdown.results) {
182
- const currentIndex = dropdown.activeItemIndex || 0
183
- const result = dropdown.results[currentIndex]
184
- setResult(result)
185
- }
186
- dropdown.activeItemIndex = 0
187
- }
188
- </script>
189
-
190
- <style scoped>
191
- .autocomplete {
192
- position: relative;
193
- }
194
-
195
- .autocomplete-results {
196
- position: absolute;
197
- left: 0;
198
- right: 0;
199
- z-index: 100;
200
- padding: 0;
201
- margin: 0;
202
- color: var(--sc-input-active-border-color);
203
- border: 1px solid var(--sc-input-active-border-color);
204
- border-radius: 0;
205
- border-top: none;
206
- background-color: var(--sc-input-field-background);
207
- list-style: none;
208
- }
209
-
210
- .autocomplete-result {
211
- text-align: left;
212
- padding: 4px 6px;
213
- cursor: pointer;
214
- border-bottom: 0.5px solid lightgray;
215
- }
216
-
217
- .autocomplete-result.is-active,
218
- .autocomplete-result:hover {
219
- background-color: var(--sc-row-color-zebra-light);
220
- color: var(--sc-input-active-border-color);
221
- }
222
- </style>
@@ -1,124 +0,0 @@
1
- <template>
2
- <div class="aduration">
3
- <template v-if="mode === 'display' || mode === 'read'">
4
- <span class="aform_display-value">{{ displayValue }}</span>
5
- <label>{{ label }}</label>
6
- </template>
7
-
8
- <template v-else>
9
- <ADateSelection
10
- ref="selectionRef"
11
- :select-range="true"
12
- :show-time="true"
13
- :show-end-time="true"
14
- :allow-military-time="allowMilitaryTime"
15
- :use-seconds="useSeconds"
16
- @get-range="handleRange" />
17
- <div class="aduration__footer">
18
- <label>{{ label }}</label>
19
- <div v-if="startDatetime && endDatetime" class="aduration__summary">
20
- <span class="aduration__label">Duration:</span>
21
- <span class="aduration__value">{{ humanDuration }}</span>
22
- <span class="aduration__ms">({{ modelValue ?? 0 }} ms)</span>
23
- </div>
24
- </div>
25
- </template>
26
- </div>
27
- </template>
28
-
29
- <script setup lang="ts">
30
- import { ref, computed, watch } from 'vue'
31
- import ADateSelection from './ADateSelection.vue'
32
-
33
- const {
34
- label = 'Duration',
35
- mode = 'edit',
36
- allowMilitaryTime = false,
37
- useSeconds = false,
38
- } = defineProps<{
39
- label?: string
40
- mode?: string
41
- allowMilitaryTime?: boolean
42
- useSeconds?: boolean
43
- }>()
44
-
45
- const modelValue = defineModel<number>()
46
-
47
- const startDatetime = ref<Date | null>(null)
48
- const endDatetime = ref<Date | null>(null)
49
-
50
- const duration = computed<number>(() => {
51
- if (!startDatetime.value || !endDatetime.value) return 0
52
- const ms = endDatetime.value.getTime() - startDatetime.value.getTime()
53
- return ms > 0 ? ms : 0
54
- })
55
-
56
- watch(duration, newMs => {
57
- modelValue.value = newMs
58
- })
59
-
60
- const handleRange = (data: { start: Date; end: Date; source?: 'init' | 'user' }) => {
61
- // Both time widgets announce their starting values as they mount, which ADateSelection turns
62
- // into a range. That is not a range the user picked: acting on it wrote a 0ms duration into the
63
- // model, and lit the summary strip, on first render.
64
- if (data.source === 'init') return
65
-
66
- startDatetime.value = data.start
67
- endDatetime.value = data.end
68
- modelValue.value = duration.value
69
- }
70
-
71
- const humanDuration = computed(() => {
72
- const ms = duration.value
73
- if (ms === 0) return '0s'
74
- const s = Math.floor(ms / 1000) % 60
75
- const m = Math.floor(ms / 60000) % 60
76
- const h = Math.floor(ms / 3600000) % 24
77
- const d = Math.floor(ms / 86400000)
78
- return [d && `${d}d`, h && `${h}h`, m && `${m}m`, s && `${s}s`].filter(Boolean).join(' ') || '0s'
79
- })
80
-
81
- const displayValue = computed(() => {
82
- const ms = modelValue.value
83
- if (!ms) return '—'
84
- const s = Math.floor(ms / 1000) % 60
85
- const m = Math.floor(ms / 60000) % 60
86
- const h = Math.floor(ms / 3600000) % 24
87
- const d = Math.floor(ms / 86400000)
88
- return [d && `${d}d`, h && `${h}h`, m && `${m}m`, s && `${s}s`].filter(Boolean).join(' ') || '0s'
89
- })
90
- </script>
91
-
92
- <style scoped>
93
- .aduration {
94
- position: relative;
95
- min-width: 40ch;
96
- width: 100%;
97
- }
98
-
99
- .aduration__summary {
100
- display: flex;
101
- gap: 6px;
102
- align-items: baseline;
103
- margin-top: 6px;
104
- font-size: 0.9em;
105
- }
106
-
107
- .aduration__label {
108
- font-weight: bold;
109
- color: var(--sc-input-active-label-color);
110
- }
111
-
112
- .aduration__value {
113
- color: var(--sc-cell-text-color);
114
- }
115
-
116
- .aduration__ms {
117
- color: var(--sc-gray-50);
118
- font-size: 0.85em;
119
- }
120
-
121
- .aduration__footer {
122
- margin-top: 8px;
123
- }
124
- </style>
@@ -1,72 +0,0 @@
1
- <template>
2
- <fieldset>
3
- <legend v-if="label || collapsible" @click="toggleCollapse" @submit="toggleCollapse">
4
- {{ label }}
5
- <CollapseButton v-if="collapsible" :collapsed="collapsed" />
6
- </legend>
7
- <slot :collapsed="collapsed">
8
- <AForm v-show="!collapsed" v-model:data="formData" :schema="formSchema" :mode="mode" />
9
- </slot>
10
- </fieldset>
11
- </template>
12
-
13
- <script setup lang="ts">
14
- import type { InteractionMode } from '@stonecrop/schema'
15
- import { ref } from 'vue'
16
-
17
- import AForm from '../AForm.vue'
18
- import CollapseButton from '../base/CollapseButton.vue'
19
- import type { ResolvedField } from '../../types'
20
-
21
- const {
22
- schema,
23
- label = undefined,
24
- collapsible,
25
- data = {},
26
- mode = 'edit',
27
- } = defineProps<{
28
- schema: ResolvedField[]
29
- label?: string
30
- collapsible?: boolean
31
- data?: Record<string, any>
32
- /** Rendering mode forwarded to the inner AForm */
33
- mode?: InteractionMode
34
- }>()
35
-
36
- const collapsed = ref(false)
37
- const formData = ref(data || [])
38
- const formSchema = ref(schema)
39
-
40
- const toggleCollapse = (event: Event) => {
41
- event.preventDefault()
42
- if (collapsible) {
43
- collapsed.value = !collapsed.value
44
- }
45
- }
46
-
47
- defineExpose({ collapsed })
48
- </script>
49
-
50
- <style scoped>
51
- fieldset {
52
- max-width: 100%;
53
- width: 100%;
54
- margin-right: 2ch;
55
- border: 1px solid transparent;
56
- border-bottom: 1px solid var(--sc-gray-50);
57
- }
58
-
59
- legend {
60
- width: 100%;
61
- height: 1.15rem;
62
- border: 1px solid transparent;
63
- padding-bottom: 0.5rem;
64
- font-size: 110%;
65
- font-weight: 600;
66
- user-select: none;
67
- }
68
-
69
- .collapse-button {
70
- float: right;
71
- }
72
- </style>
@@ -1,97 +0,0 @@
1
- <template>
2
- <div class="aform_form-element aform_file-attach aform__grid--full">
3
- <template v-if="mode === 'display'">
4
- <template v-if="files">
5
- <div class="aform_file-attach-feedback">
6
- <p>
7
- <b>{{ fileLengthText }}</b>
8
- </p>
9
- <li v-for="file of files" :key="file.name">{{ file.name }}</li>
10
- </div>
11
- </template>
12
- <span v-else class="aform_display-value">No file selected</span>
13
- </template>
14
- <template v-else>
15
- <template v-if="files">
16
- <div class="aform_file-attach-feedback">
17
- <p>
18
- You have selected: <b>{{ fileLengthText }}</b>
19
- </p>
20
- <li v-for="file of files" :key="file.name">
21
- {{ file.name }}
22
- </li>
23
- </div>
24
- </template>
25
- <button type="button" class="aform_form-btn" :disabled="mode === 'read'" @click="open()">
26
- {{ label }}
27
- </button>
28
- <button type="button" :disabled="!files || mode === 'read'" class="aform_form-btn" @click="reset()">Reset</button>
29
- </template>
30
- </div>
31
- </template>
32
-
33
- <script setup lang="ts">
34
- import { useFileDialog } from '@vueuse/core'
35
- import { computed } from 'vue'
36
-
37
- import type { ComponentProps } from '../../types'
38
-
39
- const { label, mode } = defineProps<ComponentProps>()
40
- const { files, open, reset, onChange } = useFileDialog()
41
-
42
- const fileLengthText = computed(() => {
43
- const count = files.value?.length ?? 0
44
- return `${count} ${count === 1 ? 'file' : 'files'}`
45
- })
46
-
47
- onChange(changedFiles => changedFiles)
48
- </script>
49
-
50
- <style scoped>
51
- .aform_file-attach {
52
- padding: 1rem;
53
- display: flex;
54
- flex-wrap: wrap;
55
- gap: 1rem;
56
- flex-direction: row;
57
- justify-content: center;
58
- align-items: center;
59
- border: 1px dashed var(--sc-input-border-color);
60
- width: 100%;
61
- }
62
-
63
- @media screen and (max-width: 400px) {
64
- .aform_file-attach > .aform_form-btn {
65
- width: 100%;
66
- }
67
- }
68
-
69
- .aform_file-attach-feedback {
70
- color: var(--sc-input-label-color);
71
- width: 100%;
72
- padding: 0.5rem;
73
- text-align: center;
74
- align-self: center;
75
-
76
- & > li {
77
- list-style: none;
78
- font-style: italic;
79
- }
80
- & > p {
81
- margin-top: 0;
82
- }
83
- }
84
-
85
- .aform_form-btn {
86
- padding: 0.5rem 2rem;
87
- width: auto;
88
- border: 1px solid var(--sc-input-border-color);
89
- color: var(--sc-input-label-color);
90
- cursor: pointer;
91
- background-color: white;
92
- }
93
-
94
- .aform_form-btn:disabled {
95
- background-color: var(--sc-gray-5);
96
- }
97
- </style>