@stonecrop/aform 0.30.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.
- package/dist/aform.js +3024 -2256
- package/dist/aform.js.map +1 -1
- package/dist/assets/index.css +1170 -1
- package/dist/src/components/AForm.vue.d.ts +20 -0
- package/dist/src/components/AForm.vue.d.ts.map +1 -0
- package/dist/src/components/AFormLoading.vue.d.ts +14 -0
- package/dist/src/components/AFormLoading.vue.d.ts.map +1 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts +7 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts.map +1 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts +7 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts.map +1 -0
- package/dist/src/components/form/ABadge.vue.d.ts +14 -0
- package/dist/src/components/form/ABadge.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts +15 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts +20 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADate.vue.d.ts +14 -0
- package/dist/src/components/form/ADate.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts +28 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateRange.vue.d.ts +18 -0
- package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts +52 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTime.vue.d.ts +17 -0
- package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts +30 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADropdown.vue.d.ts +20 -0
- package/dist/src/components/form/ADropdown.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADuration.vue.d.ts +18 -0
- package/dist/src/components/form/ADuration.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFieldset.vue.d.ts +28 -0
- package/dist/src/components/form/AFieldset.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts +5 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFormLink.vue.d.ts +40 -0
- package/dist/src/components/form/AFormLink.vue.d.ts.map +1 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts +14 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts +20 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts +23 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextInput.vue.d.ts +14 -0
- package/dist/src/components/form/ATextInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts +21 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts.map +1 -0
- package/dist/src/components/utilities/Login.vue.d.ts +14 -0
- package/dist/src/components/utilities/Login.vue.d.ts.map +1 -0
- package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
- package/package.json +35 -30
- package/dist/aform.d.ts +0 -441
- package/dist/aform.tsbuildinfo +0 -1
- package/dist/src/directives/mask.js +0 -107
- package/dist/src/index.js +0 -58
- package/dist/src/types/index.js +0 -0
- package/dist/src/utils/badge.js +0 -71
- package/dist/src/utils/columns.js +0 -33
- package/dist/src/utils/deserialize.js +0 -20
- package/src/components/AForm.vue +0 -314
- package/src/components/AFormLoading.vue +0 -76
- package/src/components/base/CollapseButton.vue +0 -36
- package/src/components/base/ExpandButton.vue +0 -34
- package/src/components/form/ABadge.vue +0 -135
- package/src/components/form/ACheckbox.vue +0 -62
- package/src/components/form/ACurrencyInput.vue +0 -357
- package/src/components/form/ADate.vue +0 -93
- package/src/components/form/ADatePicker.vue +0 -470
- package/src/components/form/ADateRange.vue +0 -133
- package/src/components/form/ADateSelection.vue +0 -174
- package/src/components/form/ADateTime.vue +0 -149
- package/src/components/form/ADateTimeInput.vue +0 -299
- package/src/components/form/ADropdown.vue +0 -222
- package/src/components/form/ADuration.vue +0 -124
- package/src/components/form/AFieldset.vue +0 -72
- package/src/components/form/AFileAttach.vue +0 -97
- package/src/components/form/AFormLink.vue +0 -368
- package/src/components/form/ANumericInput.vue +0 -32
- package/src/components/form/AQuantityInput.vue +0 -349
- package/src/components/form/ASegmentedControl.vue +0 -342
- package/src/components/form/ATextInput.vue +0 -40
- package/src/components/form/ATextboxInput.vue +0 -66
- package/src/components/utilities/Login.vue +0 -189
- package/src/directives/mask.ts +0 -122
- package/src/index.ts +0 -92
- package/src/shims-vue.d.ts +0 -5
- package/src/types/index.ts +0 -333
- package/src/utils/badge.ts +0 -94
- package/src/utils/columns.ts +0 -38
- 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>
|