@bagelink/vue 1.15.82 → 1.15.86

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.
Files changed (49) hide show
  1. package/dist/components/Alert.vue.d.ts +6 -1
  2. package/dist/components/Alert.vue.d.ts.map +1 -1
  3. package/dist/components/Avatar.vue.d.ts +3 -1
  4. package/dist/components/Avatar.vue.d.ts.map +1 -1
  5. package/dist/components/Badge.vue.d.ts +4 -0
  6. package/dist/components/Badge.vue.d.ts.map +1 -1
  7. package/dist/components/EmptyState.vue.d.ts.map +1 -1
  8. package/dist/components/Image.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
  10. package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  13. package/dist/components/index.d.ts +2 -0
  14. package/dist/components/index.d.ts.map +1 -1
  15. package/dist/components/kanban/KanbanBoard.vue.d.ts +42 -0
  16. package/dist/components/kanban/KanbanBoard.vue.d.ts.map +1 -0
  17. package/dist/components/kanban/kanbanTypes.d.ts +64 -0
  18. package/dist/components/kanban/kanbanTypes.d.ts.map +1 -0
  19. package/dist/composables/useGradientVariant.d.ts.map +1 -1
  20. package/dist/index.cjs +165 -138
  21. package/dist/index.mjs +35488 -41334
  22. package/dist/style.css +1 -2
  23. package/package.json +1 -1
  24. package/src/components/Alert.vue +40 -9
  25. package/src/components/Avatar.vue +8 -5
  26. package/src/components/Badge.vue +30 -0
  27. package/src/components/Dropdown.vue +1 -1
  28. package/src/components/EmptyState.vue +5 -1
  29. package/src/components/Image.vue +18 -4
  30. package/src/components/form/inputs/ArrayInput.vue +2 -2
  31. package/src/components/form/inputs/RangeInput.vue +0 -2
  32. package/src/components/form/inputs/RichText/components/TableGridSelector.vue +1 -1
  33. package/src/components/form/inputs/RichText/editor.css +14 -8
  34. package/src/components/form/inputs/RichText/index.vue +1 -1
  35. package/src/components/form/inputs/TextInput.vue +6 -2
  36. package/src/components/form/inputs/Upload/upload.css +7 -1
  37. package/src/components/index.ts +2 -0
  38. package/src/components/kanban/KanbanBoard.vue +231 -0
  39. package/src/components/kanban/kanbanTypes.ts +72 -0
  40. package/src/composables/useGradientVariant.ts +20 -3
  41. package/src/styles/appearance.css +108 -0
  42. package/src/styles/base-colors.css +830 -1
  43. package/src/styles/buttons.css +44 -2
  44. package/src/styles/color-variants.css +13 -0
  45. package/src/styles/colors.css +1115 -1
  46. package/src/styles/mobileColors.css +1156 -0
  47. package/src/styles/motion.css +107 -19
  48. package/src/styles/theme.css +84 -1
  49. package/vite.config.ts +2 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.82",
4
+ "version": "1.15.86",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -2,17 +2,21 @@
2
2
  defineOptions({ name: 'BglAlert' })
3
3
  import type { IconType } from '@bagelink/vue'
4
4
  import { Icon } from '@bagelink/vue'
5
- import { computed, ref } from 'vue'
5
+ import { computed, ref, useSlots } from 'vue'
6
6
  import Btn from './Btn.vue'
7
7
 
8
8
  type AlertType = 'info' | 'success' | 'warning' | 'error'
9
9
 
