@bagelink/vue 1.15.301 → 1.15.305
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/AccordionItem.vue.d.ts.map +1 -1
- package/dist/components/FilterQuery.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/CodeEditor/Index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/IconPickerInput.vue.d.ts +209 -0
- package/dist/components/form/inputs/IconPickerInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/index.d.ts +1 -0
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts +10 -0
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/TimelineItem.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +1 -0
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/index.cjs +34 -34
- package/dist/index.mjs +7087 -6951
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/AccordionItem.vue +1 -0
- package/src/components/FilterQuery.vue +3 -2
- package/src/components/form/inputs/CodeEditor/Index.vue +7 -1
- package/src/components/form/inputs/IconPickerInput.vue +120 -0
- package/src/components/form/inputs/index.ts +1 -0
- package/src/components/layout/AppContent.vue +48 -4
- package/src/components/layout/TimelineItem.vue +7 -3
- package/src/form-flow/FormFlow.vue +2 -0
- package/src/form-flow/form-flow.ts +7 -0
package/package.json
CHANGED
|
@@ -652,6 +652,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
652
652
|
.and-or-select {
|
|
653
653
|
--bgl-input-font-size: 12px !important;
|
|
654
654
|
--input-background-color: transparent !important;
|
|
655
|
+
min-width: 60px !important;
|
|
655
656
|
}
|
|
656
657
|
|
|
657
658
|
.between-inputs {
|
|
@@ -706,11 +707,11 @@ max-height: none;
|
|
|
706
707
|
}
|
|
707
708
|
|
|
708
709
|
.filter-query-inline .filter-row {
|
|
709
|
-
grid-template-columns:
|
|
710
|
+
grid-template-columns: 60px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
|
|
710
711
|
}
|
|
711
712
|
|
|
712
713
|
.filter-query-inline .first-filter-row {
|
|
713
|
-
grid-template-columns:
|
|
714
|
+
grid-template-columns: 60px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
|
|
714
715
|
}
|
|
715
716
|
|
|
716
717
|
.filter-query-inline .first-filter-field {
|
|
@@ -167,7 +167,7 @@ watch(() => props.modelValue, () => {
|
|
|
167
167
|
|
|
168
168
|
<template>
|
|
169
169
|
<div
|
|
170
|
-
class="bagel-input code-editor-container ltr" :class="[shellClass, { '
|
|
170
|
+
class="bagel-input code-editor-container ltr" :class="[shellClass, { '': label && !singleLine, 'has-value': hasCodeValue, 'single-line': singleLine }]"
|
|
171
171
|
:style="[{ height: maxHeight }, shellStyle]"
|
|
172
172
|
>
|
|
173
173
|
<label v-if="label" class="label">{{ label }}</label>
|
|
@@ -225,6 +225,12 @@ watch(() => props.modelValue, () => {
|
|
|
225
225
|
outline: solid 1px var(--bgl-border-color, #4f575f);
|
|
226
226
|
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.13);
|
|
227
227
|
}
|
|
228
|
+
.code-editor-grandpa textarea.code-input{
|
|
229
|
+
box-shadow: none !important;
|
|
230
|
+
background: transparent !important;
|
|
231
|
+
border-radius: 0 !important;
|
|
232
|
+
|
|
233
|
+
}
|
|
228
234
|
|
|
229
235
|
.editor-content-papa {
|
|
230
236
|
/* Stack pre + textarea in one cell so both share the content-sized box */
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
3
|
+
import { Dropdown, EmptyState, Icon, TabsNav, TextInput } from '@bagelink/vue'
|
|
4
|
+
import { FONT_AWESOME_BRANDS_ICONS, FONT_AWESOME_ICONS, MATERIAL_ICONS } from '../../Icon/constants'
|
|
5
|
+
|
|
6
|
+
defineProps<{
|
|
7
|
+
modelValue?: string
|
|
8
|
+
label?: string
|
|
9
|
+
placeholder?: string
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits<{
|
|
13
|
+
'update:modelValue': [value: string | undefined]
|
|
14
|
+
}>()
|
|
15
|
+
|
|
16
|
+
const dropdown = ref<InstanceType<typeof Dropdown> | null>(null)
|
|
17
|
+
const search = ref('')
|
|
18
|
+
const activeTab = ref<'material' | 'fa' | 'brands'>('material')
|
|
19
|
+
const visibleCount = ref(80)
|
|
20
|
+
const tabsGroup = `icon-picker-${Math.random().toString(36).slice(2)}`
|
|
21
|
+
let batchTimer: ReturnType<typeof setTimeout> | undefined
|
|
22
|
+
|
|
23
|
+
const tabs = [
|
|
24
|
+
{ label: 'Material', value: 'material' },
|
|
25
|
+
{ label: 'FA', value: 'fa' },
|
|
26
|
+
{ label: 'Brands', value: 'brands' },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const iconSets = {
|
|
30
|
+
material: MATERIAL_ICONS,
|
|
31
|
+
fa: FONT_AWESOME_ICONS,
|
|
32
|
+
brands: FONT_AWESOME_BRANDS_ICONS,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const filteredIcons = computed(() => {
|
|
36
|
+
const query = search.value.trim().toLowerCase()
|
|
37
|
+
const icons = iconSets[activeTab.value]
|
|
38
|
+
return query ? icons.filter(icon => icon.includes(query)) : icons
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const visibleIcons = computed(() => filteredIcons.value.slice(0, visibleCount.value))
|
|
42
|
+
|
|
43
|
+
function loadMore() {
|
|
44
|
+
if (visibleCount.value >= filteredIcons.value.length) return
|
|
45
|
+
batchTimer = setTimeout(() => {
|
|
46
|
+
visibleCount.value = Math.min(visibleCount.value + 80, filteredIcons.value.length)
|
|
47
|
+
loadMore()
|
|
48
|
+
}, 16)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resetAndLoad() {
|
|
52
|
+
if (batchTimer) clearTimeout(batchTimer)
|
|
53
|
+
visibleCount.value = 80
|
|
54
|
+
loadMore()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function select(icon: string) {
|
|
58
|
+
emit('update:modelValue', icon)
|
|
59
|
+
dropdown.value?.hide()
|
|
60
|
+
search.value = ''
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function clear() {
|
|
64
|
+
emit('update:modelValue', undefined)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
watch([activeTab, search], resetAndLoad)
|
|
68
|
+
onBeforeUnmount(() => {
|
|
69
|
+
if (batchTimer) clearTimeout(batchTimer)
|
|
70
|
+
})
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div class="bagel-input">
|
|
75
|
+
<label v-if="label" class="block txt12 medium pb-025">{{ label }}</label>
|
|
76
|
+
<Dropdown
|
|
77
|
+
ref="dropdown"
|
|
78
|
+
placement="bottom-start"
|
|
79
|
+
:auto-hide="true"
|
|
80
|
+
@show="resetAndLoad"
|
|
81
|
+
@hide="search = ''"
|
|
82
|
+
>
|
|
83
|
+
<template #trigger="{ shown, show, hide }">
|
|
84
|
+
<div class="icon-picker-trigger flex gap-05 w-100p bg-input px-075 rounded hover" @click="shown ? hide() : show()">
|
|
85
|
+
<span class="square-20 flex-center flex-shrink-0">
|
|
86
|
+
<Icon v-if="modelValue" :icon="modelValue" :size="1.1" />
|
|
87
|
+
<span v-else class="txt11 opacity-4">—</span>
|
|
88
|
+
</span>
|
|
89
|
+
<span class="txt12 flex-grow opacity-6 ellipsis-1">{{ modelValue || placeholder || 'Choose an icon' }}</span>
|
|
90
|
+
<Icon v-if="modelValue" icon="close" :size="0.9" class="opacity-5 hover-opacity-10" @click.stop="clear" />
|
|
91
|
+
<Icon :icon="shown ? 'expand_less' : 'expand_more'" :size="1" class="opacity-4" />
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<div class="w-300px overflow-hidden">
|
|
96
|
+
<TabsNav v-model="activeTab" :tabs="tabs" :group="tabsGroup" flat class="round-none w-100p mx-0 txt-center" />
|
|
97
|
+
<TextInput v-model="search" icon-start="search" placeholder="Search icons" class="mb-0" />
|
|
98
|
+
<div class="grid grid-wrap-7 gap-025 p-05 overflow max-h-300px">
|
|
99
|
+
<div
|
|
100
|
+
v-for="icon in visibleIcons"
|
|
101
|
+
:key="icon"
|
|
102
|
+
class="square-40 flex-center hover rounded relative overflow-hidden"
|
|
103
|
+
:class="modelValue === icon ? 'bg-primary-light color-primary' : 'bg-box'"
|
|
104
|
+
:title="icon"
|
|
105
|
+
@click="select(icon)"
|
|
106
|
+
>
|
|
107
|
+
<Icon :icon="icon" :size="1.2" />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<EmptyState v-if="visibleIcons.length === 0" compact icon="none" description="No icons found" />
|
|
111
|
+
</div>
|
|
112
|
+
</Dropdown>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<style scoped>
|
|
117
|
+
.icon-picker-trigger {
|
|
118
|
+
height: var(--bgl-input-height);
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
@@ -7,6 +7,7 @@ export { default as ColorInput } from './ColorInput.vue'
|
|
|
7
7
|
export { default as DateInput } from './DateInput.vue'
|
|
8
8
|
export { default as DatePicker } from './DatePicker.vue'
|
|
9
9
|
export { default as EmailInput } from './EmailInput.vue'
|
|
10
|
+
export { default as IconPickerInput } from './IconPickerInput.vue'
|
|
10
11
|
export { default as MarkdownEditor } from './MarkdownEditor.vue'
|
|
11
12
|
export { default as NumberInput } from './NumberInput.vue'
|
|
12
13
|
export { default as OTP } from './OTP.vue'
|
|
@@ -52,6 +52,15 @@ interface Props {
|
|
|
52
52
|
* mobile). Turn this on only for headers you know must stay one exact line.
|
|
53
53
|
*/
|
|
54
54
|
singleRow?: boolean
|
|
55
|
+
/**
|
|
56
|
+
* Mobile (≤ breakpoint) only: collapse the header's three sides into one
|
|
57
|
+
* continuous flow. The menu/back/title, center and trailing actions all sit in
|
|
58
|
+
* a single wrapping row — items run one after another (no `space-between` gap,
|
|
59
|
+
* no auto-margin pushing the actions to the far edge) and simply wrap onto the
|
|
60
|
+
* next line as a continuous stream when they run out of room. Desktop is
|
|
61
|
+
* unaffected. Off by default (mobile keeps the split leading/trailing layout).
|
|
62
|
+
*/
|
|
63
|
+
mobileSingleRow?: boolean
|
|
55
64
|
/**
|
|
56
65
|
* Cap the readable width of the page and center it on wide screens. Applies to
|
|
57
66
|
* BOTH the header and the body, so the title stays aligned above the content
|
|
@@ -80,6 +89,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
80
89
|
border: undefined,
|
|
81
90
|
headerHeight: '60px',
|
|
82
91
|
singleRow: false,
|
|
92
|
+
mobileSingleRow: false,
|
|
83
93
|
maxWidth: undefined,
|
|
84
94
|
fill: false,
|
|
85
95
|
})
|
|
@@ -135,7 +145,10 @@ const hasCenter = computed(() => !!slots['header-center'])
|
|
|
135
145
|
:class="[
|
|
136
146
|
`app-header--${resolvedVariant}`,
|
|
137
147
|
`app-header--m-${resolvedMobileVariant}`,
|
|
138
|
-
{
|
|
148
|
+
{
|
|
149
|
+
'app-header--single-row': singleRow,
|
|
150
|
+
'app-header--m-single-flow': mobileSingleRow,
|
|
151
|
+
},
|
|
139
152
|
]"
|
|
140
153
|
>
|
|
141
154
|
<!-- Left Side -->
|
|
@@ -163,17 +176,23 @@ const hasCenter = computed(() => !!slots['header-center'])
|
|
|
163
176
|
</PageTitle>
|
|
164
177
|
|
|
165
178
|
<!-- Custom Left Content -->
|
|
166
|
-
<
|
|
179
|
+
<div class="app-header-slot">
|
|
180
|
+
<slot name="header-left" />
|
|
181
|
+
</div>
|
|
167
182
|
</div>
|
|
168
183
|
|
|
169
184
|
<!-- Center Content (only rendered when the slot is provided) -->
|
|
170
185
|
<div v-if="hasCenter" class="app-header-center flex min-w-0">
|
|
171
|
-
<
|
|
186
|
+
<div class="app-header-slot">
|
|
187
|
+
<slot name="header-center" />
|
|
188
|
+
</div>
|
|
172
189
|
</div>
|
|
173
190
|
|
|
174
191
|
<!-- Right Side -->
|
|
175
192
|
<div class="app-header-side flex gap-row-05 endNavTools">
|
|
176
|
-
<
|
|
193
|
+
<div class="app-header-slot">
|
|
194
|
+
<slot name="header-right" />
|
|
195
|
+
</div>
|
|
177
196
|
</div>
|
|
178
197
|
</header>
|
|
179
198
|
|
|
@@ -262,6 +281,25 @@ padding-inline: 0;
|
|
|
262
281
|
}
|
|
263
282
|
}
|
|
264
283
|
|
|
284
|
+
/* Slot content is rendered in the consumer's scope. Keep this mobile flow rule
|
|
285
|
+
unscoped so it targets the real rendered DOM directly and remains stable across
|
|
286
|
+
async slot updates and component HMR. */
|
|
287
|
+
.app-header-slot {
|
|
288
|
+
display: contents;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@media screen and (max-width: 910px) {
|
|
292
|
+
.app-header.app-header--m-single-flow {
|
|
293
|
+
justify-content: flex-start !important;
|
|
294
|
+
}
|
|
295
|
+
.app-header--m-single-flow > .app-header-side,
|
|
296
|
+
.app-header--m-single-flow > .app-header-center,
|
|
297
|
+
.app-header--m-single-flow > .app-header-side > .app-header-slot > *,
|
|
298
|
+
.app-header--m-single-flow > .app-header-center > .app-header-slot > * {
|
|
299
|
+
display: contents !important;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
265
303
|
</style>
|
|
266
304
|
|
|
267
305
|
<style scoped>
|
|
@@ -434,6 +472,9 @@ padding-inline: 0.75rem;
|
|
|
434
472
|
.pageContent--border {
|
|
435
473
|
padding-top: 1rem;
|
|
436
474
|
}
|
|
475
|
+
.pageContent--border:has(.pageContentFull) {
|
|
476
|
+
padding: 0rem;
|
|
477
|
+
}
|
|
437
478
|
.pageContent--card {
|
|
438
479
|
padding-top: 0.25rem;
|
|
439
480
|
}
|
|
@@ -445,6 +486,9 @@ padding-top: 0;
|
|
|
445
486
|
.pageContent--m-border {
|
|
446
487
|
padding-top: 0.25rem;
|
|
447
488
|
}
|
|
489
|
+
.pageContent:has(.p-0) {
|
|
490
|
+
padding-block: 0rem !important;
|
|
491
|
+
}
|
|
448
492
|
.pageContent--m-card {
|
|
449
493
|
padding-top: 0.25rem;
|
|
450
494
|
}
|
|
@@ -30,7 +30,7 @@ const slots = useSlots()
|
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
<template>
|
|
33
|
-
<div class="bgl-tl-item flex gap-075" :class="{ 'bgl-tl-item--last': last }">
|
|
33
|
+
<div class="bgl-tl-item flex gap-075 align-items-start" :class="{ 'bgl-tl-item--last': last }">
|
|
34
34
|
<div class="bgl-tl-rail flex-column align-items-center flex-shrink-0">
|
|
35
35
|
<span class="bgl-tl-marker flex-center" :style="{ '--tl-color': `var(--bgl-${color})` }">
|
|
36
36
|
<slot name="marker">
|
|
@@ -52,9 +52,13 @@ const slots = useSlots()
|
|
|
52
52
|
.bgl-tl-item {
|
|
53
53
|
min-height: 1.75rem;
|
|
54
54
|
}
|
|
55
|
+
.bgl-tl-rail {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-self: stretch;
|
|
58
|
+
}
|
|
55
59
|
.bgl-tl-marker {
|
|
56
|
-
width: 1.
|
|
57
|
-
height: 1.
|
|
60
|
+
width: 1.25rem;
|
|
61
|
+
height: 1.25rem;
|
|
58
62
|
border-radius: 50%;
|
|
59
63
|
background: color-mix(in srgb, var(--tl-color) 15%, transparent);
|
|
60
64
|
color: var(--tl-color);
|
|
@@ -7,6 +7,7 @@ import CodeEditor from '../components/form/inputs/CodeEditor/Index.vue'
|
|
|
7
7
|
import ColorInput from '../components/form/inputs/ColorInput.vue'
|
|
8
8
|
import DateInput from '../components/form/inputs/DateInput.vue'
|
|
9
9
|
import EmailInput from '../components/form/inputs/EmailInput.vue'
|
|
10
|
+
import IconPickerInput from '../components/form/inputs/IconPickerInput.vue'
|
|
10
11
|
import NumberInput from '../components/form/inputs/NumberInput.vue'
|
|
11
12
|
import PasswordInput from '../components/form/inputs/PasswordInput.vue'
|
|
12
13
|
import RadioGroup from '../components/form/inputs/RadioGroup.vue'
|
|
@@ -283,6 +284,7 @@ function getFieldComponent(fieldOrType: FieldBuilder | string) {
|
|
|
283
284
|
array: ArrayInput,
|
|
284
285
|
upload: UploadInput,
|
|
285
286
|
color: ColorInput,
|
|
287
|
+
icon: IconPickerInput,
|
|
286
288
|
range: RangeInput,
|
|
287
289
|
}
|
|
288
290
|
|
|
@@ -371,6 +371,10 @@ export const $ = {
|
|
|
371
371
|
return new Field('color', parseArgs(labelOrConfig, config))
|
|
372
372
|
},
|
|
373
373
|
|
|
374
|
+
icon(labelOrConfig?: string | BaseFieldConfig, config?: BaseFieldConfig): FieldBuilder<string> {
|
|
375
|
+
return new Field('icon', parseArgs(labelOrConfig, config))
|
|
376
|
+
},
|
|
377
|
+
|
|
374
378
|
range(
|
|
375
379
|
labelOrConfig?: string | (BaseFieldConfig & { min?: number, max?: number, step?: number, multiRange?: boolean }),
|
|
376
380
|
config?: BaseFieldConfig & { min?: number, max?: number, step?: number, multiRange?: boolean }
|
|
@@ -1031,6 +1035,9 @@ function createFieldFromProperty(
|
|
|
1031
1035
|
case 'multiselect':
|
|
1032
1036
|
field = $.multiselect(xOptions ?? resolvedProp.enum ?? [], config)
|
|
1033
1037
|
break
|
|
1038
|
+
case 'icon':
|
|
1039
|
+
field = $.icon(config)
|
|
1040
|
+
break
|
|
1034
1041
|
case 'file':
|
|
1035
1042
|
case 'upload':
|
|
1036
1043
|
field = $.upload(config)
|