@bagelink/vue 1.15.295 → 1.15.299

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.295",
4
+ "version": "1.15.299",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -310,13 +310,10 @@ opacity: 0;
310
310
  z-index: 1;
311
311
  }
312
312
 
313
- /* Pin the ring itself to the exact center of the full-button loading overlay. */
314
- .bgl_btn .btn-loading :deep(.lds-ring) {
315
- position: absolute;
316
- top: 50%;
317
- left: 50%;
318
- transform: translate(-50%, -50%);
319
- }
313
+ /* The overlay is `absolute-fill flex-center`, so it already centers the <Loading>
314
+ container. Don't re-center the inner ring with top/left:50% — that would override
315
+ the ring's own `translate(-thickness, -thickness)` compensation (it offsets the
316
+ ring-segment margins) and push the spinner slightly off-center. */
320
317
 
321
318
  .bgl_btn .bgl_btn-icon {
322
319
  font-size: calc(var(--bgl-input-font-size) * 1.3);
@@ -251,6 +251,7 @@ function handleOpenAdvanced() {
251
251
  <!-- Separator -->
252
252
  <span v-else-if="action.name === 'separator' && shouldShowSeparator(index)" :key="`separator-${index}`" class="opacity-2 mb-025">|</span>
253
253
  </template>
254
+ <slot name="end" />
254
255
  </div>
255
256
  </template>
256
257
 
@@ -1,6 +1,6 @@
1
1
  <!-- eslint-disable vue/multi-word-component-names -->
2
2
  <script setup lang="ts">
3
- import type { ToolbarConfig } from './richTextTypes'
3
+ import type { CustomToolbarButton, RichTextToolbarApi, ToolbarConfig } from './richTextTypes'
4
4
  import { CodeEditor, copyText, Btn, Dialog, BglVideo, Icon, Card, ColorInput, pathKeyToURL } from '@bagelink/vue'
5
5
  import { watch, onUnmounted, onBeforeUnmount, onMounted, ref, computed, useAttrs } from 'vue'
6
6
  import CheckInput from '../CheckInput.vue'
@@ -23,6 +23,7 @@ defineOptions({
23
23
  const props = defineProps<{
24
24
  modelValue: string
25
25
  toolbarConfig?: ToolbarConfig
26
+ customToolbarButtons?: CustomToolbarButton[]
26
27
  debug?: boolean
27
28
  label?: string
28
29
  height?: number | string
@@ -64,6 +65,52 @@ const isInitializing = ref(false)
64
65
  const hasInitialized = ref(false)
65
66
  const commands = useCommands(editor.state, editor.state.debug)
66
67
 
68
+ function setBlock(tagName: string, className?: string, removeClasses: string[] = []) {
69
+ const { doc } = editor.state
70
+ if (!doc) return
71
+
72
+ const selection = doc.getSelection()
73
+ const savedRange = selection?.rangeCount
74
+ ? selection.getRangeAt(0).cloneRange()
75
+ : editor.state.range?.cloneRange()
76
+ if (!savedRange) return
77
+
78
+ const node = savedRange.commonAncestorContainer
79
+ const element = node.nodeType === Node.ELEMENT_NODE ? node as Element : node.parentElement
80
+ const currentBlock = element?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,li,div')
81
+ if (!currentBlock || currentBlock === doc.body) return
82
+
83
+ editor.state.undoStack.push(editor.state.content)
84
+ editor.state.redoStack = []
85
+ if (editor.state.undoStack.length > 50) editor.state.undoStack.shift()
86
+
87
+ const target = currentBlock.tagName.toLowerCase() === tagName.toLowerCase()
88
+ ? currentBlock
89
+ : doc.createElement(tagName)
90
+
91
+ if (target !== currentBlock) {
92
+ while (currentBlock.firstChild) target.appendChild(currentBlock.firstChild)
93
+ currentBlock.parentNode?.replaceChild(target, currentBlock)
94
+ }
95
+
96
+ removeClasses.forEach(name => target.classList.remove(name))
97
+ if (className) target.classList.add(className)
98
+ if (!target.classList.length) target.removeAttribute('class')
99
+
100
+ const nextRange = doc.createRange()
101
+ nextRange.selectNodeContents(target)
102
+ nextRange.collapse(false)
103
+ selection?.removeAllRanges()
104
+ selection?.addRange(nextRange)
105
+ editor.state.range = nextRange.cloneRange()
106
+ updateContentWithHistory(doc, true)
107
+ }
108
+
109
+ const toolbarApi: RichTextToolbarApi = {
110
+ execute: commands.execute,
111
+ setBlock
112
+ }
113
+
67
114
  // Reactive theme colors with forced reactivity
68
115
  const forceUpdate = ref(0)
69
116
  const currentInputColor = computed(() => {
@@ -3372,7 +3419,26 @@ const imgTransform = computed({
3372
3419
  :hide-embeds="hideEmbed" :hide-tables="hideTables" :hide-alignment="hideAlignment"
3373
3420
  :hide-directions="hideDirections" :hide-h5-h6="hideH5H6" :hide="effectiveHideArray"
3374
3421
  @action="commands.execute"
3375
- />
3422
+ >
3423
+ <template #end>
3424
+ <slot name="toolbar-end" :api="toolbarApi">
3425
+ <Btn
3426
+ v-for="button in customToolbarButtons"
3427
+ :key="button.name"
3428
+ v-tooltip="button.label"
3429
+ :icon="button.icon"
3430
+ :value="button.value"
3431
+ :aria-label="button.label"
3432
+ :class="button.class"
3433
+ thin
3434
+ flat
3435
+ tabindex="-1"
3436
+ @mousedown.prevent
3437
+ @click="button.action(toolbarApi)"
3438
+ />
3439
+ </slot>
3440
+ </template>
3441
+ </EditorToolbar>
3376
3442
  <div class="editor-container" :class="{ 'split-view': editor.state.isSplitView }">
3377
3443
  <div
3378
3444
  class="content-area radius-05"
@@ -63,6 +63,20 @@ export type ToolbarConfigOption = string
63
63
 
64
64
  export type ToolbarConfig = ToolbarConfigOption[]
65
65
 
66
+ export interface RichTextToolbarApi {
67
+ execute: (command: string, value?: string) => void
68
+ setBlock: (tagName: string, className?: string, removeClasses?: string[]) => void
69
+ }
70
+
71
+ export interface CustomToolbarButton {
72
+ name: string
73
+ label: string
74
+ icon?: string
75
+ value?: string
76
+ class?: string
77
+ action: (api: RichTextToolbarApi) => void
78
+ }
79
+
66
80
  export interface ToolbarOption {
67
81
  name: string
68
82
  label?: string
@@ -14,6 +14,7 @@ export { default as PasswordInput } from './PasswordInput.vue'
14
14
  export { default as RadioGroup } from './RadioGroup.vue'
15
15
  export { default as RangeInput } from './RangeInput.vue'
16
16
  export { default as RichText } from './RichText/index.vue'
17
+ export type { CustomToolbarButton, RichTextToolbarApi } from './RichText/richTextTypes'
17
18
  export { default as SelectBtn } from './SelectBtn.vue'
18
19
  export { default as SelectInput } from './SelectInput.vue'
19
20
  export { default as SignaturePad } from './SignaturePad.vue'
@@ -12,6 +12,8 @@ interface Props {
12
12
  subtitle?: string
13
13
  showMenuButton?: boolean
14
14
  backTo?: string | Record<string, any>
15
+ /** Optional custom back action. When set, the back button calls this instead of navigating to backTo. */
16
+ onBack?: () => void
15
17
  /**
16
18
  * Header framing:
17
19
  * 'border' → bottom hairline under the header (default — legacy behaviour).
@@ -146,7 +148,14 @@ const hasCenter = computed(() => !!slots['header-center'])
146
148
  />
147
149
 
148
150
  <!-- Back Button -->
149
- <Btn v-if="backTo" icon="arrow_back" thin :to="backTo" class="back-btn bg-bg m_-ms-05 m_-me-05 color-black" />
151
+ <Btn
152
+ v-if="backTo || onBack"
153
+ icon="arrow_back"
154
+ thin
155
+ :to="onBack ? undefined : backTo"
156
+ class="back-btn bg-bg m_-ms-05 m_-me-05 color-black"
157
+ @click="onBack?.()"
158
+ />
150
159
 
151
160
  <!-- Page Title -->
152
161
  <PageTitle v-if="title || subtitle" :subtitle="subtitle">
@@ -273,6 +273,7 @@ max-height: calc(100vh - 140px);
273
273
  object-fit: contain;
274
274
  border-radius: 3px;
275
275
  margin: auto;
276
+ width: 100%;
276
277
  }
277
278
 
278
279
  .lightbox-pdf {
@@ -289,6 +290,9 @@ border-radius: 3px;
289
290
 
290
291
  .lightbox-video iframe {
291
292
  width: 90vw;
293
+ height: auto;
294
+ aspect-ratio: 16 / 9;
295
+ max-height: calc(100vh - 140px);
292
296
  }
293
297
 
294
298
  .lightbox-video video {
@@ -10,6 +10,7 @@
10
10
 
11
11
  import type { Component } from 'vue'
12
12
  import type { IconType } from '../types'
13
+ import type { CustomToolbarButton } from '../components/form/inputs/RichText/richTextTypes'
13
14
  import { getOptionValue } from '../utils/options'
14
15
 
15
16
  // ============================================================================
@@ -297,8 +298,8 @@ export const $ = {
297
298
  },
298
299
 
299
300
  richtext(
300
- labelOrConfig?: string | (BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string }),
301
- config?: BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string }
301
+ labelOrConfig?: string | (BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string, customToolbarButtons?: CustomToolbarButton[] }),
302
+ config?: BaseFieldConfig & { autoheight?: boolean, basic?: boolean, simple?: boolean, fontSize?: number | string, customToolbarButtons?: CustomToolbarButton[] }
302
303
  ): FieldBuilder<string> {
303
304
  return new Field('richtext', parseArgs(labelOrConfig, config))
304
305
  },