@bagelink/vue 1.4.64 → 1.4.71

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 (24) hide show
  1. package/dist/components/NavBar.vue.d.ts.map +1 -1
  2. package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
  3. package/dist/components/dataTable/useTableSelection.d.ts +1 -1
  4. package/dist/components/dataTable/useTableSelection.d.ts.map +1 -1
  5. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
  6. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  7. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  8. package/dist/components/form/inputs/RichText/index.vue.d.ts +128 -1
  9. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  10. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
  11. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  12. package/dist/index.cjs +20 -20
  13. package/dist/index.mjs +20 -20
  14. package/dist/style.css +1 -1
  15. package/package.json +1 -1
  16. package/src/components/NavBar.vue +17 -5
  17. package/src/components/dataTable/DataTable.vue +20 -44
  18. package/src/components/dataTable/useTableSelection.ts +40 -1
  19. package/src/components/form/inputs/RichText/composables/useCommands.ts +9 -29
  20. package/src/components/form/inputs/RichText/composables/useEditor.ts +45 -46
  21. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +52 -3
  22. package/src/components/form/inputs/RichText/index.vue +148 -43
  23. package/src/components/form/inputs/RichText/utils/commands.ts +347 -282
  24. package/src/components/form/inputs/RichText/utils/selection.ts +55 -37
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.4.64",
4
+ "version": "1.4.71",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -41,7 +41,11 @@ onMounted(calcIsOpen)
41
41
  <div :class="{ open: isOpen, closed: !isOpen }">
42
42
  <slot name="top" :isOpen="isOpen" />
43
43
  <div
44
- class="nav-expend" role="button" aria-label="Toggle Navigation" tabindex="0" @click="toggleMenu"
44
+ class="nav-expend"
45
+ role="button"
46
+ aria-label="Toggle Navigation"
47
+ tabindex="0"
48
+ @click="toggleMenu"
45
49
  @keypress.enter="toggleMenu"
46
50
  >
47
51
  <Icon icon="chevron_right" class="top-arrow" />
@@ -51,8 +55,12 @@ onMounted(calcIsOpen)
51
55
  <div class="nav-scroll">
52
56
  <div class="nav-links-wrapper">
53
57
  <component
54
- :is="link.to ? 'router-link' : 'div'" v-for="link in links" :key="link.label"
55
- class="nav-button" :to="link.to" @click="link.onClick?.()"
58
+ :is="link.to ? 'router-link' : 'div'"
59
+ v-for="link in links"
60
+ :key="link.label"
61
+ class="nav-button"
62
+ :to="link.to"
63
+ @click="link.onClick?.()"
56
64
  >
57
65
  <Icon :icon="link.icon" />
58
66
  <div class="tooltip">
@@ -64,8 +72,12 @@ onMounted(calcIsOpen)
64
72
 
65
73
  <div class="bot-buttons-wrapper">
66
74
  <component
67
- :is="link.to ? 'router-link' : 'div'" v-for="link in footerLinks" :key="link.label"
68
- class="nav-button" :to="link.to" @click="link.onClick?.()"
75
+ :is="link.to ? 'router-link' : 'div'"
76
+ v-for="link in footerLinks"
77
+ :key="link.label"
78
+ class="nav-button"
79
+ :to="link.to"
80
+ @click="link.onClick?.()"
69
81
  >
70
82
  <Icon :icon="link.icon" />
71
83
  <div class="tooltip">
@@ -121,76 +121,46 @@ const showLoading = computed(() => loading.value)
121
121
  </script>
122
122
 
123
123
  <template>
124
- <div
125
- class="table-list-wrap h-100"
126
- v-bind="containerProps"
127
- :class="{ overflowHiddenLoading: showLoading }"
128
- >
124
+ <div class="table-list-wrap h-100" v-bind="containerProps" :class="{ overflowHiddenLoading: showLoading }">
129
125
  <Loading v-if="showLoading" class="h100p" />
