@slidev/client 0.47.5 → 0.48.0-beta.0

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.
@@ -1,7 +1,6 @@
1
+ import type { ResolvedClicksInfo } from '@slidev/types'
1
2
  import type { App, DirectiveBinding, InjectionKey } from 'vue'
2
- import { watch } from 'vue'
3
- import { remove } from '@antfu/utils'
4
- import { isClicksDisabled } from '../logic/nav'
3
+ import { computed, watchEffect } from 'vue'
5
4
  import {
6
5
  CLASS_VCLICK_CURRENT,
7
6
  CLASS_VCLICK_FADE,
@@ -9,10 +8,7 @@ import {
9
8
  CLASS_VCLICK_HIDDEN_EXP,
10
9
  CLASS_VCLICK_PRIOR,
11
10
  CLASS_VCLICK_TARGET,
12
- injectionClicks,
13
- injectionClicksDisabled,
14
- injectionClicksElements,
15
- injectionOrderMap,
11
+ injectionClicksContext,
16
12
  } from '../constants'
17
13
 
18
14
  function dirInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {
@@ -27,88 +23,36 @@ export default function createDirectives() {
27
23
  name: 'v-click',
28
24
 
29
25
  mounted(el: HTMLElement, dir) {
30
- if (isClicksDisabled.value || dirInject(dir, injectionClicksDisabled)?.value)
26
+ const resolved = resolveClick(el, dir)
27
+ if (resolved == null)
31
28
  return
32
29
 
33
- if (dir.value === false || dir.value === 'false')
34
- return
35
-
36
- const elements = dirInject(dir, injectionClicksElements)
37
- const clicks = dirInject(dir, injectionClicks)
38
- const orderMap = dirInject(dir, injectionOrderMap)
39
-
40
- const hide = dir.modifiers.hide !== false && dir.modifiers.hide != null
41
- const fade = dir.modifiers.fade !== false && dir.modifiers.fade != null
42
-
43
- const CLASS_HIDE = fade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN
44
-
45
- if (elements && !elements?.value?.includes(el))
46
- elements.value.push(el)
30
+ el.classList.toggle(CLASS_VCLICK_TARGET, true)
47
31
 
48
- const prev = elements?.value?.length || 0
32
+ // Expose the resolved clicks info to the element to make it easier to understand and debug
33
+ const clicks = Array.isArray(resolved.clicks) ? resolved.clicks : [resolved.clicks, undefined]
34
+ el.dataset.slidevClicksStart = String(clicks[0])
35
+ if (clicks[1] != null)
36
+ el.dataset.slidevClicksEnd = String(clicks[1])
49
37
 
50
- resolveDirValue(dir, prev)
38
+ watchEffect(() => {
39
+ const active = resolved.isActive.value
40
+ const current = resolved.isCurrent.value
41
+ const prior = active && !current
51
42
 
52
- // If orderMap didn't have dir.value aka the order key, then initialize it.
53
- // If key exists, then move current element to the first of order array to
54
- // make sure the v-after set correct CLASS_VCLICK_CURRENT state.
55
- if (!orderMap?.value.has(dir.value)) {
56
- orderMap?.value.set(dir.value, [el])
57
- }
58
- else {
59
- if (!orderMap?.value.get(dir.value)?.includes(el)) {
60
- const afterClicks = orderMap?.value.get(dir.value) || []
61
- orderMap?.value.set(dir.value, [el].concat(afterClicks))
43
+ if (resolved.flagHide) {
44
+ el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)
45
+ el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)
62
46
  }
63
- }
64
-
65
- el?.classList.toggle(CLASS_VCLICK_TARGET, true)
66
-
67
- if (clicks) {
68
- watch(
69
- clicks,
70
- () => {
71
- const c = clicks?.value ?? 0
72
- const show = dir.value != null
73
- ? Array.isArray(dir.value)
74
- ? c >= dir.value[0] && c < dir.value[1]
75
- : c >= dir.value
76
- : c > prev
77
-
78
- if (!el.classList.contains(CLASS_VCLICK_HIDDEN_EXP))
79
- el.classList.toggle(CLASS_HIDE, !show)
80
-
81
- if (hide !== false && hide !== undefined)
82
- el.classList.toggle(CLASS_HIDE, show)
83
-
84
- // Reset CLASS_VCLICK_CURRENT to false.
85
- el.classList.toggle(CLASS_VCLICK_CURRENT, false)
86
-
87
- const currentElArray = orderMap?.value.get(c)
88
- currentElArray?.forEach((cEl, idx) => {
89
- // Reset CLASS_VCLICK_PRIOR to false
90
- cEl.classList.toggle(CLASS_VCLICK_PRIOR, false)
91
- // If the element is the last of order array, then set it as
92
- // CLASS_VCLICK_CURRENT state, others set as CLASS_VCLICK_PRIOR state.
93
- if (idx === currentElArray.length - 1)
94
- cEl.classList.toggle(CLASS_VCLICK_CURRENT, true)
95
- else
96
- cEl.classList.toggle(CLASS_VCLICK_PRIOR, true)
97
- })
98
-
99
- if (!el.classList.contains(CLASS_VCLICK_CURRENT))
100
- el.classList.toggle(CLASS_VCLICK_PRIOR, show)
101
- },
102
- { immediate: true },
103
- )
104
- }
105
- },
106
- unmounted(el: HTMLElement, dir) {
107
- el?.classList.toggle(CLASS_VCLICK_TARGET, false)
108
- const elements = dirInject(dir, injectionClicksElements)!
109
- if (elements?.value)
110
- remove(elements.value, el)
47
+ else {
48
+ el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, !active)
49
+ }
50
+
51
+ el.classList.toggle(CLASS_VCLICK_CURRENT, current)
52
+ el.classList.toggle(CLASS_VCLICK_PRIOR, prior)
53
+ })
111
54
  },
55
+ unmounted,
112
56
  })
