@bagelink/vue 1.15.207 → 1.15.211
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/composables/useSchemaField.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 +37955 -31794
- 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 +48 -19
- 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/composables/useSchemaField.ts +13 -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 {
|
|
@@ -126,9 +128,14 @@ const computedItemHeight = computed(() => `${itemHeight.value}px`)
|
|
|
126
128
|
|
|
127
129
|
watch(
|
|
128
130
|
() => loading.value,
|
|
129
|
-
(newLoadingVal, oldLoadingVal) => {
|
|
131
|
+
async (newLoadingVal, oldLoadingVal) => {
|
|
130
132
|
if (newLoadingVal === oldLoadingVal) { return }
|
|
131
|
-
if (!newLoadingVal) {
|
|
133
|
+
if (!newLoadingVal) {
|
|
134
|
+
registerLastItemObserver()
|
|
135
|
+
scrollTo(0)
|
|
136
|
+
await nextTick()
|
|
137
|
+
scrollTo(0)
|
|
138
|
+
}
|
|
132
139
|
},
|
|
133
140
|
{ immediate: true }
|
|
134
141
|
)
|
|
@@ -139,9 +146,14 @@ watch(
|
|
|
139
146
|
)
|
|
140
147
|
|
|
141
148
|
watch(
|
|
142
|
-
() => computedData.value,
|
|
143
|
-
(
|
|
144
|
-
|
|
149
|
+
() => computedData.value.length,
|
|
150
|
+
async (newLen, oldLen) => {
|
|
151
|
+
// Infinite-scroll appends: keep position. First load (0 → n) still resets.
|
|
152
|
+
const isAppend = props.onLastItemVisible !== undefined && (oldLen ?? 0) > 0
|
|
153
|
+
if (newLen === oldLen || isAppend) return
|
|
154
|
+
|
|
155
|
+
scrollTo(0)
|
|
156
|
+
await nextTick()
|
|
145
157
|
scrollTo(0)
|
|
146
158
|
}
|
|
147
159
|
)
|
|
@@ -150,12 +162,26 @@ const showLoading = computed(() => loading.value)
|
|
|
150
162
|
|
|
151
163
|
// Show the empty state only once loading has settled and there are no rows.
|
|
152
164
|
const isEmpty = computed(() => !showLoading.value && computedData.value.length === 0)
|
|
165
|
+
|
|
166
|
+
const resolvedFontSize = computed(() => {
|
|
167
|
+
const size = fontSize.value
|
|
168
|
+
if (size == null || size === '') return null
|
|
169
|
+
return typeof size === 'number' ? `${size}px` : String(size)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
/** Applied to body cells — falls back to the historical default. */
|
|
173
|
+
const bodyFontSize = computed(() => resolvedFontSize.value ?? '0.88em')
|
|
174
|
+
/** Header is slightly smaller than body when a custom size is set. */
|
|
175
|
+
const headerFontSize = computed(() => {
|
|
176
|
+
if (!resolvedFontSize.value) return '0.8rem'
|
|
177
|
+
return `calc(${resolvedFontSize.value} * 0.9)`
|
|
178
|
+
})
|
|
153
179
|
</script>
|
|
154
180
|
|
|
155
181
|
<template>
|
|
156
182
|
<div class="table-list-wrap h-100p" v-bind="containerProps" :class="{ overflowHiddenLoading: showLoading }">
|
|
157
183
|
<Loading v-if="showLoading" class="h100p" />
|
|
158
|
-
<div v-bind="wrapperProps" :class="{ 'pointer-events-none': showLoading }">
|
|
184
|
+
<div v-if="!isEmpty" v-bind="wrapperProps" :class="{ 'pointer-events-none': showLoading }">
|
|
159
185
|
<table class="infinite-wrapper" :class="{ selecting: computedSelectedItems.length }">
|
|
160
186
|
<thead class="row first-row">
|
|
161
187
|
<th v-if="isSortable" class="col-drag" />
|
|
@@ -202,16 +228,16 @@ const isEmpty = computed(() => !showLoading.value && computedData.value.length =
|
|
|
202
228
|
<tr v-if="props.onLastItemVisible !== undefined" ref="lastItemEl" style="height: 1px;" />
|
|
203
229
|
</tbody>
|
|
204
230
|
</table>
|
|
205
|
-
<!-- Built-in empty state (overridable via the #empty slot) -->
|
|
206
|
-
<slot v-if="isEmpty" name="empty">
|
|
207
|
-
<EmptyState
|
|
208
|
-
:icon="props.emptyIcon ?? 'inbox'"
|
|
209
|
-
:title="props.emptyTitle ?? 'No results'"
|
|
210
|
-
:description="props.emptyDescription"
|
|
211
|
-
compact
|
|
212
|
-
/>
|
|
213
|
-
</slot>
|
|
214
231
|
</div>
|
|
232
|
+
<!-- Outside the virtualized wrapper so EmptyState doesn't inflate scroll height -->
|
|
233
|
+
<slot v-if="isEmpty" name="empty">
|
|
234
|
+
<EmptyState
|
|
235
|
+
:icon="props.emptyIcon ?? 'inbox'"
|
|
236
|
+
:title="props.emptyTitle ?? 'No results'"
|
|
237
|
+
:description="props.emptyDescription"
|
|
238
|
+
compact
|
|
239
|
+
/>
|
|
240
|
+
</slot>
|
|
215
241
|
</div>
|
|
216
242
|
</template>
|
|
217
243
|
|
|
@@ -238,6 +264,9 @@ cursor: grab;
|
|
|
238
264
|
|
|
239
265
|
.table-list-wrap {
|
|
240
266
|
min-height: 150px;
|
|
267
|
+
/* Prevent browser scroll anchoring from pushing content to the bottom
|
|
268
|
+
when rows mount and the virtual list height jumps. */
|
|
269
|
+
overflow-anchor: none;
|
|
241
270
|
}
|
|
242
271
|
|
|
243
272
|
.selected {
|
|
@@ -292,7 +321,7 @@ width: 100%;
|
|
|
292
321
|
}
|
|
293
322
|
|
|
294
323
|
th {
|
|
295
|
-
font-size:
|
|
324
|
+
font-size: v-bind(headerFontSize);
|
|
296
325
|
color: var(--bgl-black-tint);
|
|
297
326
|
position: sticky;
|
|
298
327
|
top: 0;
|
|
@@ -315,7 +344,7 @@ cursor: pointer;
|
|
|
315
344
|
}
|
|
316
345
|
|
|
317
346
|
.row.first-row {
|
|
318
|
-
font-size:
|
|
347
|
+
font-size: v-bind(headerFontSize);
|
|
319
348
|
color: var(--bgl-black-tint);
|
|
320
349
|
position: sticky;
|
|
321
350
|
top: 0;
|
|
@@ -377,7 +406,7 @@ margin-top: -2px;
|
|
|
377
406
|
}
|
|
378
407
|
|
|
379
408
|
.rows {
|
|
380
|
-
font-size:
|
|
409
|
+
font-size: v-bind(bodyFontSize);
|
|
381
410
|
}
|
|
382
411
|
|
|
383
412
|
.table-list {
|
|
@@ -476,7 +505,7 @@ line-height: 1;
|
|
|
476
505
|
}
|
|
477
506
|
|
|
478
507
|
tbody tr {
|
|
479
|
-
font-size:
|
|
508
|
+
font-size: v-bind(bodyFontSize);
|
|
480
509
|
height: v-bind(computedItemHeight);
|
|
481
510
|
transition: all 200ms ease;
|
|
482
511
|
}
|
|
@@ -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;
|
|
@@ -26,6 +26,16 @@ const SLOT_VALUE_COMPONENTS = new Set(['div', 'span', 'p'])
|
|
|
26
26
|
|
|
27
27
|
const SRC_VALUE_COMPONENTS = new Set(['img', 'iframe'])
|
|
28
28
|
|
|
29
|
+
/** Display components that take content via `value` (not `modelValue`). */
|
|
30
|
+
const VALUE_PROP_NAMES = new Set(['BglBadge', 'Badge', 'BglPill', 'Pill'])
|
|
31
|
+
|
|
32
|
+
function usesValueProp(Component: unknown): boolean {
|
|
33
|
+
if (!Component || typeof Component === 'string') return false
|
|
34
|
+
const name = (Component as { name?: string, __name?: string }).name
|
|
35
|
+
?? (Component as { __name?: string }).__name
|
|
36
|
+
return !!name && VALUE_PROP_NAMES.has(name)
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
export interface UseSchemaFieldOptions<T, SFP extends Path<T>> {
|
|
30
40
|
mode?: 'form' | 'preview' | 'table'
|
|
31
41
|
getFormData?: () => T
|
|
@@ -217,6 +227,9 @@ export function useSchemaField<T extends { [key: string]: any }>(optns: UseSchem
|
|
|
217
227
|
// Slot components don't need a value prop
|
|
218
228
|
} else if (isSrcValueComponent) {
|
|
219
229
|
props.src = transformedValue
|
|
230
|
+
} else if (usesValueProp(Component)) {
|
|
231
|
+
// Badge etc. read `value`, not modelValue — empty outline pills otherwise
|
|
232
|
+
props.value = transformedValue != null ? String(transformedValue) : transformedValue
|
|
220
233
|
} else {
|
|
221
234
|
props.modelValue = transformedValue
|
|
222
235
|
}
|
|
@@ -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 {
|