vite-plugin-vue-devtools 0.4.1 → 0.4.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 (32) hide show
  1. package/dist/client/assets/{IframeView.vue_vue_type_script_setup_true_lang-07716658.js → IframeView.vue_vue_type_script_setup_true_lang-74037107.js} +1 -1
  2. package/dist/client/assets/{StateFields.vue_vue_type_script_setup_true_lang-321ed214.js → StateFields.vue_vue_type_script_setup_true_lang-d4ab0b1c.js} +4 -4
  3. package/dist/client/assets/{VIcon.vue_vue_type_script_setup_true_lang-22026580.js → VIcon.vue_vue_type_script_setup_true_lang-182ad7ba.js} +1 -1
  4. package/dist/client/assets/{VIconButton.vue_vue_type_script_setup_true_lang-325f26b6.js → VIconButton.vue_vue_type_script_setup_true_lang-65ba3ce7.js} +2 -2
  5. package/dist/client/assets/{VIconTitle.vue_vue_type_script_setup_true_lang-93fee085.js → VIconTitle.vue_vue_type_script_setup_true_lang-e7d05a9b.js} +1 -1
  6. package/dist/client/assets/{VPanelGrids-ea1fc6cc.js → VPanelGrids-5c246b82.js} +1 -1
  7. package/dist/client/assets/{VSectionBlock-22944be5.js → VSectionBlock-4dc28a55.js} +3 -3
  8. package/dist/client/assets/{VTextInput.vue_vue_type_script_setup_true_lang-4c7997d1.js → VTextInput.vue_vue_type_script_setup_true_lang-3230456d.js} +2 -2
  9. package/dist/client/assets/{__eyedropper-1e766935.js → __eyedropper-bae9b7d5.js} +2 -2
  10. package/dist/client/assets/{assets-ed629e80.js → assets-90c48482.js} +8 -8
  11. package/dist/client/assets/{component-docs-df825434.js → component-docs-04e01283.js} +5 -5
  12. package/dist/client/assets/{components-8882d725.js → components-087722a2.js} +7 -7
  13. package/dist/client/assets/{documentations-37907b83.js → documentations-5d8a487f.js} +3 -3
  14. package/dist/client/assets/{graph-7d844661.js → graph-417404cd.js} +2 -2
  15. package/dist/client/assets/{index-9875586e.js → index-7aa243db.js} +14 -14
  16. package/dist/client/assets/{index-dc7de915.js → index-a030e660.js} +1 -1
  17. package/dist/client/assets/{inspect-952aeb0a.js → inspect-81d9d7d5.js} +3 -3
  18. package/dist/client/assets/{npm-ec7132af.js → npm-51b1ac71.js} +6 -6
  19. package/dist/client/assets/{overview-eb213ef4.js → overview-b953a57e.js} +5 -5
  20. package/dist/client/assets/{pages-acabe249.js → pages-33b0d419.js} +5 -5
  21. package/dist/client/assets/{pinia-9bb95365.js → pinia-47d84d80.js} +6 -6
  22. package/dist/client/assets/{routes-c562ae44.js → routes-c5afa033.js} +7 -7
  23. package/dist/client/assets/{rpc-a89d2eca.js → rpc-e7b3b2f0.js} +1 -1
  24. package/dist/client/assets/{settings-e845812f.js → settings-80569005.js} +3 -3
  25. package/dist/client/assets/{splitpanes.es-b88bf7fe.js → splitpanes.es-b6266964.js} +1 -1
  26. package/dist/client/assets/{timeline-0f12b727.js → timeline-d3b0ddc3.js} +7 -7
  27. package/dist/client/index.html +1 -1
  28. package/package.json +2 -1
  29. package/src/node/views/FrameBox.vue +212 -0
  30. package/src/node/views/Main.vue +382 -0
  31. package/src/node/views/composables.ts +412 -0
  32. package/src/node/views/utils.ts +204 -0
