@bagelink/vue 1.17.2 → 1.18.3

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 (97) hide show
  1. package/dist/assetTransport.d.ts +9 -31
  2. package/dist/assetTransport.d.ts.map +1 -1
  3. package/dist/assets.d.ts +39 -20
  4. package/dist/assets.d.ts.map +1 -1
  5. package/dist/components/AddressSearch.vue.d.ts +40 -36
  6. package/dist/components/AddressSearch.vue.d.ts.map +1 -1
  7. package/dist/components/ContextMenu.vue.d.ts +518 -0
  8. package/dist/components/ContextMenu.vue.d.ts.map +1 -0
  9. package/dist/components/Dropdown.types.d.ts +25 -0
  10. package/dist/components/Dropdown.types.d.ts.map +1 -0
  11. package/dist/components/Dropdown.vue.d.ts +20 -15
  12. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  13. package/dist/components/FilterQuery.vue.d.ts +1 -1
  14. package/dist/components/FilterQuery.vue.d.ts.map +1 -1
  15. package/dist/components/Icon/Icon.vue.d.ts +0 -1
  16. package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
  17. package/dist/components/Icon/constants.d.ts +0 -1
  18. package/dist/components/Icon/constants.d.ts.map +1 -1
  19. package/dist/components/Icon/types.d.ts +2 -3
  20. package/dist/components/Icon/types.d.ts.map +1 -1
  21. package/dist/components/Swiper.vue.d.ts +1 -1
  22. package/dist/components/draggable/useDraggable.d.ts.map +1 -1
  23. package/dist/components/form/inputs/IconPickerInput.vue.d.ts +48 -37
  24. package/dist/components/form/inputs/IconPickerInput.vue.d.ts.map +1 -1
  25. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
  26. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
  27. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +3 -0
  28. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  29. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts +1 -1
  30. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  31. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  32. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
  33. package/dist/components/form/inputs/RichText/utils/selection.d.ts +2 -0
  34. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  35. package/dist/components/form/inputs/RichText/utils/table.d.ts +34 -8
  36. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  37. package/dist/components/form/inputs/SelectInput.vue.d.ts +74 -65
  38. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  39. package/dist/components/form/inputs/TelInput.vue.d.ts +2 -2
  40. package/dist/components/index.d.ts +3 -1
  41. package/dist/components/index.d.ts.map +1 -1
  42. package/dist/composables/index.d.ts +1 -1
  43. package/dist/composables/index.d.ts.map +1 -1
  44. package/dist/composables/useBoundedRectDrag.d.ts.map +1 -1
  45. package/dist/composables/usePointerSession.d.ts +14 -3
  46. package/dist/composables/usePointerSession.d.ts.map +1 -1
  47. package/dist/core.css +3 -3
  48. package/dist/directives/vResize.d.ts.map +1 -1
  49. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  50. package/dist/form-flow/form-flow.d.ts +4 -2
  51. package/dist/form-flow/form-flow.d.ts.map +1 -1
  52. package/dist/form-flow/schema-fields.d.ts +4 -1
  53. package/dist/form-flow/schema-fields.d.ts.map +1 -1
  54. package/dist/index.cjs +240 -153
  55. package/dist/index.css +1 -1
  56. package/dist/index.mjs +18461 -18283
  57. package/dist/style.css +3 -3
  58. package/dist/types/BagelForm.d.ts +2 -0
  59. package/dist/types/BagelForm.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/assetTransport.test.ts +33 -83
  62. package/src/assetTransport.ts +24 -90
  63. package/src/assets.test.ts +58 -99
  64. package/src/assets.ts +78 -161
  65. package/src/components/ContextMenu.vue +113 -0
  66. package/src/components/Dropdown.types.ts +27 -0
  67. package/src/components/Dropdown.vue +129 -64
  68. package/src/components/FilterQuery.vue +1 -1
  69. package/src/components/Icon/Icon.vue +8 -30
  70. package/src/components/Icon/constants.ts +0 -166
  71. package/src/components/Icon/types.ts +2 -3
  72. package/src/components/draggable/useDraggable.ts +67 -100
  73. package/src/components/form/inputs/IconPickerInput.vue +14 -9
  74. package/src/components/form/inputs/RichText/components/EditorToolbar.vue +43 -111
  75. package/src/components/form/inputs/RichText/composables/useCommands.ts +32 -54
  76. package/src/components/form/inputs/RichText/composables/useEditor.ts +45 -171
  77. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +90 -14
  78. package/src/components/form/inputs/RichText/index.vue +1195 -1210
  79. package/src/components/form/inputs/RichText/utils/commands.ts +689 -1028
  80. package/src/components/form/inputs/RichText/utils/selection.ts +66 -41
  81. package/src/components/form/inputs/RichText/utils/table.ts +798 -136
  82. package/src/components/form/inputs/SelectInput.vue +6 -4
  83. package/src/components/form/inputs/Upload/AssetUploadInput.vue +2 -2
  84. package/src/components/index.ts +3 -1
  85. package/src/composables/index.ts +1 -1
  86. package/src/composables/useBoundedRectDrag.ts +9 -12
  87. package/src/composables/usePointerSession.ts +57 -12
  88. package/src/directives/vResize.ts +6 -7
  89. package/src/form-flow/FormFlow.vue +4 -0
  90. package/src/form-flow/form-flow.ts +8 -3
  91. package/src/form-flow/schema-fields.ts +9 -4
  92. package/src/styles/core.css +1 -18
  93. package/src/types/BagelForm.ts +2 -0
  94. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +0 -10
  95. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +0 -1
  96. package/src/components/form/inputs/RichText/components/gridBox.vue +0 -58
  97. package/src/components/form/inputs/RichText/utils/formatting.ts +0 -331
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import type { ToolbarConfig, ToolbarConfigOption, ToolbarOption } from '../richTextTypes'
3
- import { Btn, Dropdown } from '../../../../../index'
3
+ import { Btn, Divider, Dropdown } from '../../../../../index'
4
4
  import { basicToolbarConfig, toolbarOptions } from '../config'
