@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.301",
4
+ "version": "1.15.305",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -65,6 +65,7 @@ function toggle() {
65
65
  <template>
66
66
  <div class="accordion-item txt-start" :class="[{ flat }]">
67
67
  <button
68
+ type="button"
68
69
  :aria-expanded="isOpen ? 'true' : 'false'" class="accordion-head"
69
70
  :aria-controls="`accordion-body-${id}`" @click="toggle()"
70
71
  >
@@ -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: 50px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
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: 50px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
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, { 'mb-2': label && !singleLine, 'has-value': hasCodeValue, 'single-line': singleLine }]"
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
- { 'app-header--single-row': singleRow },
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
- <slot name="header-left" />
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
- <slot name="header-center" />
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
- <slot name="header-right" />
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.625rem;
57
- height: 1.625rem;
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)