@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,368 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div :class="['aform_form-element', { 'aform_form-element--embedded': embedded }]">
|
|
3
|
-
<span v-if="mode === 'display'" class="aform_display-value">{{ displayedText }}</span>
|
|
4
|
-
<template v-else>
|
|
5
|
-
<div v-on-click-outside="onClickOutside" class="aform_form-link-wrapper">
|
|
6
|
-
<div class="input-group">
|
|
7
|
-
<input
|
|
8
|
-
v-model="searchText"
|
|
9
|
-
type="text"
|
|
10
|
-
role="combobox"
|
|
11
|
-
autocomplete="off"
|
|
12
|
-
aria-autocomplete="list"
|
|
13
|
-
:class="['aform_input-field', { 'aform_input-field--embedded': embedded }]"
|
|
14
|
-
:placeholder="placeholder"
|
|
15
|
-
:aria-label="ariaLabel"
|
|
16
|
-
:required="required"
|
|
17
|
-
:aria-expanded="dropdownOpen"
|
|
18
|
-
:aria-controls="dropdownOpen ? listboxId : undefined"
|
|
19
|
-
:aria-activedescendant="activeIndex === null ? undefined : `${listboxId}-opt-${activeIndex}`"
|
|
20
|
-
:disabled="disabled || mode === 'read'"
|
|
21
|
-
@input="onInput"
|
|
22
|
-
@focus="onFocus"
|
|
23
|
-
@keydown.down.prevent="selectNext"
|
|
24
|
-
@keydown.up.prevent="selectPrev"
|
|
25
|
-
@keydown.enter.prevent="selectCurrent"
|
|
26
|
-
@keydown.esc="closeDropdown"
|
|
27
|
-
@keydown.tab="closeDropdown" />
|
|
28
|
-
<button
|
|
29
|
-
v-if="hasValidId && !disabled && !embedded"
|
|
30
|
-
type="button"
|
|
31
|
-
class="aform_form-btn"
|
|
32
|
-
@click="handleNavigate"
|
|
33
|
-
@keydown.enter.prevent="handleNavigate">
|
|
34
|
-
<span>{{ icon === 'chevron-right' ? '›' : '→' }}</span>
|
|
35
|
-
</button>
|
|
36
|
-
</div>
|
|
37
|
-
<ul v-if="dropdownOpen" :id="listboxId" class="autocomplete-results" role="listbox" :aria-label="ariaLabel">
|
|
38
|
-
<li v-if="loading" class="autocomplete-result loading">Loading…</li>
|
|
39
|
-
<li
|
|
40
|
-
v-for="(option, i) in dropdownResults"
|
|
41
|
-
v-else
|
|
42
|
-
:id="`${listboxId}-opt-${i}`"
|
|
43
|
-
:key="String(option.id)"
|
|
44
|
-
role="option"
|
|
45
|
-
:aria-selected="i === activeIndex"
|
|
46
|
-
class="autocomplete-result"
|
|
47
|
-
:class="{ 'is-active': i === activeIndex }"
|
|
48
|
-
@mousedown.prevent="selectOption(option)">
|
|
49
|
-
<slot name="option" :option="option">{{ option.displayText ?? String(option.id) }}</slot>
|
|
50
|
-
</li>
|
|
51
|
-
</ul>
|
|
52
|
-
</div>
|
|
53
|
-
<label v-if="label && !embedded" class="aform_field-label">{{ label }}</label>
|
|
54
|
-
</template>
|
|
55
|
-
</div>
|
|
56
|
-
</template>
|
|
57
|
-
|
|
58
|
-
<script setup lang="ts">
|
|
59
|
-
import { vOnClickOutside } from '@vueuse/components'
|
|
60
|
-
import { computed, inject, ref, useId, watch } from 'vue'
|
|
61
|
-
|
|
62
|
-
import type { AFormLinkModelValue, AFormLinkNavigator, AFormLinkValue, ComponentProps } from '../../types'
|
|
63
|
-
import { deserializeFunction } from '../../utils/deserialize'
|
|
64
|
-
|
|
65
|
-
const {
|
|
66
|
-
label,
|
|
67
|
-
mode,
|
|
68
|
-
uuid,
|
|
69
|
-
required,
|
|
70
|
-
doctype = undefined,
|
|
71
|
-
formatter = undefined,
|
|
72
|
-
icon = 'arrow-right',
|
|
73
|
-
disabled = false,
|
|
74
|
-
filterFunction = undefined,
|
|
75
|
-
isAsync = false,
|
|
76
|
-
embedded = false,
|
|
77
|
-
placeholder = undefined,
|
|
78
|
-
ariaLabel = undefined,
|
|
79
|
-
} = defineProps<
|
|
80
|
-
ComponentProps & {
|
|
81
|
-
doctype?: string
|
|
82
|
-
formatter?: (value: AFormLinkValue) => string
|
|
83
|
-
icon?: 'arrow-right' | 'chevron-right'
|
|
84
|
-
disabled?: boolean
|
|
85
|
-
filterFunction?: string | ((search: string) => AFormLinkValue[] | Promise<AFormLinkValue[]>)
|
|
86
|
-
isAsync?: boolean
|
|
87
|
-
// Bare rendering for compositing into another component's own bordered container
|
|
88
|
-
// (e.g. ACurrencyInput's merged amount+currency group): suppresses this component's
|
|
89
|
-
// own outline/border and floating label so the parent supplies both exactly once.
|
|
90
|
-
embedded?: boolean
|
|
91
|
-
placeholder?: string
|
|
92
|
-
// Accessible name for the search input. Needed in `embedded` mode, where the visible
|
|
93
|
-
// <label> is suppressed and a placeholder is not a dependable accessible name — it is
|
|
94
|
-
// not exposed consistently across screen readers and vanishes once a value is picked.
|
|
95
|
-
ariaLabel?: string
|
|
96
|
-
}
|
|
97
|
-
>()
|
|
98
|
-
|
|
99
|
-
// Ties the input to its listbox and to the active option (aria-controls / aria-activedescendant).
|
|
100
|
-
// `uuid` is per-field when AForm supplies it; the fallback keeps the ids unique for a standalone
|
|
101
|
-
// mount so two pickers on one page can't cross-wire their ARIA relationships.
|
|
102
|
-
const listboxId = `${uuid ?? `aform-link-${useId()}`}-listbox`
|
|
103
|
-
|
|
104
|
-
const modelValue = defineModel<AFormLinkModelValue>({ default: () => ({ id: '', displayText: '' }) })
|
|
105
|
-
|
|
106
|
-
const hasValidId = computed(() => linkId(modelValue.value) !== undefined)
|
|
107
|
-
|
|
108
|
-
const displayedText = computed(() => {
|
|
109
|
-
const id = linkId(modelValue.value)
|
|
110
|
-
if (id === undefined) return '—'
|
|
111
|
-
if (formatter) return formatter(asLinkValue(modelValue.value))
|
|
112
|
-
return linkDisplayText(modelValue.value) ?? id
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const searchText = ref(hasValidId.value ? displayedText.value : '')
|
|
116
|
-
const dropdownOpen = ref(false)
|
|
117
|
-
const loading = ref(false)
|
|
118
|
-
const dropdownResults = ref<AFormLinkValue[]>([])
|
|
119
|
-
const activeIndex = ref<number | null>(null)
|
|
120
|
-
|
|
121
|
-
const navigator = inject<AFormLinkNavigator | null>('aformLinkNavigator', null)
|
|
122
|
-
|
|
123
|
-
type FilterFn = (search: string) => AFormLinkValue[] | Promise<AFormLinkValue[]>
|
|
124
|
-
type ResolverFn = (doctype: string, id: string) => string | undefined | Promise<string | undefined>
|
|
125
|
-
const resolver = inject<ResolverFn | null>('aformLinkResolver', null)
|
|
126
|
-
|
|
127
|
-
// A link's value arrives as a bare scalar as often as it does as an AFormLinkValue: a record
|
|
128
|
-
// loaded from the DB carries the raw FK column, and a parent bound to that column coerces every
|
|
129
|
-
// update it receives back to a scalar. So read the value through these three helpers rather than
|
|
130
|
-
// reaching into `modelValue.value` directly.
|
|
131
|
-
//
|
|
132
|
-
// Rejected: normalizing on write — an immediate watch that replaced a scalar model with `{ id }`.
|
|
133
|
-
// It survives only until the parent writes its scalar back, so it fixed a scalar passed once at
|
|
134
|
-
// mount and nothing else, and it emitted an `update:modelValue` the user never made on every
|
|
135
|
-
// mount of a scalar-valued link field.
|
|
136
|
-
function linkId(value: AFormLinkModelValue | null | undefined): string | undefined {
|
|
137
|
-
if (value == null) return undefined
|
|
138
|
-
if (typeof value === 'string') return value === '' ? undefined : value
|
|
139
|
-
if (typeof value === 'number') return String(value)
|
|
140
|
-
const id = value.id
|
|
141
|
-
if (id === null || id === undefined || id === '') return undefined
|
|
142
|
-
return String(id)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function linkDisplayText(value: AFormLinkModelValue | null | undefined): string | undefined {
|
|
146
|
-
if (value == null || typeof value !== 'object') return undefined
|
|
147
|
-
const text = value.displayText
|
|
148
|
-
return typeof text === 'string' || typeof text === 'number' ? String(text) : undefined
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function asLinkValue(value: AFormLinkModelValue | null | undefined): AFormLinkValue {
|
|
152
|
-
if (value != null && typeof value === 'object') return value
|
|
153
|
-
const id = linkId(value)
|
|
154
|
-
return id !== undefined ? { id } : { id: '' }
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// When the id changes (including on first render), resolve its display text.
|
|
158
|
-
// Tries filterFunction first (returns a list of candidates to search); falls back to the
|
|
159
|
-
// injected resolver (a direct doctype+id lookup). Skips if displayText is already set.
|
|
160
|
-
watch(
|
|
161
|
-
() => linkId(modelValue.value),
|
|
162
|
-
async id => {
|
|
163
|
-
// Already carries its text: nothing to resolve, but the input still has to show it.
|
|
164
|
-
// `searchText` is assigned at setup and then only by the branches below, by `selectOption`
|
|
165
|
-
// and by `closeDropdown` — and at setup a record's value has not arrived yet. So a link the
|
|
166
|
-
// server resolved for us (the middleware returns `{ id, displayText }` for an inline link)
|
|
167
|
-
// rendered blank, while a bare id rendered fine because it took the resolve branch.
|
|
168
|
-
if (id && linkDisplayText(modelValue.value)) {
|
|
169
|
-
// Through `displayedText` rather than the raw text, for the reason the resolve branch
|
|
170
|
-
// formats too: the same value must not render differently depending on how it arrived.
|
|
171
|
-
searchText.value = displayedText.value
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
if (!id) return
|
|
175
|
-
try {
|
|
176
|
-
let match: AFormLinkValue | undefined
|
|
177
|
-
let displayText: string | undefined
|
|
178
|
-
if (filterFunction) {
|
|
179
|
-
const fn: FilterFn =
|
|
180
|
-
typeof filterFunction === 'string' ? deserializeFunction<FilterFn>(filterFunction) : filterFunction
|
|
181
|
-
const results = await fn(String(id))
|
|
182
|
-
match = results.find(r => String(r.id) === String(id))
|
|
183
|
-
displayText = match?.displayText
|
|
184
|
-
if (displayText === undefined) {
|
|
185
|
-
console.warn(
|
|
186
|
-
`[AFormLink] filterFunction returned no matching result for id "${id}". ` +
|
|
187
|
-
`The function must return an item whose \`id\` exactly matches the search string.`
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
} else if (resolver && doctype) {
|
|
191
|
-
displayText = (await resolver(doctype, id.toString())) ?? undefined
|
|
192
|
-
}
|
|
193
|
-
if (displayText) {
|
|
194
|
-
// Merge the whole matched record, not just its display text — the extra properties
|
|
195
|
-
// an AFormLinkValue carries are part of the value (ACurrencyInput's `symbol`, which
|
|
196
|
-
// its formatter renders, is one). `id` is pinned to the value we already hold so a
|
|
197
|
-
// loosely-typed match (1 vs '1') can't change the FK's type underneath the record.
|
|
198
|
-
const resolved: AFormLinkValue = { ...asLinkValue(modelValue.value), ...match, id, displayText }
|
|
199
|
-
// Format for the same reason selectOption does: the input shows `formatter`'s output
|
|
200
|
-
// everywhere else (initial render, blur), so assigning the raw displayText here would
|
|
201
|
-
// make a value that arrived as a bare id render differently from the identical value
|
|
202
|
-
// picked by hand — e.g. "Euro" instead of "€".
|
|
203
|
-
searchText.value = formatter ? formatter(resolved) : displayText
|
|
204
|
-
modelValue.value = resolved
|
|
205
|
-
}
|
|
206
|
-
} catch {
|
|
207
|
-
// silent — fall back to showing the raw id
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
{ immediate: true }
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
const handleNavigate = () => {
|
|
214
|
-
const id = linkId(modelValue.value)
|
|
215
|
-
if (navigator && doctype && id !== undefined) {
|
|
216
|
-
navigator.navigate(doctype, id)
|
|
217
|
-
} else if (navigator && !doctype) {
|
|
218
|
-
console.warn(
|
|
219
|
-
`[AFormLink] Navigation requested but \`doctype\` prop is missing. ` +
|
|
220
|
-
`Add \`options\` (or \`doctype\`) to the Link field definition to enable navigation.`
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const openDropdown = async (text: string) => {
|
|
226
|
-
if (!filterFunction || mode === 'read') return
|
|
227
|
-
activeIndex.value = null
|
|
228
|
-
dropdownOpen.value = true
|
|
229
|
-
if (isAsync) loading.value = true
|
|
230
|
-
try {
|
|
231
|
-
const fn: FilterFn =
|
|
232
|
-
typeof filterFunction === 'string' ? deserializeFunction<FilterFn>(filterFunction) : filterFunction
|
|
233
|
-
dropdownResults.value = (await fn(text)) ?? []
|
|
234
|
-
} catch {
|
|
235
|
-
dropdownResults.value = []
|
|
236
|
-
} finally {
|
|
237
|
-
loading.value = false
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const closeDropdown = () => {
|
|
242
|
-
dropdownOpen.value = false
|
|
243
|
-
activeIndex.value = null
|
|
244
|
-
searchText.value = hasValidId.value ? displayedText.value : ''
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const onClickOutside = () => {
|
|
248
|
-
if (dropdownOpen.value) closeDropdown()
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const onFocus = () => openDropdown(searchText.value)
|
|
252
|
-
|
|
253
|
-
const onInput = () => openDropdown(searchText.value)
|
|
254
|
-
|
|
255
|
-
const selectOption = (option: AFormLinkValue) => {
|
|
256
|
-
modelValue.value = option
|
|
257
|
-
// Format `option` directly rather than reading back displayedText/modelValue: under a real
|
|
258
|
-
// two-way v-model (e.g. ACurrencyInput binding to a computed with a side-effecting setter),
|
|
259
|
-
// modelValue.value still reflects the *prop* until the parent's update round-trips back,
|
|
260
|
-
// so reading it synchronously here would show stale (pre-selection) text for a tick.
|
|
261
|
-
searchText.value = formatter ? formatter(option) : (option.displayText ?? String(option.id))
|
|
262
|
-
dropdownOpen.value = false
|
|
263
|
-
activeIndex.value = null
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const selectNext = () => {
|
|
267
|
-
const len = dropdownResults.value.length
|
|
268
|
-
if (!len) return
|
|
269
|
-
activeIndex.value = activeIndex.value === null ? 0 : (activeIndex.value + 1) % len
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const selectPrev = () => {
|
|
273
|
-
const len = dropdownResults.value.length
|
|
274
|
-
if (!len) return
|
|
275
|
-
if (activeIndex.value === null || activeIndex.value === 0) {
|
|
276
|
-
activeIndex.value = null
|
|
277
|
-
} else {
|
|
278
|
-
activeIndex.value -= 1
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const selectCurrent = () => {
|
|
283
|
-
if (activeIndex.value !== null && dropdownResults.value[activeIndex.value]) {
|
|
284
|
-
selectOption(dropdownResults.value[activeIndex.value])
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
</script>
|
|
288
|
-
|
|
289
|
-
<style scoped>
|
|
290
|
-
.aform_form-link-wrapper {
|
|
291
|
-
position: relative;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.input-group {
|
|
295
|
-
display: flex;
|
|
296
|
-
align-items: stretch;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.aform_input-field {
|
|
300
|
-
flex: 1;
|
|
301
|
-
min-width: 0;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
.aform_form-element--embedded {
|
|
305
|
-
min-width: 0;
|
|
306
|
-
flex-grow: 0;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/* Embedded mode: the host component's own container supplies the border, so this input
|
|
310
|
-
goes borderless and inherits the host's padding scale instead of the standalone default. */
|
|
311
|
-
.aform_input-field--embedded {
|
|
312
|
-
outline: none;
|
|
313
|
-
background: transparent;
|
|
314
|
-
padding: 0.5ch 1ch;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/* Only in embedded mode is the trigger deliberately narrower than its options (e.g. a currency
|
|
318
|
-
symbol box): let the dropdown grow past it rather than truncate. Standalone pickers keep the
|
|
319
|
-
dropdown flush with the input, so a long display text can't overhang a narrow form column. */
|
|
320
|
-
.aform_form-element--embedded .autocomplete-results {
|
|
321
|
-
min-width: 100%;
|
|
322
|
-
width: max-content;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/* Give the button the same outline as the input, then slide it 2px left so the outlines
|
|
326
|
-
overlap exactly at the join: input right outline sits at (input_right - 1px),
|
|
327
|
-
button left outline also sits at (button_left + 1px) = (input_right - 2px + 1px) = same pixel. */
|
|
328
|
-
.aform_form-btn {
|
|
329
|
-
appearance: none;
|
|
330
|
-
border: none;
|
|
331
|
-
outline: 1px solid var(--sc-input-border-color);
|
|
332
|
-
outline-offset: -1px;
|
|
333
|
-
margin-left: -2px;
|
|
334
|
-
background: var(--sc-input-field-background);
|
|
335
|
-
padding: 0 0.75rem;
|
|
336
|
-
cursor: pointer;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
.aform_form-btn:focus,
|
|
340
|
-
.input-group:focus-within .aform_form-btn {
|
|
341
|
-
outline-color: var(--sc-input-active-border-color);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.autocomplete-results {
|
|
345
|
-
position: absolute;
|
|
346
|
-
width: 100%;
|
|
347
|
-
z-index: 100;
|
|
348
|
-
padding: 0;
|
|
349
|
-
margin: 0;
|
|
350
|
-
list-style: none;
|
|
351
|
-
border: 1px solid var(--sc-input-active-border-color);
|
|
352
|
-
border-top: none;
|
|
353
|
-
border-radius: 0 0 0.25rem 0.25rem;
|
|
354
|
-
background: #fff;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.autocomplete-result {
|
|
358
|
-
padding: 4px 6px;
|
|
359
|
-
cursor: pointer;
|
|
360
|
-
border-bottom: 0.5px solid lightgray;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.autocomplete-result.is-active,
|
|
364
|
-
.autocomplete-result:hover {
|
|
365
|
-
background-color: var(--sc-row-color-zebra-light);
|
|
366
|
-
color: var(--sc-input-active-border-color);
|
|
367
|
-
}
|
|
368
|
-
</style>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="aform_form-element">
|
|
3
|
-
<template v-if="mode === 'display'">
|
|
4
|
-
<span class="aform_display-value">{{ inputNumber ?? '' }}</span>
|
|
5
|
-
<label class="aform_field-label">{{ label }}</label>
|
|
6
|
-
</template>
|
|
7
|
-
<template v-else>
|
|
8
|
-
<input
|
|
9
|
-
:id="uuid"
|
|
10
|
-
v-model="inputNumber"
|
|
11
|
-
class="aform_input-field"
|
|
12
|
-
type="number"
|
|
13
|
-
:disabled="mode === 'read'"
|
|
14
|
-
:required="required" />
|
|
15
|
-
<label class="aform_field-label" :for="uuid">{{ label }}</label>
|
|
16
|
-
<p v-show="errorText" class="aform_error" v-html="errorText"></p>
|
|
17
|
-
</template>
|
|
18
|
-
</div>
|
|
19
|
-
</template>
|
|
20
|
-
|
|
21
|
-
<script setup lang="ts">
|
|
22
|
-
import { computed } from 'vue'
|
|
23
|
-
|
|
24
|
-
import { ComponentProps } from '../../types'
|
|
25
|
-
|
|
26
|
-
const { label, required, mode, uuid, errors, validation = { errorMessage: '' } } = defineProps<ComponentProps>()
|
|
27
|
-
|
|
28
|
-
// Dynamic trigger errors take precedence over a static schema errorMessage; empty means the slot hides.
|
|
29
|
-
const errorText = computed(() => (errors?.length ? errors.join('; ') : (validation.errorMessage ?? '')))
|
|
30
|
-
|
|
31
|
-
const inputNumber = defineModel<number>()
|
|
32
|
-
</script>
|