@slidev/client 0.48.9 → 0.49.0-beta.2

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.
@@ -63,10 +63,10 @@ onMounted(() => {
63
63
  if (!clicks || !props.ranges?.length)
64
64
  return
65
65
 
66
- const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
67
- clicks.register(id, { max: end, delta })
66
+ const clicksInfo = clicks.calculateSince(props.at, props.ranges.length - 1)
67
+ clicks.register(id, clicksInfo)
68
68
 
69
- const index = computed(() => Math.max(0, clicks.current - start + 1))
69
+ const index = computed(() => Math.max(0, clicks.current - clicksInfo.start + 1))
70
70
 
71
71
  const finallyRange = computed(() => {
72
72
  return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
@@ -58,10 +58,10 @@ onMounted(() => {
58
58
  if (!clicks || !props.ranges?.length)
59
59
  return
60
60
 
61
- const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
62
- clicks.register(id, { max: end, delta })
61
+ const clicksInfo = clicks.calculateSince(props.at, props.ranges.length - 1)
62
+ clicks.register(id, clicksInfo)
63
63
 
64
- const index = computed(() => Math.max(0, clicks.current - start + 1))
64
+ const index = computed(() => Math.max(0, clicks.current - clicksInfo.start + 1))
65
65
 
66
66
  const finallyRange = computed(() => {
67
67
  return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
@@ -16,6 +16,7 @@ import { debounce } from '@antfu/utils'
16
16
  import lz from 'lz-string'
17
17
  import type * as monaco from 'monaco-editor'
18
18
  import { computed, nextTick, onMounted, ref } from 'vue'
19
+ import type { RawAtValue } from '@slidev/types'
19
20
  import { makeId } from '../logic/utils'
20
21
  import CodeRunner from '../internals/CodeRunner.vue'
21
22
 
@@ -30,6 +31,7 @@ const props = withDefaults(defineProps<{
30
31
  ata?: boolean
31
32
  runnable?: boolean
32
33
  autorun?: boolean | 'once'
34
+ showOutputAt?: RawAtValue
33
35
  outputHeight?: string
34
36
  highlightOutput?: boolean
35
37
  runnerOptions?: Record<string, unknown>
@@ -165,6 +167,7 @@ onMounted(async () => {
165
167
  v-model="code"
166
168
  :lang="lang"
167
169
  :autorun="props.autorun"
170
+ :show-output-at="props.showOutputAt"
168
171
  :height="props.outputHeight"
169
172
  :highlight-output="props.highlightOutput"
170
173
  :runner-options="props.runnerOptions"
@@ -36,14 +36,14 @@ onMounted(() => {
36
36
  throw new Error('[slidev] The length of stepRanges does not match the length of steps, this is an internal error.')
37
37
 
38
38
  const clickCounts = ranges.value.map(s => s.length).reduce((a, b) => a + b, 0)
39
- const { start, end, delta } = clicks.resolve(props.at ?? '+1', clickCounts - 1)
40
- clicks.register(id, { max: end, delta })
39
+ const clickInfo = clicks.calculateSince(props.at ?? '+1', clickCounts - 1)
40
+ clicks.register(id, clickInfo)
41
41
 
42
42
  watch(
43
43
  () => clicks.current,
44
44
  () => {
45
45
  // Calculate the step and rangeStr based on the current click count
46
- const clickCount = clicks.current - start
46
+ const clickCount = clicks.current - clickInfo.start
47
47
  let step = steps.length - 1
48
48
  let currentClickSum = 0
49
49
  let rangeStr = 'all'
@@ -1,86 +1,74 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
2
+ import { computed, onMounted, ref, watch } from 'vue'
3
+ import { and } from '@vueuse/math'
3
4
  import { useSlideContext } from '../context'
5
+ import { resolvedClickMap } from '../modules/v-click'
6
+ import { useNav } from '../composables/useNav'
4
7
 
5
8
  const props = defineProps<{
6
- autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
7
- autoPause?: 'slide' | 'click'
8
- autoReset?: 'slide' | 'click'
9
+ autoplay?: boolean | 'once'
10
+ autoreset?: 'slide' | 'click'
11
+ poster?: string
12
+ printPoster?: string
13
+ timestamp?: string | number
14
+ printTimestamp?: string | number | 'last'
15
+ controls?: boolean
9
16
  }>()
10
17
 
11
- const {
12
- $slidev,
13
- $clicksContext: clicks,
14
- $renderContext: currentContext,
15
- $route: route,
16
- } = useSlideContext()
18
+ const printPoster = computed(() => props.printPoster ?? props.poster)
19
+ const printTimestamp = computed(() => props.printTimestamp ?? props.timestamp ?? 0)
17
20
 
18
- const video = ref<HTMLMediaElement>()
19
- const played = ref(false)
20
- const ended = ref(false)
21
-
22
- const matchRoute = computed(() => {
23
- if (!video.value || currentContext?.value !== 'slide')
24
- return false
25
- return route && route.no === $slidev?.nav.currentSlideNo
26
- })
27
-
28
- const matchClick = computed(() => {
29
- if (!video.value || currentContext?.value !== 'slide' || !clicks)
30
- return false
31
- return clicks.map.get(video.value)?.isShown?.value ?? true
32
- })
21
+ const { $slidev, $renderContext, $route } = useSlideContext()
22
+ const { isPrintMode } = useNav()
33
23
 
34
- const matchRouteAndClick = computed(() => matchRoute.value && matchClick.value)
24
+ const noPlay = computed(() => isPrintMode.value || !['slide', 'presenter'].includes($renderContext.value))
35
25
 
36
- watch(matchRouteAndClick, () => {
37
- if (!video.value || currentContext?.value !== 'slide')
38
- return
39
-
40
- if (matchRouteAndClick.value) {
41
- if (props.autoReset === 'click')
42
- video.value.currentTime = 0
43
- if (props.autoPlay && (!played.value || props.autoPlay === 'resume' || (props.autoPlay === 'resumeOnce' && !ended.value)))
44
- video.value.play()
45
- }
46
-
47
- if ((props.autoPause === 'click' && !matchRouteAndClick.value) || (props.autoPause === 'slide' && !matchRoute.value))
48
- video.value.pause()
49
- })
26
+ const video = ref<HTMLMediaElement>()
27
+ const played = ref(false)
50
28
 
51
- watch(matchRoute, () => {
52
- if (!video.value || currentContext?.value !== 'slide')
29
+ onMounted(() => {
30
+ if (noPlay.value)
53
31
  return
54
32
 
55
- if (matchRoute.value && props.autoReset === 'slide')
56
- video.value.currentTime = 0
33
+ const timestamp = +(props.timestamp ?? 0)
34
+ video.value!.currentTime = timestamp
35
+
36
+ const matchRoute = computed(() => !!$route && $route.no === $slidev?.nav.currentSlideNo)
37
+ const matchClick = computed(() => !!video.value && (resolvedClickMap.get(video.value)?.isShown?.value ?? true))
38
+ const matchRouteAndClick = and(matchRoute, matchClick)
39
+
40
+ watch(matchRouteAndClick, () => {
41
+ if (matchRouteAndClick.value) {
42
+ if (props.autoplay === true || (props.autoplay === 'once' && !played.value))
43
+ video.value!.play()
44
+ }
45
+ else {
46
+ video.value!.pause()
47
+ if (props.autoreset === 'click' || (props.autoreset === 'slide' && !matchRoute.value))
48
+ video.value!.currentTime = timestamp
49
+ }
50
+ }, { immediate: true })
57
51
  })
58
52
 
59
- function onPlay() {
60
- played.value = true
61
- }
62
-
63
- function onEnded() {
64
- ended.value = true
53
+ function onLoadedMetadata(ev: Event) {
54
+ // The video may be loaded before component mounted
55
+ const element = ev.target as HTMLMediaElement
56
+ if (noPlay.value && (!printPoster.value || props.printTimestamp)) {
57
+ element.currentTime = printTimestamp.value === 'last'
58
+ ? element.duration
59
+ : +printTimestamp.value
60
+ }
65
61
  }
66
-
67
- onMounted(() => {
68
- if (!video.value || currentContext?.value !== 'slide')
69
- return
70
- video.value?.addEventListener('play', onPlay)
71
- video.value?.addEventListener('ended', onEnded)
72
- })
73
-
74
- onUnmounted(() => {
75
- if (!video.value || currentContext?.value !== 'slide')
76
- return
77
- video.value?.removeEventListener('play', onPlay)
78
- video.value?.removeEventListener('ended', onEnded)
79
- })
80
62
  </script>
81
63
 
82
64
  <template>
83
- <video ref="video">
65
+ <video
66
+ ref="video"
67
+ :poster="noPlay ? printPoster : props.poster"
68
+ :controls="!noPlay && props.controls"
69
+ @play="played = true"
70
+ @loadedmetadata="onLoadedMetadata"
71
+ >
84
72
  <slot />
85
73
  </video>
86
74
  </template>
@@ -7,7 +7,7 @@
7
7
  import { toArray } from '@antfu/utils'
8
8
  import type { VNode, VNodeArrayChildren } from 'vue'
9
9
  import { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'
10
- import { normalizeAtProp } from '../logic/utils'
10
+ import { normalizeAtValue } from '../composables/useClicks'
11
11
  import VClickGap from './VClickGap.vue'
12
12
 
13
13
  const listTags = ['ul', 'ol']
@@ -37,7 +37,12 @@ export default defineComponent({
37
37
  },
38
38
  render() {
39
39
  const every = +this.every
40
- const [isRelative, at] = normalizeAtProp(this.at)
40
+ const at = normalizeAtValue(this.at)
41
+ const isRelative = typeof at === 'string'
42
+ if (typeof at !== 'string' && typeof at !== 'number') {
43
+ console.warn('[slidev] Invalid at prop for v-clicks component:', at)
44
+ return
45
+ }
41
46
 
42
47
  const click = resolveDirective('click')!
43
48
 
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, onUnmounted } from 'vue'
3
+ import type { DragElementMarkdownSource } from '../composables/useDragElements'
4
+ import { useDragElement } from '../composables/useDragElements'
5
+
6
+ const props = defineProps<{
7
+ pos?: string
8
+ markdownSource?: DragElementMarkdownSource
9
+ }>()
10
+
11
+ const { id, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)
12
+
13
+ onMounted(mounted)
14
+ onUnmounted(unmounted)
15
+ </script>
16
+
17
+ <template>
18
+ <div
19
+ ref="container"
20
+ :data-drag-id="id"
21
+ :style="containerStyle"
22
+ class="p-1"
23
+ @dblclick="startDragging"
24
+ >
25
+ <slot />
26
+ </div>
27
+ </template>
@@ -1,67 +1,115 @@
1
1
  import { clamp, sum } from '@antfu/utils'
2
- import type { ClicksContext, SlideRoute } from '@slidev/types'
2
+ import type { ClicksContext, NormalizedAtValue, RawAtValue, SlideRoute } from '@slidev/types'
3
3
  import type { Ref } from 'vue'
4
- import { ref, shallowReactive } from 'vue'
5
- import { normalizeAtProp } from '../logic/utils'
4
+ import { computed, ref, shallowReactive } from 'vue'
6
5
  import { routeForceRefresh } from '../logic/route'
7
6
 
7
+ export function normalizeAtValue(at: RawAtValue): NormalizedAtValue {
8
+ if (at === false || at === 'false')
9
+ return null
10
+ if (at == null || at === true || at === 'true')
11
+ return '+1'
12
+ if (Array.isArray(at))
13
+ return [+at[0], +at[1]]
14
+ if (typeof at === 'string' && '+-'.includes(at[0]))
15
+ return at
16
+ return +at
17
+ }
18
+
8
19
  export function createClicksContextBase(
9
20
  current: Ref<number>,
10
21
  clicksStart = 0,
11
22
  clicksTotalOverrides?: number,
12
23
  ): ClicksContext {
13
- const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
14
- const map: ClicksContext['map'] = shallowReactive(new Map())
15
-
16
- return {
24
+ const context: ClicksContext = {
17
25
  get current() {
18
26
  // Here we haven't know clicksTotal yet.
19
- return clamp(+current.value, clicksStart, this.total)
27
+ return clamp(+current.value, clicksStart, context.total)
20
28
  },
21
29
  set current(value) {
22
- current.value = clamp(+value, clicksStart, this.total)
30
+ current.value = clamp(+value, clicksStart, context.total)
23
31
  },
24
32
  clicksStart,
25
- relativeOffsets,
26
- map,
33
+ relativeOffsets: new Map(),
34
+ maxMap: shallowReactive(new Map()),
27
35
  onMounted() { },
28
- resolve(at, size = 1) {
29
- const [isRelative, value] = normalizeAtProp(at)
30
- if (isRelative) {
31
- const offset = this.currentOffset
32
- return {
33
- start: offset + value,
34
- end: offset + value + size - 1,
35
- delta: value + size - 1,
36
- }
36
+ calculateSince(at, size = 1) {
37
+ let start: number, max: number, delta: number
38
+ if (typeof at === 'string') {
39
+ const offset = context.currentOffset
40
+ const value = +at
41
+ start = offset + value
42
+ max = offset + value + size - 1
43
+ delta = value + size - 1
37
44
  }
38
45
  else {
39
- return {
40
- start: value,
41
- end: value + size - 1,
42
- delta: 0,
43
- }
46
+ start = at
47
+ max = at + size - 1
48
+ delta = 0
49
+ }
50
+ return {
51
+ start,
52
+ end: +Number.POSITIVE_INFINITY,
53
+ max,
54
+ delta,
55
+ isCurrent: computed(() => context.current === start),
56
+ isActive: computed(() => context.current >= start),
44
57
  }
45
58
  },
46
- register(el, resolved) {
47
- relativeOffsets.set(el, resolved.delta)
48
- map.set(el, resolved)
59
+ calculateRange([a, b]) {
60
+ let start: number, end: number, delta: number
61
+ if (typeof a === 'string') {
62
+ const offset = context.currentOffset
63
+ start = offset + +a
64
+ delta = +a
65
+ }
66
+ else {
67
+ start = a
68
+ delta = 0
69
+ }
70
+ if (typeof b === 'string') {
71
+ end = start + +b
72
+ delta += +b
73
+ }
74
+ else {
75
+ end = b
76
+ }
77
+ return {
78
+ start,
79
+ end,
80
+ max: end,
81
+ delta,
82
+ isCurrent: computed(() => context.current === start),
83
+ isActive: computed(() => start <= context.current && context.current < end),
84
+ }
85
+ },
86
+ calculate(at) {
87
+ if (at == null)
88
+ return null
89
+ if (Array.isArray(at))
90
+ return context.calculateRange(at)
91
+ return context.calculateSince(at)
92
+ },
93
+ register(el, { delta, max }) {
94
+ context.relativeOffsets.set(el, delta)
95
+ context.maxMap.set(el, max)
49
96
  },
50
97
  unregister(el) {
51
- relativeOffsets.delete(el)
52
- map.delete(el)
98
+ context.relativeOffsets.delete(el)
99
+ context.maxMap.delete(el)
53
100
  },
54
101
  get currentOffset() {
55
102
  // eslint-disable-next-line no-unused-expressions
56
103
  routeForceRefresh.value
57
- return sum(...relativeOffsets.values())
104
+ return sum(...context.relativeOffsets.values())
58
105
  },
59
106
  get total() {
60
107
  // eslint-disable-next-line no-unused-expressions
61
108
  routeForceRefresh.value
62
- return clicksTotalOverrides ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
109
+ return clicksTotalOverrides ?? Math.max(0, ...context.maxMap.values())
63
110
  },
64
111
  }
112
+ return context
65
113
  }
66
114
 
67
115
  export function createFixedClicks(