@bagelink/vue 1.6.43 → 1.6.47

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.6.43",
4
+ "version": "1.6.47",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -28,11 +28,11 @@ interface AccordionState {
28
28
  openItem?: string
29
29
  }
30
30
 
31
- const accordionState = inject<AccordionState>('accordionState')
31
+ const accordionState = inject<AccordionState | undefined>('accordionState', undefined)
32
32
  const id = props.id || Math.random().toString(36).slice(7)
33
33
 
34
34
  const computedIcon = computed(() => {
35
- return 'plus_minus' === props.iconType
35
+ return props.iconType === 'plus_minus'
36
36
  ? (isOpen ? 'remove' : 'add')
37
37
  : 'expand_more'
38
38
  })
@@ -44,7 +44,7 @@ if (accordionState) {
44
44
  watch(
45
45
  () => accordionState.openItem,
46
46
  (currentOpenId) => {
47
- if (currentOpenId !== id) {isOpen = false}
47
+ if (currentOpenId !== id) { isOpen = false }
48
48
  },
49
49
  { immediate: true }
50
50
  )
@@ -5,7 +5,10 @@ import type { ModalFormOptions } from '../plugins/modalTypes'
5
5
  import { Btn, Modal, useBagel, BagelForm } from '@bagelink/vue'
6
6
 
7
7
  // eslint-disable-next-line vue/prop-name-casing
8
- const props = defineProps<ModalFormOptions<T>>()
8
+ const props = withDefaults(defineProps<ModalFormOptions<T>>(), {
9
+ visible: true,
10
+ dismissable: true,
11
+ })
9
12
 
10
13
  const emit = defineEmits<{
11
14
  'update:modelValue': [value: T]
@@ -28,16 +31,16 @@ const closeModal = () => modal?.closeModal()
28
31
  let submitting = $ref(false)
29
32
 
30
33
  async function runSubmit() {
31
- if (submitting) {return}
32
- if (false === form?.validateForm()) {return}
34
+ if (submitting) { return }
35
+ if (form?.validateForm() === false) { return }
33
36
  submitting = true
34
37
  try {
35
38
  await props.onSubmit?.(formData.value)
36
39
  closeModal()
37
40
  } catch (err: any) {
38
41
  submitting = false
39
- if (props.onError) {props.onError(err)}
40
- else {bagel.onError?.(err)}
42
+ if (props.onError) { props.onError(err) }
43
+ else { bagel.onError?.(err) }
41
44
  }
42
45
  }
43
46
 
@@ -116,8 +116,8 @@ const emit = defineEmits<{
116
116
 
117
117
  defineSlots<{
118
118
  'default'?: (props: { item: T, index: number, currentIndex: number }) => any
119
- 'prev-button'?: () => any
120
- 'next-button'?: () => any
119
+ 'prev-button'?: (props: { prev: () => void }) => any
120
+ 'next-button'?: (props: { next: () => void }) => any
121
121
  'navigation'?: (props: {
122
122
  items: T[]
123
123
  currentIndex: number
@@ -434,12 +434,12 @@ defineExpose({
434
434
  <!-- Default Navigation -->
435
435
  <div v-if="navigation" class="swi-ctrl">
436
436
  <div class="swi-prev hover" @click="handleSlideNav('prev')">
437
- <slot name="prev-button">
437
+ <slot name="prev-button" :prev="() => handleSlideNav('prev')">
438
438
  <Icon name="chevron_left" class="user-select-none hover z-99 color-primary" :size="2" />
439
439
  </slot>
440
440
  </div>
441
441
  <div class="swi-next hover" @click="handleSlideNav('next')">
442
- <slot name="next-button">
442
+ <slot name="next-button" :next="() => handleSlideNav('next')">
443
443
  <Icon name="chevron_right" class="user-select-none hover z-99 color-primary" :size="2" />
444
444
  </slot>
445
445
  </div>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import { useDebounceFn } from '@bagelink/vue'
3
- import { onMounted, onUnmounted, watch } from 'vue'
3
+ import { onMounted as vueOnMounted, onUnmounted as vueOnUnmounted, watch as vueWatch } from 'vue'
4
4
  import TapDetector from '../utils/tapDetector'
5
5
 
6
6
  // Props interface
@@ -44,9 +44,9 @@ let translateX = $ref(0)
44
44
  let animTranslateX = $ref(0)
45
45
  let translateY = $ref(0)
46
46
  let animTranslateY = $ref(0)
47
- let _zoom = $ref(zoom || 1)
47
+ let _zoom = $ref(zoom ?? 1)
48
48
  let scale = $computed({
49
- get: () => zoom === undefined ? _zoom : zoom,
49
+ get: () => zoom ?? _zoom,
50
50
  set: (val) => {
51
51
  _zoom = val
52
52
  emit('update:zoom', _zoom)
@@ -68,7 +68,15 @@ let tapDetector = $ref<TapDetector | undefined>()
68
68
  let translateXValue = $ref(0)
69
69
  let translateYValue = $ref(0)
70
70
 
71
+ // Momentum/inertia tracking
72
+ let velocityX = $ref(0)
73
+ let velocityY = $ref(0)
74
+ let lastMoveTime = $ref(0)
75
+ let momentumRaf = $ref<number | undefined>()
76
+
71
77
  const wrapperStyle = $computed(() => {
78
+ // Translation is in normalized coordinates (0.5 = half container)
79
+ // Convert to pixels
72
80
  translateXValue = containerWidth * animTranslateX
73
81
  translateYValue = containerHeight * animTranslateY
74
82
  return {
@@ -76,14 +84,15 @@ const wrapperStyle = $computed(() => {
76
84
  `translate(${translateXValue}px, ${translateYValue}px)`,
77
85
  `scale(${animScale})`,
78
86
  ].join(' '),
87
+ transformOrigin: '50% 50%',
79
88
  }
80
89
  })
81
90
 
82
- watch(() => scale, (newScale) => {
91
+ vueWatch(() => scale, (newScale) => {
83
92
  const { x, y } = calcTranslateLimit()
84
93
  translateX = Math.max(-x, Math.min(x, translateX))
85
94
  translateY = Math.max(-y, Math.min(y, translateY))
86
- if (1 !== newScale) {
95
+ if (newScale !== 1) {
87
96
  panLocked = false
88
97
  }
89
98
  })
@@ -98,22 +107,25 @@ function reset() {
98
107
  defineExpose({ reset })
99
108
 
100
109
  function tryToScale(scaleDelta: number) {
101
- if (disabled) {return}
110
+ if (disabled) { return }
102
111
  let newScale = scale * scaleDelta
103
112
  if (zoomingElastic) {
104
- if (newScale < minScale! || newScale > maxScale!) {
113
+ if (newScale < minScale || newScale > maxScale) {
105
114
  let log = Math.log2(scaleDelta)
106
115
  log *= 0.2
107
116
  scaleDelta = 2 ** log
108
117
  newScale = scale * scaleDelta
109
118
  }
110
119
  } else {
111
- if (newScale < minScale!) {newScale = minScale!}
112
- else if (newScale > maxScale!) {newScale = maxScale!}
120
+ if (newScale < minScale) {
121
+ newScale = minScale
122
+ } else if (newScale > maxScale) {
123
+ newScale = maxScale
124
+ }
113
125
  }
114
126
  scaleDelta = newScale / scale
115
127
  scale = newScale
116
- if ('image-center' !== pivot) {
128
+ if (pivot !== 'image-center') {
117
129
  const normMousePosX = (pointerPosX - containerLeft) / containerWidth
118
130
  const normMousePosY = (pointerPosY - containerTop) / containerHeight
119
131
  translateX = (0.5 + translateX - normMousePosX) * scaleDelta + normMousePosX - 0.5
@@ -128,32 +140,78 @@ function setPointerPosCenter() {
128
140
 
129
141
  function onPointerMove(newMousePosX: number, newMousePosY: number) {
130
142
  if (isPointerDown) {
143
+ const currentTime = Date.now()
131
144
  const pixelDeltaX = newMousePosX - pointerPosX
132
145
  const pixelDeltaY = newMousePosY - pointerPosY
146
+
133
147
  if (!panLocked) {
148
+ // Calculate velocity for momentum
149
+ const timeDelta = currentTime - lastMoveTime
150
+ if (timeDelta > 0) {
151
+ velocityX = pixelDeltaX / timeDelta
152
+ velocityY = pixelDeltaY / timeDelta
153
+ }
154
+
134
155
  const translateLimit = calcTranslateLimit()
135
156
  const maxTranslateX = translateLimit.x
136
157
  const maxTranslateY = translateLimit.y
137
158
  const newTranslateX = translateX + pixelDeltaX / containerWidth
138
159
  const newTranslateY = translateY + pixelDeltaY / containerHeight
160
+
139
161
  translateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
140
162
  translateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
163
+
164
+ lastMoveTime = currentTime
141
165
  }
142
166
  }
143
167
  pointerPosX = newMousePosX
144
168
  pointerPosY = newMousePosY
145
169
  }
146
170
 
171
+ function applyMomentum() {
172
+ if (Math.abs(velocityX) < 0.01 && Math.abs(velocityY) < 0.01) {
173
+ velocityX = 0
174
+ velocityY = 0
175
+ if (momentumRaf) {
176
+ cancelAnimationFrame(momentumRaf)
177
+ momentumRaf = undefined
178
+ }
179
+ return
180
+ }
181
+
182
+ const translateLimit = calcTranslateLimit()
183
+ const newTranslateX = translateX + velocityX * 16 / containerWidth
184
+ const newTranslateY = translateY + velocityY * 16 / containerHeight
185
+
186
+ translateX = Math.max(-translateLimit.x, Math.min(translateLimit.x, newTranslateX))
187
+ translateY = Math.max(-translateLimit.y, Math.min(translateLimit.y, newTranslateY))
188
+
189
+ // Apply friction
190
+ velocityX *= 0.95
191
+ velocityY *= 0.95
192
+
193
+ momentumRaf = requestAnimationFrame(applyMomentum)
194
+ }
195
+
147
196
  const onInteractionEnd = useDebounceFn(() => {
148
197
  limit()
149
- panLocked = 1 === scale
150
- }, 100)
198
+ panLocked = scale === 1
199
+
200
+ // Start momentum animation if there's significant velocity
201
+ if (!panLocked && (Math.abs(velocityX) > 0.1 || Math.abs(velocityY) > 0.1)) {
202
+ if (momentumRaf) cancelAnimationFrame(momentumRaf)
203
+ applyMomentum()
204
+ } else {
205
+ velocityX = 0
206
+ velocityY = 0
207
+ }
208
+ }, 50)
151
209
 
152
210
  function limit() {
153
- if (scale < minScale!) {
154
- scale = minScale!
155
- } else if (scale > maxScale!) {
156
- tryToScale(maxScale! / scale)
211
+ if (scale < minScale) {
212
+ scale = minScale
213
+ } else if (scale > maxScale) {
214
+ tryToScale(maxScale / scale)
157
215
  }
158
216
 
159
217
  if (limitTranslation) {
@@ -168,42 +226,39 @@ function limit() {
168
226
  }
169
227
 
170
228
  function calcTranslateLimit() {
171
- if ('y' === getMarginDirection()) {
172
- const imageToContainerRatio = containerWidth / aspectRatio / containerHeight
173
- let translateLimitY = (scale * imageToContainerRatio - 1) / 2
174
- if (0 > translateLimitY) {translateLimitY = 0}
175
- return {
176
- x: scale - 1, // Allow full movement to edges horizontally
177
- y: translateLimitY * 2 // Allow full movement to edges vertically
178
- }
179
- }
180
- const imageToContainerRatio = containerHeight * aspectRatio / containerWidth
181
- let translateLimitX = (scale * imageToContainerRatio - 1) / 2
182
- if (0 > translateLimitX) {translateLimitX = 0}
229
+ // When scale = 1, no panning needed (image fits in container)
230
+ // When scale > 1, we can pan to see parts of the image outside the viewport
231
+
232
+ // The image is scaled from center, so:
233
+ // - At scale 2, the image is 2x larger
234
+ // - The amount that extends beyond the container on EACH side is: (scale - 1) * containerSize / 2
235
+ // - In normalized coordinates (where 1.0 = full container), this is: (scale - 1) / 2
236
+
237
+ // Simple and correct approach:
238
+ // Max pan distance in normalized coords = (scale - 1) / 2
239
+ // This works for both X and Y directions regardless of aspect ratio
240
+
241
+ const basePanLimit = Math.max(0, (scale - 1) / 2)
242
+
183
243
  return {
184
- x: translateLimitX * 2, // Allow full movement to edges horizontally
185
- y: scale - 1 // Allow full movement to edges vertically
244
+ x: basePanLimit,
245
+ y: basePanLimit,
186
246
  }
187
247
  }
188
248
 
189
- function getMarginDirection() {
190
- const containerRatio = containerWidth / containerHeight
191
- return containerRatio > aspectRatio ? 'x' : 'y'
192
- }
193
-
194
249
  function onMouseWheel(ev: WheelEvent) {
195
- if (!mouseWheelToZoom) {return}
250
+ if (!mouseWheelToZoom) { return }
196
251
  ev.preventDefault()
197
252
  const currTime = Date.now()
198
- if (120 === Math.abs(ev.deltaY)) {
199
- if (50 < currTime - lastFullWheelTime) {
253
+ if (Math.abs(ev.deltaY) === 120) {
254
+ if (currTime - lastFullWheelTime > 50) {
200
255
  onMouseWheelDo(ev.deltaY)
201
256
  lastFullWheelTime = currTime
202
257
  }
203
258
  } else {
204
- if (50 < currTime - lastWheelTime) {
259
+ if (currTime - lastWheelTime > 50) {
205
260
  lastWheelDirection = ev.deltaX > ev.deltaY ? 'x' : 'y'
206
- if ('y' === lastWheelDirection) {
261
+ if (lastWheelDirection === 'y') {
207
262
  onMouseWheelDo(ev.deltaY)
208
263
  }
209
264
  }
@@ -212,7 +267,8 @@ function onMouseWheel(ev: WheelEvent) {
212
267
  }
213
268
 
214
269
  function onMouseWheelDo(wheelDelta: number) {
215
- const scaleDelta = 1.25 ** (wheelDelta / 120)
270
+ // More responsive zoom speed
271
+ const scaleDelta = 1.15 ** (wheelDelta / 120)
216
272
  tryToScale(scaleDelta)
217
273
  onInteractionEnd()
218
274
  }
@@ -222,6 +278,13 @@ function onMouseDown(ev: MouseEvent) {
222
278
  isPointerDown = true
223
279
  pointerPosX = ev.clientX
224
280
  pointerPosY = ev.clientY
281
+ lastMoveTime = Date.now()
282
+ velocityX = 0
283
+ velocityY = 0
284
+ if (momentumRaf) {
285
+ cancelAnimationFrame(momentumRaf)
286
+ momentumRaf = undefined
287
+ }
225
288
  document.addEventListener('mousemove', onMouseMove)
226
289
  document.addEventListener('mouseup', onMouseUp)
227
290
  }
@@ -234,16 +297,33 @@ function onMouseUp() {
234
297
  }
235
298
 
236
299
  function onMouseMove(ev: MouseEvent) {
300
+ // Prevent swiper from handling mouse drag when panning zoomed image
301
+ if (!panLocked && scale > 1) {
302
+ ev.stopPropagation()
303
+ }
237
304
  onPointerMove(ev.clientX, ev.clientY)
238
305
  }
239
306
 
240
307
  function onTouchStart(ev: TouchEvent) {
241
- if (1 === ev.touches.length) {
308
+ if (ev.touches.length === 1) {
242
309
  refreshContainerPos()
243
310
  pointerPosX = ev.touches[0].clientX
244
311
  pointerPosY = ev.touches[0].clientY
312
+ lastMoveTime = Date.now()
313
+ velocityX = 0
314
+ velocityY = 0
315
+ if (momentumRaf) {
316
+ cancelAnimationFrame(momentumRaf)
317
+ momentumRaf = undefined
318
+ }
245
319
  isPointerDown = true
246
- } else if (2 === ev.touches.length) {
320
+ } else if (ev.touches.length === 2) {
321
+ if (momentumRaf) {
322
+ cancelAnimationFrame(momentumRaf)
323
+ momentumRaf = undefined
324
+ }
325
+ velocityX = 0
326
+ velocityY = 0
247
327
  isPointerDown = true
248
328
  pointerPosX = (ev.touches[0].clientX + ev.touches[1].clientX) / 2
249
329
  pointerPosY = (ev.touches[0].clientY + ev.touches[1].clientY) / 2
@@ -255,11 +335,11 @@ function onTouchStart(ev: TouchEvent) {
255
335
  }
256
336
 
257
337
  function onTouchEnd(ev: TouchEvent) {
258
- if (0 === ev.touches.length) {
338
+ if (ev.touches.length === 0) {
259
339
  isPointerDown = false
260
- if (0.1 > Math.abs(scale - 1)) {scale = 1}
340
+ if (Math.abs(scale - 1) < 0.1) { scale = 1 }
261
341
  onInteractionEnd()
262
- } else if (1 === ev.touches.length) {
342
+ } else if (ev.touches.length === 1) {
263
343
  pointerPosX = ev.touches[0].clientX
264
344
  pointerPosY = ev.touches[0].clientY
265
345
  }
@@ -267,11 +347,15 @@ function onTouchEnd(ev: TouchEvent) {
267
347
  }
268
348
 
269
349
  function onTouchMove(ev: TouchEvent) {
270
- if (1 === ev.touches.length) {
350
+ if (ev.touches.length === 1) {
351
+ // Prevent swiper from handling touch when panning zoomed image
352
+ if (!panLocked && scale > 1) {
353
+ ev.stopPropagation()
354
+ }
271
355
  onPointerMove(ev.touches[0].clientX, ev.touches[0].clientY)
272
- } else if (2
273
-
274
- === ev.touches.length) {
356
+ } else if (ev.touches.length === 2) {
357
+ // Always prevent default during pinch zoom
358
+ ev.stopPropagation()
275
359
  const distX = ev.touches[0].clientX - ev.touches[1].clientX
276
360
  const distY = ev.touches[0].clientY - ev.touches[1].clientY
277
361
  const newTwoFingerDist = Math.sqrt(distX * distX + distY * distY)
@@ -296,16 +380,21 @@ function loop() {
296
380
  }
297
381
 
298
382
  function gainOn(from: number, to: number) {
299
- const delta = (to - from) * 0.3
300
- return 1e-5 < Math.abs(delta) ? from + delta : to
383
+ // Smoother easing with better responsiveness
384
+ const delta = (to - from) * 0.4
385
+ return Math.abs(delta) > 1e-5 ? from + delta : to
301
386
  }
302
387
 
303
388
  // Lifecycle hooks
304
- onMounted(() => {
389
+ vueOnMounted(() => {
305
390
  tapDetector = new TapDetector()
306
- if (zoomElement) {tapDetector.attach(zoomElement)}
391
+ if (zoomElement) { tapDetector.attach(zoomElement) }
307
392
  if (doubleClickToZoom) {
308
393
  tapDetector.onDoubleTap(onDoubleTap as any)
394
+ // Also add native dblclick event for mouse
395
+ if (zoomElement) {
396
+ zoomElement.addEventListener('dblclick', onDoubleTap as any)
397
+ }
309
398
  }
310
399
  window.addEventListener('resize', onWindowResize)
311
400
  onWindowResize()
@@ -313,20 +402,38 @@ onMounted(() => {
313
402
  loop()
314
403
  })
315
404
 
316
- onUnmounted(() => {
317
- if (zoomElement) {tapDetector?.detach(zoomElement)}
405
+ vueOnUnmounted(() => {
406
+ if (zoomElement) {
407
+ tapDetector?.detach(zoomElement)
408
+ if (doubleClickToZoom) {
409
+ zoomElement.removeEventListener('dblclick', onDoubleTap as any)
410
+ }
411
+ }
318
412
  window.removeEventListener('resize', onWindowResize)
319
- if (raf) {window.cancelAnimationFrame(raf)}
413
+ if (raf) { window.cancelAnimationFrame(raf) }
414
+ if (momentumRaf) { window.cancelAnimationFrame(momentumRaf) }
320
415
  })
321
416
 
322
417
  function onDoubleTap(ev: MouseEvent) {
323
- if (1 === scale) {
324
- if (0 < ev.clientX) {
325
- pointerPosX = ev.clientX
326
- pointerPosY = ev.clientY
327
- }
328
- tryToScale(Math.min(3, maxScale!))
418
+ // Update pointer position if available
419
+ if (ev.clientX > 0) {
420
+ pointerPosX = ev.clientX
421
+ pointerPosY = ev.clientY
422
+ }
423
+
424
+ // Calculate zoom levels
425
+ const mediumZoom = Math.min(2.5, maxScale / 2 + 0.5)
426
+ const highZoom = maxScale
427
+
428
+ // Cycle through three states: 1x -> medium -> max -> 1x
429
+ if (scale === 1) {
430
+ // From no zoom -> medium zoom
431
+ tryToScale(mediumZoom)
432
+ } else if (scale < highZoom - 0.1) {
433
+ // From medium zoom -> max zoom
434
+ tryToScale(highZoom / scale)
329
435
  } else {
436
+ // From max zoom -> no zoom (reset)
330
437
  reset()
331
438
  }
332
439
  }
@@ -344,16 +451,9 @@ function onWindowResize() {
344
451
 
345
452
  <template>
346
453
  <div
347
- ref="zoomElement"
348
- class="vue-zoomer"
349
- :style="{ backgroundColor }"
350
- @mousewheel="onMouseWheel"
351
- @DOMMouseScroll="onMouseWheel"
352
- @mousedown="onMouseDown"
353
- @mouseout="setPointerPosCenter"
354
- @focusout="setPointerPosCenter"
355
- @touchstart="onTouchStart"
356
- @touchmove="onTouchMove"
454
+ ref="zoomElement" class="vue-zoomer" :style="{ backgroundColor }" @mousewheel="onMouseWheel"
455
+ @DOMMouseScroll="onMouseWheel" @mousedown="onMouseDown" @mouseout="setPointerPosCenter"
456
+ @focusout="setPointerPosCenter" @touchstart="onTouchStart" @touchmove="onTouchMove"
357
457
  >
358
458
  <div class="zoomer" :style="wrapperStyle">
359
459
  <slot />
@@ -371,24 +471,27 @@ function onWindowResize() {
371
471
  z-index: 1000;
372
472
  height: 100px
373
473
  }
474
+
374
475
  .vue-zoomer {
375
- overflow: hidden;
476
+ overflow: hidden;
376
477
  }
478
+
377
479
  .zoomer {
378
- transform-origin: 50% 50%;
379
- width: 100%;
380
- height: 100%;
381
- max-width: 100vw;
382
- max-height: 100vh;
480
+ transform-origin: 50% 50%;
481
+ width: 100%;
482
+ height: 100%;
483
+ max-width: 100vw;
484
+ max-height: 100vh;
383
485
  }
384
- .zoomer > img {
385
- vertical-align: top;
386
- user-select: none;
387
- -moz-user-drag: none;
388
- -webkit-user-select: none;
389
- -moz-user-select: none;
390
- -ms-user-select: none;
391
- -webkit-user-drag: none;
392
- -moz-user-drag: none;
486
+
487
+ .zoomer>img {
488
+ vertical-align: top;
489
+ user-select: none;
490
+ -moz-user-drag: none;
491
+ -webkit-user-select: none;
492
+ -moz-user-select: none;
493
+ -ms-user-select: none;
494
+ -webkit-user-drag: none;
495
+ -moz-user-drag: none;
393
496
  }
394
497
  </style>
@@ -4,7 +4,7 @@ import { bagelFormUtils as frm } from '../../../../../utils'
4
4
 
5
5
  export function insertImage(modal: ModalApi, state: EditorState) {
6
6
  const { range, doc } = state
7
- if (!range || !doc) {return}
7
+ if (!range || !doc) { return }
8
8
 
9
9
  modal.showModalForm<{
10
10
  src: string
@@ -73,7 +73,7 @@ export function insertLink(modal: ModalApi, state: EditorState) {
73
73
  if (!success) {
74
74
  console.log('Failed to select word at cursor')
75
75
  // Show a helpful tooltip to the user
76
- const showTooltipMessage = (state as any).showTooltipMessage
76
+ const { showTooltipMessage } = state as any
77
77
  if (showTooltipMessage) {
78
78
  showTooltipMessage('To add a link, please select text first or place cursor within a word')
79
79
  } else {
@@ -107,7 +107,7 @@ export function insertLink(modal: ModalApi, state: EditorState) {
107
107
 
108
108
  // Helper function to select word at cursor
109
109
  function selectWordAtCursor(doc: Document, range: Range): boolean {
110
- if (!range.collapsed) {return true} // Already has selection
110
+ if (!range.collapsed) { return true } // Already has selection
111
111
 
112
112
  let textNode = range.startContainer
113
113
  let offset = range.startOffset
@@ -115,7 +115,7 @@ function selectWordAtCursor(doc: Document, range: Range): boolean {
115
115
  // If we're in an element node, try to find a text node
116
116
  if (textNode.nodeType !== Node.TEXT_NODE) {
117
117
  const element = textNode as Element
118
- if (0 < element.childNodes.length && offset < element.childNodes.length) {
118
+ if (element.childNodes.length > 0 && offset < element.childNodes.length) {
119
119
  const childNode = element.childNodes[offset]
120
120
  if (childNode.nodeType === Node.TEXT_NODE) {
121
121
  textNode = childNode
@@ -129,7 +129,7 @@ function selectWordAtCursor(doc: Document, range: Range): boolean {
129
129
  }
130
130
 
131
131
  const text = textNode.textContent || ''
132
- if (!text) {return false}
132
+ if (!text) { return false }
133
133
 
134
134
  // Find word boundaries - support Hebrew, English, and numbers
135
135
  let start = offset
@@ -139,7 +139,7 @@ function selectWordAtCursor(doc: Document, range: Range): boolean {
139
139
  const wordRegex = /[\u0590-\u05FF\u0600-\u06FF\w]/
140
140
 
141
141
  // Move start backwards to find beginning of word
142
- while (0 < start && wordRegex.test(text[start - 1])) {
142
+ while (start > 0 && wordRegex.test(text[start - 1])) {
143
143
  start--
144
144
  }
145
145
 
@@ -173,7 +173,7 @@ export interface InsertImbedModalData {
173
173
 
174
174
  export function insertEmbed(modal: ModalApi, state: EditorState) {
175
175
  const { range, doc } = state
176
- if (!range || !doc) {return}
176
+ if (!range || !doc) { return }
177
177
 
178
178
  modal.showModalForm<InsertImbedModalData>({
179
179
  title: 'Embed iframe',
@@ -18,7 +18,11 @@ withDefaults(defineProps<Props>(), {
18
18
  })
19
19
 
20
20
  // Inject menu state from parent
21
- const menuState = inject('menuState') as {
21
+ const menuState = inject('menuState', {
22
+ isOpen: { value: true },
23
+ isMobile: { value: false },
24
+ toggleMenu: () => { },
25
+ }) as {
22
26
  isOpen: { value: boolean }
23
27
  isMobile: { value: boolean }
24
28
  toggleMenu: () => void
@@ -83,8 +87,7 @@ const hasSidebarCard = computed(() => {
83
87
 
84
88
  <!-- Page Content -->
85
89
  <main
86
- class="pageContent flex-grow overflow pt-1 pb-05 w-100p m_p-05 m_scrollbar-gutter-auto m_vw100"
87
- :class="{
90
+ class="pageContent flex-grow overflow pt-1 pb-05 w-100p m_p-05 m_scrollbar-gutter-auto m_vw100" :class="{
88
91
  'px-1': !hasSidebarCard,
89
92
  }"
90
93
  >
@@ -38,7 +38,13 @@ const route = useRoute()
38
38
  const isTransitioning = ref(false)
39
39
 
40
40
  // Inject menu state from parent
41
- const menuState = inject('menuState') as {
41
+ const menuState = inject('menuState', {
42
+ isOpen: { value: true },
43
+ isMobile: { value: false },
44
+ closeOnMobile: () => {},
45
+ sidebarWidth: '260px',
46
+ sidebarCollapsedWidth: '66px',
47
+ }) as {
42
48
  isOpen: { value: boolean }
43
49
  isMobile: { value: boolean }
44
50
  closeOnMobile: () => void