@bagelink/vue 1.15.166 → 1.15.170

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 (32) hide show
  1. package/dist/components/Badge.vue.d.ts +5 -3
  2. package/dist/components/Badge.vue.d.ts.map +1 -1
  3. package/dist/components/BglVideo.vue.d.ts +2 -0
  4. package/dist/components/BglVideo.vue.d.ts.map +1 -1
  5. package/dist/components/Btn.vue.d.ts.map +1 -1
  6. package/dist/components/RouterWrapper.vue.d.ts.map +1 -1
  7. package/dist/components/form/inputs/ArrayInput.vue.d.ts +2 -0
  8. package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
  10. package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
  11. package/dist/composables/index.d.ts +1 -0
  12. package/dist/composables/index.d.ts.map +1 -1
  13. package/dist/composables/usePrefersReducedMotion.d.ts +3 -0
  14. package/dist/composables/usePrefersReducedMotion.d.ts.map +1 -0
  15. package/dist/dialog/useDialog.d.ts.map +1 -1
  16. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  17. package/dist/index.cjs +177 -150
  18. package/dist/index.mjs +36089 -42101
  19. package/dist/style.css +1 -2
  20. package/package.json +1 -1
  21. package/src/components/Badge.vue +55 -19
  22. package/src/components/BglVideo.vue +4 -1
  23. package/src/components/Btn.vue +4 -3
  24. package/src/components/RouterWrapper.vue +21 -1
  25. package/src/components/form/inputs/ArrayInput.vue +6 -2
  26. package/src/components/form/inputs/RadioGroup.vue +18 -2
  27. package/src/composables/index.ts +1 -0
  28. package/src/composables/usePrefersReducedMotion.ts +24 -0
  29. package/src/dialog/useDialog.ts +8 -1
  30. package/src/form-flow/FormFlow.vue +52 -7
  31. package/src/styles/appearance.css +8 -0
  32. package/src/styles/text.css +232 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.166",
4
+ "version": "1.15.170",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -53,14 +53,16 @@ const props = defineProps<{
53
53
  round?: boolean
54
54
  btn?: BtnProp
55
55
  btnEnd?: BtnProp
56
- size?: 'sm' | 'md' | 'lg'
57
- /** Boolean size shorthands: <Badge sm value="New" /> */
56
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
57
+ /** Boolean size shorthands: <Badge sm value="New" /> — xs · sm · md · lg · xl */
58
+ xs?: boolean
58
59
  sm?: boolean
59
60
  lg?: boolean
61
+ xl?: boolean
60
62
  /** Mobile-only size override (≤ 910px). Lets a `lg` desktop badge shrink on
61
63
  phones so it doesn't overflow / touch neighbours. e.g.
62
64
  <Badge lg mobile-size="sm" />. Mirrors `Icon`'s `mobileSize` prop. */
63
- mobileSize?: 'sm' | 'md' | 'lg'
65
+ mobileSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
64
66
  uppercase?: boolean
65
67
  }>()
66
68
 