@@ -0,0 +1,412 @@
1
+ import { computed, onMounted, reactive, ref, shallowRef, watchEffect } from 'vue'
2
+ import type { CSSProperties, Ref } from 'vue'
3
+ import { clamp, useObjectStorage, useScreenSafeArea, useWindowEventListener } from './utils'
4
+
5
+ interface DevToolsFrameState {
6
+ width: number
7
+ height: number
8
+ top: number
9
+ left: number
10
+ open: boolean
11
+ route: string
12
+ position: string
13
+ isFirstVisit: boolean
14
+ closeOnOutsideClick: boolean
15
+ }
16
+
17
+ // ---- state ----
18
+ export const PANEL_PADDING = 10
19
+ export const PANEL_MIN = 20
20
+ export const PANEL_MAX = 100
21
+
22
+ export const popupWindow = shallowRef<Window | null>(null)
23
+
24
+ export const state = useObjectStorage<DevToolsFrameState>('__vue-devtools-frame-state__', {
25
+ width: 80,
26
+ height: 60,
27
+ top: 0,
28
+ left: 50,
29
+ open: false,
30
+ route: '/',
31
+ position: 'bottom',
32
+ isFirstVisit: true,
33
+ closeOnOutsideClick: false,
34
+ })
35
+
36
+ // ---- useIframe ----
37
+ export function useIframe(clientUrl: string, onLoad: () => void) {
38
+ const iframe = ref<HTMLIFrameElement>()
39
+ function getIframe() {
40
+ if (iframe.value)
41
+ return iframe.value
42
+ iframe.value = document.createElement('iframe')
43
+ iframe.value.id = 'vue-devtools-iframe'
44
+ iframe.value.src = clientUrl
45
+ iframe.value.setAttribute('data-v-inspector-ignore', 'true')
46
+ iframe.value.onload = onLoad
47
+ return iframe.value
48
+ }
49
+
50
+ return {
51
+ getIframe,
52
+ iframe,
53
+ }
54
+ }
55
+
56
+ // ---- useInspector ----
57
+ export function useInspector() {
58
+ const inspectorEnabled = ref(false)
59
+ const inspectorLoaded = ref(false)
60
+
61
+ const enable = () => {
62
+ window.__VUE_INSPECTOR__?.enable()
63
+ inspectorEnabled.value = true
64
+ }
65
+
66
+ const disable = () => {
67
+ window.__VUE_INSPECTOR__?.disable()
68
+ inspectorEnabled.value = false
69
+ }
70
+
71
+ const setupInspector = () => {
72
+ const componentInspector = window.__VUE_INSPECTOR__
73
+ if (componentInspector) {
74
+ const _openInEditor = componentInspector.openInEditor
75
+ componentInspector.openInEditor = async (...params: any[]) => {
76
+ disable()
77
+ _openInEditor(...params)
78
+ }
79
+ }
80
+ }
81
+
82
+ const waitForInspectorInit = () => {
83
+ const timer = setInterval(() => {
84
+ if (window.__VUE_INSPECTOR__) {
85
+ clearInterval(timer)
86
+ inspectorLoaded.value = true
87
+ setupInspector()
88
+ }
89
+ }, 30)
90
+ }
91
+
92
+ useWindowEventListener('keydown', (e: KeyboardEvent) => {
93
+ if (!inspectorEnabled.value || !inspectorLoaded.value)
94
+ return
95
+ if (e.key === 'Escape')
96
+ disable()
97
+ })
98
+
99
+ waitForInspectorInit()
100
+
101
+ return {
102
+ toggleInspector() {
103
+ if (!inspectorLoaded.value)
104
+ return
105
+ inspectorEnabled.value ? disable() : enable()
106
+ },
107
+ inspectorEnabled,
108
+ enableInspector: enable,
109
+ disableInspector: disable,
110
+ setupInspector,
111
+ inspectorLoaded,
112
+ }
113
+ }
114
+
115
+ // ---- usePanelVisible ----
116
+ export function usePanelVisible() {
117
+ const visible = computed({
118
+ get() {
119
+ return state.value.open
120
+ },
121
+ set(value) {
122
+ state.value.open = value
123
+ },
124
+ })
125
+
126
+ const toggleVisible = () => {
127
+ visible.value = !visible.value
128
+ }
129
+
130
+ const closePanel = () => {
131
+ if (!visible.value)
132
+ return
133
+ visible.value = false
134
+ if (popupWindow.value) {
135
+ try {
136
+ popupWindow.value.close()
137
+ }
138
+ catch { }
139
+ popupWindow.value = null
140
+ }
141
+ }
142
+
143
+ onMounted(() => {
144
+ useWindowEventListener('keydown', (e) => {
145
+ // cmd + shift + D in <macOS>
146
+ // alt + shift + D in <Windows>
147
+ if (e.code === 'KeyD' && e.altKey && e.shiftKey)
148
+ toggleVisible()
149
+ })
150
+ })
151
+
152
+ return {
153
+ panelVisible: visible,
154
+ togglePanelVisible: toggleVisible,
155
+ closePanel,
156
+ }
157
+ }
158
+
159
+ // ---- usePipMode ----
160
+ export function usePiPMode(iframeGetter: () => HTMLIFrameElement | undefined, hook: object) {
161
+ // Experimental: Picture-in-Picture mode
162
+ // https://developer.chrome.com/docs/web-platform/document-picture-in-picture/
163
+ // @ts-expect-error experimental API
164
+ const documentPictureInPicture = window.documentPictureInPicture
165
+ async function popup() {
166
+ const iframe = iframeGetter()
167
+ const pip = popupWindow.value = await documentPictureInPicture.requestWindow({
168
+ width: Math.round(window.innerWidth * state.value.width / 100),
169
+ height: Math.round(window.innerHeight * state.value.height / 100),
170
+ })
171
+ const style = pip.document.createElement('style')
172
+ style.innerHTML = `
173
+ body {
174
+ margin: 0;
175
+ padding: 0;
176
+ }
177
+ iframe {
178
+ width: 100vw;
179
+ height: 100vh;
180
+ border: none;
181
+ outline: none;
182
+ }
183
+ `
184
+ pip.__VUE_DEVTOOLS_GLOBAL_HOOK__ = hook
185
+ pip.__VUE_DEVTOOLS_IS_POPUP__ = true
186
+ pip.document.title = 'Vue DevTools'
187
+ pip.document.head.appendChild(style)
188
+ pip.document.body.appendChild(iframe)
189
+ pip.addEventListener('resize', () => {
190
+ state.value.width = Math.round(pip.innerWidth / window.innerWidth * 100)
191
+ state.value.height = Math.round(pip.innerHeight / window.innerHeight * 100)
192
+ })
193
+ pip.addEventListener('pagehide', () => {
194
+ popupWindow.value = null
195
+ pip.close()
196
+ })
197
+ }
198
+ return {
199
+ popup,
200
+ }
201
+ }
202
+
203
+ // ---- usePosition ----
204
+ const SNAP_THRESHOLD = 2
205
+
206
+ function snapToPoints(value: number) {
207
+ if (value < 5)
208
+ return 0
209
+ if (value > 95)
210
+ return 100
211
+ if (Math.abs(value - 50) < SNAP_THRESHOLD)
212
+ return 50
213
+ return value
214
+ }
215
+
216
+ export function usePosition(panelEl: Ref<HTMLElement | undefined>) {
217
+ const isDragging = ref(false)
218
+ const draggingOffset = reactive({ x: 0, y: 0 })
219
+ const windowSize = reactive({ width: 0, height: 0 })
220
+ const mousePosition = reactive({ x: 0, y: 0 })
221
+ const panelMargins = reactive({
222
+ left: 10,
223
+ top: 10,
224
+ right: 10,
225
+ bottom: 10,
226
+ })
227
+
228
+ const safeArea = useScreenSafeArea()
229
+
230
+ watchEffect(() => {
231
+ panelMargins.left = safeArea.left.value + 10
232
+ panelMargins.top = safeArea.top.value + 10
233
+ panelMargins.right = safeArea.right.value + 10
234
+ panelMargins.bottom = safeArea.bottom.value + 10
235
+ })
236
+
237
+ const onPointerDown = (e: PointerEvent) => {
238
+ isDragging.value = true
239
+ const { left, top, width, height } = panelEl.value!.getBoundingClientRect()
240
+ draggingOffset.x = e.clientX - left - width / 2
241
+ draggingOffset.y = e.clientY - top - height / 2
242
+ }
243
+
244
+ const setWindowSize = () => {
245
+ windowSize.width = window.innerWidth
246
+ windowSize.height = window.innerHeight
247
+ }
248
+
249
+ onMounted(() => {
250
+ setWindowSize()
251
+
252
+ useWindowEventListener('resize', () => {
253
+ setWindowSize()
254
+ })
255
+
256
+ useWindowEventListener('pointerup', () => {
257
+ isDragging.value = false
258
+ })
259
+ useWindowEventListener('pointerleave', () => {
260
+ isDragging.value = false
261
+ })
262
+ useWindowEventListener('pointermove', (e) => {
263
+ if (!isDragging.value)
264
+ return
265
+
266
+ const centerX = windowSize.width / 2
267
+ const centerY = windowSize.height / 2
268
+
269
+ const x = e.clientX - draggingOffset.x
270
+ const y = e.clientY - draggingOffset.y
271
+
272
+ mousePosition.x = x
273
+ mousePosition.y = y
274
+
275
+ // Get position
276
+ const deg = Math.atan2(y - centerY, x - centerX)
277
+ const HORIZONTAL_MARGIN = 70
278
+ const TL = Math.atan2(0 - centerY + HORIZONTAL_MARGIN, 0 - centerX)
279
+ const TR = Math.atan2(0 - centerY + HORIZONTAL_MARGIN, windowSize.width - centerX)
280
+ const BL = Math.atan2(windowSize.height - HORIZONTAL_MARGIN - centerY, 0 - centerX)
281
+ const BR = Math.atan2(windowSize.height - HORIZONTAL_MARGIN - centerY, windowSize.width - centerX)
282
+
283
+ state.value.position = (deg >= TL && deg <= TR)
284
+ ? 'top'
285
+ : (deg >= TR && deg <= BR)
286
+ ? 'right'
287
+ : (deg >= BR && deg <= BL)
288
+ ? 'bottom'
289
+ : 'left'
290
+
291
+ state.value.left = snapToPoints(x / windowSize.width * 100)
292
+ state.value.top = snapToPoints(y / windowSize.height * 100)
293
+ })
294
+ })
295
+
296
+ const isVertical = computed(() => state.value.position === 'left' || state.value.position === 'right')
297
+
298
+ const anchorPos = computed(() => {
299
+ const halfWidth = (panelEl.value?.clientWidth || 0) / 2
300
+ const halfHeight = (panelEl.value?.clientHeight || 0) / 2
301
+
302
+ const left = state.value.left * windowSize.width / 100
303
+ const top = state.value.top * windowSize.height / 100
304
+
305
+ switch (state.value.position) {
306
+ case 'top':
307
+ return {
308
+ left: clamp(left, halfWidth + panelMargins.left, windowSize.width - halfWidth - panelMargins.right),
309
+ top: panelMargins.top + halfHeight,
310
+ }
311
+ case 'right':
312
+ return {
313
+ left: windowSize.width - panelMargins.right - halfHeight,
314
+ top: clamp(top, halfWidth + panelMargins.top, windowSize.height - halfWidth - panelMargins.bottom),
315
+ }
316
+ case 'left':
317
+ return {
318
+ left: panelMargins.left + halfHeight,
319
+ top: clamp(top, halfWidth + panelMargins.top, windowSize.height - halfWidth - panelMargins.bottom),
320
+ }
321
+ case 'bottom':
322
+ default:
323
+ return {
324
+ left: clamp(left, halfWidth + panelMargins.left, windowSize.width - halfWidth - panelMargins.right),
325
+ top: windowSize.height - panelMargins.bottom - halfHeight,
326
+ }
327
+ }
328
+ })
329
+
330
+ const anchorStyle = computed(() => ({ left: `${anchorPos.value.left}px`, top: `${anchorPos.value.top}px` }))
331
+
332
+ const iframeStyle = computed(() => {
333
+ // eslint-disable-next-line no-unused-expressions, no-sequences
334
+ mousePosition.x, mousePosition.y
335
+
336
+ const halfHeight = (panelEl.value?.clientHeight || 0) / 2
337
+
338
+ const frameMargin = {
339
+ left: panelMargins.left + halfHeight,
340
+ top: panelMargins.top + halfHeight,
341
+ right: panelMargins.right + halfHeight,
342
+ bottom: panelMargins.bottom + halfHeight,
343
+ }
344
+
345
+ const marginHorizontal = frameMargin.left + frameMargin.right
346
+ const marginVertical = frameMargin.top + frameMargin.bottom
347
+
348
+ const maxWidth = windowSize.width - marginHorizontal
349
+ const maxHeight = windowSize.height - marginVertical
350
+
351
+ const style: CSSProperties = {
352
+ zIndex: -1,
353
+ pointerEvents: isDragging.value ? 'none' : 'auto',
354
+ width: `min(${state.value.width}vw, calc(100vw - ${marginHorizontal}px))`,
355
+ height: `min(${state.value.height}vh, calc(100vh - ${marginVertical}px))`,
356
+ }
357
+
358
+ const anchor = anchorPos.value
359
+ const width = Math.min(maxWidth, state.value.width * windowSize.width / 100)
360
+ const height = Math.min(maxHeight, state.value.height * windowSize.height / 100)
361
+
362
+ const anchorX = anchor?.left || 0
363
+ const anchorY = anchor?.top || 0
364
+
365
+ switch (state.value.position) {
366
+ case 'top':
367
+ case 'bottom':
368
+ style.left = 0
369
+ style.transform = 'translate(-50%, 0)'
370
+ if ((anchorX - frameMargin.left) < width / 2)
371
+ style.left = `${width / 2 - anchorX + frameMargin.left}px`
372
+ else if ((windowSize.width - anchorX - frameMargin.right) < width / 2)
373
+ style.left = `${windowSize.width - anchorX - width / 2 - frameMargin.right}px`
374
+ break
375
+ case 'right':
376
+ case 'left':
377
+ style.top = 0
378
+ style.transform = 'translate(0, -50%)'
379
+ if ((anchorY - frameMargin.top) < height / 2)
380
+ style.top = `${height / 2 - anchorY + frameMargin.top}px`
381
+ else if ((windowSize.height - anchorY - frameMargin.bottom) < height / 2)
382
+ style.top = `${windowSize.height - anchorY - height / 2 - frameMargin.bottom}px`
383
+ break
384
+ }
385
+
386
+ switch (state.value.position) {
387
+ case 'top':
388
+ style.top = 0
389
+ break
390
+ case 'right':
391
+ style.right = 0
392
+ break
393
+ case 'left':
394
+ style.left = 0
395
+ break
396
+ case 'bottom':
397
+ default:
398
+ style.bottom = 0
399
+ break
400
+ }
401
+
402
+ return style
403
+ })
404
+
405
+ return {
406
+ isDragging,
407
+ onPointerDown,
408
+ isVertical,
409
+ anchorStyle,
410
+ iframeStyle,
411
+ }
412
+ }
@@ -0,0 +1,204 @@
1
+ import {
2
+ computed, getCurrentScope, onScopeDispose, ref, toRef, watch, watchEffect,
3
+ } from 'vue'
4
+ import type { Ref } from 'vue'
5
+
6
+ export function tryOnScopeDispose(fn: () => void) {
7
+ const scope = getCurrentScope()
8
+ if (scope)
9
+ onScopeDispose(fn)
10
+ }
11
+
12
+ // ---- storage ----
13
+ export function useObjectStorage<T>(key: string, initial: T, readonly = false): Ref<T> {
14
+ const raw = localStorage.getItem(key)
15
+ const data = ref(raw ? JSON.parse(raw) : initial)
16
+
17
+ for (const key in initial) {
18
+ if (data.value[key] === undefined)
19
+ data.value[key] = initial[key]
20
+ }
21
+
22
+ let updating = false
23
+ let wrote = ''
24
+
25
+ if (!readonly) {
26
+ watch(data, (value) => {
27
+ if (updating)
28
+ return
29
+ wrote = JSON.stringify(value)
30
+ localStorage.setItem(key, wrote)
31
+ }, { deep: true, flush: 'post' })
32
+ }
33
+
34
+ useWindowEventListener('storage', (e: StorageEvent) => {
35
+ if (e.key === key && e.newValue && e.newValue !== wrote) {
36
+ updating = true
37
+ data.value = JSON.parse(e.newValue)
38
+ updating = false
39
+ }
40
+ })
41
+
42
+ return data
43
+ }
44
+
45
+ export function useStorage<T>(key: string, initial: T, readonly = false) {
46
+ const raw = localStorage.getItem(key)
47
+ const data = ref(raw || initial)
48
+
49
+ let updating = false
50
+ let wrote = ''
51
+
52
+ if (!readonly) {
53
+ watch(data, (value) => {
54
+ if (updating)
55
+ return
56
+ wrote = `${value}`
57
+ localStorage.setItem(key, wrote)
58
+ }, { deep: true, flush: 'post' })
59
+ }
60
+
61
+ useWindowEventListener('storage', (e: StorageEvent) => {
62
+ if (e.key === key && e.newValue && e.newValue !== wrote) {
63
+ updating = true
64
+ data.value = e.newValue
65
+ updating = false
66
+ }
67
+ })
68
+
69
+ return data
70
+ }
71
+
72
+ // ---- index ----
73
+ export const checkIsSafari = () => navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')
74
+ export function clamp(value: number, min: number, max: number) {
75
+ return Math.min(Math.max(value, min), max)
76
+ }
77
+
78
+ // ---- event ----
79
+ export function useEventListener(
80
+ target: EventTarget,
81
+ type: keyof WindowEventHandlersEventMap,
82
+ listener: EventListenerOrEventListenerObject,
83
+ options?: boolean | AddEventListenerOptions,
84
+ ) {
85
+ target.addEventListener(type, listener, options)
86
+ tryOnScopeDispose(() => target.removeEventListener(type, listener, options))
87
+ }
88
+
89
+ export function useWindowEventListener<K extends keyof WindowEventMap>(
90
+ type: K,
91
+ listener: (this: Window, ev: WindowEventMap[K]) => any,
92
+ options?: boolean | AddEventListenerOptions,
93
+ ) {
94
+ useEventListener(window, type as keyof WindowEventHandlersEventMap, listener as EventListener, options)
95
+ }
96
+
97
+ // ---- screen ----
98
+ const topVarName = '--vite-plugin-vue-devtools-safe-area-top'
99
+ const rightVarName = '--vite-plugin-vue-devtools-devtools-safe-area-right'
100
+ const bottomVarName = '--vite-plugin-vue-devtools-safe-area-bottom'
101
+ const leftVarName = '--vite-plugin-vue-devtools-safe-area-left'
102
+
103
+ /**
104
+ * Reactive `env(safe-area-inset-*)`
105
+ *
106
+ * @see https://vueuse.org/useScreenSafeArea
107
+ */
108
+ export function useScreenSafeArea() {
109
+ const top = ref(0)
110
+ const right = ref(0)
111
+ const bottom = ref(0)
112
+ const left = ref(0)
113
+
114
+ document.documentElement.style.setProperty(topVarName, 'env(safe-area-inset-top, 0px)')
115
+ document.documentElement.style.setProperty(rightVarName, 'env(safe-area-inset-right, 0px)')
116
+ document.documentElement.style.setProperty(bottomVarName, 'env(safe-area-inset-bottom, 0px)')
117
+ document.documentElement.style.setProperty(leftVarName, 'env(safe-area-inset-left, 0px)')
118
+
119
+ update()
120
+ useWindowEventListener('resize', update)
121
+
122
+ function getValue(position: string) {
123
+ return Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue(position)) || 0
124
+ }
125
+
126
+ function update() {
127
+ top.value = getValue(topVarName)
128
+ right.value = getValue(rightVarName)
129
+ bottom.value = getValue(bottomVarName)
130
+ left.value = getValue(leftVarName)
131
+ }
132
+
133
+ return {
134
+ top,
135
+ right,
136
+ bottom,
137
+ left,
138
+ update,
139
+ }
140
+ }
141
+
142
+ // color-scheme
143
+ export const useColorScheme = () => useStorage('vueuse-color-scheme', 'auto', true)
144
+
145
+ /**
146
+ * Reactive Media Query.
147
+ *
148
+ * @see https://vueuse.org/useMediaQuery
149
+ * @param query
150
+ * @param options
151
+ */
152
+ export function useMediaQuery(query: string) {
153
+ const isSupported = () => window && 'matchMedia' in window && typeof window.matchMedia === 'function'
154
+
155
+ let mediaQuery: MediaQueryList | undefined
156
+ const matches = ref(false)
157
+
158
+ const cleanup = () => {
159
+ if (!mediaQuery)
160
+ return
161
+ if ('removeEventListener' in mediaQuery)
162
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
163
+ mediaQuery.removeEventListener('change', update)
164
+ else
165
+ // @ts-expect-error deprecated API
166
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
167
+ mediaQuery.removeListener(update)
168
+ }
169
+
170
+ const update = () => {
171
+ if (!isSupported)
172
+ return
173
+
174
+ cleanup()
175
+
176
+ mediaQuery = window!.matchMedia(toRef(query).value)
177
+ matches.value = !!mediaQuery?.matches
178
+
179
+ if (!mediaQuery)
180
+ return
181
+
182
+ if ('addEventListener' in mediaQuery)
183
+ mediaQuery.addEventListener('change', update)
184
+ else
185
+ // @ts-expect-error deprecated API
186
+ mediaQuery.addListener(update)
187
+ }
188
+ watchEffect(update)
189
+
190
+ tryOnScopeDispose(() => cleanup())
191
+
192
+ return matches
193
+ }
194
+ /**
195
+ * Reactive prefers-color-scheme media query.
196
+ *
197
+ * @see https://vueuse.org/usePreferredColorScheme
198
+ * @param [options]
199
+ */
200
+ export function usePreferredColorScheme() {
201
+ const isDark = useMediaQuery('(prefers-color-scheme: dark)')
202
+
203
+ return computed(() => isDark.value ? 'dark' : 'light')
204
+ }