5
5
  import TableGridSelector from './TableGridSelector.vue'
6
6
 
@@ -29,16 +29,14 @@ const {
29
29
  }>()
30
30
  const emit = defineEmits(['action'])
31
31
 
32
- // Function to get the current alignment icon based on active styles
33
32
  function getCurrentAlignmentIcon(): string {
34
33
  if (selectedStyles.has('alignLeft')) { return 'format_align_left' }
35
34
  if (selectedStyles.has('alignCenter')) { return 'format_align_center' }
36
35
  if (selectedStyles.has('alignRight')) { return 'format_align_right' }
37
36
  if (selectedStyles.has('alignJustify')) { return 'format_align_justify' }
38
- return 'format_align_left' // default
37
+ return 'format_align_left'
39
38
  }
40
39
 
41
- // Function to check if any alignment is active
42
40
  function isAlignmentActive(): boolean {
43
41
  return selectedStyles.has('alignLeft')
44
42
  || selectedStyles.has('alignCenter')
@@ -46,44 +44,46 @@ function isAlignmentActive(): boolean {
46
44
  || selectedStyles.has('alignJustify')
47
45
  }
48
46
 
49
- // Helper function to check if an action should be hidden
50
47
  function shouldHideAction(actionName: string): boolean {
51
- // Check if it's in the hide array - this covers ALL items including:
52
- // bold, italic, underline, h1, h2, h3, h4, h5, h6, link, image, video, embed,
53
- // ul, ol, blockquote, code, clear, direction, table, fullScreen
54
48
  if (hide.includes(actionName)) { return true }
55
49
 
56
- // Alternative name mappings for some actions
57
50
  const alternativeNames: Record<string, string[]> = {
58
51
  image: ['insertImage', 'image'],
59
52
  video: ['insertVideo', 'video'],
60
53
  embed: ['insertEmbed', 'embed'],
61
54
  table: ['insertTable'],
62
- direction: ['textDirection'],
55
+ direction: ['textDirection', 'ltrDirection', 'rtlDirection'],
63
56
  fullScreen: ['fullScreen'],
64
57
  ul: ['unorderedList'],
65
58
  ol: ['orderedList'],
66
59
  splitView: ['splitView'],
67
60
  p: ['p'],
68
- align: ['alignMenu'],
69
- alignment: ['alignMenu']
61
+ align: ['alignMenu', 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],
62
+ alignment: ['alignMenu', 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify']
70
63
  }
71
64
 
72
- // Check alternative names
73
65
  for (const [hideKey, actionNames] of Object.entries(alternativeNames)) {
74
66
  if (hide.includes(hideKey) && actionNames.includes(actionName)) {
75
67
  return true
76
68
  }
77
69
  }
78
70
 
79
- // Map action names to check against specific hide props (for backward compatibility)
80
71
  const actionMap: Record<string, boolean> = {
81
72
  insertImage: hideImages,
73
+ image: hideImages,
82
74
  insertVideo: hideVideos,
75
+ video: hideVideos,
83
76
  insertEmbed: hideEmbeds,
77
+ embed: hideEmbeds,
84
78
  insertTable: hideTables,
85
79
  alignMenu: hideAlignment,
80
+ alignLeft: hideAlignment,
81
+ alignCenter: hideAlignment,
82
+ alignRight: hideAlignment,
83
+ alignJustify: hideAlignment,
86
84
  textDirection: hideDirections,
85
+ ltrDirection: hideDirections,
86
+ rtlDirection: hideDirections,
87
87
  h5: hideH5H6,
88
88
  h6: hideH5H6
89
89
  }
@@ -93,11 +93,9 @@ function shouldHideAction(actionName: string): boolean {
93
93
 
94
94
  const configToOption = (action: ToolbarConfigOption) => toolbarOptions.find(option => option.name === action) as ToolbarOption
95
95
 
96
- // Helper function to check if a separator should be shown
97
96
  function shouldShowSeparator(currentIndex: number): boolean {
98
97
  const allActions = config.map(configToOption).filter(Boolean)
99
98
 
100
- // Simple approach: find the last visible item before this separator
101
99
  let lastVisibleBeforeIndex = -1
102
100
  for (let i = currentIndex - 1; i >= 0; i--) {
103
101
  const action = allActions[i]
@@ -107,7 +105,6 @@ function shouldShowSeparator(currentIndex: number): boolean {
107
105
  }
108
106
  }
109
107
 
110
- // Find the first visible item after this separator
111
108
  let firstVisibleAfterIndex = -1
112
109
  for (let i = currentIndex + 1; i < allActions.length; i++) {
113
110
  const action = allActions[i]
@@ -117,16 +114,13 @@ function shouldShowSeparator(currentIndex: number): boolean {
117
114
  }
118
115
  }
119
116
 
120
- // Don't show if we don't have visible items on both sides
121
117
  if (lastVisibleBeforeIndex === -1 || firstVisibleAfterIndex === -1) {
122
118
  return false
123
119
  }
124
120
 
125
- // Check if there's already a visible separator between these items
126
121
  for (let i = lastVisibleBeforeIndex + 1; i < currentIndex; i++) {
127
122
  const action = allActions[i]
128
123
  if (action && action.name === 'separator') {
129
- // There's already a separator closer to the visible items
130
124
  return false
131
125
  }
132
126
  }
@@ -135,49 +129,14 @@ function shouldShowSeparator(currentIndex: number): boolean {
135
129
  }
136
130
 
137
131
  function runAction(name: ToolbarConfigOption, value?: string) {
138
- console.log('EditorToolbar: runAction called', { name, value })
139
132
  emit('action', name, value)
140
133
  }
141
134
 
142
135
  function handleQuickTableInsert(rows: number, cols: number) {
143
- // Create a simple table quickly
144
- const table = document.createElement('table')
145
- table.style.width = '100%'
146
- table.style.borderCollapse = 'collapse'
147
- table.style.marginBottom = '1rem'
148
-
149
- // Add header
150
- const thead = document.createElement('thead')
151
- const headerRow = thead.insertRow()
152
- for (let j = 0; j < cols; j++) {
153
- const th = document.createElement('th')
154
- th.innerHTML = `Header ${j + 1}`
155
- th.style.padding = '8px'
156
- th.style.border = '1px solid #ddd'
157
- th.style.backgroundColor = '#f4f4f4'
158
- headerRow.appendChild(th)
159
- }
160
- table.appendChild(thead)
161
-
162
- // Add body
163
- const tbody = document.createElement('tbody')
164
- for (let i = 0; i < rows; i++) {
165
- const row = tbody.insertRow()
166
- for (let j = 0; j < cols; j++) {
167
- const cell = row.insertCell()
168
- cell.innerHTML = '&nbsp;'
169
- cell.style.padding = '8px'
170
- cell.style.border = '1px solid #ddd'
171
- }
172
- }
173
- table.appendChild(tbody)
174
-
175
- emit('action', 'insertTable', table.outerHTML)
136
+ emit('action', 'insertTable', `${rows}x${cols}`)
176
137
  }
177
138
 
178
139
  function handleOpenAdvanced() {
179
- // The advanced table editor is now handled by the main RichText component
180
- // through the insertTable command which opens the main modal
181
140
  emit('action', 'insertTable')
182
141
  }
183
142
  </script>
@@ -187,35 +146,18 @@ function handleOpenAdvanced() {
187
146
  <template v-for="(action, index) in config.map(configToOption).filter(Boolean)" :key="index">
188
147
  <!-- Tables -->
189
148
  <TableGridSelector v-if="action.name === 'insertTable' && !shouldHideAction('insertTable')" @insert="handleQuickTableInsert" @open-advanced="handleOpenAdvanced" />
190
- <!-- Images -->
191
- <Btn
192
- v-else-if="action.name === 'insertImage' && !shouldHideAction('insertImage')" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
193
- :class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)"
194
- />
195
-
196
- <!-- Videos -->
197
- <Btn
198
- v-else-if="action.name === 'insertVideo' && !shouldHideAction('insertVideo')" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
199
- :class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)"
200
- />
201
-
202
- <!-- Embeds -->
203
- <Btn
204
- v-else-if="action.name === 'insertEmbed' && !shouldHideAction('insertEmbed')" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
205
- :class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)"
206
- />
207
149
 
208
150
  <!-- Alignment Menu -->
209
151
  <Dropdown
210
152
  v-else-if="action.name === 'alignMenu' && !shouldHideAction('alignMenu')" placement="bottom-start" thin flat :icon="getCurrentAlignmentIcon()"
211
153
  :class="{ 'alignment-active': isAlignmentActive() }"
212
154
  >
213
- <template #default="{ hide }">
155
+ <template #default="{ hide: closeMenu }">
214
156
  <div class="flex flex-column p-025">
215
- <Btn thin flat icon="format_align_left" :class="{ active: selectedStyles.has('alignLeft') }" @click="runAction('alignLeft'); hide()" />
216
- <Btn thin flat icon="format_align_center" :class="{ active: selectedStyles.has('alignCenter') }" @click="runAction('alignCenter'); hide()" />
217
- <Btn thin flat icon="format_align_right" :class="{ active: selectedStyles.has('alignRight') }" @click="runAction('alignRight'); hide()" />
218
- <Btn thin flat icon="format_align_justify" :class="{ active: selectedStyles.has('alignJustify') }" @click="runAction('alignJustify'); hide()" />
157
+ <Btn thin flat icon="format_align_left" :class="{ active: selectedStyles.has('alignLeft') }" @click="runAction('alignLeft'); closeMenu()" />
158
+ <Btn thin flat icon="format_align_center" :class="{ active: selectedStyles.has('alignCenter') }" @click="runAction('alignCenter'); closeMenu()" />
159
+ <Btn thin flat icon="format_align_right" :class="{ active: selectedStyles.has('alignRight') }" @click="runAction('alignRight'); closeMenu()" />
160
+ <Btn thin flat icon="format_align_justify" :class="{ active: selectedStyles.has('alignJustify') }" @click="runAction('alignJustify'); closeMenu()" />
219
161
  </div>
220
162
  </template>
221
163
  </Dropdown>
@@ -224,66 +166,56 @@ function handleOpenAdvanced() {
224
166
  <Btn
225
167
  v-else-if="action.name === 'textDirection' && !shouldHideAction('textDirection')" v-tooltip="action.label"
226
168
  :icon="selectedStyles.has('textDirection') ? 'format_textdirection_l_to_r' : 'format_textdirection_r_to_l'" thin flat :aria-label="action.name"
227
- :class="[action.class, { active: selectedStyles.has('textDirection') }]" class="" tabindex="-1" @click="runAction(action.name)"
228
- />
229
-
230
- <!-- H5 and H6 -->
231
- <Btn
232
- v-else-if="(action.name === 'h5' || action.name === 'h6') && !shouldHideAction(action.name)" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name"
233
- :class="[action.class, { active: selectedStyles.has(action.name) }]" class="" tabindex="-1" @click="runAction(action.name)"
169
+ :class="[action.class, { active: selectedStyles.has('textDirection') }]" tabindex="-1" @click="runAction(action.name)"
234
170
  />
235
171
 
236
- <!-- All other buttons - check if they should be hidden by the hide array -->
172
+ <!-- All other buttons -->
237
173
  <Btn
238
174
  v-else-if="action.name !== 'separator'
239
175
  && action.name !== 'insertTable'
240
- && action.name !== 'insertImage'
241
- && action.name !== 'insertVideo'
242
- && action.name !== 'insertEmbed'
243
176
  && action.name !== 'alignMenu'
244
177
  && action.name !== 'textDirection'
245
- && action.name !== 'h5'
246
- && action.name !== 'h6'
247
- && !shouldHideAction(action.name)" v-tooltip="action.label" :icon="action.icon" thin flat :aria-label="action.name" :class="[action.class, { active: selectedStyles.has(action.name) }]" class=""
248
- tabindex="-1" @click="runAction(action.name)"
178
+ && !shouldHideAction(action.name)"
179
+ v-tooltip="action.label"
180
+ :icon="action.icon"
181
+ thin
182
+ flat
183
+ :aria-label="action.name"
184
+ :class="[action.class, { active: selectedStyles.has(action.name) }]"
185
+ tabindex="-1"
186
+ @click="runAction(action.name)"
249
187
  />
250
188
 
251
189
  <!-- Separator -->
252
- <span v-else-if="action.name === 'separator' && shouldShowSeparator(index)" :key="`separator-${index}`" class="opacity-2 mb-025">|</span>
190
+ <Divider v-else-if="action.name === 'separator' && shouldShowSeparator(index)" :key="`separator-${index}`" vertical size="1.25rem" class="mx-0125 align-self-center" />
253
191
  </template>
254
192
  <slot name="end" />
255
193
  </div>
256
194
  </template>
257
195
 
258
196
  <style scoped>
259
-
260
197
  .toolbar :deep(.active) {
261
- background: var(--bgl-primary);
262
- color: white !important;
198
+ background: var(--bgl-primary);
199
+ color: white !important;
263
200
  }
264
201
  .toolbar :deep(.active):hover {
265
- background: var(--bgl-primary) !important;
202
+ background: var(--bgl-primary) !important;
266
203
  color: white;
267
204
  }
268
- /* Headings menu styling */
269
205
  .toolbar :deep(.dropdown-content) {
270
- min-width: 40px;
206
+ min-width: 40px;
271
207
  }
272
-
273
208
  .toolbar :deep(.dropdown-content .btn) {
274
- min-width: 32px;
275
- height: 32px;
276
- padding: 0;
209
+ min-width: 32px;
210
+ height: 32px;
211
+ padding: 0;
277
212
  }
278
- /* Alignment active state */
279
213
  .alignment-active :deep(button) {
280
- background: var(--bgl-primary) !important;
281
- color: white !important;
214
+ background: var(--bgl-primary) !important;
215
+ color: white !important;
282
216
  }
283
-
284
217
  .alignment-active :deep(button:hover) {
285
- background: var(--bgl-primary) !important;
286
- color: white !important;
218
+ background: var(--bgl-primary) !important;
219
+ color: white !important;
287
220
  }
288
-
289
221
  </style>
@@ -1,60 +1,24 @@
1
1
  import type { EditorState } from '../richTextTypes'
2
2
  import { createCommandExecutor, createCommandRegistry } from '../utils/commands'
3
- import { isStyleActive } from '../utils/selection'
3
+ import { computeActiveStyles } from '../utils/selection'
4
4
 
5
5
  export function useCommands(state: EditorState, debug?: { logCommand: (command: string, value?: string) => void }) {
6
6
  const commands = createCommandRegistry(state)
7
7
  const executor = createCommandExecutor(state, commands)
8
8
 
9
- // Function to immediately update styles
10
9
  const updateStylesImmediately = () => {
11
10
  if (!state.doc) { return }
12
-
13
- const styles = new Set<string>()
14
- const styleTypes = [
15
- 'bold',
16
- 'italic',
17
- 'underline',
18
- 'link',
19
- 'h1',
20
- 'h2',
21
- 'h3',
22
- 'h4',
23
- 'h5',
24
- 'h6',
25
- 'blockquote',
26
- 'p',
27
- 'orderedList',
28
- 'unorderedList',
29
- 'alignLeft',
30
- 'alignCenter',
31
- 'alignRight',
32
- 'alignJustify',
33
- 'textDirection',
34
- 'ltrDirection',
35
- 'rtlDirection'
36
- ]
37
-
38
- styleTypes.forEach((style) => {
39
- if (isStyleActive(style, state.doc!)) {
40
- styles.add(style)
41
- }
42
- })
43
-
44
- state.selectedStyles = styles
11
+ state.selectedStyles = computeActiveStyles(state.doc)
45
12
  }
46
13
 
47
14
  return {
48
15
  execute: (command: string, value?: string) => {
49
16
  if (!state.doc) {
50
- console.log('[useCommands] No document available, skipping command')
51
17
  return
52
18
  }
53
19
 
54
- // Log command if debug is enabled
55
20
  debug?.logCommand(command, value)
56
21
 
57
- // Handle view state commands directly (these don't need DOM manipulation)
58
22
  if (['splitView', 'codeView', 'fullScreen'].includes(command)) {
59
23
  switch (command) {
60
24
  case 'splitView':
@@ -69,30 +33,44 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
69
33
  }
70
34
  }
71
35
 
72
- // Focus the editor before executing command
73
36
  try {
74
- state.doc.body.focus()
75
- } catch (e) {
76
- console.error('[useCommands] Error focusing editor:', e)
37
+ state.doc.body.focus({ preventScroll: true })
38
+ } catch {
39
+ // Ignore focus errors
77
40
  }
78
41
 
79
- // Update current selection state before command execution
42
+ // Restore selection if focusing the body cleared or collapsed the user's active selection
80
43
  const selection = state.doc.getSelection()
81
- if (selection?.rangeCount) {
82
- state.selection = selection
83
- state.range = selection.getRangeAt(0).cloneRange()
84
- state.rangeCount = selection.rangeCount
44
+ if (selection) {
45
+ if (selection.rangeCount > 0) {
46
+ const currentRange = selection.getRangeAt(0)
47
+ if (currentRange.collapsed && state.range && !state.range.collapsed) {
48
+ try {
49
+ selection.removeAllRanges()
50
+ selection.addRange(state.range)
51
+ } catch {
52
+ // Ignore invalid range
53
+ }
54
+ } else {
55
+ state.selection = selection
56
+ state.range = currentRange.cloneRange()
57
+ state.rangeCount = selection.rangeCount
58
+ }
59
+ } else if (state.range) {
60
+ try {
61
+ selection.removeAllRanges()
62
+ selection.addRange(state.range)
63
+ state.selection = selection
64
+ state.rangeCount = selection.rangeCount
65
+ } catch {
66
+ // Ignore invalid range
67
+ }
68
+ }
85
69
  }
86
70
 
87
- // Execute the command
88
71
  try {
89
72
  executor.execute(command, value)
90
-
91
- // Update styles immediately after command execution for all commands except view state
92
- const viewCommands = ['splitView', 'codeView', 'fullScreen']
93
- if (!viewCommands.includes(command)) {
94
- updateStylesImmediately()
95
- }
73
+ updateStylesImmediately()
96
74
  } catch (e) {
97
75
  console.error('[useCommands] Error during command execution:', e)
98
76
  }