@@ -91,8 +93,10 @@ const computedPairClass = computed(() => {
91
93
 
92
94
  const computedSize = computed(() => {
93
95
  if (props.size) { return props.size }
96
+ if (props.xs) { return 'xs' }
94
97
  if (props.sm) { return 'sm' }
95
98
  if (props.lg) { return 'lg' }
99
+ if (props.xl) { return 'xl' }
96
100
  return 'md'
97
101
  })
98
102
 
@@ -122,11 +126,15 @@ const computedClasses = computed(() => {
122
126
  'soft': computedVariant.value === 'soft',
123
127
  'frost': computedVariant.value === 'frost',
124
128
  'gradient': computedVariant.value === 'gradient',
125
- 'pillLarge': computedSize.value === 'lg',
129
+ 'pillXSmall': computedSize.value === 'xs',
126
130
  'pillSmall': computedSize.value === 'sm',
127
- 'm_pillLarge': computedMobileSize.value === 'lg',
128
- 'm_pillMd': computedMobileSize.value === 'md',
131
+ 'pillLarge': computedSize.value === 'lg',
132
+ 'pillXLarge': computedSize.value === 'xl',
133
+ 'm_pillXSmall': computedMobileSize.value === 'xs',
129
134
  'm_pillSmall': computedMobileSize.value === 'sm',
135
+ 'm_pillMd': computedMobileSize.value === 'md',
136
+ 'm_pillLarge': computedMobileSize.value === 'lg',
137
+ 'm_pillXLarge': computedMobileSize.value === 'xl',
130
138
  'bgl_pill-flush': props.flush,
131
139
  }
132
140
 
@@ -146,7 +154,7 @@ const computedClasses = computed(() => {
146
154
  >
147
155
  <div class="flex h-100 justify-content-center relative ">
148
156
  <div v-if="loading" class="loading absolute inset-0 mx-auto" />
149
- <div class="px-025 flex gap-025 justify-content-center align-items-center" :class="{ 'opacity-0': loading }">
157
+ <div class="px-025 flex gap-025 justify-content-center align-items-center" :class="{ 'opacity-0': loading }" style="font-size: var(--bgl-pill-font-size)">
150
158
  <Btn v-if="btn" class="bgl_pill-btn -ms-025" icon-size="0.8" round v-bind="btn" />
151
159
  <span v-if="dot" class="bgl_pill-dot" />
152
160
  <Icon v-if="icon" class="line-height-0" :icon="icon" style="font-size: var(--bgl-pill-font-size)" />
@@ -165,30 +173,58 @@ const computedClasses = computed(() => {
165
173
 
166
174
  <style scoped>
167
175
 
168
- .pillLarge{
169
- --bgl-pill-font-size: var(--bgl-font-size);
170
- --bgl-pill-height:auto;
171
- }
172
- .pillSmall{
176
+ /* Size scale (xs → xl). Each size drives font-size, height, and corner radius
177
+ together so the pill scales naturally; padding derives from font-size on
178
+ .bgl_pill, so it follows along. `md` is the base (theme.css defaults). */
179
+ .pillXSmall {
173
180
  --bgl-pill-font-size: 9px;
174
- --bgl-pill-height:15px;
181
+ --bgl-pill-height: 15px;
182
+ --bgl-pill-border-radius: 5px;
183
+ }
184
+ .pillSmall {
185
+ --bgl-pill-font-size: 10px;
186
+ --bgl-pill-height: 17px;
187
+ --bgl-pill-border-radius: 6px;
188
+ }
189
+ .pillLarge {
190
+ --bgl-pill-font-size: 16px;
191
+ --bgl-pill-height: 28px;
192
+ --bgl-pill-border-radius: 10px;
193
+ }
194
+ .pillXLarge {
195
+ --bgl-pill-font-size: 18px;
196
+ --bgl-pill-height: 34px;
197
+ --bgl-pill-border-radius: 12px;
175
198
  }
176
199
 
177
200
  /* Mobile-only size overrides (≤ 910px). These win over the desktop size class
178
201
  because the size is driven entirely by --bgl-pill-font-size / --bgl-pill-height,
179
202
  and a later @media rule with equal specificity overrides the base class. */
180
203
  @media screen and (max-width: 910px) {
181
- .m_pillLarge {
182
- --bgl-pill-font-size: var(--bgl-font-size);
183
- --bgl-pill-height: auto;
204
+ .m_pillXSmall {
205
+ --bgl-pill-font-size: 9px;
206
+ --bgl-pill-height: 15px;
207
+ --bgl-pill-border-radius: 5px;
208
+ }
209
+ .m_pillSmall {
210
+ --bgl-pill-font-size: 10px;
211
+ --bgl-pill-height: 17px;
212
+ --bgl-pill-border-radius: 6px;
184
213
  }
185
214
  .m_pillMd {
186
215
  --bgl-pill-font-size: 12px;
187
216
  --bgl-pill-height: 20px;
217
+ --bgl-pill-border-radius: 8px;
188
218
  }
189
- .m_pillSmall {
190
- --bgl-pill-font-size: 9px;
191
- --bgl-pill-height: 15px;
219
+ .m_pillLarge {
220
+ --bgl-pill-font-size: 16px;
221
+ --bgl-pill-height: 28px;
222
+ --bgl-pill-border-radius: 10px;
223
+ }
224
+ .m_pillXLarge {
225
+ --bgl-pill-font-size: 18px;
226
+ --bgl-pill-height: 34px;
227
+ --bgl-pill-border-radius: 12px;
192
228
  }
193
229
  }
194
230
  .pillText{
@@ -12,6 +12,8 @@ interface Props {
12
12
  loop?: boolean
13
13
  status?: string
14
14
  playsinline?: boolean
15
+ /** Accessible name for embed iframes / native video */
16
+ title?: string
15
17
  }
16
18
 
17
19
  const props = defineProps<Props>()
@@ -108,13 +110,14 @@ const videoUrl = computed(() => {
108
110
  <template>
109
111
  <div ref="videoContainer" class="bgl_vid" :class="{ vid_empty: !src, vid_short: isYoutubeShort }">
110
112
  <iframe
111
- v-if="embedType" :src="videoUrl" :style="{ aspectRatio }" frameborder="0" title="Video"
113
+ v-if="embedType" :src="videoUrl" :style="{ aspectRatio }" frameborder="0" :title="title || 'Video'"
112
114
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
113
115
  referrerpolicy="strict-origin-when-cross-origin" allowfullscreen
114
116
  />
115
117
  <video
116
118
  v-else-if="src" ref="video" :autoplay="autoplay === true" :muted="mute" :loop="loop"
117
119
  :style="{ aspectRatio }" :controls="controls" :playsinline="playsinline"
120
+ :aria-label="title || undefined"
118
121
  >
119
122
  <source :src="src" :type="`video/${videoFormat}`">
120
123
  </video>
@@ -81,6 +81,8 @@ const emit = defineEmits<{
81
81
  }>()
82
82
 
83
83
  const { $t } = useI18n()
84
+ // Must run in setup — inject() cannot be called from click handlers.
85
+ const dialog = useDialog()
84
86
 
85
87
  const { isGradient, gradientStyle } = useGradientVariant({
86
88
  gradient: () => props.gradient,
@@ -152,11 +154,10 @@ onUnmounted(() => {
152
154
 
153
155
  async function handleClick(event: MouseEvent) {
154
156
  if (props.confirm !== undefined && props.confirm !== false) {
155
- const { confirm } = useDialog()
156
157
  const message = typeof props.confirm === 'string' ? props.confirm : $t('modalConfirm.message')
157
- const confirmed = await confirm({
158
+ const confirmed = await dialog.confirm({
158
159
  title: $t('modalConfirm.title'),
159
- message
160
+ message,
160
161
  })
161
162
 
162
163
  if (!confirmed) { return }
@@ -1,8 +1,28 @@
1
1
  <script setup lang="ts">
2
+ import type { RouteLocationNormalizedLoaded } from 'vue-router'
2
3
  import { nextTick, ref, watch } from 'vue'
3
4
  import { useRoute } from 'vue-router'
4
5
  import Loading from './Loading.vue'
5
6
 
7
+ // Key used for the routed component + its transition. The key IS the identity of
8
+ // the mounted view: it changes only when we actually switch to a different view,
9
+ // so we remount + animate on real view changes but NOT on same-view param/query
10
+ // changes (e.g. /ai/:chatId → /ai/new, or /user/1 → /user/2 — params drive
11
+ // reactive updates inside the component, not a remount).
12
+ //
13
+ // The identity of a view is the deepest matched route record's static `path`
14
+ // (which component is mounted), independent of its dynamic params. A parent can
15
+ // still opt to stay mounted while a nested <RouterView> swaps its children by
16
+ // setting `meta.persistOnChildNav: true`: the key then resolves to that record's
17
+ // path so the parent shell isn't remounted on child navigation.
18
+ function viewKey(route: RouteLocationNormalizedLoaded): string {
19
+ const persistRecord = [...route.matched].reverse().find(r => r.meta?.persistOnChildNav)
20
+ if (persistRecord) return persistRecord.path
21
+ // Default: the deepest matched record — i.e. the routed component's identity.
22
+ const leaf = route.matched[route.matched.length - 1]
23
+ return leaf?.path ?? route.path
24
+ }
25
+
6
26
  // Reset the scroll position on every route change. The app's scroll container is
7
27
  // the inner `.pageContent` element in AppContent (the window itself doesn't
8
28
  // scroll), so a plain vue-router `scrollBehavior` wouldn't help. We scroll the
@@ -32,7 +52,7 @@ watch(
32
52
  </div>
33
53
  </slot>
34
54
  <transition v-else :duration="300" name="slide-fade" mode="out-in" appear>
35
- <component :is="Component" :key="route.fullPath" />
55
+ <component :is="Component" :key="viewKey(route)" />
36
56
  </transition>
37
57
  </RouterView>
38
58
  </template>
@@ -21,6 +21,8 @@ const props = defineProps<{
21
21
  itemLabel?: string
22
22
  collapsible?: boolean
23
23
  simple?: boolean
24
+ /** Factory for new rows. Prefer this over pushing `undefined` (which becomes `null` after JSON sync). */
25
+ createItem?: () => T
24
26
  } & BagelInputShellProps>()
25
27
 
26
28
  const { shellClass, shellStyle } = useBagelInputShell(props)
@@ -96,8 +98,10 @@ watch(
96
98
  )
97
99
 
98
100
  function addItem() {
99
- items.value.push(undefined as T)
100
- minimizedItems.value.push(true)
101
+ const next = props.createItem ? props.createItem() : ({} as T)
102
+ items.value.push(next)
103
+ // Open the new row so nested FormFlow mounts and the user can edit immediately.
104
+ minimizedItems.value.push(false)
101
105
  updateModel()
102
106
  }
103
107
 
@@ -45,6 +45,7 @@ const props = withDefaults(
45
45
  textColor?: string
46
46
  textAlign?: 'left' | 'center' | 'right'
47
47
  wrapperClass?: string
48
+ inline?: boolean
48
49
  } & BagelInputShellProps>(),
49
50
  {
50
51
  align: 'center'
@@ -143,9 +144,9 @@ function handleChange() {
143
144
  <p v-if="label" :id="`${name}-label`" class="group-label">
144
145
  {{ resolveI18n(label) }} <span v-if="required">*</span>
145
146
  </p>
146
- <div class="radio-group-wrap" :class="wrapperClass">
147
+ <div class="radio-group-wrap" :class="[wrapperClass, { 'radio-group-wrap--inline': inline }]">
147
148
  <label v-for="(opt, index) in visibleOptions" :key="opt.id || `${name}-${index}`" class="border rounded flex active-list-item hover mb-05" :for="opt.id || `${name}-${index}`"
148
- :class="{ 'p-05 gap-025': thin, 'py-1 gap-075': !thin, 'ps-05': !hideRadio, 'bg-gray-light': !bgColor && !flat, 'align-items-start': align === 'start' || align === 'top', 'align-items-center': align === 'center', 'align-items-end': align === 'end' || align === 'bottom', invertedActive, 'is-option-disabled': disabled || opt.disabled }"
149
+ :class="{ 'p-05 gap-025': thin, 'py-1 gap-075': !thin, 'ps-05': !hideRadio, 'px-1': hideRadio && !thin, 'bg-gray-light': !bgColor && !flat, 'align-items-start': align === 'start' || align === 'top', 'align-items-center': align === 'center', 'align-items-end': align === 'end' || align === 'bottom', invertedActive, 'is-option-disabled': disabled || opt.disabled }"
149
150
  :style="{ backgroundColor: bgColor, borderColor }">
150
151
  <input :id="opt.id || `${name}-${index}`" v-model="selectedOption" :disabled="disabled || opt.disabled" :tabindex="optionTabindex(opt, index)" class="radio-input-list" type="radio" :name :value="opt.value" :required="required" :class="{
151
152
  'mt-025': align === 'start' || align === 'top',
@@ -200,6 +201,21 @@ accent-color: var(--bgl-input-label-active-color, var(--bgl-primary));
200
201
  display: none;
201
202
  }
202
203
 
204
+ .radio-group-wrap--inline {
205
+ display: flex;
206
+ flex-wrap: wrap;
207
+ gap: 0.5rem;
208
+ }
209
+
210
+ .radio-group-wrap--inline > label {
211
+ flex: 1 1 0;
212
+ margin-bottom: 0 !important;
213
+ }
214
+
215
+ .radio-group-wrap--inline > label > .w-100 {
216
+ width: auto;
217
+ }
218
+
203
219
  .is-option-disabled {
204
220
  opacity: 0.5;
205
221
  cursor: not-allowed;
@@ -13,6 +13,7 @@ export { useGradientVariant } from './useGradientVariant'
13
13
  export { useImageSrc } from './useImageSrc'
14
14
  export { useLocalStore } from './useLocalStore'
15
15
  export { usePolling } from './usePolling'
16
+ export { usePrefersReducedMotion } from './usePrefersReducedMotion'
16
17
  export { useQuery } from './useQuery'
17
18
  export { useResizableLayoutProvider } from './useResizableLayout'
18
19
  export type { PanelConfig, PanelState, ResizableContext } from './useResizableLayout'
@@ -0,0 +1,24 @@
1
+ import { onMounted, onUnmounted, ref } from 'vue'
2
+
3
+ /** Whether the user requested reduced motion — use to disable autoplay, etc. */
4
+ export function usePrefersReducedMotion() {
5
+ const prefersReducedMotion = ref(false)
6
+
7
+ let mq: MediaQueryList | undefined
8
+ function onChange(e: MediaQueryListEvent) {
9
+ prefersReducedMotion.value = e.matches
10
+ }
11
+
12
+ onMounted(() => {
13
+ if (typeof window === 'undefined' || !window.matchMedia) return
14
+ mq = window.matchMedia('(prefers-reduced-motion: reduce)')
15
+ prefersReducedMotion.value = mq.matches
16
+ mq.addEventListener('change', onChange)
17
+ })
18
+
19
+ onUnmounted(() => {
20
+ mq?.removeEventListener('change', onChange)
21
+ })
22
+
23
+ return prefersReducedMotion
24
+ }
@@ -19,7 +19,14 @@ export const DialogSymbol: InjectionKey<DialogApi> = Symbol('dialog')
19
19
  let globalDialogApi: DialogApi | null = null
20
20
 
21
21
  export function useDialog(): DialogApi {
22
- const dialogApi = inject(DialogSymbol, null)
22
+ // Prefer inject when called inside setup(); fall back to the plugin singleton
23
+ // when inject is unavailable (e.g. legacy call sites outside setup).
24
+ let dialogApi: DialogApi | null = null
25
+ try {
26
+ dialogApi = inject(DialogSymbol, null)
27
+ } catch {
28
+ dialogApi = null
29
+ }
23
30
  if (!dialogApi) {
24
31
  if (globalDialogApi) {
25
32
  return globalDialogApi
@@ -90,17 +90,52 @@ function setNestedValue(obj: any, path: string, value: any): void {
90
90
  target[keys[keys.length - 1]] = value
91
91
  }
92
92
 
93
+ /** Deep-clone defaults so array/object defaults are never shared across form instances. */
94
+ function cloneDefault<V>(value: V): V {
95
+ if (value === undefined || value === null || typeof value !== 'object') return value
96
+ try {
97
+ return structuredClone(value)
98
+ } catch {
99
+ return JSON.parse(JSON.stringify(value)) as V
100
+ }
101
+ }
102
+
93
103
  function applyDefaults(modelValue: T | undefined): T {
94
104
  const data = { ...(modelValue ?? {}) } as T
95
105
  for (const [key, field] of Object.entries(props.schema._fields)) {
96
106
  const defaultValue = (field as FieldBuilder)._config.default
97
107
  if (defaultValue !== undefined && getNestedValue(data, key) === undefined) {
98
- setNestedValue(data, key, defaultValue)
108
+ setNestedValue(data, key, cloneDefault(defaultValue))
99
109
  }
100
110
  }
101
111
  return data
102
112
  }
103
113
 
114
+ /** Build a new array row from nested schema field defaults, or a clone of the array's sample default item. */
115
+ function createArrayItem(field: FieldBuilder): Record<string, unknown> {
116
+ const nested = field._config.schema as SchemaDefinition | undefined
117
+ if (nested && typeof (nested as any).getDefaults === 'function') {
118
+ const fromFields = cloneDefault((nested as any).getDefaults() as Record<string, unknown>)
119
+ if (fromFields && Object.keys(fromFields).length > 0) return fromFields
120
+ }
121
+ // Prefer a template from the array field's default sample (e.g. TeamGroupBlox).
122
+ const sample = field._config.default
123
+ if (Array.isArray(sample) && sample.length > 0 && sample[0] != null && typeof sample[0] === 'object') {
124
+ return cloneDefault(toRawPlain(sample[0] as Record<string, unknown>))
125
+ }
126
+ // Nested schema fields without defaults → empty plain object (not undefined/null).
127
+ return {}
128
+ }
129
+
130
+ /** Strip Vue proxies before clone (structuredClone rejects Proxy). */
131
+ function toRawPlain<V extends Record<string, unknown>>(value: V): V {
132
+ try {
133
+ return JSON.parse(JSON.stringify(value)) as V
134
+ } catch {
135
+ return { ...value }
136
+ }
137
+ }
138
+
104
139
  const formData = ref(applyDefaults(props.modelValue)) as any
105
140
  const validationErrors = ref<Record<string, string>>({})
106
141
  const hasSubmitted = ref(false)
@@ -116,7 +151,9 @@ watch(() => props.modelValue, (newVal) => {
116
151
 
117
152
  watch(formData, (newVal) => {
118
153
  isInternalUpdate = true
119
- emit('update:modelValue', newVal)
154
+ // Emit a plain clone so parent state never shares reactive refs with
155
+ // formData (shared refs make deep modelValue watches fight array pushes).
156
+ emit('update:modelValue', cloneDefault(newVal))
120
157
  nextTick(() => { isInternalUpdate = false })
121
158
  }, { deep: true })
122
159
 
@@ -288,10 +325,18 @@ function getFieldProps(field: FieldBuilder, key: string) {
288
325
  }
289
326
 
290
327
  if (field._type === 'array') {
328
+ // `default` / `schema` are form-schema metadata — never pass them through to ArrayInput
329
+ // (and the earlier `...field._config` spread may have already set them).
330
+ delete baseProps.default
331
+ delete baseProps.schema
332
+ delete baseProps.itemType
291
333
  const configWithoutSchema = Object.fromEntries(
292
- Object.entries(field._config).filter(([k]) => k !== 'schema' && k !== 'itemType')
334
+ Object.entries(field._config).filter(([k]) => k !== 'schema' && k !== 'itemType' && k !== 'default')
293
335
  )
294
336
  Object.assign(baseProps, configWithoutSchema)
337
+ // Re-apply binding after config spread so handlers never get overwritten.
338
+ baseProps.modelValue = getFieldValue(key)
339
+ baseProps['onUpdate:modelValue'] = (val: any) => { updateField(key, val, field) }
295
340
  baseProps.allowAdd = baseProps.allowAdd ?? true
296
341
  baseProps.allowDelete = baseProps.allowDelete ?? true
297
342
  }
@@ -412,7 +457,7 @@ defineExpose({
412
457
  >
413
458
  <!-- Array field with nested schema -->
414
459
  <template v-if="field._type === 'array' && field._config.schema?._isSchema">
415
- <ArrayInput v-bind="getFieldProps(field, key)">
460
+ <ArrayInput v-bind="getFieldProps(field, key)" :create-item="() => createArrayItem(field)">
416
461
  <template #default="{ item }">
417
462
  <!-- @vue-ignore -->
418
463
  <FormFlow
@@ -427,7 +472,7 @@ defineExpose({
427
472
 
428
473
  <!-- Array field with primitive string item type e.g. $.array('Label', 'text') -->
429
474
  <template v-else-if="field._type === 'array' && typeof field._config.schema === 'string'">
430
- <ArrayInput v-bind="getFieldProps(field, key)">
475
+ <ArrayInput v-bind="getFieldProps(field, key)" :create-item="() => '' as any">
431
476
  <template #default="{ item }">
432
477
  <component
433
478
  :is="getFieldComponent(field._config.schema as string)"
@@ -440,7 +485,7 @@ defineExpose({
440
485
 
441
486
  <!-- Array field with custom component -->
442
487
  <template v-else-if="field._type === 'array' && field._config.schema">
443
- <ArrayInput v-bind="getFieldProps(field, key)">
488
+ <ArrayInput v-bind="getFieldProps(field, key)" :create-item="() => createArrayItem(field)">
444
489
  <template #default="{ item }">
445
490
  <component :is="resolveComponent(field._config.schema)" v-model="item.value" />
446
491
  </template>
@@ -449,7 +494,7 @@ defineExpose({
449
494
 
450
495
  <!-- Array field with primitive item type (e.g. from JSON Schema type: array with items: { type: string }) -->
451
496
  <template v-else-if="field._type === 'array' && field._config.itemType">
452
- <ArrayInput v-bind="getFieldProps(field, key)">
497
+ <ArrayInput v-bind="getFieldProps(field, key)" :create-item="() => '' as any">
453
498
  <template #default="{ item }">
454
499
  <component
455
500
  :is="getFieldComponent(field._config.itemType)"
@@ -340,7 +340,15 @@
340
340
  mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%);
341
341
  }
342
342
 
343
+ .isolation-isolate {
344
+ isolation: isolate !important;
345
+ }
346
+
343
347
  @media screen and (max-width: 910px) {
348
+ .m_isolation-isolate {
349
+ isolation: isolate !important;
350
+ }
351
+
344
352
  .m_filters-mask {
345
353
  -webkit-mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%);
346
354
  mask-image: linear-gradient(to right, transparent 0, black 24px, black calc(100% - 24px), transparent 100%);