113
57
 
114
58
  app.directive('after', {
@@ -116,57 +60,30 @@ export default function createDirectives() {
116
60
  name: 'v-after',
117
61
 
118
62
  mounted(el: HTMLElement, dir) {
119
- if (isClicksDisabled.value || dirInject(dir, injectionClicksDisabled)?.value)
63
+ const resolved = resolveClick(el, dir, true)
64
+ if (resolved == null)
120
65
  return
121
66
 
122
- if (dir.value === false || dir.value === 'false')
123
- return
67
+ el.classList.toggle(CLASS_VCLICK_TARGET, true)
124
68
 
125
- const elements = dirInject(dir, injectionClicksElements)
126
- const clicks = dirInject(dir, injectionClicks)
127
- const orderMap = dirInject(dir, injectionOrderMap)
128
-
129
- const prev = elements?.value.length || 0
130
-
131
- resolveDirValue(dir, prev)
132
-
133
- // If a v-click order before v-after is lower than v-after, the order map will
134
- // not contain the key for v-after, so we need to set it first, then move v-after
135
- // to the last of order array.
136
- if (orderMap?.value.has(dir.value))
137
- orderMap?.value.get(dir.value)?.push(el)
138
- else
139
- orderMap?.value.set(dir.value, [el])
140
-
141
- el?.classList.toggle(CLASS_VCLICK_TARGET, true)
142
-
143
- if (clicks) {
144
- watch(
145
- clicks,
146
- () => {
147
- const c = (clicks.value ?? 0)
148
- const show = dir.value != null
149
- ? Array.isArray(dir.value)
150
- ? c >= dir.value[0] && c < dir.value[1]
151
- : c >= dir.value
152
- : c >= prev
153
-
154
- if (!el.classList.contains(CLASS_VCLICK_HIDDEN_EXP))
155
- el.classList.toggle(CLASS_VCLICK_HIDDEN, !show)
156
-
157
- // Reset CLASS_VCLICK_CURRENT to false.
158
- el.classList.toggle(CLASS_VCLICK_CURRENT, false)
159
-
160
- if (!el.classList.contains(CLASS_VCLICK_CURRENT))
161
- el.classList.toggle(CLASS_VCLICK_PRIOR, show)
162
- },
163
- { immediate: true },
164
- )
165
- }
166
- },
167
- unmounted(el: HTMLElement) {
168
- el?.classList.toggle(CLASS_VCLICK_TARGET, true)
69
+ watchEffect(() => {
70
+ const active = resolved.isActive.value
71
+ const current = resolved.isCurrent.value
72
+ const prior = active && !current
73
+
74
+ if (resolved.flagHide) {
75
+ el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)
76
+ el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)
77
+ }
78
+ else {
79
+ el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, !active)
80
+ }
81
+
82
+ el.classList.toggle(CLASS_VCLICK_CURRENT, current)
83
+ el.classList.toggle(CLASS_VCLICK_PRIOR, prior)
84
+ })
169
85
  },
