@rimelight/ui 0.0.36 → 0.0.37
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 +24 -20
- package/src/components/astro/RLAButton.astro +58 -26
- package/src/components/astro/RLACarousel.astro +202 -26
- package/src/components/astro/RLALink.astro +10 -12
- package/src/components/astro/RLALocaleSelector.astro +2 -1
- package/src/components/astro/RLAPopover.astro +99 -5
- package/src/components/astro/RLAScrollToTop.astro +10 -19
- package/src/components/astro/RLASelect.astro +52 -47
- package/src/components/astro/RLASidebar.astro +1 -1
- package/src/components/astro/RLATableOfContents.astro +76 -27
- package/src/components/astro/RLAThemeSelector.astro +1 -0
- package/src/components/astro/RLAToast.astro +2 -2
- package/src/components/vue/RLVScrollToTop.vue +12 -10
- package/src/components/vue/RLVToast.vue +1 -1
- package/src/components/vue/RLVToaster.vue +15 -3
- package/src/config/index.ts +0 -1
- package/src/integrations/index.ts +0 -1
- package/src/integrations/ui.ts +12 -11
- package/src/themes/link-group.theme.ts +1 -1
- package/src/themes/locale-selector.theme.ts +1 -1
- package/src/themes/popover.theme.ts +2 -5
- package/src/themes/select.theme.ts +1 -1
- package/src/themes/toast.theme.ts +9 -3
- package/src/types/components/button.ts +2 -1
- package/src/types/components/link.ts +0 -10
- package/src/types/components/sidebar.ts +1 -1
- package/src/types/components/table-of-contents.ts +5 -0
- package/src/types/components/toast.ts +1 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/merge.ts +62 -0
- package/src/utils/scroll.ts +41 -0
- package/src/utils/toast.ts +37 -0
- package/src/utils/useUi.ts +2 -2
- package/src/components/HttpObservatory.astro +0 -103
- package/src/components/LighthouseScores.astro +0 -204
- package/src/composables/index.ts +0 -4
- package/src/composables/useHeaderStore.ts +0 -14
- package/src/composables/useScrollToTop.ts +0 -62
- package/src/composables/useShortcuts.ts +0 -11
- package/src/composables/useToast.ts +0 -43
- package/src/config/security.ts +0 -227
- package/src/integrations/sri.ts +0 -92
- package/src/middleware/index.ts +0 -1
- package/src/middleware/security.ts +0 -210
|
@@ -21,6 +21,11 @@ export interface TableOfContentsProps {
|
|
|
21
21
|
* @default ".markdown-content"
|
|
22
22
|
*/
|
|
23
23
|
containerSelector?: string
|
|
24
|
+
/**
|
|
25
|
+
* Render as a collapsible dropdown (<details>/<summary>) with a scrollable list and a built-in
|
|
26
|
+
* bottom gap. Intended for small-screen / mobile contexts.
|
|
27
|
+
*/
|
|
28
|
+
collapsible?: boolean
|
|
24
29
|
/**
|
|
25
30
|
* The component the root element should render as.
|
|
26
31
|
*
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function isPlainObject(value: unknown): boolean {
|
|
2
|
+
if (value === null || typeof value !== "object") {
|
|
3
|
+
return false
|
|
4
|
+
}
|
|
5
|
+
const prototype = Object.getPrototypeOf(value)
|
|
6
|
+
|
|
7
|
+
if (
|
|
8
|
+
prototype !== null &&
|
|
9
|
+
prototype !== Object.prototype &&
|
|
10
|
+
Object.getPrototypeOf(prototype) !== null
|
|
11
|
+
) {
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (Symbol.iterator in value) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (Symbol.toStringTag in value) {
|
|
20
|
+
return Object.prototype.toString.call(value) === "[object Module]"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function merge<T extends Record<string, any>>(base: T, defaults: Record<string, any>): T
|
|
27
|
+
|
|
28
|
+
export function merge(
|
|
29
|
+
base: Record<string, any>,
|
|
30
|
+
defaults: Record<string, any>
|
|
31
|
+
): Record<string, any> {
|
|
32
|
+
if (!isPlainObject(defaults)) {
|
|
33
|
+
return merge(base, {})
|
|
34
|
+
}
|
|
35
|
+
if (!isPlainObject(base)) {
|
|
36
|
+
return { ...defaults }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const object = { ...defaults }
|
|
40
|
+
|
|
41
|
+
for (const key of Object.keys(base)) {
|
|
42
|
+
if (key === "__proto__" || key === "constructor") {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const value = base[key]
|
|
47
|
+
|
|
48
|
+
if (value === null || value === undefined) {
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (Array.isArray(value) && Array.isArray(object[key])) {
|
|
53
|
+
object[key] = [...value, ...object[key]]
|
|
54
|
+
} else if (isPlainObject(value) && isPlainObject(object[key])) {
|
|
55
|
+
object[key] = merge(value, object[key])
|
|
56
|
+
} else {
|
|
57
|
+
object[key] = value
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return object
|
|
62
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface ScrollState {
|
|
2
|
+
isVisible: boolean
|
|
3
|
+
scrollPercentage: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function trackScroll(
|
|
7
|
+
options: { threshold?: number } = {},
|
|
8
|
+
onUpdate: (state: ScrollState) => void
|
|
9
|
+
): () => void {
|
|
10
|
+
if (typeof window === "undefined") {
|
|
11
|
+
onUpdate({ isVisible: false, scrollPercentage: 0 })
|
|
12
|
+
return () => {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { threshold = 200 } = options
|
|
16
|
+
|
|
17
|
+
function update() {
|
|
18
|
+
const scrollTop = window.scrollY || document.documentElement.scrollTop
|
|
19
|
+
const scrollHeight = document.documentElement.scrollHeight - window.innerHeight
|
|
20
|
+
onUpdate({
|
|
21
|
+
isVisible: scrollTop > threshold,
|
|
22
|
+
scrollPercentage: scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
window.addEventListener("scroll", update, { passive: true })
|
|
27
|
+
update()
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
window.removeEventListener("scroll", update)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function scrollToTop() {
|
|
35
|
+
if (typeof window !== "undefined") {
|
|
36
|
+
window.scrollTo({
|
|
37
|
+
top: 0,
|
|
38
|
+
behavior: "smooth"
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { atom } from "nanostores"
|
|
2
|
+
import type { ThemeColor } from "../types"
|
|
3
|
+
|
|
4
|
+
export interface Toast {
|
|
5
|
+
id?: string | number
|
|
6
|
+
title?: string
|
|
7
|
+
description?: string
|
|
8
|
+
icon?: string
|
|
9
|
+
color?: ThemeColor
|
|
10
|
+
timeout?: number
|
|
11
|
+
callback?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const toastsStore = atom<Toast[]>([])
|
|
15
|
+
|
|
16
|
+
export function addToast(toast: Toast) {
|
|
17
|
+
const id = toast.id || Date.now()
|
|
18
|
+
const newToast = { ...toast, id }
|
|
19
|
+
toastsStore.set([...toastsStore.get(), newToast])
|
|
20
|
+
|
|
21
|
+
if (toast.timeout !== 0) {
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
removeToast(id)
|
|
24
|
+
}, toast.timeout || 5000)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return newToast
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function removeToast(id: string | number) {
|
|
31
|
+
const currentToasts = toastsStore.get()
|
|
32
|
+
const target = currentToasts.find((t) => t.id === id)
|
|
33
|
+
if (target && target.callback) {
|
|
34
|
+
target.callback()
|
|
35
|
+
}
|
|
36
|
+
toastsStore.set(currentToasts.filter((t) => t.id !== id))
|
|
37
|
+
}
|
package/src/utils/useUi.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { merge } from "./merge"
|
|
2
2
|
import { getUIConfig } from "virtual:rimelight-ui"
|
|
3
3
|
import type { DefaultUIConfig } from "../themes"
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ export function useUi(
|
|
|
8
8
|
): Record<string, string | undefined> {
|
|
9
9
|
const globalConfig = getUIConfig()
|
|
10
10
|
const defaults = globalConfig[componentName] ?? {}
|
|
11
|
-
const merged =
|
|
11
|
+
const merged = merge(uiProp ?? {}, defaults)
|
|
12
12
|
|
|
13
13
|
return Object.fromEntries(
|
|
14
14
|
Object.entries(merged).map(([key, value]) => [
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* HttpObservatory.astro
|
|
4
|
-
* A callout component displaying an HTTP Observatory score
|
|
5
|
-
* Designed to match the authentic Mozilla Observatory report aesthetic
|
|
6
|
-
*/
|
|
7
|
-
export interface HttpObservatoryProps {
|
|
8
|
-
grade?: string;
|
|
9
|
-
score?: number;
|
|
10
|
-
testsPassed?: number;
|
|
11
|
-
totalTests?: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Props = HttpObservatoryProps;
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
grade = "A+",
|
|
18
|
-
score = 145,
|
|
19
|
-
testsPassed = 10,
|
|
20
|
-
totalTests = 10,
|
|
21
|
-
} = Astro.props;
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
<div class="http-obs-container mt-6xl px-2xs sm:px-0">
|
|
25
|
-
<!-- Header -->
|
|
26
|
-
<div class="mb-4xl text-center">
|
|
27
|
-
<h3 class="font-heading text-foreground text-3xl font-bold md:text-4xl">
|
|
28
|
-
HTTP Observatory
|
|
29
|
-
</h3>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div
|
|
33
|
-
class="mx-auto max-w-4xl overflow-hidden rounded-xl border border-border bg-[#181a1b] p-6xl text-white shadow-xl sm:p-4xl"
|
|
34
|
-
>
|
|
35
|
-
<div
|
|
36
|
-
class="flex flex-col items-center gap-4xl sm:flex-row sm:items-start sm:justify-between"
|
|
37
|
-
>
|
|
38
|
-
<!-- Grade & Stats -->
|
|
39
|
-
<div class="flex flex-col items-center gap-4xl sm:flex-row sm:items-center">
|
|
40
|
-
<!-- Grade Box -->
|
|
41
|
-
<div
|
|
42
|
-
class="flex size-6xl shrink-0 items-center p-lg justify-center rounded-lg border-2 border-[#1eac4e] bg-[#1eac4e]/20 text-5xl font-bold text-[#1eac4e] sm:size-5xl sm:text-6xl"
|
|
43
|
-
aria-label={`Grade ${grade}`}
|
|
44
|
-
>
|
|
45
|
-
{grade}
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<!-- Stats -->
|
|
49
|
-
<div class="flex flex-col gap-2xs text-center sm:text-left">
|
|
50
|
-
<a
|
|
51
|
-
href="https://developer.mozilla.org/en-US/observatory/analyze?host=danielmarchi.dev"
|
|
52
|
-
target="_blank"
|
|
53
|
-
rel="noopener noreferrer"
|
|
54
|
-
class="text-sm font-semibold text-[#4fa9ff] hover:underline sm:text-base"
|
|
55
|
-
>
|
|
56
|
-
Run the scan.
|
|
57
|
-
</a>
|
|
58
|
-
|
|
59
|
-
<div class="flex flex-col gap-2xs font-medium text-gray-200">
|
|
60
|
-
<p>
|
|
61
|
-
<span
|
|
62
|
-
class="text-white decoration-white/40 underline-offset-4 hover:underline"
|
|
63
|
-
>Score</span
|
|
64
|
-
>:
|
|
65
|
-
<span class="text-white">{score} / 100</span>
|
|
66
|
-
</p>
|
|
67
|
-
<p>
|
|
68
|
-
<span
|
|
69
|
-
class="text-white decoration-white/40 underline-offset-4 hover:underline"
|
|
70
|
-
>Tests Passed</span
|
|
71
|
-
>:
|
|
72
|
-
<span class="text-white">{testsPassed} / {totalTests}</span>
|
|
73
|
-
</p>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<style>
|
|
82
|
-
.http-obs-container {
|
|
83
|
-
animation: fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
84
|
-
animation-delay: 0.4s;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@keyframes fade-up {
|
|
88
|
-
from {
|
|
89
|
-
opacity: 0;
|
|
90
|
-
transform: translateY(20px);
|
|
91
|
-
}
|
|
92
|
-
to {
|
|
93
|
-
opacity: 1;
|
|
94
|
-
transform: translateY(0);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@media (prefers-reduced-motion: reduce) {
|
|
99
|
-
.http-obs-container {
|
|
100
|
-
animation: none;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
</style>
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* LighthouseScores.astro
|
|
4
|
-
* A callout component displaying perfect Lighthouse scores
|
|
5
|
-
* Designed to match the authentic Lighthouse report aesthetic
|
|
6
|
-
*/
|
|
7
|
-
export interface LighthouseScore {
|
|
8
|
-
label: string;
|
|
9
|
-
value: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface LighthouseScoresProps {
|
|
13
|
-
performance?: number;
|
|
14
|
-
accessibility?: number;
|
|
15
|
-
bestPractices?: number;
|
|
16
|
-
seo?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
performance = 100,
|
|
21
|
-
accessibility = 100,
|
|
22
|
-
bestPractices = 100,
|
|
23
|
-
seo = 100,
|
|
24
|
-
} = Astro.props;
|
|
25
|
-
|
|
26
|
-
const scores = [
|
|
27
|
-
{ label: 'Performance', value: performance },
|
|
28
|
-
{ label: 'Accessibility', value: accessibility },
|
|
29
|
-
{ label: 'Best Practices', value: bestPractices },
|
|
30
|
-
{ label: 'SEO', value: seo },
|
|
31
|
-
];
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
<div class="lighthouse-container">
|
|
35
|
-
<!-- Header -->
|
|
36
|
-
<div class="mb-4xl text-center">
|
|
37
|
-
<h3 class="font-heading text-foreground text-3xl font-bold md:text-4xl">
|
|
38
|
-
Lighthouse Report
|
|
39
|
-
</h3>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<!-- Scores Grid -->
|
|
43
|
-
<div
|
|
44
|
-
class="lighthouse-scores mx-auto grid max-w-2xl grid-cols-2 gap-4xl sm:grid-cols-4"
|
|
45
|
-
role="list"
|
|
46
|
-
aria-label="Lighthouse scores"
|
|
47
|
-
>
|
|
48
|
-
{
|
|
49
|
-
scores.map((score) => {
|
|
50
|
-
const radius = 54;
|
|
51
|
-
const circumference = 2 * Math.PI * radius;
|
|
52
|
-
const offset = circumference - (score.value / 100) * circumference;
|
|
53
|
-
const color = score.value >= 90 ? "#0cce6b" : score.value >= 50 ? "#ffa400" : "#ff4e42";
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div
|
|
57
|
-
class="lighthouse-score group flex flex-col items-center"
|
|
58
|
-
role="listitem"
|
|
59
|
-
>
|
|
60
|
-
<div class="relative">
|
|
61
|
-
<svg
|
|
62
|
-
class="lighthouse-gauge"
|
|
63
|
-
width="120"
|
|
64
|
-
height="120"
|
|
65
|
-
viewBox="0 0 120 120"
|
|
66
|
-
aria-hidden="true"
|
|
67
|
-
style="width: 96px; height: 96px;"
|
|
68
|
-
>
|
|
69
|
-
<style>
|
|
70
|
-
@media (min-width: 640px) {
|
|
71
|
-
.lighthouse-gauge {
|
|
72
|
-
width: 112px !important;
|
|
73
|
-
height: 112px !important;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
@media (min-width: 768px) {
|
|
77
|
-
.lighthouse-gauge {
|
|
78
|
-
width: 144px !important;
|
|
79
|
-
height: 144px !important;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
</style>
|
|
83
|
-
<circle
|
|
84
|
-
cx="60"
|
|
85
|
-
cy="60"
|
|
86
|
-
r={radius}
|
|
87
|
-
fill="none"
|
|
88
|
-
stroke="#e0e0e0"
|
|
89
|
-
stroke-width="8"
|
|
90
|
-
/>
|
|
91
|
-
<circle
|
|
92
|
-
cx="60"
|
|
93
|
-
cy="60"
|
|
94
|
-
r={radius}
|
|
95
|
-
fill="none"
|
|
96
|
-
stroke={color}
|
|
97
|
-
stroke-width="8"
|
|
98
|
-
stroke-linecap="round"
|
|
99
|
-
stroke-dasharray={circumference}
|
|
100
|
-
stroke-dashoffset={offset}
|
|
101
|
-
class="lighthouse-progress"
|
|
102
|
-
transform="rotate(-90 60 60)"
|
|
103
|
-
style={`--target-offset: ${offset}`}
|
|
104
|
-
/>
|
|
105
|
-
<text
|
|
106
|
-
x="60"
|
|
107
|
-
y="60"
|
|
108
|
-
text-anchor="middle"
|
|
109
|
-
dominant-baseline="central"
|
|
110
|
-
class="lighthouse-number font-semibold font-sans"
|
|
111
|
-
fill={color}
|
|
112
|
-
style="font-size: 2rem;"
|
|
113
|
-
>
|
|
114
|
-
{score.value}
|
|
115
|
-
</text>
|
|
116
|
-
</svg>
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<span class="text-muted-foreground mt-2xl text-center text-sm font-medium">
|
|
120
|
-
{score.label}
|
|
121
|
-
</span>
|
|
122
|
-
</div>
|
|
123
|
-
);
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<!-- Footer note -->
|
|
129
|
-
<p class="text-muted-foreground/60 mt-4xl text-center text-xs italic">
|
|
130
|
-
*Results represent the starting template. Changes to styling and features may affect final results.
|
|
131
|
-
</p>
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<style>
|
|
135
|
-
/* Entrance animations */
|
|
136
|
-
.lighthouse-score {
|
|
137
|
-
animation: score-enter 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.lighthouse-progress {
|
|
141
|
-
animation: progress-fill 1.2s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* Staggered delays via nth-child to avoid inline style attributes */
|
|
145
|
-
.lighthouse-score:nth-child(1) { animation-delay: 100ms; }
|
|
146
|
-
.lighthouse-score:nth-child(2) { animation-delay: 180ms; }
|
|
147
|
-
.lighthouse-score:nth-child(3) { animation-delay: 260ms; }
|
|
148
|
-
.lighthouse-score:nth-child(4) { animation-delay: 340ms; }
|
|
149
|
-
|
|
150
|
-
.lighthouse-progress:nth-child(1) { animation-delay: 200ms; }
|
|
151
|
-
.lighthouse-progress:nth-child(2) { animation-delay: 280ms; }
|
|
152
|
-
.lighthouse-progress:nth-child(3) { animation-delay: 360ms; }
|
|
153
|
-
.lighthouse-progress:nth-child(4) { animation-delay: 440ms; }
|
|
154
|
-
|
|
155
|
-
@keyframes score-enter {
|
|
156
|
-
from {
|
|
157
|
-
opacity: 0;
|
|
158
|
-
transform: scale(0.9) translateY(10px);
|
|
159
|
-
}
|
|
160
|
-
to {
|
|
161
|
-
opacity: 1;
|
|
162
|
-
transform: scale(1) translateY(0);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
@keyframes progress-fill {
|
|
167
|
-
from {
|
|
168
|
-
stroke-dashoffset: 339.292; /* Approximate circumference */
|
|
169
|
-
}
|
|
170
|
-
to {
|
|
171
|
-
stroke-dashoffset: var(--target-offset, 0);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@media (prefers-reduced-motion: reduce) {
|
|
176
|
-
.lighthouse-score,
|
|
177
|
-
.lighthouse-progress {
|
|
178
|
-
animation: none;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.lighthouse-progress {
|
|
182
|
-
stroke-dashoffset: 0;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
</style>
|
|
186
|
-
|
|
187
|
-
<script>
|
|
188
|
-
// Run animations immediately
|
|
189
|
-
const initLighthouse = () => {
|
|
190
|
-
const scoresContainer = document.querySelector('.lighthouse-scores');
|
|
191
|
-
if (scoresContainer) {
|
|
192
|
-
const elements = scoresContainer.querySelectorAll<HTMLElement>('.lighthouse-score, .lighthouse-progress');
|
|
193
|
-
elements.forEach((el) => {
|
|
194
|
-
el.style.animationPlayState = 'running';
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Run on initial load
|
|
200
|
-
initLighthouse();
|
|
201
|
-
|
|
202
|
-
// Run on View Transitions navigate
|
|
203
|
-
document.addEventListener('astro:after-swap', initLighthouse);
|
|
204
|
-
</script>
|
package/src/composables/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { map } from "nanostores"
|
|
2
|
-
|
|
3
|
-
export interface HeaderLayerInfo {
|
|
4
|
-
type: "banner" | "header"
|
|
5
|
-
order: number
|
|
6
|
-
isVisible: boolean
|
|
7
|
-
height: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Global store tracking all registered header layers. This allows child components to subscribe to
|
|
12
|
-
* header layer changes even across Vue/Astro boundaries, since nanostores is framework-agnostic.
|
|
13
|
-
*/
|
|
14
|
-
export const headerLayersStore = map<Record<string, HeaderLayerInfo>>({})
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { ref, onMounted, onUnmounted, type Ref } from "vue"
|
|
2
|
-
|
|
3
|
-
export interface UseScrollToTopOptions {
|
|
4
|
-
/**
|
|
5
|
-
* The scroll threshold (in pixels) before the button becomes visible.
|
|
6
|
-
*
|
|
7
|
-
* @default 200
|
|
8
|
-
*/
|
|
9
|
-
threshold?: number
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface UseScrollToTopReturn {
|
|
13
|
-
/**
|
|
14
|
-
* Whether the scroll-to-top button should be visible.
|
|
15
|
-
*/
|
|
16
|
-
isVisible: Ref<boolean>
|
|
17
|
-
/**
|
|
18
|
-
* The current scroll percentage (0-100).
|
|
19
|
-
*/
|
|
20
|
-
scrollPercentage: Ref<number>
|
|
21
|
-
/**
|
|
22
|
-
* Scrolls the window to the top with a smooth animation.
|
|
23
|
-
*/
|
|
24
|
-
scrollToTop: () => void
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function scrollToTop() {
|
|
28
|
-
window.scrollTo({
|
|
29
|
-
top: 0,
|
|
30
|
-
behavior: "smooth"
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function useScrollToTop(options: UseScrollToTopOptions = {}): UseScrollToTopReturn {
|
|
35
|
-
const { threshold = 200 } = options
|
|
36
|
-
|
|
37
|
-
const isVisible = ref(false)
|
|
38
|
-
const scrollPercentage = ref(0)
|
|
39
|
-
|
|
40
|
-
function updateScrollState() {
|
|
41
|
-
const scrollTop = window.scrollY || document.documentElement.scrollTop
|
|
42
|
-
const scrollHeight = document.documentElement.scrollHeight - window.innerHeight
|
|
43
|
-
|
|
44
|
-
isVisible.value = scrollTop > threshold
|
|
45
|
-
scrollPercentage.value = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
onMounted(() => {
|
|
49
|
-
window.addEventListener("scroll", updateScrollState, { passive: true })
|
|
50
|
-
updateScrollState()
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
onUnmounted(() => {
|
|
54
|
-
window.removeEventListener("scroll", updateScrollState)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
isVisible,
|
|
59
|
-
scrollPercentage,
|
|
60
|
-
scrollToTop
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { onUnmounted } from "vue"
|
|
2
|
-
import { defineShortcuts } from "../utils/shortcuts"
|
|
3
|
-
import type { ShortcutsConfig, ShortcutsOptions } from "../utils/shortcuts"
|
|
4
|
-
|
|
5
|
-
export function useShortcuts(config: ShortcutsConfig, options?: ShortcutsOptions) {
|
|
6
|
-
const cleanup = defineShortcuts(config, options)
|
|
7
|
-
onUnmounted(() => {
|
|
8
|
-
cleanup()
|
|
9
|
-
})
|
|
10
|
-
return cleanup
|
|
11
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { reactive } from "vue"
|
|
2
|
-
import type { ThemeColor } from "../types"
|
|
3
|
-
|
|
4
|
-
export interface Toast {
|
|
5
|
-
id?: string | number
|
|
6
|
-
title?: string
|
|
7
|
-
description?: string
|
|
8
|
-
icon?: string
|
|
9
|
-
color?: ThemeColor
|
|
10
|
-
timeout?: number
|
|
11
|
-
callback?: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const toasts = reactive<Toast[]>([])
|
|
15
|
-
|
|
16
|
-
export function useToast() {
|
|
17
|
-
const add = (toast: Toast) => {
|
|
18
|
-
const id = toast.id || Date.now()
|
|
19
|
-
const newToast = { ...toast, id }
|
|
20
|
-
toasts.push(newToast)
|
|
21
|
-
|
|
22
|
-
if (toast.timeout !== 0) {
|
|
23
|
-
setTimeout(() => {
|
|
24
|
-
remove(id)
|
|
25
|
-
}, toast.timeout || 5000)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return newToast
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const remove = (id: string | number) => {
|
|
32
|
-
const index = toasts.findIndex((t) => t.id === id)
|
|
33
|
-
if (index !== -1) {
|
|
34
|
-
toasts.splice(index, 1)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
toasts,
|
|
40
|
-
add,
|
|
41
|
-
remove
|
|
42
|
-
}
|
|
43
|
-
}
|