130
126
  <div v-bind="wrapperProps" :class="{ 'pointer-events-none': showLoading }">
131
- <table class="infinite-wrapper">
127
+ <table class="infinite-wrapper" :class="{ selecting: computedSelectedItems.length }">
132
128
  <thead class="row first-row">
133
129
  <th v-if="isSelectable">
134
- <input
135
- ref="allSelectorEl"
136
- type="checkbox"
137
- @click.stop
138
- @change="(e) => toggleSelectAll(e)"
139
- >
130
+ <input ref="allSelectorEl" type="checkbox" @click.stop @change="(e) => toggleSelectAll(e)">
140
131
  <!-- @change="toggleSelectAll" -->
141
132
  </th>
142
133
  <th
143
- v-for="field in computedSchema"
144
- :key="field.id"
145
- class="col"
134
+ v-for="field in computedSchema" :key="field.id" class="col"
146
135
  @click="toggleSort(field?.id || '')"
147
136
  >
148
137
  <div class="flex">
149
138
  {{ field.label || keyToLabel(field?.id) }}
150
- <div
151
- class="list-arrows"
152
- :class="{ sorted: sortField === field?.id }"
153
- >
154
- <Icon
155
- :class="{ desc: sortDirection === 'DESC' }"
156
- icon="keyboard_arrow_up"
157
- />
139
+ <div class="list-arrows" :class="{ sorted: sortField === field?.id }">
140
+ <Icon :class="{ desc: sortDirection === 'DESC' }" icon="keyboard_arrow_up" />
158
141
  </div>
159
142
  </div>
160
143
  </th>
161
144
  </thead>
162
145
  <tbody>
163
146
  <tr
164
- v-for="{ data: row } in list"
165
- :key="row?.id || `row-${Math.random()}`"
147
+ v-for="{ data: row } in list" :key="row?.id || `row-${Math.random()}`"
166
148
  class="row row-item position-relative"
167
149
  :class="{ selected: row?.id && computedSelectedItems.includes(row.id) }"
168
- @click="toggleSelectItem(row)"
150
+ @click="(event) => toggleSelectItem(row, event)"
169
151
  >
170
152
  <td v-if="isSelectable">
171
153
  <div @click.stop>
172
- <input
173
- v-model="selectedItems"
174
- type="checkbox"
175
- :value="row?.id || ''"
176
- >
154
+ <input v-model="selectedItems" type="checkbox" :value="row?.id || ''">
177
155
  </div>
178
156
  </td>
179
157
  <td
180
- v-for="field in computedSchema"
181
- :key="`${field.id}-${row?.id || Math.random()}`"
158
+ v-for="field in computedSchema" :key="`${field.id}-${row?.id || Math.random()}`"
182
159
  class="col"
183
160
  >
184
- <slot
185
- v-if="field.id && slots?.[field.id]"
186
- :name="field.id"
187
- :row="row"
188
- :field="field"
189
- />
161
+ <slot v-if="field.id && slots?.[field.id]" :name="field.id" :row="row" :field="field" />
190
162
  <div v-else>
191
- <component
192
- :is="renderFieldForRow(field, row)"
193
- />
163
+ <component :is="renderFieldForRow(field, row)" />
194
164
  </div>
195
165
  </td>
196
166
  </tr>
@@ -233,13 +203,19 @@ tbody tr.selected:hover {
233
203
  border-radius: 5px;
234
204
  object-fit: cover;
235
205
  }
206
+
236
207
  .col:has(img) {
237
208
  padding-inline-end: 0.5rem;
238
209
  }
239
210
 