86
+ unmounted,
170
87
  })
171
88
 
172
89
  app.directive('click-hide', {
@@ -174,50 +91,90 @@ export default function createDirectives() {
174
91
  name: 'v-click-hide',
175
92
 
176
93
  mounted(el: HTMLElement, dir) {
177
- if (isClicksDisabled.value || dirInject(dir, injectionClicksDisabled)?.value)
94
+ const resolved = resolveClick(el, dir, false, true)
95
+ if (resolved == null)
178
96
  return
179
97
 
180
- const elements = dirInject(dir, injectionClicksElements)
181
- const clicks = dirInject(dir, injectionClicks)
182
-
183
- const prev = elements?.value?.length || 0
184
-
185
- if (elements && !elements?.value?.includes(el))
186
- elements.value.push(el)
187
-
188
- el?.classList.toggle(CLASS_VCLICK_TARGET, true)
189
-
190
- if (clicks) {
191
- watch(
192
- clicks,
193
- () => {
194
- const c = clicks?.value ?? 0
195
- const hide = dir.value != null
196
- ? c >= dir.value
197
- : c > prev
198
- el.classList.toggle(CLASS_VCLICK_HIDDEN, hide)
199
- el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, hide)
200
- },
201
- { immediate: true },
202
- )
203
- }
204
- },
205
- unmounted(el, dir) {
206
- el?.classList.toggle(CLASS_VCLICK_TARGET, false)
207
- const elements = dirInject(dir, injectionClicksElements)
208
- if (elements?.value)
209
- remove(elements.value, el)
98
+ el.classList.toggle(CLASS_VCLICK_TARGET, true)
99
+
100
+ watchEffect(() => {
101
+ const active = resolved.isActive.value
102
+ const current = resolved.isCurrent.value
103
+ const prior = active && !current
104
+
105
+ el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)
106
+ el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)
107
+
108
+ el.classList.toggle(CLASS_VCLICK_CURRENT, current)
109
+ el.classList.toggle(CLASS_VCLICK_PRIOR, prior)
110
+ })
210
111
  },
112
+ unmounted,
211
113
  })
212
114
  },
213
115
  }
214
116
  }
215
117
 