10
10
  interface Props {
11
11
  message?: string
12
+ /** Bold heading above the message / default slot. */
13
+ title?: string
12
14
  thin?: boolean
13
15
  /** Border + transparent background (drops the tinted fill). */
14
16
  outline?: boolean
15
- /** Adds a border while keeping the tinted background. */
17
+ /** Neutral surface card: standard box background + a subtle neutral
18
+ (non-accent) border. Only the icon/title keep the accent color — use
19
+ when a full tinted fill reads as too loud. */
16
20
  frame?: boolean
17
21
  dismissable?: boolean
18
22
  type?: AlertType
@@ -25,6 +29,7 @@ interface Props {
25
29
  }
26
30
 
27
31
  const props = defineProps<Props>()
32
+ const slots = useSlots()
28
33
 
29
34
  const computedType = computed<AlertType>(() => {
30
35
  if (props.type) { return props.type }
@@ -47,11 +52,17 @@ const typeIcon: Record<AlertType, IconType> = {
47
52
  <template>
48
53
  <div v-if="!isDismissed" class="alert" :class="[computedType, { thin, outline, frame }]" :dismissable="dismissable">
49
54
  <Icon v-if="icon !== 'none'" class="alert_icon" :icon="icon || typeIcon[computedType]" :size="1.7" />
50
- <slot>
51
- <p class="m-0">
52
- {{ message }}
53
- </p>
54
- </slot>
55
+ <div class="alert_content">
56
+ <p v-if="title" class="alert_title m-0">{{ title }}</p>
57
+ <slot>
58
+ <p class="m-0 light">
59
+ {{ message }}
60
+ </p>
61
+ </slot>
62
+ </div>
63
+ <div v-if="slots.actions" class="alert_actions">
64
+ <slot name="actions" />
65
+ </div>
55
66
  <Btn flat thin class="alert_close" icon="close" @click="isDismissed = true" />
56
67
  </div>
57
68
  </template>
@@ -94,6 +105,23 @@ const typeIcon: Record<AlertType, IconType> = {
94
105
  color: var(--alert-outline);
95
106
  }
96
107
 
108
+ .alert_content {
109
+ flex: 1;
110
+ min-width: 0;
111
+ }
112
+
113
+ .alert_title {
114
+ font-weight: 600;
115
+ }
116
+
117
+ .alert_actions {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 0.5rem;
121
+ flex-shrink: 0;
122
+ align-self: center;
123
+ }
124
+
97
125
  .alert.thin {
98
126
  padding: 1rem;
99
127
  }
@@ -103,9 +131,12 @@ const typeIcon: Record<AlertType, IconType> = {
103
131
  background: unset;
104
132
  }
105
133
 
106
- /* frame: border in the alert's accent color, keeps the tinted background. */
134
+ /* frame: neutral surface card box bg + subtle border tinted by the accent;
135
+ accent stays on the icon/title only (use when a full tinted fill is too loud). */
107
136
  .alert.frame {
108
- border: 1px solid var(--alert-outline);
137
+ background: var(--bgl-box-bg);
138
+ border: 1px solid color-mix(in srgb, var(--alert-outline) 35%, var(--bgl-border-color));
139
+ color: inherit;
109
140
  }
110
141
 
111
142
  .alert_icon {
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
- // Forward consumer class/style/attrs to the inner (rounded, clipped) avatar
3
- // element rather than the positioning wrapper otherwise a custom background
4
- // would render as a square behind the circle.
5
- defineOptions({ name: 'BglAvatar', inheritAttrs: false })
2
+ // A consumer `class` lands on the positioning wrapper (the root) like any normal
3
+ // component so spacing/layout utilities (mb-2, me-2, …) work as expected. To
4
+ // style the inner rounded/clipped element (e.g. a custom background that must
5
+ // stay circular), use the `innerClass` prop — mirrors `triggerClass` on Dropdown.
6
+ defineOptions({ name: 'BglAvatar' })
6
7
  import type { IconType, ThemeType } from '@bagelink/vue'
7
8
  import { initials, Icon } from '@bagelink/vue'
8
9
  import { computed } from 'vue'
@@ -24,6 +25,8 @@ const props = withDefaults(defineProps<{
24
25
  status?: boolean | ThemeType
25
26
  /** Accessible label / tooltip for the status dot. */
26
27
  statusTitle?: string
28
+ /** Class applied to the inner rounded/clipped avatar element (vs. the wrapper). */
29
+ innerClass?: string
27
30
  }>(), { size: 50 })
28
31
 
29
32
  const statusColor = computed(() => (props.status === true ? 'green' : props.status || null))
@@ -48,8 +51,8 @@ const themed = computed(() => props.color
48
51
  <template>
49
52
  <div class="avatar-wrap" :style="{ width: `${size}px`, height: `${size}px` }">
50
53
  <div
51
- v-bind="$attrs"
52
54
  class="overflow-hidden txt-center p-0 avatar flex justify-content-center align-items-center"
55
+ :class="innerClass"
53
56
  :style="{ width: `${size}px`, height: `${size}px`, borderRadius: radius, ...themed }"
54
57
  >
55
58
  <Image v-if="src" :src="src" :alt="alt || name" />
@@ -52,6 +52,10 @@ const props = defineProps<{
52
52
  /** Boolean size shorthands: <Badge sm value="New" /> */
53
53
  sm?: boolean
54
54
  lg?: boolean
55
+ /** Mobile-only size override (≤ 910px). Lets a `lg` desktop badge shrink on
56
+ phones so it doesn't overflow / touch neighbours. e.g.
57
+ <Badge lg mobile-size="sm" />. Mirrors `Icon`'s `mobileSize` prop. */
58
+ mobileSize?: 'sm' | 'md' | 'lg'
55
59
  uppercase?: boolean
56
60
  }>()
57
61
 
@@ -87,6 +91,10 @@ const computedSize = computed(() => {
87
91
  return 'md'
88
92
  })
89
93
 
94
+ /** Mobile-only size override (≤ 910px). Falls back to undefined when not set,
95
+ so the desktop size keeps applying at all widths. */
96
+ const computedMobileSize = computed(() => props.mobileSize)
97
+
90
98
  const computedVariant = computed(() => {
91
99
  if (props.variant) { return props.variant }
92
100
  if (isGradient.value) { return 'gradient' }
@@ -111,6 +119,9 @@ const computedClasses = computed(() => {
111
119
  'gradient': computedVariant.value === 'gradient',
112
120
  'pillLarge': computedSize.value === 'lg',
113
121
  'pillSmall': computedSize.value === 'sm',
122
+ 'm_pillLarge': computedMobileSize.value === 'lg',
123
+ 'm_pillMd': computedMobileSize.value === 'md',
124
+ 'm_pillSmall': computedMobileSize.value === 'sm',
114
125
  }
115
126
 
116
127
  // Add the pair class
@@ -156,8 +167,27 @@ const computedClasses = computed(() => {
156
167
  --bgl-pill-font-size: 9px;
157
168
  --bgl-pill-height:15px;
158
169
  }
170
+
171
+ /* Mobile-only size overrides (≤ 910px). These win over the desktop size class
172
+ because the size is driven entirely by --bgl-pill-font-size / --bgl-pill-height,
173
+ and a later @media rule with equal specificity overrides the base class. */
174
+ @media screen and (max-width: 910px) {
175
+ .m_pillLarge {
176
+ --bgl-pill-font-size: var(--bgl-font-size);
177
+ --bgl-pill-height: auto;
178
+ }
179
+ .m_pillMd {
180
+ --bgl-pill-font-size: 12px;
181
+ --bgl-pill-height: 20px;
182
+ }
183
+ .m_pillSmall {
184
+ --bgl-pill-font-size: 9px;
185
+ --bgl-pill-height: 15px;
186
+ }
187
+ }
159
188
  .pillText{
160
189
  font-size: var(--bgl-pill-font-size);
190
+ white-space: nowrap;
161
191
  }
162
192
  .bgl_pill-dot{
163
193
  width: 6px;
@@ -271,7 +271,7 @@ function onDocumentPointerDown(e: PointerEvent) {
271
271
  // that each Dropdown sets on its popover element to point to the nearest ancestor popover's id.
272
272
  const openPopovers = document.querySelectorAll<HTMLElement>('[popover]:popover-open')
273
273
  const myId = popoverRef.value?.dataset.bglId
274
- for (const p of openPopovers) {
274
+ for (const p of Array.from(openPopovers)) {
275
275
  if (p === popoverRef.value) continue
276
276
  if (p.contains(target) && myId && p.dataset.bglOwner === myId) return
277
277
  }
@@ -39,7 +39,7 @@ const slots = useSlots()
39
39
  </script>
40
40
 
41
41
  <template>
42
- <div class="bgl-empty flex-column align-items-center justify-content-center txt-center" :class="[{ 'bgl-empty--compact': compact }, className]">
42
+ <div class="bgl-empty txt-center" :class="[{ 'bgl-empty--compact': compact }, className]">
43
43
  <div v-if="slots.icon || icon !== 'none'" class="bgl-empty-icon flex-center">
44
44
  <slot name="icon"><Icon :icon="icon" :size="compact ? 1.6 : 2.2" /></slot>
45
45
  </div>
@@ -57,6 +57,10 @@ const slots = useSlots()
57
57
 
58
58
  <style scoped>
59
59
  .bgl-empty {
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ justify-content: center;
60
64
  gap: 0.75rem;
61
65
  padding: 2.5rem 1.5rem;
62
66
  color: var(--bgl-text-soft, var(--bgl-gray));
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  defineOptions({ name: 'BglImage', inheritAttrs: true })
3
3
  import { Skeleton, normalizeDimension, Icon, useImageSrc } from '@bagelink/vue'
4
- import { computed, useSlots } from 'vue'
4
+ import { Comment, computed, useSlots, type StyleValue, type VNode } from 'vue'
5
5
 
6
6
  interface ImageProps {
7
7
  src?: string
@@ -29,9 +29,16 @@ const { imageSrc, loadingError } = useImageSrc(() => props.src ?? props.pathKey
29
29
 
30
30
  // ── Framed mode (ratio / gradient / blend / rounded / overlay slot) ──────────
31
31
  const slots = useSlots()
32
+ // Only count the default slot as "framed" when it actually renders nodes — an
33
+ // empty/whitespace slot (or none) must NOT force framed mode, otherwise a plain
34
+ // `<Image :src />` renders an absolute-fill img that collapses the box to 0×0.
35
+ const hasOverlay = computed(() => {
36
+ const nodes = slots.default?.({}) as VNode[] | undefined
37
+ return !!nodes?.some((n: VNode) => n.type !== Comment && !(typeof n.children === 'string' && n.children.trim() === ''))
38
+ })
32
39
  const framed = computed(() =>
33
40
  props.ratio !== undefined || props.gradient !== undefined || props.blend !== undefined
34
- || props.rounded !== undefined || !!slots.default)
41
+ || props.rounded !== undefined || hasOverlay.value)
35
42
 
36
43
  // Resolve `gradient` into a CSS background. Accepts a tone name, a [from,to]
37
44
  // pair (tone or color), or a raw CSS gradient/color string.
@@ -49,6 +56,10 @@ const gradientCss = computed(() => {
49
56
  })
50
57
 
51
58
  const ratioClass = computed(() => (props.ratio ? `ratio-${props.ratio}` : ''))
59
+ // When the frame has no intrinsic height (no ratio + no explicit height), the
60
+ // absolute-fill img would collapse the box to 0. In that case let the img flow
61
+ // naturally (relative) so the frame sizes to the image.
62
+ const frameHasHeight = computed(() => props.ratio !== undefined || props.height !== undefined)
52
63
  const radiusStyle = computed(() => {
53
64
  if (props.rounded === undefined || props.rounded === false) return undefined
54
65
  return typeof props.rounded === 'number' ? `${props.rounded}px` : 'var(--bgl-card-radius, 12px)'
@@ -73,8 +84,9 @@ const imgFit = computed(() => props.fit ?? ((props.ratio || props.gradient) ? 'c
73
84
  v-if="imageSrc"
74
85
  :src="imageSrc"
75
86
  :alt="alt"
76
- class="bgl-image-frame-img absolute-fill"
77
- :style="{ objectFit: imgFit, mixBlendMode: blend }"
87
+ class="bgl-image-frame-img"
88
+ :class="frameHasHeight ? 'absolute-fill' : 'bgl-image-frame-img--flow'"
89
+ :style="{ objectFit: imgFit, mixBlendMode: blend } as StyleValue"
78
90
  >
79
91
  <Skeleton v-else-if="!gradientCss && !loadingError" class="absolute-fill" />
80
92
  <div v-else-if="loadingError && !gradientCss" class="flex-center absolute-fill error-image">
@@ -140,5 +152,7 @@ background-color: var(--bgl-skeleton-bg);
140
152
 
141
153
  .bgl-image-frame { display: block; }
142
154
  .bgl-image-frame-img { width: 100%; height: 100%; }
155
+ /* no ratio/height → flow naturally so the frame gets the image's height (avoids 0×0 collapse) */
156
+ .bgl-image-frame-img--flow { position: relative; height: auto; display: block; }
143
157
 
144
158
  </style>
@@ -4,7 +4,7 @@ import type { Ref, WritableComputedRef } from 'vue'
4
4
  import type { DraggableEvent } from '../../draggable/useDraggable'
5
5
 
6
6
  import { Btn, resolveI18n, useI18n, pathKeyToURL } from '@bagelink/vue'
7
- import { computed, ref, watch, useSlots } from 'vue'
7
+ import { computed, ref, watch, useSlots, type VNode } from 'vue'
8
8
  import Draggable from '../../draggable/Draggable.vue'
9
9
  import type { BagelInputShellProps } from './bagelInputShell'
10
10
  import { useBagelInputShell } from './bagelInputShell'
@@ -141,7 +141,7 @@ const isSimple = computed(() => {
141
141
  if (props.simple) return true
142
142
  if (!slots.default) return false
143
143
  const dummyItem = computed({ get: () => undefined as T, set: () => { } })
144
- const vnodes = slots.default({ item: dummyItem, index: 0 })
144
+ const vnodes = slots.default({ item: dummyItem, index: 0 }) as VNode[]
145
145
  return vnodes.filter(vn => typeof vn.type !== 'symbol').length === 1
146
146
  })
147
147
 
@@ -46,8 +46,6 @@ onMounted(() => {
46
46
  inheritedRtl.value = getComputedStyle(rootEl.value).direction === 'rtl'
47
47
  }
48
48
  })
49
- const isRtl = computed(() => props.rtl ?? inheritedRtl.value)
50
-
51
49
  const from = ref<number>(Array.isArray(props.modelValue) ? props.modelValue[0] : (props.modelValue ?? min))
52
50
  const to = ref<number>(Array.isArray(props.modelValue) ? props.modelValue[1] : max)
53
51
 
@@ -80,7 +80,7 @@ function handleMouseLeave() {
80
80
  cursor: 'pointer',
81
81
  borderRadius: '2px',
82
82
  transition: 'background-color 0.1s ease',
83
- }" @focus="handleCellHover(cell)" @mouseenter="handleCellHover(cell.row, cell.col)" @click="handleCellClick(cell.row, cell.col, hide)"
83
+ }" @focus="handleCellHover(cell.row, cell.col)" @mouseenter="handleCellHover(cell.row, cell.col)" @click="handleCellClick(cell.row, cell.col, hide)"
84
84
  />
85
85
  </div>
86
86
 
@@ -164,11 +164,15 @@ th {
164
164
  text-align: end;
165
165
  max-width: 100%;
166
166
  cursor: pointer;
167
- transition: opacity 0.2s ease;
167
+ transition: var(--bgl-transition);
168
168
  }
169
169
 
170
170
  .richtext-editor-content .image-figure:hover {
171
- opacity: 0.8;
171
+ filter: var(--bgl-hover-filter);
172
+ }
173
+
174
+ .richtext-editor-content .image-figure:active {
175
+ filter: var(--bgl-active-filter);
172
176
  }
173
177
 
174
178
  .richtext-editor-content .image-figure img {
@@ -229,9 +233,7 @@ th {
229
233
  }
230
234
 
231
235
  .richtext-editor-content .video-figure:hover {
232
- cursor: pointer;
233
- filter: brightness(90%);
234
- --bgl-transition-400: all 400ms ease;
236
+ filter: var(--bgl-hover-filter);
235
237
  }
236
238
 
237
239
  .richtext-editor-content .video-figure:active {
@@ -292,13 +294,17 @@ th {
292
294
  text-align: center;
293
295
  max-width: 100%;
294
296
  cursor: pointer;
295
- transition: all 0.3s ease;
297
+ transition: var(--bgl-transition);
296
298
  }
297
299
 
298
300
  .richtext-editor-content .embed-figure:hover {
299
301
  filter: var(--bgl-hover-filter);
300
302
  }
301
303
 
304
+ .richtext-editor-content .embed-figure:active {
305
+ filter: var(--bgl-active-filter);
306
+ }
307
+
302
308
  .richtext-editor-content .embed-figure iframe {
303
309
  display: block;
304
310
  max-width: 100%;
@@ -307,12 +313,12 @@ th {
307
313
  border: 1px solid var(--bgl-border-color, #ddd);
308
314
  border-radius: 8px;
309
315
  background: white;
310
- transition: all 0.3s ease;
316
+ transition: var(--bgl-transition);
311
317
  pointer-events: none;
312
318
  }
313
319
 
314
320
  .richtext-editor-content .embed-figure:hover iframe {
315
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
321
+ box-shadow: 0 4px 12px var(--bgl-shadow);
316
322
  }
317
323
 
318
324
  .richtext-editor-content .embed-figure figcaption {
@@ -2119,7 +2119,7 @@ onUnmounted(() => {
2119
2119
  function setupTableEditButtons(doc: Document) {
2120
2120
  // Simple function to add edit buttons to all tables
2121
2121
  function addEditButtonsToTables() {
2122
- const tables = doc.querySelectorAll('table:not([data-edit-button-added])')
2122
+ const tables = doc.querySelectorAll<HTMLTableElement>('table:not([data-edit-button-added])')
2123
2123
 
2124
2124
  tables.forEach((table, index) => {
2125
2125
  // Create edit button as a span element instead of button
@@ -247,7 +247,9 @@ background: #f5f5f5;
247
247
  color: var(--bgl-input-color);
248
248
  position: absolute;
249
249
  inset-inline-end: calc(var(--bgl-input-height) / 3 - 0.25rem);
250
- margin-top: calc(var(--bgl-input-height) / 2 + 0.1rem);
250
+ /* center on the input row regardless of input height (dense/small/default) */
251
+ top: calc(var(--bgl-input-height) / 2);
252
+ transform: translateY(-50%);
251
253
  line-height: 0;
252
254
  }
253
255
 
@@ -260,7 +262,9 @@ padding-inline-end: calc(var(--bgl-input-height) / 3 + 1.5rem);
260
262
  color: var(--bgl-input-color);
261
263
  position: absolute;
262
264
  inset-inline-start: calc(var(--bgl-input-height) / 3 - 0.25rem);
263
- margin-top: calc(var(--bgl-input-height) / 2);
265
+ /* center on the input row regardless of input height (dense/small/default) */
266
+ top: calc(var(--bgl-input-height) / 2);
267
+ transform: translateY(-50%);
264
268
  line-height: 0;
265
269
  }
266
270
 
@@ -23,7 +23,13 @@
23
23
 
24
24
  .fileUploadWrap.dragover,
25
25
  .fileUploadWrap:hover {
26
- box-shadow: inset 0 0 10px #00000012;
26
+ box-shadow: inset 0 0 10px var(--bgl-shadow);
27
+ filter: var(--bgl-hover-filter);
28
+ }
29
+
30
+ .fileUploadWrap.dragover:active,
31
+ .fileUploadWrap:active {
32
+ filter: var(--bgl-active-filter);
27
33
  }
28
34
 
29
35
  .fileUploadWrap[style*='height: auto;'] {
@@ -22,6 +22,8 @@ export { default as DragOver } from './DragOver.vue'
22
22
  export { default as Dropdown } from './Dropdown.vue'
23
23
  export { default as EmptyState } from './EmptyState.vue'
24
24
  export { default as FieldSetVue } from './FieldSetVue.vue'
25
+ export { default as KanbanBoard } from './kanban/KanbanBoard.vue'
26
+ export type { KanbanBoardProps, KanbanColumn, KanbanCardSchema, KanbanMoveEvent } from './kanban/kanbanTypes'
25
27
  export { default as FilterQuery } from './FilterQuery.vue'
26
28
  export type { FilterField, QueryOption } from './FilterQuery.types'
27
29
  export { default as Flag } from './Flag.vue'