240
- .overflowHiddenLoading{
211
+ .selecting .col {
212
+ user-select: none;
213
+ }
214
+
215
+ .overflowHiddenLoading {
241
216
  overflow: hidden !important;
242
217
  }
218
+
243
219
  .list-arrows.sorted .desc {
244
220
  transform: rotate(180deg);
245
221
  display: inline-block;
@@ -313,7 +289,7 @@ th {
313
289
  padding: 0rem 0.25rem;
314
290
  }
315
291
 
316
- .col > div {
292
+ .col>div {
317
293
  display: flex;
318
294
  gap: 0.5rem;
319
295
  }
@@ -9,6 +9,7 @@ export interface UseTableSelectionOptions<T> extends TableSelectionOptions<T> {
9
9
 
10
10
  export function useTableSelection<T extends { [key: string]: any }>(options: UseTableSelectionOptions<T>) {
11
11
  const allSelectorEl = ref<HTMLInputElement>()
12
+ const lastSelectedIndex = ref<number | null>(null)
12
13
  const computedSelectedItems = computed(() => options.selectedItems.value)
13
14
  const isSelectable = computed(() => options.selectable === true && Array.isArray(options.selectedItems.value))
14
15
  const allItems = $computed(() => options.data.value)
@@ -39,7 +40,37 @@ export function useTableSelection<T extends { [key: string]: any }>(options: Use
39
40
  = !allSelected && options.selectedItems.value.length > 0
40
41
  }
41
42
 
42
- function toggleSelectItem(item: T) {
43
+ function toggleSelectItem(item: T, event?: MouseEvent) {
44
+ const currentIndex = allItems.findIndex(i => i.id === item.id)
45
+
46
+ // Handle Shift+click for range selection
47
+ if (event?.shiftKey && lastSelectedIndex.value !== null && currentIndex !== -1) {
48
+ const startIndex = Math.min(lastSelectedIndex.value, currentIndex)
49
+ const endIndex = Math.max(lastSelectedIndex.value, currentIndex)
50
+
51
+ // Get all items in the range
52
+ const rangeItems = allItems.slice(startIndex, endIndex + 1)
53
+ const rangeItemIds = rangeItems.map(i => i.id)
54
+
55
+ // Check if all items in range are currently selected
56
+ const allRangeSelected = rangeItemIds.every(id => options.selectedItems.value.includes(id)
57
+ )
58
+
59
+ if (allRangeSelected) {
60
+ // Deselect all items in range
61
+ options.selectedItems.value = options.selectedItems.value.filter(id => !rangeItemIds.includes(id)
62
+ )
63
+ } else {
64
+ // Select all items in range (add missing ones)
65
+ const newSelections = rangeItemIds.filter(id => !options.selectedItems.value.includes(id)
66
+ )
67
+ options.selectedItems.value.push(...newSelections)
68
+ }
69
+
70
+ return
71
+ }
72
+
73
+ // Regular single item selection logic
43
74
  if (computedSelectedItems.value.length === 0) {
44
75
  // Clean the item if a cleanData function is provided, otherwise use the default cleaning
45
76
  const cleanItem = options.cleanData ? options.cleanData(item) : { ...item }
@@ -54,14 +85,19 @@ export function useTableSelection<T extends { [key: string]: any }>(options: Use
54
85
  }
55
86
 
56
87
  options.onSelect(cleanItem)
88
+ lastSelectedIndex.value = currentIndex
57
89
  return
58
90
  }
91
+
59
92
  const index = computedSelectedItems.value.indexOf(item.id)
60
93
  if (index > -1) {
61
94
  options.selectedItems.value.splice(index, 1)
62
95
  } else {
63
96
  options.selectedItems.value.push(item.id)
64
97
  }
98
+
99
+ // Update last selected index for future range selections
100
+ lastSelectedIndex.value = currentIndex
65
101
  }
66
102
 
67
103
  // Update toggleSelectAll to pass allItems to updateAllSelectorState
@@ -73,6 +109,9 @@ export function useTableSelection<T extends { [key: string]: any }>(options: Use
73
109
  } else {
74
110
  options.selectedItems.value = []
75
111
  }
112
+
113
+ // Reset last selected index when selecting/deselecting all
114
+ lastSelectedIndex.value = null
76
115
  }
77
116
 
78
117
  return {
@@ -7,8 +7,6 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
7
7
 
8
8
  return {
9
9
  execute: (command: string, value?: string) => {
10
- console.log(`[useCommands] Executing command: ${command}`, value ? `with value: ${value}` : '')
11
-
12
10
  if (!state.doc) {
13
11
  console.log('[useCommands] No document available, skipping command')
14
12
  return
@@ -17,9 +15,8 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
17
15
  // Log command if debug is enabled
18
16
  debug?.logCommand(command, value)
19
17
 
20
- // Handle view state commands directly
18
+ // Handle view state commands directly (these don't need DOM manipulation)
21
19
  if (['splitView', 'codeView', 'fullScreen'].includes(command)) {
22
- console.log(`[useCommands] Handling view state command: ${command}`)
23
20
  switch (command) {
24
21
  case 'splitView':
25
22
  state.isSplitView = !state.isSplitView
@@ -36,41 +33,24 @@ export function useCommands(state: EditorState, debug?: { logCommand: (command:
36
33
  // Focus the editor before executing command
37
34
  try {
38
35
  state.doc.body.focus()
39
- console.log('[useCommands] Editor focused')
40
36
  } catch (e) {
41
37
  console.error('[useCommands] Error focusing editor:', e)
42
38
  }
43
39
 
40
+ // Update current selection state before command execution
41
+ const selection = state.doc.getSelection()
42
+ if (selection?.rangeCount) {
43
+ state.selection = selection
44
+ state.range = selection.getRangeAt(0).cloneRange()
45
+ state.rangeCount = selection.rangeCount
46
+ }
47
+
44
48
  // Execute the command
45
49
  try {
46
- console.log('[useCommands] Executing command via executor')
47
50
  executor.execute(command, value)
48
- console.log('[useCommands] Command execution completed')
49
51
  } catch (e) {
50
52
  console.error('[useCommands] Error during command execution:', e)
51
53
  }
52
-
53
- // Update content state only if it has changed
54
- const newContent = state.doc.body.innerHTML
55
- if (newContent !== state.content) {
56
- console.log('[useCommands] Content changed, updating state')
57
- state.content = newContent
58
- }
59
-
60
- // Update selection state if needed
61
- const selection = state.doc.getSelection()
62
- if (selection?.rangeCount) {
63
- const hasSelectionChanged = !state.selection
64
- || state.selection !== selection
65
- || state.rangeCount !== selection.rangeCount
66
-
67
- if (hasSelectionChanged) {
68
- console.log('[useCommands] Selection changed, updating state')
69
- state.selection = selection
70
- state.range = selection.getRangeAt(0).cloneRange()
71
- state.rangeCount = selection.rangeCount
72
- }
73
- }
74
54
  },
75
55
  isActive: executor.isActive,
76
56
  getValue: executor.getValue
@@ -70,10 +70,9 @@ export function useEditor() {
70
70
  'h5',
71
71
  'h6',
72
72
  'blockquote',
73
- 'table',
74
73
  'p',
75
- 'ol',
76
- 'li'
74
+ 'orderedList',
75
+ 'unorderedList'
77
76
  ]
78
77
  styleTypes.forEach((style) => {
79
78
  if (state.doc && isStyleActive(style, state.doc)) {
@@ -209,44 +208,28 @@ export function useEditor() {
209
208
  const isDirectChildOfBody = node.parentElement === doc.body
210
209
 
211
210
  if (isEmpty || hasOnlyNbsp || (hasOnlyBr && !isDirectChildOfBody)) {
212
- if (isDirectChildOfBody && !node.matches('p')) {
213
- const p = doc.createElement('p')
214
- p.innerHTML = '<br>'
215
- node.parentNode?.replaceChild(p, node)
216
- } else {
217
- nodesToRemove.push(node)
218
- }
211
+ nodesToRemove.push(node)
219
212
  }
220
213
  node = walker.nextNode() as Element
221
214
  }
222
215
  nodesToRemove.forEach((node) => { node.remove() })
223
216
  },
224
217
  normalizeContent: (doc: Document) => {
225
- if (!doc.body.firstElementChild) {
226
- const p = doc.createElement('p')
227
- p.dir = doc.body.dir
228
- p.innerHTML = '<br>'
229
- doc.body.appendChild(p)
230
- } else {
231
- const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
232
- const textNodes: Text[] = []
233
- let node: Node | null
234
- while ((node = walker.nextNode())) {
235
- if (node.parentElement === doc.body) {
236
- textNodes.push(node as Text)
237
- }
218
+ // Only normalize direct text nodes to paragraphs, don't force any structure
219
+ const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
220
+ const textNodes: Text[] = []
221
+ let node: Node | null
222
+ while ((node = walker.nextNode())) {
223
+ if (node.parentElement === doc.body && node.textContent?.trim()) {
224
+ textNodes.push(node as Text)
238
225
  }
239
- textNodes.forEach((textNode) => {
240
- if (textNode.textContent?.trim()) {
241
- const p = doc.createElement('p')
242
- p.dir = doc.body.dir
243
- p.appendChild(textNode.cloneNode())
244
- doc.body.replaceChild(p, textNode)
245
- } else {
246
- doc.body.removeChild(textNode)
247
- }
248
- })
249
226
  }
227
+ textNodes.forEach((textNode) => {
228
+ const p = doc.createElement('p')
229
+ p.dir = doc.body.dir
230
+ p.appendChild(textNode.cloneNode())
231
+ doc.body.replaceChild(p, textNode)
232
+ })
250
233
  }
251
234
  }
252
235
 
@@ -320,8 +303,9 @@ export function useEditor() {
320
303
  // Store state reference in document for table operations
321
304
  (doc as any).editorState = state
322
305
 
323
- // Initial setup without triggering updates
324
- if (state.content) {
306
+ // Initial setup without triggering updates - only set content if it exists and is not just empty paragraphs
307
+ if (state.content && state.content.trim()
308
+ && !state.content.match(/^<p(?:\s[^>]*)?>(?:<br\s*\/?>)?\s*<\/p>$/i)) {
325
309
  const preserved = preserveIframes(state.content)
326
310
  doc.body.innerHTML = preserved.html
327
311
  setTimeout(() => {
@@ -333,17 +317,32 @@ export function useEditor() {
333
317
 
334
318
  cleanup.normalizeContent(doc)
335
319
 
336
- // Set initial selection at the end
337
- const range = doc.createRange()
338
- const selection = doc.getSelection()
339
- if (selection) {
340
- range.selectNodeContents(doc.body)
341
- range.collapse(false)
342
- selection.removeAllRanges()
343
- selection.addRange(range)
344
- state.range = range.cloneRange()
345
- state.selection = selection
346
- state.rangeCount = selection.rangeCount
320
+ // Set initial selection only if there's content
321
+ if (doc.body.firstElementChild) {
322
+ const range = doc.createRange()
323
+ const selection = doc.getSelection()
324
+ if (selection) {
325
+ range.selectNodeContents(doc.body)
326
+ range.collapse(false)
327
+ selection.removeAllRanges()
328
+ selection.addRange(range)
329
+ state.range = range.cloneRange()
330
+ state.selection = selection
331
+ state.rangeCount = selection.rangeCount
332
+ }
333
+ } else {
334
+ // For empty editor, set cursor at the beginning of body
335
+ const selection = doc.getSelection()
336
+ if (selection) {
337
+ const range = doc.createRange()
338
+ range.setStart(doc.body, 0)
339
+ range.setEnd(doc.body, 0)
340
+ selection.removeAllRanges()
341
+ selection.addRange(range)
342
+ state.range = range.cloneRange()
343
+ state.selection = selection
344
+ state.rangeCount = selection.rangeCount
345
+ }
347
346
  }
348
347
 
349
348
  // Setup event listeners immediately
@@ -31,7 +31,7 @@ const shortcuts: KeyboardShortcut[] = [
31
31
  export function useEditorKeyboard(doc: Document, executor: CommandExecutor): void {
32
32
  // Handle keyboard shortcuts
33
33
  doc.addEventListener('keydown', (e) => {
34
- // Handle Enter key in lists
34
+ // Handle Enter key in lists and empty editor
35
35
  if (e.key === 'Enter' && !e.shiftKey) {
36
36
  const selection = doc.getSelection()
37
37
  if (!selection || !selection.rangeCount) return
@@ -56,7 +56,7 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
56
56
  // If the list is now empty, remove it
57
57
  if (!list.querySelector('li')) {
58
58
  const p = doc.createElement('p')
59
- p.innerHTML = '<br>'
59
+ p.innerHTML = ''
60
60
  list.parentNode?.replaceChild(p, list)
61
61
 
62
62
  // Set cursor in the new paragraph
@@ -69,7 +69,7 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
69
69
  // Create a new list item
70
70
  e.preventDefault()
71
71
  const newLi = doc.createElement('li')
72
- newLi.innerHTML = '&nbsp;<br>' // Use non-breaking space with br
72
+ newLi.innerHTML = '' // Start with empty list item
73
73
  listItem.insertAdjacentElement('afterend', newLi)
74
74
 
75
75
  // Move cursor to new list item
@@ -79,6 +79,55 @@ export function useEditorKeyboard(doc: Document, executor: CommandExecutor): voi
79
79
  selection.addRange(range)
80
80
  }
81
81
  }
82
+ } else {
83
+ // Handle Enter in regular content - create new paragraph
84
+ const blockElement = (container.nodeType === 3 ? container.parentElement : container as Element)?.closest('p,h1,h2,h3,h4,h5,h6,blockquote,div')
85
+
86
+ if (blockElement && range.collapsed) {
87
+ // If we're at the end of a block element, create a new paragraph
88
+ if (isAtEndOfNode(blockElement, range)) {
89
+ e.preventDefault()
90
+ const newP = doc.createElement('p')
91
+ newP.innerHTML = ''
92
+ blockElement.insertAdjacentElement('afterend', newP)
93
+
94
+ // Move cursor to new paragraph
95
+ range.selectNodeContents(newP)
96
+ range.collapse(true)
97
+ selection.removeAllRanges()
98
+ selection.addRange(range)
99
+ }
100
+ } else if (!blockElement && doc.body.textContent?.trim()) {
101
+ // If we're typing directly in the body and press Enter, wrap in paragraphs
102
+ e.preventDefault()
103
+
104
+ // Split content at cursor position
105
+ const textContent = doc.body.textContent || ''
106
+ const cursorPos = range.startOffset
107
+ const beforeText = textContent.substring(0, cursorPos).trim()
108
+ const afterText = textContent.substring(cursorPos).trim()
109
+
110
+ // Clear body
111
+ doc.body.innerHTML = ''
112
+
113
+ // Create first paragraph with before text
114
+ if (beforeText) {
115
+ const p1 = doc.createElement('p')
116
+ p1.textContent = beforeText
117
+ doc.body.appendChild(p1)
118
+ }
119
+
120
+ // Create second paragraph for after text
121
+ const p2 = doc.createElement('p')
122
+ p2.textContent = afterText
123
+ doc.body.appendChild(p2)
124
+
125
+ // Set cursor at beginning of second paragraph
126
+ range.selectNodeContents(p2)
127
+ range.collapse(true)
128
+ selection.removeAllRanges()
129
+ selection.addRange(range)
130
+ }
82
131
  }
83
132
  }
84
133