216
- function resolveDirValue(dir: DirectiveBinding<any>, prev: number) {
217
- // Set default dir.value
218
- if (dir.value == null || dir.value === true || dir.value === 'true')
219
- dir.value = prev
220
- // Relative value starts with '+' o '-'
221
- if (typeof dir.value === 'string' && (dir.value.startsWith('+') || dir.value.startsWith('-')))
222
- dir.value = prev + Number(dir.value)
118
+ function isActive(thisClick: number | [number, number], clicks: number) {
119
+ return Array.isArray(thisClick)
120
+ ? thisClick[0] <= clicks && clicks < thisClick[1]
121
+ : thisClick <= clicks
122
+ }
123
+
124
+ function isCurrent(thisClick: number | [number, number], clicks: number) {
125
+ return Array.isArray(thisClick)
126
+ ? thisClick[0] === clicks
127
+ : thisClick === clicks
128
+ }
129
+
130
+ function resolveClick(el: Element, dir: DirectiveBinding<any>, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
131
+ const ctx = dirInject(dir, injectionClicksContext)?.value
132
+
133
+ if (!el || !ctx || ctx.disabled)
134
+ return null
135
+
136
+ let value = dir.value
137
+
138
+ if (value === false || value === 'false')
139
+ return null
140
+
141
+ flagHide ||= dir.modifiers.hide !== false && dir.modifiers.hide != null
142
+ const flagFade = dir.modifiers.fade !== false && dir.modifiers.fade != null
143
+
144
+ if (clickAfter)
145
+ value = '+0'
146
+ else if (value == null || value === true || value === 'true')
147
+ value = '+1'
148
+
149
+ let delta: number
150
+ let thisClick: number | [number, number]
151
+ let maxClick: number
152
+ if (Array.isArray(value)) {
153
+ // range (absolute)
154
+ delta = 0
155
+ thisClick = value as [number, number]
156
+ maxClick = value[1]
157
+ }
158
+ else {
159
+ ({ start: thisClick, end: maxClick, delta } = ctx.resolve(value))
160
+ }
161
+
162
+ const resolved: ResolvedClicksInfo = {
163
+ max: maxClick,
164
+ clicks: thisClick,
165
+ delta,
166
+ isActive: computed(() => isActive(thisClick, ctx.current)),
167
+ isCurrent: computed(() => isCurrent(thisClick, ctx.current)),
168
+ isShown: computed(() => flagHide ? !isActive(thisClick, ctx.current) : isActive(thisClick, ctx.current)),
169
+ flagFade,
170
+ flagHide,
171
+ }
172
+ ctx.register(el, resolved)
173
+ return resolved
174
+ }
175
+
176
+ function unmounted(el: HTMLElement, dir: DirectiveBinding<any>) {
177
+ el.classList.toggle(CLASS_VCLICK_TARGET, false)
178
+ const ctx = dirInject(dir, injectionClicksContext)?.value
179
+ ctx?.unregister(el)
223
180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
- "version": "0.47.5",
3
+ "version": "0.48.0-beta.0",
4
4
  "description": "Presentation slides for developers",
5
5
  "author": "antfu <anthonyfu117@hotmail.com>",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  "@antfu/utils": "^0.7.7",
23
23
  "@iconify-json/carbon": "^1.1.30",
24
24
  "@iconify-json/ph": "^1.1.11",
25
- "@shikijs/vitepress-twoslash": "^1.1.1",
25
+ "@shikijs/vitepress-twoslash": "^1.1.2",
26
26
  "@unhead/vue": "^1.8.10",
27
27
  "@unocss/reset": "^0.58.5",
28
28
  "@vueuse/core": "^10.7.2",
@@ -44,10 +44,10 @@
44
44
  "recordrtc": "^5.6.2",
45
45
  "resolve": "^1.22.8",
46
46
  "unocss": "^0.58.5",
47
- "vue": "^3.4.18",
47
+ "vue": "^3.4.19",
48
48
  "vue-router": "^4.2.5",
49
- "@slidev/parser": "0.47.5",
50
- "@slidev/types": "0.47.5"
49
+ "@slidev/types": "0.48.0-beta.0",
50
+ "@slidev/parser": "0.48.0-beta.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "vite": "^5.1.1"
package/routes.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
2
2
  import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
3
3
  import type { TransitionGroupProps } from 'vue'
4
+ import type { ClicksContext } from '@slidev/types'
4
5
  import Play from './internals/Play.vue'
5
6
  import Print from './internals/Print.vue'
6
7
 
@@ -101,7 +102,7 @@ declare module 'vue-router' {
101
102
  }
102
103
 
103
104
  // private fields
104
- __clicksElements: HTMLElement[]
105
+ __clicksContext: null | ClicksContext
105
106
  __preloaded?: boolean
106
107
  }
107
108
  }
@@ -9,9 +9,7 @@ import 'codemirror/mode/htmlmixed/htmlmixed'
9
9
  import 'codemirror/addon/display/placeholder'
10
10
  import 'codemirror/lib/codemirror.css'
11
11
 
