@bagelink/vue 1.15.558 → 1.15.565

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.558",
4
+ "version": "1.15.565",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -55,6 +55,8 @@ const props = withDefaults(
55
55
  ripple?: boolean
56
56
  fullWidth?: boolean
57
57
  fullWidthMobile?: boolean
58
+ /** Allow the label to wrap onto multiple lines and grow the button vertically. */
59
+ multiline?: boolean
58
60
  alignTxt?: 'center' | 'start' | 'end'
59
61
  alignTxtMobile?: 'center' | 'start' | 'end'
60
62
  onClick?: (e: MouseEvent) => void
@@ -224,6 +226,7 @@ const slots: SetupContext['slots'] = useSlots()
224
226
  'bgl_btn_xlSize': computedSize === 'xl',
225
227
  'bgl_btn_fullWidth': fullWidth,
226
228
  'bgl_btn_fullWidthMobile': fullWidthMobile,
229
+ 'bgl_btn_multiline': multiline,
227
230
  'bgl_btn_alignStart': alignTxt === 'start',
228
231
  'bgl_btn_alignEnd': alignTxt === 'end',
229
232
  'bgl_btn_alignStartMobile': alignTxtMobile === 'start',
@@ -447,6 +450,32 @@ height: calc(var(--bgl-btn-height) * 1.5);
447
450
  width: calc(var(--bgl-btn-height) * 1.5);
448
451
  }
449
452
 
453
+ .bgl_btn.bgl_btn_multiline {
454
+ height: auto;
455
+ min-height: var(--bgl-btn-height);
456
+ line-height: 1.3;
457
+ padding-block: calc(var(--bgl-btn-padding) / 4);
458
+ white-space: normal;
459
+ overflow-wrap: anywhere;
460
+ }
461
+
462
+ .bgl_btn.bgl_btn_multiline.thin,
463
+ .bgl_btn.bgl_btn_multiline.bgl_btn_sSize {
464
+ min-height: calc(var(--bgl-btn-height) * 0.7);
465
+ }
466
+
467
+ .bgl_btn.bgl_btn_multiline.bgl_btn_xsSize {
468
+ min-height: calc(var(--bgl-btn-height) / 2);
469
+ }
470
+
471
+ .bgl_btn.bgl_btn_multiline.bgl_btn_lSize {
472
+ min-height: calc(var(--bgl-btn-height) * 1.2);
473
+ }
474
+
475
+ .bgl_btn.bgl_btn_multiline.bgl_btn_xlSize {
476
+ min-height: calc(var(--bgl-btn-height) * 1.5);
477
+ }
478
+
450
479
  .bgl_btn_fullWidth {
451
480
  width: 100%;
452
481
  }
@@ -34,7 +34,7 @@ const loading = defineModel('loading', { default: false })
34
34
  const itemHeight = defineModel('itemHeight', { default: 50 })
35
35
  /** Cell font size. Number = px; string = any CSS size (e.g. '0.875rem'). */
36
36
  const fontSize = defineModel<number | string | undefined>('fontSize', { default: undefined })
37
- const selectedItems = defineModel<string[]>('selectedItems', { default: [] as string[] })
37
+ const selectedItems = defineModel<string[]>('selectedItems', { default: () => [] })
38
38
 
