@bagelink/vue 1.15.207 → 1.15.209
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/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts +2 -2
- package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/charts/MapChart.vue.d.ts +4 -0
- package/dist/components/charts/MapChart.vue.d.ts.map +1 -1
- package/dist/components/dataTable/DataTable.vue.d.ts +4 -2
- package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/dataTable/useTableVirtualization.d.ts +25 -0
- package/dist/components/dataTable/useTableVirtualization.d.ts.map +1 -0
- package/dist/components/form/inputs/ArrayInput.vue.d.ts +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SignaturePad.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ToggleInput.vue.d.ts +1 -0
- package/dist/components/form/inputs/ToggleInput.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/index.cjs +152 -175
- package/dist/index.mjs +37613 -31467
- package/dist/style.css +2 -1
- package/package.json +1 -1
- package/src/components/charts/MapChart.vue +191 -24
- package/src/components/dataTable/DataTable.vue +20 -4
- package/src/components/form/inputs/ArrayInput.vue +3 -3
- package/src/components/form/inputs/EmailInput.vue +1 -1
- package/src/components/form/inputs/NumberInput.vue +1 -1
- package/src/components/form/inputs/SelectInput.vue +3 -3
- package/src/components/form/inputs/SignaturePad.vue +0 -7
- package/src/components/form/inputs/TextInput.vue +1 -1
- package/src/components/form/inputs/ToggleInput.vue +8 -0
- package/src/form-flow/FormFlow.vue +9 -4
- package/src/styles/inputs.css +18 -0
package/package.json
CHANGED
|
@@ -5,21 +5,38 @@
|
|
|
5
5
|
*
|
|
6
6
|
* <MapChart :data="[{ id: 'US', value: 4200 }, { id: 'IL', label: 'Israel', value: 900 }]" />
|
|
7
7
|
* <MapChart :data="rows" color="green" :height="280" />
|
|
8
|
+
* <MapChart :data="rows" :zoomable="false" />
|
|
8
9
|
*/
|
|
9
10
|
import { computed, ref } from 'vue'
|
|
10
11
|
import type { ThemeType } from '../../types'
|
|
12
|
+
import Btn from '../Btn.vue'
|
|
11
13
|
import ChartTooltip from './ChartTooltip.vue'
|
|
12
14
|
import { formatValue, type ValueFormat } from './core/format'
|
|
13
15
|
import { resolveColor } from './core/palette'
|
|
14
16
|
import { useChartAnim } from './core/useChartAnim'
|
|
15
17
|
import { tipFollowStyle, trackLocalPointer } from './core/useChartTip'
|
|
16
|
-
import { WORLD_NAMES, WORLD_PATHS
|
|
18
|
+
import { WORLD_NAMES, WORLD_PATHS } from './core/world-paths'
|
|
17
19
|
import type { MapDatum } from './mapTypes'
|
|
18
20
|
|
|
19
21
|
defineOptions({ name: 'BglMapChart' })
|
|
20
22
|
|
|
21
23
|
export type { MapDatum }
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Crop empty ocean padding from Natural Earth sphere-fit (paths leave ~16%
|
|
27
|
+
* empty at the bottom and thin side margins inside 960×480).
|
|
28
|
+
*/
|
|
29
|
+
const MAP_VIEW = {
|
|
30
|
+
x: 16,
|
|
31
|
+
y: 0,
|
|
32
|
+
width: 928,
|
|
33
|
+
height: 412,
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
const MIN_ZOOM = 1
|
|
37
|
+
const MAX_ZOOM = 8
|
|
38
|
+
const ZOOM_STEP = 1.35
|
|
39
|
+
|
|
23
40
|
const props = withDefaults(defineProps<{
|
|
24
41
|
data: MapDatum[]
|
|
25
42
|
/** Base tone for the value ramp (low → high intensity). */
|
|
@@ -27,6 +44,8 @@ const props = withDefaults(defineProps<{
|
|
|
27
44
|
height?: number
|
|
28
45
|
/** Show min→max legend strip under the map. */
|
|
29
46
|
legend?: boolean
|
|
47
|
+
/** Wheel + button zoom / drag-to-pan. */
|
|
48
|
+
zoomable?: boolean
|
|
30
49
|
currency?: string
|
|
31
50
|
prefix?: string
|
|
32
51
|
suffix?: string
|
|
@@ -35,10 +54,12 @@ const props = withDefaults(defineProps<{
|
|
|
35
54
|
color: 'primary',
|
|
36
55
|
height: 280,
|
|
37
56
|
legend: true,
|
|
57
|
+
zoomable: true,
|
|
38
58
|
animated: true,
|
|
39
59
|
})
|
|
40
60
|
|
|
41
61
|
const el = ref<HTMLElement>()
|
|
62
|
+
const svgEl = ref<SVGSVGElement>()
|
|
42
63
|
const { progress } = useChartAnim({ el, enabled: props.animated, duration: 700 })
|
|
43
64
|
|
|
44
65
|
const fmt = computed<ValueFormat>(() => ({
|
|
@@ -64,8 +85,8 @@ const byId = computed(() => {
|
|
|
64
85
|
const maxValue = computed(() => Math.max(0, ...[...byId.value.values()].map(d => d.value)))
|
|
65
86
|
|
|
66
87
|
const baseColor = computed(() => resolveColor(props.color))
|
|
67
|
-
const emptyFill = '
|
|
68
|
-
const strokeColor = '
|
|
88
|
+
const emptyFill = 'var(--bgl-bg)'
|
|
89
|
+
const strokeColor = 'var(--bgl-border-color)'
|
|
69
90
|
|
|
70
91
|
/** Continuous ramp: empty → light tint → full base color by value share. */
|
|
71
92
|
function fillFor(value: number | undefined): string {
|
|
@@ -103,12 +124,17 @@ const hoverId = ref<string | null>(null)
|
|
|
103
124
|
const tipX = ref(0)
|
|
104
125
|
const tipY = ref(0)
|
|
105
126
|
|
|
106
|
-
function onEnter(id: string) {
|
|
127
|
+
function onEnter(id: string) {
|
|
128
|
+
if (dragging.value) return
|
|
129
|
+
hoverId.value = id
|
|
130
|
+
}
|
|
107
131
|
function onLeave() { hoverId.value = null }
|
|
108
|
-
function onMove(e: MouseEvent) {
|
|
132
|
+
function onMove(e: MouseEvent) {
|
|
133
|
+
if (!dragging.value) trackLocalPointer(e, tipX, tipY)
|
|
134
|
+
}
|
|
109
135
|
|
|
110
136
|
const hover = computed(() => {
|
|
111
|
-
if (!hoverId.value) return null
|
|
137
|
+
if (!hoverId.value || dragging.value) return null
|
|
112
138
|
const c = countries.value.find(x => x.id === hoverId.value)
|
|
113
139
|
return c?.hasData ? c : null
|
|
114
140
|
})
|
|
@@ -121,41 +147,178 @@ const tooltipStyle = tipFollowStyle(tipX, tipY)
|
|
|
121
147
|
|
|
122
148
|
const legendMin = computed(() => formatValue(0, fmt.value))
|
|
123
149
|
const legendMax = computed(() => formatValue(maxValue.value, fmt.value))
|
|
150
|
+
|
|
151
|
+
// ── Zoom / pan ──────────────────────────────────────────────────────────────
|
|
152
|
+
const scale = ref(1)
|
|
153
|
+
const tx = ref(0)
|
|
154
|
+
const ty = ref(0)
|
|
155
|
+
const dragging = ref(false)
|
|
156
|
+
const dragOrigin = ref<{ x: number; y: number; tx: number; ty: number } | null>(null)
|
|
157
|
+
|
|
158
|
+
const mapTransform = computed(() =>
|
|
159
|
+
`translate(${tx.value} ${ty.value}) scale(${scale.value})`,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const isZoomed = computed(() => scale.value > MIN_ZOOM + 0.001)
|
|
163
|
+
|
|
164
|
+
function clamp(n: number, min: number, max: number) {
|
|
165
|
+
return Math.min(max, Math.max(min, n))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Client → SVG user coords (cropped viewBox space). */
|
|
169
|
+
function clientToSvg(clientX: number, clientY: number) {
|
|
170
|
+
const svg = svgEl.value
|
|
171
|
+
if (!svg) return { x: 0, y: 0 }
|
|
172
|
+
const pt = svg.createSVGPoint()
|
|
173
|
+
pt.x = clientX
|
|
174
|
+
pt.y = clientY
|
|
175
|
+
const ctm = svg.getScreenCTM()?.inverse()
|
|
176
|
+
if (!ctm) return { x: 0, y: 0 }
|
|
177
|
+
const p = pt.matrixTransform(ctm)
|
|
178
|
+
return { x: p.x, y: p.y }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function zoomAt(clientX: number, clientY: number, next: number) {
|
|
182
|
+
const newScale = clamp(next, MIN_ZOOM, MAX_ZOOM)
|
|
183
|
+
if (newScale === scale.value) return
|
|
184
|
+
const { x, y } = clientToSvg(clientX, clientY)
|
|
185
|
+
const k = newScale / scale.value
|
|
186
|
+
tx.value = x - (x - tx.value) * k
|
|
187
|
+
ty.value = y - (y - ty.value) * k
|
|
188
|
+
scale.value = newScale
|
|
189
|
+
if (newScale <= MIN_ZOOM) {
|
|
190
|
+
tx.value = 0
|
|
191
|
+
ty.value = 0
|
|
192
|
+
scale.value = MIN_ZOOM
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function onWheel(e: WheelEvent) {
|
|
197
|
+
if (!props.zoomable) return
|
|
198
|
+
e.preventDefault()
|
|
199
|
+
const factor = e.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP
|
|
200
|
+
zoomAt(e.clientX, e.clientY, scale.value * factor)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function zoomBy(factor: number) {
|
|
204
|
+
const svg = svgEl.value
|
|
205
|
+
if (!svg) return
|
|
206
|
+
const r = svg.getBoundingClientRect()
|
|
207
|
+
zoomAt(r.left + r.width / 2, r.top + r.height / 2, scale.value * factor)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function resetZoom() {
|
|
211
|
+
scale.value = MIN_ZOOM
|
|
212
|
+
tx.value = 0
|
|
213
|
+
ty.value = 0
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function onPointerDown(e: PointerEvent) {
|
|
217
|
+
if (!props.zoomable || !isZoomed.value || e.button !== 0) return
|
|
218
|
+
dragging.value = true
|
|
219
|
+
hoverId.value = null
|
|
220
|
+
const p = clientToSvg(e.clientX, e.clientY)
|
|
221
|
+
dragOrigin.value = { x: p.x, y: p.y, tx: tx.value, ty: ty.value }
|
|
222
|
+
;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function onPointerMove(e: PointerEvent) {
|
|
226
|
+
if (!dragging.value || !dragOrigin.value) return
|
|
227
|
+
const p = clientToSvg(e.clientX, e.clientY)
|
|
228
|
+
tx.value = dragOrigin.value.tx + (p.x - dragOrigin.value.x)
|
|
229
|
+
ty.value = dragOrigin.value.ty + (p.y - dragOrigin.value.y)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onPointerUp(e: PointerEvent) {
|
|
233
|
+
if (!dragging.value) return
|
|
234
|
+
dragging.value = false
|
|
235
|
+
dragOrigin.value = null
|
|
236
|
+
;(e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const croppedViewBox = `${MAP_VIEW.x} ${MAP_VIEW.y} ${MAP_VIEW.width} ${MAP_VIEW.height}`
|
|
124
240
|
</script>
|
|
125
241
|
|
|
126
242
|
<template>
|
|
127
243
|
<div ref="el" class="bgl-map w-100p flex column gap-075">
|
|
128
244
|
<div
|
|
129
|
-
class="bgl-map__plot relative w-100p"
|
|
245
|
+
class="bgl-map__plot relative w-100p overflow-hidden"
|
|
246
|
+
:class="{
|
|
247
|
+
'bgl-map__plot--zoomable': zoomable,
|
|
248
|
+
'bgl-map__plot--zoomed': zoomable && isZoomed,
|
|
249
|
+
'bgl-map__plot--dragging': dragging,
|
|
250
|
+
}"
|
|
130
251
|
:style="{ height: `${height}px` }"
|
|
131
252
|
@mousemove="onMove"
|
|
132
253
|
@mouseleave="onLeave"
|
|
254
|
+
@wheel="onWheel"
|
|
255
|
+
@pointerdown="onPointerDown"
|
|
256
|
+
@pointermove="onPointerMove"
|
|
257
|
+
@pointerup="onPointerUp"
|
|
258
|
+
@pointercancel="onPointerUp"
|
|
133
259
|
>
|
|
134
260
|
<svg
|
|
261
|
+
ref="svgEl"
|
|
135
262
|
class="bgl-map__svg"
|
|
136
263
|
dir="ltr"
|
|
137
|
-
:viewBox="
|
|
264
|
+
:viewBox="croppedViewBox"
|
|
138
265
|
preserveAspectRatio="xMidYMid meet"
|
|
139
266
|
width="100%"
|
|
140
267
|
height="100%"
|
|
141
268
|
>
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
269
|
+
<g :transform="mapTransform">
|
|
270
|
+
<path
|
|
271
|
+
v-for="c in countries"
|
|
272
|
+
:key="c.id"
|
|
273
|
+
:d="c.d"
|
|
274
|
+
:fill="c.fill"
|
|
275
|
+
:stroke="strokeColor"
|
|
276
|
+
stroke-width="0.6"
|
|
277
|
+
vector-effect="non-scaling-stroke"
|
|
278
|
+
class="bgl-map__country"
|
|
279
|
+
:class="{
|
|
280
|
+
'opacity-5': hoverId != null && hoverId !== c.id,
|
|
281
|
+
'bgl-map__country--hot': hoverId === c.id && c.hasData,
|
|
282
|
+
}"
|
|
283
|
+
@mouseenter="onEnter(c.id)"
|
|
284
|
+
/>
|
|
285
|
+
</g>
|
|
157
286
|
</svg>
|
|
158
287
|
|
|
288
|
+
<div
|
|
289
|
+
v-if="zoomable"
|
|
290
|
+
class="bgl-map__zoom flex column gap-025 absolute bottom-05 end-025 z-1 p-025 round bg-box shadow-10 border"
|
|
291
|
+
@pointerdown.stop
|
|
292
|
+
>
|
|
293
|
+
<Btn
|
|
294
|
+
flat
|
|
295
|
+
thin
|
|
296
|
+
round
|
|
297
|
+
size="xs"
|
|
298
|
+
icon="add"
|
|
299
|
+
:disabled="scale >= MAX_ZOOM"
|
|
300
|
+
@click="zoomBy(ZOOM_STEP)"
|
|
301
|
+
/>
|
|
302
|
+
<Btn
|
|
303
|
+
flat
|
|
304
|
+
thin
|
|
305
|
+
round
|
|
306
|
+
size="xs"
|
|
307
|
+
icon="remove"
|
|
308
|
+
:disabled="scale <= MIN_ZOOM"
|
|
309
|
+
@click="zoomBy(1 / ZOOM_STEP)"
|
|
310
|
+
/>
|
|
311
|
+
<Btn
|
|
312
|
+
:disabled="!isZoomed"
|
|
313
|
+
flat
|
|
314
|
+
thin
|
|
315
|
+
round
|
|
316
|
+
size="xs"
|
|
317
|
+
icon="zoom_out_map"
|
|
318
|
+
@click="resetZoom"
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
159
322
|
<ChartTooltip
|
|
160
323
|
v-if="hover"
|
|
161
324
|
prefer="above"
|
|
@@ -183,12 +346,16 @@ const legendMax = computed(() => formatValue(maxValue.value, fmt.value))
|
|
|
183
346
|
|
|
184
347
|
<style scoped>
|
|
185
348
|
.bgl-map__plot { min-height: 0; }
|
|
186
|
-
.bgl-
|
|
349
|
+
.bgl-map__plot--zoomable { touch-action: none; }
|
|
350
|
+
.bgl-map__plot--zoomed { cursor: grab; }
|
|
351
|
+
.bgl-map__plot--dragging { cursor: grabbing; }
|
|
352
|
+
.bgl-map__svg { display: block; overflow: hidden; }
|
|
187
353
|
.bgl-map__country {
|
|
188
354
|
cursor: default;
|
|
189
355
|
transition: opacity 0.15s ease, fill 0.15s ease;
|
|
190
356
|
outline: none;
|
|
191
357
|
}
|
|
358
|
+
.bgl-map__plot--zoomed .bgl-map__country { cursor: inherit; }
|
|
192
359
|
.bgl-map__country--hot { stroke: var(--bgl-gray); stroke-width: 1.2; }
|
|
193
360
|
.bgl-map__ramp {
|
|
194
361
|
height: 0.5rem;
|
|
@@ -32,6 +32,8 @@ const slots: SetupContext['slots'] = useSlots()
|
|
|
32
32
|
|
|
33
33
|
const loading = defineModel('loading', { default: false })
|
|
34
34
|
const itemHeight = defineModel('itemHeight', { default: 50 })
|
|
35
|
+
/** Cell font size. Number = px; string = any CSS size (e.g. '0.875rem'). */
|
|
36
|
+
const fontSize = defineModel<number | string | undefined>('fontSize', { default: undefined })
|
|
35
37
|
const selectedItems = defineModel<string[]>('selectedItems', { default: [] as string[] })
|
|
36
38
|
|
|
37
39
|
const {
|
|
@@ -150,6 +152,20 @@ const showLoading = computed(() => loading.value)
|
|
|
150
152
|
|
|
151
153
|
// Show the empty state only once loading has settled and there are no rows.
|
|
152
154
|
const isEmpty = computed(() => !showLoading.value && computedData.value.length === 0)
|
|
155
|
+
|
|
156
|
+
const resolvedFontSize = computed(() => {
|
|
157
|
+
const size = fontSize.value
|
|
158
|
+
if (size == null || size === '') return null
|
|
159
|
+
return typeof size === 'number' ? `${size}px` : String(size)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
/** Applied to body cells — falls back to the historical default. */
|
|
163
|
+
const bodyFontSize = computed(() => resolvedFontSize.value ?? '0.88em')
|
|
164
|
+
/** Header is slightly smaller than body when a custom size is set. */
|
|
165
|
+
const headerFontSize = computed(() => {
|
|
166
|
+
if (!resolvedFontSize.value) return '0.8rem'
|
|
167
|
+
return `calc(${resolvedFontSize.value} * 0.9)`
|
|
168
|
+
})
|
|
153
169
|
</script>
|
|
154
170
|
|
|
155
171
|
<template>
|
|
@@ -292,7 +308,7 @@ width: 100%;
|
|
|
292
308
|
}
|
|
293
309
|
|
|
294
310
|
th {
|
|
295
|
-
font-size:
|
|
311
|
+
font-size: v-bind(headerFontSize);
|
|
296
312
|
color: var(--bgl-black-tint);
|
|
297
313
|
position: sticky;
|
|
298
314
|
top: 0;
|
|
@@ -315,7 +331,7 @@ cursor: pointer;
|
|
|
315
331
|
}
|
|
316
332
|
|
|
317
333
|
.row.first-row {
|
|
318
|
-
font-size:
|
|
334
|
+
font-size: v-bind(headerFontSize);
|
|
319
335
|
color: var(--bgl-black-tint);
|
|
320
336
|
position: sticky;
|
|
321
337
|
top: 0;
|
|
@@ -377,7 +393,7 @@ margin-top: -2px;
|
|
|
377
393
|
}
|
|
378
394
|
|
|
379
395
|
.rows {
|
|
380
|
-
font-size:
|
|
396
|
+
font-size: v-bind(bodyFontSize);
|
|
381
397
|
}
|
|
382
398
|
|
|
383
399
|
.table-list {
|
|
@@ -476,7 +492,7 @@ line-height: 1;
|
|
|
476
492
|
}
|
|
477
493
|
|
|
478
494
|
tbody tr {
|
|
479
|
-
font-size:
|
|
495
|
+
font-size: v-bind(bodyFontSize);
|
|
480
496
|
height: v-bind(computedItemHeight);
|
|
481
497
|
transition: all 200ms ease;
|
|
482
498
|
}
|
|
@@ -11,7 +11,7 @@ import { useBagelInputShell } from './bagelInputShell'
|
|
|
11
11
|
|
|
12
12
|
const props = defineProps<{
|
|
13
13
|
label?: string
|
|
14
|
-
|
|
14
|
+
helptext?: string
|
|
15
15
|
modelValue: T[]
|
|
16
16
|
allowAdd?: boolean
|
|
17
17
|
allowDelete?: boolean
|
|
@@ -176,8 +176,8 @@ function getItemRef(i: number) {
|
|
|
176
176
|
<label v-if="label" class="txt12 txt-gray-90 mb-025 block">
|
|
177
177
|
{{ resolveI18n(label) }}
|
|
178
178
|
</label>
|
|
179
|
-
<p v-if="
|
|
180
|
-
{{
|
|
179
|
+
<p v-if="helptext" class="helptext">
|
|
180
|
+
{{ helptext }}
|
|
181
181
|
</p>
|
|
182
182
|
<template v-if="!collapsible">
|
|
183
183
|
<div v-for="(_, i) in items" :key="i" class="array-input-row grid align-items-center" :class="{ 'simpleArray': isSimple, 'pb-1': !isSimple }">
|
|
@@ -351,7 +351,7 @@ onMounted(() => {
|
|
|
351
351
|
<label :for="id">
|
|
352
352
|
<span v-if="label || (underlined && placeholder)" class="label-text">{{ resolveI18n(label) || resolveI18n(placeholder) }}<span v-if="required"> *</span></span>
|
|
353
353
|
<div v-if="!underlined" class="flex">
|
|
354
|
-
<span v-if="helptext" class="
|
|
354
|
+
<span v-if="helptext" class="helptext">{{ helptext }}</span>
|
|
355
355
|
<span
|
|
356
356
|
v-if="suggestedCorrection" class="pointer nowrap inline-block ms-auto color-red txt-10px p-0"
|
|
357
357
|
@click.prevent="applyCorrection"
|
|
@@ -217,7 +217,7 @@ const hasValue = computed(() => formattedValue.value.length > 0)
|
|
|
217
217
|
:class="[{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }]" tabindex="-1" @click="decrement"
|
|
218
218
|
/>
|
|
219
219
|
</div>
|
|
220
|
-
<p v-if="helptext" class="
|
|
220
|
+
<p v-if="helptext" class="helptext">
|
|
221
221
|
{{ helptext }}
|
|
222
222
|
</p>
|
|
223
223
|
</div>
|
|
@@ -36,7 +36,7 @@ interface PropTypes extends BagelInputShellProps {
|
|
|
36
36
|
icon?: IconType
|
|
37
37
|
hideLabel?: boolean
|
|
38
38
|
defaultValue?: Option
|
|
39
|
-
|
|
39
|
+
helptext?: string
|
|
40
40
|
clearable?: boolean
|
|
41
41
|
placement?: AlignedPlacement
|
|
42
42
|
searchPlaceholder?: string
|
|
@@ -315,8 +315,8 @@ onMounted(() => {
|
|
|
315
315
|
{{ error }}
|
|
316
316
|
</div>
|
|
317
317
|
</label>
|
|
318
|
-
<p v-if="
|
|
319
|
-
{{
|
|
318
|
+
<p v-if="helptext" class="helptext">
|
|
319
|
+
{{ helptext }}
|
|
320
320
|
</p>
|
|
321
321
|
</template>
|
|
322
322
|
<Skeleton v-if="isLoading" :count="6" width="200px" height="25px" class="mx-1 my-1" />
|
|
@@ -294,13 +294,6 @@ font-size: var(--bgl-label-font-size);
|
|
|
294
294
|
margin-bottom: 0.25rem;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
.bgl_input.signature-pad > .helptext {
|
|
298
|
-
font-size: var(--bgl-label-font-size);
|
|
299
|
-
margin: 0 0 0.25rem 0;
|
|
300
|
-
line-height: 1.4;
|
|
301
|
-
opacity: 0.7;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
297
|
.bgl_input.signature-pad .canvas[disabled] {
|
|
305
298
|
background: var(--input-disabled-bg);
|
|
306
299
|
border: 1px solid var(--bgl-gray);
|
|
@@ -186,10 +186,10 @@ onMounted(async () => {
|
|
|
186
186
|
:placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))" :disabled :required
|
|
187
187
|
v-bind="nativeInputAttrs" :pattern="normalizedPattern" @input="updateInputVal" @focusout="onFocusout"
|
|
188
188
|
/>
|
|
189
|
-
<p v-if="helptext" class="opacity-7 light">{{ helptext }}</p>
|
|
190
189
|
<Icon v-if="iconStart" :class="{ ' -top-025 line-height-0 ': underlined }" class="iconStart" :icon="iconStart" />
|
|
191
190
|
<Icon v-if="icon" :class="{ ' -top-025 line-height-0 ': underlined }" :icon />
|
|
192
191
|
</label>
|
|
192
|
+
<p v-if="helptext" class="helptext">{{ helptext }}</p>
|
|
193
193
|
<div v-if="error" class="error-message">
|
|
194
194
|
{{ error }}
|
|
195
195
|
</div>
|
|
@@ -8,6 +8,7 @@ const props = withDefaults(defineProps<{
|
|
|
8
8
|
label?: string
|
|
9
9
|
id?: string
|
|
10
10
|
title?: string
|
|
11
|
+
helptext?: string
|
|
11
12
|
small?: boolean
|
|
12
13
|
required?: boolean
|
|
13
14
|
defaultValue?: boolean
|
|
@@ -47,6 +48,10 @@ onMounted(() => {
|
|
|
47
48
|
{{ resolveI18n(label) }}
|
|
48
49
|
</slot>
|
|
49
50
|
</label>
|
|
51
|
+
|
|
52
|
+
<p v-if="helptext" class="helptext">
|
|
53
|
+
{{ helptext }}
|
|
54
|
+
</p>
|
|
50
55
|
</div>
|
|
51
56
|
</template>
|
|
52
57
|
|
|
@@ -58,6 +63,9 @@ display: unset !important;
|
|
|
58
63
|
.bagel-input.bgl-toggle *{
|
|
59
64
|
cursor: pointer !important;
|
|
60
65
|
}
|
|
66
|
+
.bagel-input.bgl-toggle .helptext {
|
|
67
|
+
cursor: default !important;
|
|
68
|
+
}
|
|
61
69
|
.bagel-input.bgl-toggle input{
|
|
62
70
|
padding: unset !important;
|
|
63
71
|
min-width: unset !important;
|
|
@@ -293,12 +293,15 @@ const mergedErrors = computed(() => {
|
|
|
293
293
|
|
|
294
294
|
function getFieldProps(field: FieldBuilder, key: string) {
|
|
295
295
|
const label = generateLabel(key, field)
|
|
296
|
+
const { helpText, ...configRest } = field._config
|
|
296
297
|
const baseProps: any = {
|
|
297
298
|
label,
|
|
298
299
|
'modelValue': getFieldValue(key),
|
|
299
300
|
'onUpdate:modelValue': (val: any) => { updateField(key, val, field) },
|
|
300
301
|
'error': mergedErrors.value[key],
|
|
301
|
-
...
|
|
302
|
+
...configRest,
|
|
303
|
+
// Inputs use `helptext`; FormFlow schema API keeps `helpText`
|
|
304
|
+
...(helpText != null ? { helptext: helpText } : {}),
|
|
302
305
|
name: field._config.name ?? key,
|
|
303
306
|
}
|
|
304
307
|
|
|
@@ -326,14 +329,16 @@ function getFieldProps(field: FieldBuilder, key: string) {
|
|
|
326
329
|
|
|
327
330
|
if (field._type === 'array') {
|
|
328
331
|
// `default` / `schema` are form-schema metadata — never pass them through to ArrayInput
|
|
329
|
-
// (and the earlier
|
|
332
|
+
// (and the earlier config spread may have already set them).
|
|
330
333
|
delete baseProps.default
|
|
331
334
|
delete baseProps.schema
|
|
332
335
|
delete baseProps.itemType
|
|
333
|
-
const
|
|
336
|
+
const { helpText: arrayHelpText, ...arrayConfigRest } = Object.fromEntries(
|
|
334
337
|
Object.entries(field._config).filter(([k]) => k !== 'schema' && k !== 'itemType' && k !== 'default')
|
|
335
338
|
)
|
|
336
|
-
Object.assign(baseProps,
|
|
339
|
+
Object.assign(baseProps, arrayConfigRest)
|
|
340
|
+
if (arrayHelpText != null) baseProps.helptext = arrayHelpText
|
|
341
|
+
delete baseProps.helpText
|
|
337
342
|
// Re-apply binding after config spread so handlers never get overwritten.
|
|
338
343
|
baseProps.modelValue = getFieldValue(key)
|
|
339
344
|
baseProps['onUpdate:modelValue'] = (val: any) => { updateField(key, val, field) }
|
package/src/styles/inputs.css
CHANGED
|
@@ -59,6 +59,24 @@ select {
|
|
|
59
59
|
color: var(--bgl-input-label-active-color);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
.helptext {
|
|
63
|
+
display: block;
|
|
64
|
+
margin: 0.15rem 0 0.25rem;
|
|
65
|
+
line-height: 1.4;
|
|
66
|
+
font-size: var(--bgl-label-font-size);
|
|
67
|
+
font-weight: 300;
|
|
68
|
+
color: var(--bgl-label-color);
|
|
69
|
+
cursor: default;
|
|
70
|
+
user-select: text;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Keep helptext muted even when nested inside a focused/active label */
|
|
74
|
+
.bagel-input:focus-within .helptext,
|
|
75
|
+
.bagel-input.open .helptext,
|
|
76
|
+
.bagel-input:has(:checked) .helptext {
|
|
77
|
+
color: var(--bgl-label-color);
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
.bagel-input input,
|
|
63
81
|
.bagel-input select,
|
|
64
82
|
.custom-select .input {
|