12
- // eslint-disable-next-line ts/ban-ts-comment
13
- // @ts-expect-error
14
- const CodeMirror: typeof _CodeMirror = _CodeMirror.fromTextArea ? _CodeMirror : globalThis.CodeMirror
12
+ const CodeMirror = _CodeMirror.default ?? ('fromTextArea' in _CodeMirror ? _CodeMirror : globalThis.CodeMirror)
15
13
 
16
14
  export async function useCodeMirror(
17
15
  textarea: Ref<HTMLTextAreaElement | null | undefined>,
package/setup/root.ts CHANGED
@@ -5,7 +5,7 @@ import { nanoid } from 'nanoid'
5
5
  import { configs } from '../env'
6
6
  import { initSharedState, onPatch, patch } from '../state/shared'
7
7
  import { initDrawingState } from '../state/drawings'
8
- import { clicks, currentPage, getPath, isNotesViewer, isPresenter } from '../logic/nav'
8
+ import { clicksContext, currentPage, getPath, isNotesViewer, isPresenter } from '../logic/nav'
9
9
  import { router } from '../routes'
10
10
  import { TRUST_ORIGINS } from '../constants'
11
11
  import { skipTransition } from '../composables/hmr'
@@ -38,11 +38,11 @@ export default function setupRoot() {
38
38
 
39
39
  if (isPresenter.value) {
40
40
  patch('page', +currentPage.value)
41
- patch('clicks', clicks.value)
41
+ patch('clicks', clicksContext.value.current)
42
42
  }
43
43
  else {
44
44
  patch('viewerPage', +currentPage.value)
45
- patch('viewerClicks', clicks.value)
45
+ patch('viewerClicks', clicksContext.value.current)
46
46
  }
47
47
 
48
48
  patch('lastUpdate', {
@@ -52,13 +52,13 @@ export default function setupRoot() {
52
52
  })
53
53
  }
54
54
  router.afterEach(updateSharedState)
55
- watch(clicks, updateSharedState)
55
+ watch(clicksContext, updateSharedState)
56
56
 
57
57
  onPatch((state) => {
58
58
  const routePath = router.currentRoute.value.path
59
59
  if (!routePath.match(/^\/(\d+|presenter)\/?/))
60
60
  return
61
- if (state.lastUpdate?.type === 'presenter' && (+state.page !== +currentPage.value || +clicks.value !== +state.clicks)) {
61
+ if (state.lastUpdate?.type === 'presenter' && (+state.page !== +currentPage.value || +clicksContext.value.current !== +state.clicks)) {
62
62
  skipTransition.value = false
63
63
  router.replace({
64
64
  path: getPath(state.page),
@@ -1,38 +0,0 @@
1
- import type { ComputedRef, WritableComputedRef } from 'vue'
2
- import { computed, nextTick, ref } from 'vue'
3
- import type { RouteRecordRaw } from 'vue-router'
4
- import type { SlidevContextNavClicks } from '../modules/context'
5
- import { rawRoutes, router } from '../routes'
6
-
7
- export function useNavClicks(
8
- clicks: WritableComputedRef<number>,
9
- currentRoute: ComputedRef<RouteRecordRaw | undefined>,
10
- currentPage: ComputedRef<number>,
11
- ): SlidevContextNavClicks {
12
- // force update collected elements when the route is fully resolved
13
- const routeForceRefresh = ref(0)
14
- nextTick(() => {
15
- router.afterEach(async () => {
16
- await nextTick()
17
- routeForceRefresh.value += 1
18
- })
19
- })
20
-
21
- const clicksElements = computed<HTMLElement[]>(() => {
22
- // eslint-disable-next-line no-unused-expressions
23
- routeForceRefresh.value
24
- return currentRoute.value?.meta?.__clicksElements || []
25
- })
26
-
27
- const clicksTotal = computed(() => +(currentRoute.value?.meta?.clicks ?? clicksElements.value.length))
28
-
29
- const hasNext = computed(() => currentPage.value < rawRoutes.length - 1 || clicks.value < clicksTotal.value)
30
- const hasPrev = computed(() => currentPage.value > 1 || clicks.value > 0)
31
- return {
32
- clicks,
33
- clicksElements,
34
- clicksTotal,
35
- hasNext,
36
- hasPrev,
37
- }
38
- }