39
39
  const {
40
40
  computedSchema,
@@ -20,7 +20,7 @@ const { shellClass, shellStyle } = useBagelInputShell(props)
20
20
  const uid = Math.random().toString(36).slice(7)
21
21
  const inputId = computed(() => props.id || uid)
22
22
 
23
- const checked = defineModel<T>('modelValue', { default: undefined })
23
+ const checked = defineModel<T>('modelValue')
24
24
 
25
25
  const hasValue = computed(() => {
26
26
  const v = checked.value
@@ -23,7 +23,7 @@ defineOptions({
23
23
  })
24
24
 
25
25
  const props = defineProps<{
26
- modelValue: string
26
+ modelValue?: string | null
27
27
  toolbarConfig?: ToolbarConfig
28
28
  customToolbarButtons?: CustomToolbarButton[]
29
29
  extensions?: Array<RichTextExtension | string>
@@ -61,16 +61,28 @@ const emit = defineEmits(['update:modelValue'])
61
61
 
62
62
  const { shellClass, shellStyle } = useBagelInputShell(props)
63
63
  const extensionRegistry = inject(RICH_TEXT_EXTENSION_REGISTRY_KEY, new Map())
64
- const hasEditorValue = computed(() => Boolean(props.modelValue?.trim()))
65
- const resolvedExtensions = computed(() => (props.extensions ?? []).flatMap((extension) => {
66
- if (typeof extension !== 'string') return [extension]
67
- const resolved = extensionRegistry.get(extension)
68
- if (!resolved) {
69
- console.warn(`[RichText] Unknown extension "${extension}".`)
70
- return []
64
+ const normalizedModelValue = computed(() => props.modelValue ?? '')
65
+ const hasEditorValue = computed(() => Boolean(normalizedModelValue.value.trim()))
66
+ const resolvedExtensions = computed(() => {
67
+ const active = new Map(
68
+ [...extensionRegistry.values()]
69
+ .filter(extension => extension.alwaysActive)
70
+ .map(extension => [extension.name, extension]),
71
+ )
72
+ for (const extension of props.extensions ?? []) {
73
+ if (typeof extension !== 'string') {
74
+ active.set(extension.name, extension)
75
+ continue
76
+ }
77
+ const resolved = extensionRegistry.get(extension)
78
+ if (!resolved) {
79
+ console.warn(`[RichText] Unknown extension "${extension}".`)
80
+ continue
81
+ }
82
+ active.set(resolved.name, resolved)
71
83
  }
72
- return [resolved]
73
- }))
84
+ return [...active.values()]
85
+ })
74
86
  const extensionToolbarButtons = computed(() => resolvedExtensions.value.flatMap(extension => extension.toolbarButtons ?? []))
75
87
  const effectiveCustomToolbarButtons = computed(() => [
76
88
  ...(props.customToolbarButtons ?? []),
@@ -309,6 +321,7 @@ const effectiveHideArray = computed(() => {
309
321
  const showLinkModal = ref(false)
310
322
  const linkForm = ref({
311
323
  url: '',
324
+ title: '',
312
325
  openInNewTab: true
313
326
  })
314
327
 
@@ -497,7 +510,7 @@ const tablePreviewHtml = computed(() => {
497
510
  })
498
511
 
499
512
  // Initialize content from modelValue
500
- editor.state.content = props.modelValue
513
+ editor.state.content = normalizedModelValue.value
501
514
 
502
515
  // Matches Hebrew, Arabic, Syriac, Thaana, Arabic Supplement, Arabic Presentation Forms
503
516
  const RTL_REGEX = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\uFB1D-\uFDFD\uFE70-\uFEFC]/
@@ -725,6 +738,7 @@ function openLinkModal(selection: Selection, range: Range, existingLink: HTMLAnc
725
738
  pendingLinkData = { selection, range, existingLink }
726
739
  linkForm.value = {
727
740
  url: existingLink?.href || '',
741
+ title: existingLink?.getAttribute('title') || '',
728
742
  openInNewTab: existingLink?.target !== '_self'
729
743
  }
730
744
  showLinkModal.value = true
@@ -735,7 +749,7 @@ function submitLink() {
735
749
  if (!pendingLinkData || !linkForm.value.url) { return }
736
750
 
737
751
  const { selection, range, existingLink } = pendingLinkData
738
- const { url, openInNewTab } = linkForm.value
752
+ const { url, title, openInNewTab } = linkForm.value
739
753
  const { doc } = editor.state
740
754
 
741
755
  if (!doc) { return }
@@ -743,6 +757,11 @@ function submitLink() {
743
757
  if (existingLink) {
744
758
  // Update existing link
745
759
  existingLink.href = url
760
+ if (title.trim()) {
761
+ existingLink.title = title.trim()
762
+ } else {
763
+ existingLink.removeAttribute('title')
764
+ }
746
765
  existingLink.target = openInNewTab ? '_blank' : '_self'
747
766
  if (openInNewTab) {
748
767
  existingLink.rel = 'noopener noreferrer'
@@ -754,6 +773,9 @@ function submitLink() {
754
773
  try {
755
774
  const anchor = doc.createElement('a')
756
775
  anchor.href = url
776
+ if (title.trim()) {
777
+ anchor.title = title.trim()
778
+ }
757
779
  anchor.target = openInNewTab ? '_blank' : '_self'
758
780
  if (openInNewTab) {
759
781
  anchor.rel = 'noopener noreferrer'
@@ -2357,7 +2379,7 @@ function handleTableContextMenu(event: MouseEvent) {
2357
2379
  // Expose debug methods if debug mode is enabled
2358
2380
  const debugMethods = computed(() => editor.state.debug)
2359
2381
  const hasRTL = computed(() => getTextDirection(
2360
- props.modelValue.replace(/<[^>]*>/g, ''),
2382
+ normalizedModelValue.value.replace(/<[^>]*>/g, ''),
2361
2383
  'ltr'
2362
2384
  ) === 'rtl')
2363
2385
 
@@ -2926,6 +2948,7 @@ function setupAutoWrapping(doc: Document) {
2926
2948
 
2927
2949
  // Populate the form with existing link data
2928
2950
  linkForm.value.url = link.href
2951
+ linkForm.value.title = link.getAttribute('title') || ''
2929
2952
  linkForm.value.openInNewTab = link.target === '_blank'
2930
2953
 
2931
2954
  // Store the link data for editing
@@ -3235,7 +3258,7 @@ async function initEditor() {
3235
3258
  background: #007bff !important;
3236
3259
  }
3237
3260
  ` // Create a complete HTML document with proper doctype and meta tags
3238
- const initialContent = props.modelValue || (props.placeholder ? `<p class="placeholder">${props.placeholder}</p>` : '')
3261
+ const initialContent = normalizedModelValue.value || (props.placeholder ? `<p class="placeholder">${props.placeholder}</p>` : '')
3239
3262
  const htmlContent = `
3240
3263
  <!DOCTYPE html>
3241
3264
  <html>
@@ -3324,7 +3347,7 @@ async function initEditor() {
3324
3347
  useEditorKeyboard(doc, commands)
3325
3348
 
3326
3349
  // Auto clear format for basic mode
3327
- if (props.basic && props.modelValue) {
3350
+ if (props.basic && normalizedModelValue.value) {
3328
3351
  setTimeout(() => {
3329
3352
  // Clear all formatting by removing styles and replacing with plain text
3330
3353
  const elements = doc.body.querySelectorAll('*')
@@ -3535,7 +3558,7 @@ watch([currentInputColor, currentTextColor, currentFontFamily, effectiveContentS
3535
3558
  // Reset initialization state when content changes significantly.
3536
3559
  // Compare incoming value against the CLEAN output (not raw DOM innerHTML) to avoid
3537
3560
  // false positives caused by editor chrome (table wrappers, edit buttons, etc.)
3538
- watch(() => props.modelValue, (newValue, oldValue) => {
3561
+ watch(normalizedModelValue, (newValue, oldValue) => {
3539
3562
  const cleanCurrent = editor.state.doc ? getCleanHTML(editor.state.doc ?? null) : ''
3540
3563
  if (newValue !== cleanCurrent) {
3541
3564
  if (!oldValue || Math.abs(newValue.length - oldValue.length) > 50) {
@@ -3813,7 +3836,10 @@ const imgTransform = computed({
3813
3836
  </div>
3814
3837
  </div>
3815
3838
  <!-- Link Modal -->
3816
- <Dialog :open="showLinkModal" title="Add Link" width="s" @update:open="showLinkModal = $event">
3839
+ <Dialog
3840
+ :open="showLinkModal" :title="pendingLinkData?.existingLink ? 'Edit Link' : 'Add Link'"
3841
+ width="s" @update:open="showLinkModal = $event"
3842
+ >
3817
3843
  <div class="flex gap-05 align-items-end">
3818
3844
  <TextInput
3819
3845
  v-model="linkForm.url" label="URL" type="url" placeholder="https://example.com"
@@ -3824,10 +3850,14 @@ const imgTransform = computed({
3824
3850
  @click="visitLink"
3825
3851
  />
3826
3852
  </div>
3853
+ <TextInput v-model="linkForm.title" label="Title" placeholder="Optional link title" />
3827
3854
  <CheckInput v-model="linkForm.openInNewTab" label="Open in new tab" type="checkbox" class="mb-2 mt-05" />
3828
3855
  <template #footer>
3829
3856
  <Btn value="Cancel" flat thin @click="showLinkModal = false" />
3830
- <Btn value="Add Link" :disabled="!isValidUrl(linkForm.url)" @click="submitLink" />
3857
+ <Btn
3858
+ :value="pendingLinkData?.existingLink ? 'Update Link' : 'Add Link'"
3859
+ :disabled="!isValidUrl(linkForm.url)" @click="submitLink"
3860
+ />
3831
3861
  </template>
3832
3862
  </Dialog>
3833
3863
 
@@ -89,6 +89,8 @@ export interface CustomToolbarButton {
89
89
  export interface RichTextExtension {
90
90
  name: string
91
91
  label: string
92
+ /** Apply this extension to every RichText editor in the app without field-level opt-in. */
93
+ alwaysActive?: boolean
92
94
  toolbarButtons?: CustomToolbarButton[]
93
95
  contentStyles?: string
94
96
  }
@@ -28,7 +28,7 @@ onMounted(() => {
28
28
 
29
29
  <template>
30
30
  <div
31
- class="bagel-input bgl-toggle"
31
+ class="bagel-input bgl-toggle w-a"
32
32
  :title
33
33
  :class="[shellClass, { small, 'has-value': checked }]" :style="shellStyle"
34
34
  >
@@ -99,6 +99,10 @@ outline: 1px solid var(--bgl-border-color) !important;
99
99
  box-shadow:0 1px 5px 0 rgba(0,0,0,.1), 0 1px 2px -1px rgba(0,0,0,.1);
100
100
  }
101
101
 
102
+ :global(.bgl-dark-mode) .bgl-toggle input::before {
103
+ background: var(--bgl-dm-text-muted);
104
+ }
105
+
102
106
  .bgl-toggle label {
103
107
  padding-inline-start: 0.5rem;
104
108
  transition: var(--bgl-transition);
@@ -108,7 +108,7 @@ defineExpose({ open, close })
108
108
  <embed :src="normalizeURL(item?.src)" type="application/pdf" :title="item?.name">
109
109
  </div>
110
110
 
111
- <div v-else class="lightbox-file">
111
+ <div v-else class="lightbox-file flex-center">
112
112
  <div class="file-info txt-white flex m_block align-items-start gap-025">
113
113
  <Icon class="m-0 m_none" icon="draft" :size="10" weight="12" />
114
114
  <Icon class="m-0 none m_block m_-mb-1" icon="draft" :size="4" weight="2" />
@@ -130,11 +130,14 @@ defineExpose({ open, close })
130
130
  </template>
131
131
 
132
132
  <template #prev-button="{ prev }">
133
- <Btn icon="arrow_back" color="black" @click="prev" />
133
+ <Btn icon="arrow_back" color="black" :disabled="currentIndex === 0"
134
+ :class="{ 'color-black-70': currentIndex === 0 }" @click="prev"/>
134
135
  </template>
135
136
 
136
137
  <template #next-button="{ next }">
137
- <Btn icon="arrow_forward" color="black" @click="next" />
138
+ <Btn icon="arrow_forward" color="black" :disabled="currentIndex === group.length - 1"
139
+ :class="{ 'color-black-70': currentIndex === group.length - 1 }"
140
+ @click="next" />
138
141
  </template>
139
142
  </Swiper>
140
143
 
@@ -151,6 +154,11 @@ defineExpose({ open, close })
151
154
  </template>
152
155
 
153
156
  <style>
157
+ .bgl-lightbox-swiper .swi-prev,
158
+ .bgl-lightbox-swiper .swi-next {
159
+ background: transparent;
160
+ padding: 0;
161
+ }
154
162
 
155
163
  .bgl-lightbox:has(.bgl_vid) {
156
164
  width: 90vw;