@bagelink/vue 1.15.309 → 1.15.321

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.309",
4
+ "version": "1.15.321",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -15,6 +15,7 @@ const props = defineProps<{
15
15
  const emit = defineEmits(['update:open'])
16
16
 
17
17
  const openVal = ref(!!props.open)
18
+ const hasOpened = ref(openVal.value)
18
19
 
19
20
  const isOpen = computed({
20
21
  get: () => openVal.value,
@@ -39,6 +40,9 @@ const computedIcon = computed(() => {
39
40
  const iconPosition = computed(() => props.iconPosition || 'end')
40
41
 
41
42
  watch(() => props.open, () => isOpen.value = props.open, { immediate: true })
43
+ watch(isOpen, (open) => {
44
+ if (open) hasOpened.value = true
45
+ }, { immediate: true })
42
46
 
43
47
  if (accordionState) {
44
48
  watch(
@@ -94,7 +98,7 @@ function toggle() {
94
98
  :class="{ open: isOpen }" :aria-hidden="isOpen ? 'false' : 'true'"
95
99
  >
96
100
  <div class="accordion-body-inner">
97
- <slot />
101
+ <slot v-if="hasOpened" />
98
102
  </div>
99
103
  </div>
100
104
  </div>
@@ -61,6 +61,7 @@ const props = withDefaults(defineProps<{
61
61
  const emit = defineEmits<{ show: []; hide: [] }>()
62
62
 
63
63
  const shown = defineModel('shown', { type: Boolean, default: false })
64
+ const hasOpened = ref(shown.value)
64
65
 
65
66
  const triggerWrapRef = ref<HTMLElement | null>(null)
66
67
  const popoverRef = ref<HTMLElement | null>(null)
@@ -141,6 +142,7 @@ function isOpen(): boolean {
141
142
  async function internalShow() {
142
143
  if (props.disabled || isOpen()) return
143
144
 
145
+ hasOpened.value = true
144
146
  teleportTarget.value = triggerWrapRef.value?.closest('dialog[open]') ?? 'body'
145
147
 
146
148
  if (props.showGroup && groupRegistry.has(props.showGroup)) {
@@ -255,10 +257,12 @@ watch(shown, (val) => {
255
257
  function onPopoverToggle(e: Event) {
256
258
  const te = e as ToggleEvent
257
259
  if (te.newState === 'open') {
260
+ addOpenListeners()
258
261
  shown.value = true
259
262
  nextTick(updatePosition)
260
263
  emit('show')
261
264
  } else {
265
+ removeOpenListeners()
262
266
  shown.value = false
263
267
  emit('hide')
264
268
  }
@@ -360,9 +364,22 @@ function onScroll() {
360
364
  if (isOpen()) updatePosition()
361
365
  }
362
366
 
363
- onMounted(() => {
367
+ let openListenersActive = false
368
+ function addOpenListeners() {
369
+ if (openListenersActive) return
364
370
  document.addEventListener('pointerdown', onDocumentPointerDown, true)
365
371
  document.addEventListener('scroll', onScroll, { capture: true, passive: true })
372
+ openListenersActive = true
373
+ }
374
+
375
+ function removeOpenListeners() {
376
+ if (!openListenersActive) return
377
+ document.removeEventListener('pointerdown', onDocumentPointerDown, true)
378
+ document.removeEventListener('scroll', onScroll, true)
379
+ openListenersActive = false
380
+ }
381
+
382
+ onMounted(() => {
366
383
  if (props.showGroup) {
367
384
  if (!groupRegistry.has(props.showGroup)) groupRegistry.set(props.showGroup, new Set())
368
385
  groupRegistry.get(props.showGroup)!.add(internalHide)
@@ -372,8 +389,7 @@ onMounted(() => {
372
389
  })
373
390
 
374
391
  onBeforeUnmount(() => {
375
- document.removeEventListener('pointerdown', onDocumentPointerDown, true)
376
- document.removeEventListener('scroll', onScroll, true)
392
+ removeOpenListeners()
377
393
  clearTimers()
378
394
  if (props.showGroup) groupRegistry.get(props.showGroup)?.delete(internalHide)
379
395
  if (isOpen()) popoverRef.value?.hidePopover()
@@ -405,6 +421,7 @@ defineExpose({ show, hide, shown })
405
421
  <!-- Popup — teleported to closest dialog[open] or body, promoted to top layer via popover -->
406
422
  <Teleport :to="teleportTarget">
407
423
  <div
424
+ v-if="hasOpened"
408
425
  :id="bglId" ref="popoverRef" popover="manual" class="bgl-dropdown m-0 p-0 border-none"
409
426
  role="menu"
410
427
  style="border-radius:var(--bgl-card-border-radius);"
@@ -14,7 +14,7 @@ interface UICondition {
14
14
  id: string
15
15
  field: string
16
16
  operator: ComparisonOperator
17
- value: string | string[]
17
+ value: string | number | boolean | null | Array<string | number | boolean | null>
18
18
  connector: LogicalOperator | ''
19
19
  }
20
20
 
@@ -145,8 +145,8 @@ const conditions = computed(() => {
145
145
  field: cond.field as string,
146
146
  operator: cond.op,
147
147
  value: Array.isArray(cond.value)
148
- ? cond.value.map(String)
149
- : (cond.value === null ? 'null' : cond.value === undefined ? '' : String(cond.value)),
148
+ ? [...cond.value]
149
+ : (cond.value === undefined ? '' : cond.value),
150
150
  connector: index > 0 ? (cond.connector || 'and') : '',
151
151
  }
152
152
  return uiCond
@@ -271,20 +271,21 @@ function collapseToPresets(conditions: QueryConditions<T>): QueryConditions<T> {
271
271
  }
272
272
 
273
273
  const booleanOptions = computed(() => [
274
- { label: currentTexts.value.boolean.true, value: 'true' },
275
- { label: currentTexts.value.boolean.false, value: 'false' },
274
+ { label: currentTexts.value.boolean.true, value: true },
275
+ { label: currentTexts.value.boolean.false, value: false },
276
276
  ])
277
277
 
278
- function parseValue(value: string | number | boolean | null): string | number | boolean | null {
278
+ function parseValue(value: string | number | boolean | null, fieldType: string): string | number | boolean | null {
279
279
  if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value
280
- if (value === 'true') return true
281
- if (value === 'false') return false
282
- if (value === 'null') return null
283
- if (!Number.isNaN(Number(value)) && value.trim() !== '') return Number(value)
280
+ if (fieldType === 'boolean') {
281
+ if (value === 'true') return true
282
+ if (value === 'false') return false
283
+ }
284
+ if (fieldType === 'number' && !Number.isNaN(Number(value)) && value.trim() !== '') return Number(value)
284
285
  return value
285
286
  }
286
287
 
287
- function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: string | number | boolean | null | string[], connector: LogicalOperator }>) {
288
+ function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: UICondition['value'], connector: LogicalOperator }>) {
288
289
  const newModel = [...internalModel.value]
289
290
  const existing = newModel[index]
290
291
  if (!existing) return
@@ -293,11 +294,12 @@ function updateConditionAtIndex(index: number, updates: Partial<{ field: string,
293
294
  if (updates.field !== undefined) updated.field = updates.field
294
295
  if (updates.op !== undefined) updated.op = updates.op
295
296
  if (updates.value !== undefined) {
297
+ const fieldType = getFieldType(updated.field as string)
296
298
  updated.value = Array.isArray(updates.value)
297
299
  // Coerce each list/range element (e.g. "18" → 18) so numeric filters
298
300
  // emit bare numbers in the bracket literal.
299
- ? updates.value.map(v => parseValue(typeof v === 'string' ? v : String(v)))
300
- : parseValue(updates.value)
301
+ ? updates.value.map(v => parseValue(v, fieldType))
302
+ : parseValue(updates.value, fieldType)
301
303
  }
302
304
  if (updates.connector !== undefined) updated.connector = updates.connector
303
305
 
@@ -361,8 +363,8 @@ function isFreeListOperator(condition: UICondition): boolean {
361
363
  }
362
364
 
363
365
  /** Convert an array value to comma-separated text for the free-list input. */
364
- function listToText(value: string | string[]): string {
365
- return Array.isArray(value) ? value.join(', ') : (value ?? '')
366
+ function listToText(value: UICondition['value']): string {
367
+ return Array.isArray(value) ? value.join(', ') : String(value ?? '')
366
368
  }
367
369
 
368
370
  /** Split comma-separated free-list text into a trimmed string[]. */
@@ -372,7 +374,7 @@ function textToList(text: string): string[] {
372
374
 
373
375
  /** Read one bound of a `between` value ([lo, hi]); index 0 = lo, 1 = hi. */
374
376
  function betweenBound(condition: UICondition, index: 0 | 1): string {
375
- return Array.isArray(condition.value) ? (condition.value[index] ?? '') : ''
377
+ return Array.isArray(condition.value) ? String(condition.value[index] ?? '') : ''
376
378
  }
377
379
 
378
380
  /** Update one bound of a `between` value, preserving the other. */
@@ -384,7 +386,7 @@ function onBetweenBoundChange(id: string, index: 0 | 1, v: string) {
384
386
  }
385
387
 
386
388
  const activeFiltersCount = computed(() => {
387
- return conditions.value.filter((c: UICondition) => Boolean(c.field) && (c.operator === 'pr' || Boolean(c.value))).length
389
+ return conditions.value.filter(c => isComplete({ field: c.field, op: c.operator, value: c.value })).length
388
390
  })
389
391
 
390
392
  function getConditionIndex(id: string): number {
@@ -429,7 +431,7 @@ function onOperatorChange(id: string, operator: ComparisonOperator) {
429
431
  updateConditionAtIndex(index, updates)
430
432
  }
431
433
 
432
- function onValueChange(id: string, value: string | string[]) {
434
+ function onValueChange(id: string, value: UICondition['value']) {
433
435
  const index = getConditionIndex(id)
434
436
  if (index >= 0) updateConditionAtIndex(index, { value })
435
437
  }
@@ -440,7 +442,7 @@ function onQueryOptionChange(id: string, optionValue: string) {
440
442
  }
441
443
 
442
444
  function getQueryOptionValue(condition: UICondition): string {
443
- return Array.isArray(condition.value) ? '' : condition.value
445
+ return Array.isArray(condition.value) ? '' : String(condition.value ?? '')
444
446
  }
445
447
 
446
448
  function onConnectorChange(id: string, connector: LogicalOperator) {
@@ -591,7 +593,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
591
593
  :model-value="condition.value" :options="booleanOptions"
592
594
  :placeholder="currentTexts.placeholders.selectOption"
593
595
  class="m-0 light-input borderHover"
594
- @update:model-value="(v: string) => onValueChange(condition.id, v)"
596
+ @update:model-value="(v: boolean) => onValueChange(condition.id, v)"
595
597
  />
596
598
  <DateInput
597
599
  v-else-if="getFieldType(condition.field) === 'date'"
@@ -17,6 +17,8 @@ const props = withDefaults(
17
17
  multiselect?: boolean
18
18
  /** Render only each option's icon (label becomes a tooltip). */
19
19
  iconOnly?: boolean
20
+ /** Background applied to the options area only; the field label stays outside it. */
21
+ bgColor?: string
20
22
  } & BagelInputShellProps>(),
21
23
  { multiselect: false, iconOnly: false },
22
24
  )
@@ -34,6 +36,13 @@ const hasValue = computed(() => {
34
36
  return Array.isArray(v) ? v.length > 0 : v != null && v !== ''
35
37
  })
36
38
 
39
+ /** Theme token names resolve to Bagel CSS variables; raw CSS values pass through. */
40
+ const resolvedBgColor = computed(() => {
41
+ const color = props.bgColor
42
+ if (!color) { return undefined }
43
+ return /^[a-z][a-z0-9-]*$/i.test(color) ? `var(--bgl-${color})` : color
44
+ })
45
+
37
46
  type OptionValue = string | number | boolean
38
47
 
39
48
  const selected = defineModel<OptionValue | OptionValue[] | null>('modelValue', { default: null })
@@ -57,10 +66,14 @@ function isSelected(val: OptionValue) {
57
66
  <div
58
67
  class="bagel-input SelectBtn" :class="[shellClass, { 'has-value': hasValue }]" :style="shellStyle"
59
68
  >
60
- <label v-if="label || required" class="pb-025">
69
+ <label v-if="label || required" >
61
70
  {{ resolveI18n(label) }}<span v-if="required"> *</span>
62
71
  </label>
63
- <div class="flex flex-wrap gap-05">
72
+ <div
73
+ class="select-btn-items flex flex-wrap gap-025"
74
+ :class="{ 'has-background': !!bgColor, 'default-size': !thin && !size }"
75
+ :style="{ backgroundColor: resolvedBgColor }"
76
+ >
64
77
  <Btn
65
78
  v-for="option in options" :key="String(getOptionValue(option))" :thin="thin"
66
79
  :size="size"
@@ -93,10 +106,47 @@ function isSelected(val: OptionValue) {
93
106
  <style scoped>
94
107
 
95
108
  .SelectBtn {
96
- --bgl-btn-height: 30px;
109
+ --bgl-btn-height: var(--bgl-input-height);
110
+ font-size: var(--bgl-input-font-size);
111
+ }
112
+
113
+ .SelectBtn-padding {
114
+ padding: 0 var(--bgl-select-btn-padding, 0.7rem);
115
+ }
116
+
117
+ .select-btn-items {
118
+ width: fit-content;
119
+ max-width: 100%;
120
+ }
121
+
122
+ .select-btn-items.has-background,
123
+ .SelectBtn.frame .select-btn-items,
124
+ .SelectBtn.bgl-outline .select-btn-items {
125
+ --bgl-select-btn-group-padding: 0.25rem;
126
+ padding: var(--bgl-select-btn-group-padding);
127
+ border-radius: calc(var(--bgl-radius) * 2);
97
128
  }
98
- .SelectBtn-padding{
99
- padding: var(--bgl-select-btn-padding, 0.75rem);
129
+
130
+ .SelectBtn.frame .select-btn-items.default-size .bgl_btn,
131
+ .SelectBtn.bgl-outline .select-btn-items.default-size .bgl_btn,
132
+ .select-btn-items.has-background.default-size .bgl_btn {
133
+ height: calc(var(--bgl-input-height) - var(--bgl-select-btn-group-padding) - var(--bgl-select-btn-group-padding));
134
+ line-height: calc(var(--bgl-input-height) - var(--bgl-select-btn-group-padding) - var(--bgl-select-btn-group-padding));
135
+ }
136
+
137
+ .SelectBtn.frame .select-btn-items,
138
+ .SelectBtn.bgl-outline .select-btn-items {
139
+ outline: 1.5px solid var(--bgl-border-color);
140
+ outline-offset: -1px;
141
+ }
142
+
143
+ .SelectBtn.frame .select-btn-items {
144
+ transition: outline-color 0.2s ease, box-shadow 0.2s ease;
145
+ }
146
+
147
+ .SelectBtn.frame:focus-within .select-btn-items {
148
+ outline-color: var(--bgl-input-label-active-color, var(--bgl-primary));
149
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--bgl-input-label-active-color, var(--bgl-primary)) 12%, transparent);
100
150
  }
101
151
 
102
152
  </style>
@@ -486,7 +486,7 @@ padding-top: 0;
486
486
  .pageContent--m-border {
487
487
  padding-top: 0.25rem;
488
488
  }
489
- .pageContent:has(.p-0) {
489
+ .pageContent:has(.pageContentFull) {
490
490
  padding-block: 0rem !important;
491
491
  }
492
492
  .pageContent--m-card {
@@ -217,6 +217,22 @@
217
217
  transform: rotate(180deg);
218
218
  }
219
219
 
220
+ .rotate-45 {
221
+ transform: rotate(45deg);
222
+ }
223
+
224
+ .rotate-135 {
225
+ transform: rotate(135deg);
226
+ }
227
+
228
+ .rotate-225 {
229
+ transform: rotate(225deg);
230
+ }
231
+
232
+ .rotate-315 {
233
+ transform: rotate(315deg);
234
+ }
235
+
220
236
  [dir="rtl"] .rotate-180-rtl,
221
237
  [dir="rtl"] .rtl-rotate-180,
222
238
  [dir="rtl"] .rotate-rtl,