@celestia-island/hikari 0.4.4
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/README.md +54 -0
- package/package.json +63 -0
- package/src/DemoApp.vue +400 -0
- package/src/components/HkActionBar.tsx +15 -0
- package/src/components/HkAlert.scss +121 -0
- package/src/components/HkAlert.tsx +85 -0
- package/src/components/HkAvatar.tsx +29 -0
- package/src/components/HkBadge.scss +117 -0
- package/src/components/HkBadge.tsx +50 -0
- package/src/components/HkBreadcrumb.tsx +39 -0
- package/src/components/HkButton.scss +128 -0
- package/src/components/HkButton.tsx +74 -0
- package/src/components/HkCard.scss +49 -0
- package/src/components/HkCard.tsx +38 -0
- package/src/components/HkCheckbox.scss +102 -0
- package/src/components/HkCheckbox.tsx +85 -0
- package/src/components/HkCollapse.tsx +48 -0
- package/src/components/HkColorPicker.scss +148 -0
- package/src/components/HkColorPicker.tsx +204 -0
- package/src/components/HkConfirmDialog.scss +12 -0
- package/src/components/HkConfirmDialog.tsx +75 -0
- package/src/components/HkDateTimePicker.scss +361 -0
- package/src/components/HkDateTimePicker.tsx +424 -0
- package/src/components/HkDivider.scss +85 -0
- package/src/components/HkDivider.tsx +51 -0
- package/src/components/HkDraggableGrid.scss +132 -0
- package/src/components/HkDraggableGrid.tsx +291 -0
- package/src/components/HkDraggableList.scss +110 -0
- package/src/components/HkDraggableList.tsx +238 -0
- package/src/components/HkDrawer.scss +175 -0
- package/src/components/HkDrawer.tsx +171 -0
- package/src/components/HkEmptyState.scss +42 -0
- package/src/components/HkEmptyState.tsx +41 -0
- package/src/components/HkErrorBoundary.scss +64 -0
- package/src/components/HkErrorBoundary.tsx +82 -0
- package/src/components/HkGaugeRing.scss +4 -0
- package/src/components/HkGaugeRing.tsx +130 -0
- package/src/components/HkHoverRevealAction.scss +66 -0
- package/src/components/HkHoverRevealAction.tsx +77 -0
- package/src/components/HkIcon.tsx +43 -0
- package/src/components/HkIconButton.tsx +42 -0
- package/src/components/HkImageViewer.scss +33 -0
- package/src/components/HkImageViewer.tsx +250 -0
- package/src/components/HkInput.scss +172 -0
- package/src/components/HkInput.tsx +175 -0
- package/src/components/HkKbd.tsx +26 -0
- package/src/components/HkKeywordSearchModal.scss +148 -0
- package/src/components/HkKeywordSearchModal.tsx +308 -0
- package/src/components/HkListTransition.scss +131 -0
- package/src/components/HkListTransition.tsx +29 -0
- package/src/components/HkLocalePickerPopup.tsx +42 -0
- package/src/components/HkLogo.scss +36 -0
- package/src/components/HkLogo.tsx +93 -0
- package/src/components/HkMarkdownRenderer.scss +141 -0
- package/src/components/HkMarkdownRenderer.tsx +123 -0
- package/src/components/HkMediaControlBar.scss +65 -0
- package/src/components/HkMediaControlBar.tsx +115 -0
- package/src/components/HkMediaPlayer.scss +29 -0
- package/src/components/HkMediaPlayer.tsx +210 -0
- package/src/components/HkMediaSlider.scss +54 -0
- package/src/components/HkMediaSlider.tsx +81 -0
- package/src/components/HkMediaVisualizer.scss +7 -0
- package/src/components/HkMediaVisualizer.tsx +119 -0
- package/src/components/HkMinimap.scss +76 -0
- package/src/components/HkMinimap.tsx +255 -0
- package/src/components/HkModal.scss +318 -0
- package/src/components/HkModal.tsx +481 -0
- package/src/components/HkModalBreadcrumb.scss +55 -0
- package/src/components/HkModalBreadcrumb.tsx +113 -0
- package/src/components/HkMorphingTabs.scss +141 -0
- package/src/components/HkMorphingTabs.tsx +113 -0
- package/src/components/HkNavItem.scss +81 -0
- package/src/components/HkNavItem.tsx +157 -0
- package/src/components/HkNumberInput.scss +154 -0
- package/src/components/HkNumberInput.tsx +161 -0
- package/src/components/HkPasswordInput.scss +289 -0
- package/src/components/HkPasswordInput.tsx +533 -0
- package/src/components/HkPhaseTransition.scss +25 -0
- package/src/components/HkPhaseTransition.tsx +40 -0
- package/src/components/HkPopover.scss +82 -0
- package/src/components/HkPopover.tsx +410 -0
- package/src/components/HkPopupSelect.scss +136 -0
- package/src/components/HkPopupSelect.tsx +199 -0
- package/src/components/HkProgressBar.scss +106 -0
- package/src/components/HkProgressBar.tsx +61 -0
- package/src/components/HkProgressDialog.tsx +67 -0
- package/src/components/HkProgressRing.scss +56 -0
- package/src/components/HkProgressRing.tsx +82 -0
- package/src/components/HkRadio.scss +119 -0
- package/src/components/HkRadio.tsx +75 -0
- package/src/components/HkRollingNumber.scss +37 -0
- package/src/components/HkRollingNumber.tsx +97 -0
- package/src/components/HkScrollContainer.scss +101 -0
- package/src/components/HkScrollContainer.tsx +362 -0
- package/src/components/HkSearchInput.scss +132 -0
- package/src/components/HkSearchInput.tsx +117 -0
- package/src/components/HkSelect.scss +143 -0
- package/src/components/HkSelect.tsx +230 -0
- package/src/components/HkSelectionGrid.scss +177 -0
- package/src/components/HkSelectionGrid.tsx +99 -0
- package/src/components/HkSelectionWaterfall.scss +93 -0
- package/src/components/HkSelectionWaterfall.tsx +90 -0
- package/src/components/HkSidebar.scss +81 -0
- package/src/components/HkSidebar.tsx +45 -0
- package/src/components/HkSkeleton.scss +44 -0
- package/src/components/HkSkeleton.tsx +30 -0
- package/src/components/HkSkeletonList.tsx +35 -0
- package/src/components/HkSpinner.scss +67 -0
- package/src/components/HkSpinner.tsx +41 -0
- package/src/components/HkSplash.scss +60 -0
- package/src/components/HkSplash.tsx +40 -0
- package/src/components/HkSwitch.scss +157 -0
- package/src/components/HkSwitch.tsx +72 -0
- package/src/components/HkTable.scss +198 -0
- package/src/components/HkTable.tsx +246 -0
- package/src/components/HkTabs.scss +109 -0
- package/src/components/HkTabs.tsx +111 -0
- package/src/components/HkTag.scss +88 -0
- package/src/components/HkTag.tsx +44 -0
- package/src/components/HkTextarea.scss +72 -0
- package/src/components/HkTextarea.tsx +104 -0
- package/src/components/HkTimeline.scss +139 -0
- package/src/components/HkTimeline.tsx +95 -0
- package/src/components/HkToast.scss +168 -0
- package/src/components/HkToast.tsx +175 -0
- package/src/components/HkTooltip.scss +47 -0
- package/src/components/HkTooltip.tsx +108 -0
- package/src/components/HkTree.scss +188 -0
- package/src/components/HkTree.tsx +217 -0
- package/src/components/HkTrendChart.tsx +180 -0
- package/src/components/HkWindowedItem.scss +17 -0
- package/src/components/HkWindowedItem.tsx +107 -0
- package/src/components/HkZoomToolbar.scss +35 -0
- package/src/components/HkZoomToolbar.tsx +69 -0
- package/src/composables/useBreakpoint.ts +41 -0
- package/src/composables/useClipboard.ts +37 -0
- package/src/composables/useConfirm.ts +35 -0
- package/src/composables/useProgressDialog.ts +52 -0
- package/src/composables/useReportedTransition.ts +69 -0
- package/src/composables/useScrollWindow.ts +21 -0
- package/src/composables/useToast.ts +2 -0
- package/src/demo.scss +24 -0
- package/src/demo.ts +22 -0
- package/src/env.d.ts +6 -0
- package/src/i18n/context.ts +89 -0
- package/src/i18n/locales/ar/components.json +43 -0
- package/src/i18n/locales/ar/components.toml +40 -0
- package/src/i18n/locales/de/components.json +43 -0
- package/src/i18n/locales/de/components.toml +40 -0
- package/src/i18n/locales/en/components.json +43 -0
- package/src/i18n/locales/en/components.toml +40 -0
- package/src/i18n/locales/es/components.json +43 -0
- package/src/i18n/locales/es/components.toml +40 -0
- package/src/i18n/locales/fr/components.json +43 -0
- package/src/i18n/locales/fr/components.toml +40 -0
- package/src/i18n/locales/ja/components.json +43 -0
- package/src/i18n/locales/ja/components.toml +40 -0
- package/src/i18n/locales/ko/components.json +43 -0
- package/src/i18n/locales/ko/components.toml +40 -0
- package/src/i18n/locales/pt/components.json +43 -0
- package/src/i18n/locales/pt/components.toml +40 -0
- package/src/i18n/locales/ru/components.json +43 -0
- package/src/i18n/locales/ru/components.toml +40 -0
- package/src/i18n/locales/zh-Hans/components.json +43 -0
- package/src/i18n/locales/zh-Hans/components.toml +40 -0
- package/src/i18n/locales/zh-Hant/components.json +43 -0
- package/src/i18n/locales/zh-Hant/components.toml +40 -0
- package/src/index.ts +137 -0
- package/src/render.ts +18 -0
- package/src/runtime/animationBus.ts +278 -0
- package/src/runtime/cronBus.ts +25 -0
- package/src/runtime/index.ts +12 -0
- package/src/runtime/useAsyncData.ts +30 -0
- package/src/runtime/useBreakpoint.ts +62 -0
- package/src/runtime/useClipboard.ts +37 -0
- package/src/runtime/useConfirm.ts +35 -0
- package/src/runtime/useOverlay.ts +82 -0
- package/src/runtime/usePopupManager.ts +89 -0
- package/src/runtime/useToast.ts +140 -0
- package/src/styles/index.scss +10 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/presets.ts +307 -0
- package/src/theme/theme.scss +17 -0
- package/src/theme/useBackgroundLuminance.ts +145 -0
- package/src/theme/useSolarTime.ts +101 -0
- package/src/theme/useTheme.ts +141 -0
- package/src/tokens.scss +26 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
export interface FrameContext {
|
|
2
|
+
delta: number;
|
|
3
|
+
elapsed: number;
|
|
4
|
+
now: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface AnimationHandle {
|
|
8
|
+
disconnect(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Callback = (ctx: FrameContext) => void;
|
|
12
|
+
type Priority = "sync" | "normal" | "idle";
|
|
13
|
+
|
|
14
|
+
interface Entry {
|
|
15
|
+
cb: Callback;
|
|
16
|
+
priority: Priority;
|
|
17
|
+
lastRun: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface OneShotEntry {
|
|
21
|
+
cb: Callback;
|
|
22
|
+
cancelled: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface IntervalEntry {
|
|
26
|
+
cb: () => void;
|
|
27
|
+
interval: number;
|
|
28
|
+
lastRun: number;
|
|
29
|
+
once: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const syncEntries = new Map<string, Entry>();
|
|
33
|
+
const normalEntries = new Map<string, Entry>();
|
|
34
|
+
const idleEntries = new Map<string, Entry>();
|
|
35
|
+
const oneShotEntries = new Map<string, OneShotEntry>();
|
|
36
|
+
const intervalEntries = new Map<string, IntervalEntry>();
|
|
37
|
+
|
|
38
|
+
const activeTransitions = new Set<string>();
|
|
39
|
+
|
|
40
|
+
let raf = 0;
|
|
41
|
+
let origin = 0;
|
|
42
|
+
let prev = 0;
|
|
43
|
+
let paused = false;
|
|
44
|
+
let scrolling = false;
|
|
45
|
+
let scrollTimer: ReturnType<typeof setTimeout> | null = null;
|
|
46
|
+
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
47
|
+
let oneShotRaf = 0;
|
|
48
|
+
|
|
49
|
+
const NORMAL_FRAME_BUDGET = 33;
|
|
50
|
+
const IDLE_FRAME_BUDGET = 2000;
|
|
51
|
+
|
|
52
|
+
function uid(): string {
|
|
53
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
54
|
+
return crypto.randomUUID();
|
|
55
|
+
}
|
|
56
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function busyCount(): number {
|
|
60
|
+
return syncEntries.size + normalEntries.size + idleEntries.size + intervalEntries.size + activeTransitions.size;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function pickRegistry(priority: Priority): Map<string, Entry> {
|
|
64
|
+
switch (priority) {
|
|
65
|
+
case "sync": return syncEntries;
|
|
66
|
+
case "normal": return normalEntries;
|
|
67
|
+
case "idle": return idleEntries;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function tick(now: number) {
|
|
72
|
+
raf = 0;
|
|
73
|
+
if (busyCount() === 0 || paused) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!origin) {
|
|
77
|
+
origin = now;
|
|
78
|
+
prev = now;
|
|
79
|
+
}
|
|
80
|
+
const ctx: FrameContext = {
|
|
81
|
+
delta: (now - prev) / 1000,
|
|
82
|
+
elapsed: (now - origin) / 1000,
|
|
83
|
+
now,
|
|
84
|
+
};
|
|
85
|
+
prev = now;
|
|
86
|
+
|
|
87
|
+
for (const e of syncEntries.values()) {
|
|
88
|
+
e.cb(ctx);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const skipNormal = scrolling;
|
|
92
|
+
if (!skipNormal) {
|
|
93
|
+
for (const e of normalEntries.values()) {
|
|
94
|
+
if (now - e.lastRun >= NORMAL_FRAME_BUDGET) {
|
|
95
|
+
e.lastRun = now;
|
|
96
|
+
e.cb(ctx);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const e of idleEntries.values()) {
|
|
102
|
+
if (now - e.lastRun >= IDLE_FRAME_BUDGET) {
|
|
103
|
+
e.lastRun = now;
|
|
104
|
+
e.cb(ctx);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const disconnected: string[] = [];
|
|
109
|
+
for (const [id, e] of intervalEntries) {
|
|
110
|
+
if (now - e.lastRun >= e.interval) {
|
|
111
|
+
e.lastRun = now;
|
|
112
|
+
e.cb();
|
|
113
|
+
if (e.once) disconnected.push(id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const id of disconnected) intervalEntries.delete(id);
|
|
117
|
+
|
|
118
|
+
if (raf !== 0 || idleTimer !== null) return;
|
|
119
|
+
|
|
120
|
+
if (syncEntries.size > 0 || normalEntries.size > 0 || intervalEntries.size > 0 || activeTransitions.size > 0) {
|
|
121
|
+
raf = requestAnimationFrame(tick);
|
|
122
|
+
} else if (idleEntries.size > 0) {
|
|
123
|
+
idleTimer = setTimeout(() => {
|
|
124
|
+
idleTimer = null;
|
|
125
|
+
raf = requestAnimationFrame(tick);
|
|
126
|
+
}, IDLE_FRAME_BUDGET);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function ensure() {
|
|
131
|
+
if (paused) return;
|
|
132
|
+
if (syncEntries.size > 0 || normalEntries.size > 0 || intervalEntries.size > 0 || activeTransitions.size > 0) {
|
|
133
|
+
if (idleTimer !== null) {
|
|
134
|
+
clearTimeout(idleTimer);
|
|
135
|
+
idleTimer = null;
|
|
136
|
+
}
|
|
137
|
+
if (raf === 0) {
|
|
138
|
+
origin = 0;
|
|
139
|
+
prev = 0;
|
|
140
|
+
raf = requestAnimationFrame(tick);
|
|
141
|
+
}
|
|
142
|
+
} else if (idleEntries.size > 0 && raf === 0 && idleTimer === null) {
|
|
143
|
+
origin = 0;
|
|
144
|
+
prev = 0;
|
|
145
|
+
idleTimer = setTimeout(() => {
|
|
146
|
+
idleTimer = null;
|
|
147
|
+
raf = requestAnimationFrame(tick);
|
|
148
|
+
}, IDLE_FRAME_BUDGET);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function halt() {
|
|
153
|
+
if (raf !== 0) {
|
|
154
|
+
cancelAnimationFrame(raf);
|
|
155
|
+
raf = 0;
|
|
156
|
+
}
|
|
157
|
+
if (idleTimer !== null) {
|
|
158
|
+
clearTimeout(idleTimer);
|
|
159
|
+
idleTimer = null;
|
|
160
|
+
}
|
|
161
|
+
if (busyCount() > 0 && !paused) {
|
|
162
|
+
ensure();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function setReducedMotion(flag: boolean) {
|
|
167
|
+
if (paused === flag) return;
|
|
168
|
+
paused = flag;
|
|
169
|
+
if (flag) {
|
|
170
|
+
if (raf !== 0) {
|
|
171
|
+
cancelAnimationFrame(raf);
|
|
172
|
+
raf = 0;
|
|
173
|
+
}
|
|
174
|
+
if (idleTimer !== null) {
|
|
175
|
+
clearTimeout(idleTimer);
|
|
176
|
+
idleTimer = null;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
ensure();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function notifyScrollStart() {
|
|
184
|
+
scrolling = true;
|
|
185
|
+
if (scrollTimer) clearTimeout(scrollTimer);
|
|
186
|
+
scrollTimer = setTimeout(() => {
|
|
187
|
+
scrolling = false;
|
|
188
|
+
scrollTimer = null;
|
|
189
|
+
}, 150);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function onFrame(cb: Callback, priority: Priority = "sync"): AnimationHandle {
|
|
193
|
+
const id = uid();
|
|
194
|
+
const registry = pickRegistry(priority);
|
|
195
|
+
registry.set(id, { cb, priority, lastRun: 0 });
|
|
196
|
+
ensure();
|
|
197
|
+
return {
|
|
198
|
+
disconnect() {
|
|
199
|
+
registry.delete(id);
|
|
200
|
+
halt();
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function ensureOneShotDrain(): void {
|
|
206
|
+
if (oneShotRaf !== 0) return;
|
|
207
|
+
oneShotRaf = requestAnimationFrame(pumpOneShots);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function pumpOneShots(now: number): void {
|
|
211
|
+
oneShotRaf = 0;
|
|
212
|
+
if (oneShotEntries.size === 0) return;
|
|
213
|
+
const ctx: FrameContext = { delta: 0, elapsed: 0, now };
|
|
214
|
+
const batch = Array.from(oneShotEntries.values());
|
|
215
|
+
oneShotEntries.clear();
|
|
216
|
+
for (const e of batch) {
|
|
217
|
+
if (!e.cancelled) e.cb(ctx);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function onceFrame(cb: Callback): void {
|
|
222
|
+
oneShotEntries.set(uid(), { cb, cancelled: false });
|
|
223
|
+
ensureOneShotDrain();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function scheduleFrame(cb: Callback): AnimationHandle {
|
|
227
|
+
const entry: OneShotEntry = { cb, cancelled: false };
|
|
228
|
+
const id = uid();
|
|
229
|
+
oneShotEntries.set(id, entry);
|
|
230
|
+
ensureOneShotDrain();
|
|
231
|
+
return {
|
|
232
|
+
disconnect() {
|
|
233
|
+
entry.cancelled = true;
|
|
234
|
+
oneShotEntries.delete(id);
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function reportTransition(durationMs: number): AnimationHandle {
|
|
240
|
+
const id = uid();
|
|
241
|
+
activeTransitions.add(id);
|
|
242
|
+
ensure();
|
|
243
|
+
const timer = setTimeout(() => {
|
|
244
|
+
activeTransitions.delete(id);
|
|
245
|
+
halt();
|
|
246
|
+
}, Math.max(0, durationMs));
|
|
247
|
+
return {
|
|
248
|
+
disconnect() {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
activeTransitions.delete(id);
|
|
251
|
+
halt();
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function scheduleEvery(cb: () => void, intervalMs: number): AnimationHandle {
|
|
257
|
+
const id = uid();
|
|
258
|
+
intervalEntries.set(id, { cb, interval: intervalMs, lastRun: 0, once: false });
|
|
259
|
+
ensure();
|
|
260
|
+
return {
|
|
261
|
+
disconnect() {
|
|
262
|
+
intervalEntries.delete(id);
|
|
263
|
+
halt();
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function scheduleAfter(cb: () => void, delayMs: number): AnimationHandle {
|
|
269
|
+
const id = uid();
|
|
270
|
+
intervalEntries.set(id, { cb, interval: delayMs, lastRun: performance.now(), once: true });
|
|
271
|
+
ensure();
|
|
272
|
+
return {
|
|
273
|
+
disconnect() {
|
|
274
|
+
intervalEntries.delete(id);
|
|
275
|
+
halt();
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface CronHandle {
|
|
2
|
+
disconnect(): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function scheduleCron(cb: () => void, intervalMs: number): CronHandle {
|
|
6
|
+
const id = setInterval(cb, intervalMs);
|
|
7
|
+
return {
|
|
8
|
+
disconnect() {
|
|
9
|
+
clearInterval(id);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function scheduleCronAfter(cb: () => void, delayMs: number): CronHandle {
|
|
15
|
+
let fired = false;
|
|
16
|
+
const id = setTimeout(() => {
|
|
17
|
+
fired = true;
|
|
18
|
+
cb();
|
|
19
|
+
}, delayMs);
|
|
20
|
+
return {
|
|
21
|
+
disconnect() {
|
|
22
|
+
if (!fired) clearTimeout(id);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./animationBus";
|
|
2
|
+
export * from "./cronBus";
|
|
3
|
+
export { useOverlay, closeAll, isOverlayOpen, type OverlayHandle, type UseOverlayOptions } from "./useOverlay";
|
|
4
|
+
export { usePopupManager, type PopupHandle, type PopupKind } from "./usePopupManager";
|
|
5
|
+
export { useToast, TOAST_DURATION, type ToastItem, type ToastMessage, type ToastType } from "./useToast";
|
|
6
|
+
export { useConfirm } from "./useConfirm";
|
|
7
|
+
export { useBreakpoint } from "./useBreakpoint";
|
|
8
|
+
export { useClipboard } from "./useClipboard";
|
|
9
|
+
export { useAsyncData, type UseAsyncDataReturn } from "./useAsyncData";
|
|
10
|
+
export { provideScrollWindow, useScrollWindow, type ScrollWindowContext } from "../composables/useScrollWindow";
|
|
11
|
+
export { useReportedTransition, type ReportedTransition, type ReportedTransitionTrack } from "../composables/useReportedTransition";
|
|
12
|
+
export { showProgressDialog, useProgressDialog, type ProgressDialogHandle, type ProgressDialogState } from "../composables/useProgressDialog";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ref, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface UseAsyncDataReturn<T> {
|
|
4
|
+
data: Ref<T | null>;
|
|
5
|
+
loading: Ref<boolean>;
|
|
6
|
+
error: Ref<Error | null>;
|
|
7
|
+
refresh: () => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useAsyncData<T>(
|
|
11
|
+
fetcher: () => Promise<T>,
|
|
12
|
+
): UseAsyncDataReturn<T> {
|
|
13
|
+
const data = ref<T | null>(null) as Ref<T | null>;
|
|
14
|
+
const loading = ref(false);
|
|
15
|
+
const error = ref<Error | null>(null);
|
|
16
|
+
|
|
17
|
+
async function refresh() {
|
|
18
|
+
loading.value = true;
|
|
19
|
+
error.value = null;
|
|
20
|
+
try {
|
|
21
|
+
data.value = await fetcher();
|
|
22
|
+
} catch (e) {
|
|
23
|
+
error.value = e instanceof Error ? e : new Error(String(e));
|
|
24
|
+
} finally {
|
|
25
|
+
loading.value = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { data, loading, error, refresh };
|
|
30
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { computed, onMounted, onUnmounted, ref } from "vue";
|
|
2
|
+
|
|
3
|
+
type BreakpointKey = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
4
|
+
|
|
5
|
+
const breakpointValues: Record<BreakpointKey, number> = {
|
|
6
|
+
xs: 0,
|
|
7
|
+
sm: 640,
|
|
8
|
+
md: 768,
|
|
9
|
+
lg: 1024,
|
|
10
|
+
xl: 1280,
|
|
11
|
+
"2xl": 1536,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const isServer = typeof window === "undefined";
|
|
15
|
+
|
|
16
|
+
export function useBreakpoint() {
|
|
17
|
+
if (isServer) {
|
|
18
|
+
const fallbackWidth = ref(1024);
|
|
19
|
+
const current = computed<BreakpointKey>(() => "lg");
|
|
20
|
+
const isMobile = computed(() => false);
|
|
21
|
+
const isTablet = computed(() => false);
|
|
22
|
+
const isDesktop = computed(() => true);
|
|
23
|
+
const isAbove = (_bp: BreakpointKey) => computed(() => true);
|
|
24
|
+
const isBelow = (_bp: BreakpointKey) => computed(() => false);
|
|
25
|
+
return { width: fallbackWidth, current, isMobile, isTablet, isDesktop, isAbove, isBelow };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const width = ref(window.innerWidth);
|
|
29
|
+
|
|
30
|
+
function handleResize() {
|
|
31
|
+
width.value = window.innerWidth;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
window.addEventListener("resize", handleResize);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
onUnmounted(() => {
|
|
39
|
+
window.removeEventListener("resize", handleResize);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const current = computed<BreakpointKey>(() => {
|
|
43
|
+
const w = width.value;
|
|
44
|
+
if (w >= 1536) return "2xl";
|
|
45
|
+
if (w >= 1280) return "xl";
|
|
46
|
+
if (w >= 1024) return "lg";
|
|
47
|
+
if (w >= 768) return "md";
|
|
48
|
+
if (w >= 640) return "sm";
|
|
49
|
+
return "xs";
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const isMobile = computed(() => width.value < 768);
|
|
53
|
+
const isTablet = computed(() => width.value >= 768 && width.value < 1024);
|
|
54
|
+
const isDesktop = computed(() => width.value >= 1024);
|
|
55
|
+
|
|
56
|
+
const isAbove = (bp: BreakpointKey) =>
|
|
57
|
+
computed(() => width.value >= breakpointValues[bp]);
|
|
58
|
+
const isBelow = (bp: BreakpointKey) =>
|
|
59
|
+
computed(() => width.value < breakpointValues[bp]);
|
|
60
|
+
|
|
61
|
+
return { width, current, isMobile, isTablet, isDesktop, isAbove, isBelow };
|
|
62
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export function useClipboard() {
|
|
4
|
+
const copied = ref(false);
|
|
5
|
+
|
|
6
|
+
async function copy(text: string): Promise<boolean> {
|
|
7
|
+
try {
|
|
8
|
+
await navigator.clipboard.writeText(text);
|
|
9
|
+
copied.value = true;
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
copied.value = false;
|
|
12
|
+
}, 2000);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
const textarea = document.createElement("textarea");
|
|
16
|
+
textarea.value = text;
|
|
17
|
+
textarea.style.position = "fixed";
|
|
18
|
+
textarea.style.opacity = "0";
|
|
19
|
+
document.body.appendChild(textarea);
|
|
20
|
+
textarea.select();
|
|
21
|
+
try {
|
|
22
|
+
document.execCommand("copy");
|
|
23
|
+
copied.value = true;
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
copied.value = false;
|
|
26
|
+
}, 2000);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
} finally {
|
|
31
|
+
document.body.removeChild(textarea);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { copied, copy };
|
|
37
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export function useConfirm() {
|
|
4
|
+
const resolveRef = ref<((value: boolean) => void) | null>(null);
|
|
5
|
+
const open = ref(false);
|
|
6
|
+
const message = ref("");
|
|
7
|
+
const title = ref("Confirm");
|
|
8
|
+
const confirmText = ref("Confirm");
|
|
9
|
+
const cancelText = ref("Cancel");
|
|
10
|
+
|
|
11
|
+
function confirm(text: string, opts?: { title?: string; confirmText?: string; cancelText?: string }): Promise<boolean> {
|
|
12
|
+
return new Promise<boolean>((resolve) => {
|
|
13
|
+
resolveRef.value = resolve;
|
|
14
|
+
message.value = text;
|
|
15
|
+
title.value = opts?.title ?? "Confirm";
|
|
16
|
+
confirmText.value = opts?.confirmText ?? "Confirm";
|
|
17
|
+
cancelText.value = opts?.cancelText ?? "Cancel";
|
|
18
|
+
open.value = true;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function onConfirm() {
|
|
23
|
+
open.value = false;
|
|
24
|
+
resolveRef.value?.(true);
|
|
25
|
+
resolveRef.value = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function onCancel() {
|
|
29
|
+
open.value = false;
|
|
30
|
+
resolveRef.value?.(false);
|
|
31
|
+
resolveRef.value = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { open, title, message, confirmText, cancelText, confirm, onConfirm, onCancel };
|
|
35
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { onUnmounted, ref, type Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
interface OverlayRegistryEntry {
|
|
4
|
+
close: () => void;
|
|
5
|
+
group?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const registry = new Map<string, OverlayRegistryEntry>();
|
|
9
|
+
|
|
10
|
+
function closeGroup(group: string) {
|
|
11
|
+
for (const [name, entry] of registry) {
|
|
12
|
+
if (entry.group === group) {
|
|
13
|
+
entry.close();
|
|
14
|
+
registry.delete(name);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function register(name: string, close: () => void, group?: string) {
|
|
20
|
+
registry.set(name, { close, group });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function unregister(name: string) {
|
|
24
|
+
registry.delete(name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function closeAll() {
|
|
28
|
+
for (const [, entry] of registry) {
|
|
29
|
+
entry.close();
|
|
30
|
+
}
|
|
31
|
+
registry.clear();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isOverlayOpen(name: string): boolean {
|
|
35
|
+
return registry.has(name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UseOverlayOptions {
|
|
39
|
+
name: string;
|
|
40
|
+
group?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface OverlayHandle {
|
|
44
|
+
isOpen: Ref<boolean>;
|
|
45
|
+
open: () => void;
|
|
46
|
+
close: () => void;
|
|
47
|
+
toggle: () => void;
|
|
48
|
+
onUpdate: (v: boolean) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useOverlay(opts: UseOverlayOptions): OverlayHandle {
|
|
52
|
+
const isOpen = ref(false);
|
|
53
|
+
|
|
54
|
+
function open(): void {
|
|
55
|
+
if (isOpen.value) return;
|
|
56
|
+
if (opts.group) closeGroup(opts.group);
|
|
57
|
+
isOpen.value = true;
|
|
58
|
+
register(opts.name, close, opts.group);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function close(): void {
|
|
62
|
+
if (!isOpen.value) return;
|
|
63
|
+
isOpen.value = false;
|
|
64
|
+
unregister(opts.name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toggle(): void {
|
|
68
|
+
if (isOpen.value) close();
|
|
69
|
+
else open();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onUpdate(v: boolean): void {
|
|
73
|
+
if (v) open();
|
|
74
|
+
else close();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onUnmounted(() => {
|
|
78
|
+
if (isOpen.value) unregister(opts.name);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return { isOpen, open, close, toggle, onUpdate };
|
|
82
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readonly, ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export type PopupKind = "dropdown" | "modal" | "drawer" | "tooltip" | "toast";
|
|
4
|
+
|
|
5
|
+
interface PopupEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: PopupKind;
|
|
8
|
+
locksScroll: boolean;
|
|
9
|
+
zIndex: number;
|
|
10
|
+
title?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Z_BASE = 1000;
|
|
14
|
+
const Z_STEP = 2;
|
|
15
|
+
|
|
16
|
+
function uid(): string {
|
|
17
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
18
|
+
return crypto.randomUUID();
|
|
19
|
+
}
|
|
20
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const registry = ref<Map<string, PopupEntry>>(new Map());
|
|
24
|
+
let nextZ = Z_BASE;
|
|
25
|
+
let scrollLockCount = 0;
|
|
26
|
+
|
|
27
|
+
function updateBodyScroll() {
|
|
28
|
+
if (scrollLockCount > 0) {
|
|
29
|
+
document.body.style.overflow = "hidden";
|
|
30
|
+
} else {
|
|
31
|
+
document.body.style.overflow = "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PopupHandle {
|
|
36
|
+
id: string;
|
|
37
|
+
zIndex: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function usePopupManager() {
|
|
41
|
+
function register(
|
|
42
|
+
kind: PopupKind,
|
|
43
|
+
locksScroll = false,
|
|
44
|
+
title?: string,
|
|
45
|
+
): PopupHandle {
|
|
46
|
+
const id = uid();
|
|
47
|
+
const zIndex = nextZ;
|
|
48
|
+
nextZ += Z_STEP;
|
|
49
|
+
const entry: PopupEntry = { id, kind, locksScroll, zIndex, title };
|
|
50
|
+
registry.value.set(id, entry);
|
|
51
|
+
if (locksScroll) {
|
|
52
|
+
scrollLockCount++;
|
|
53
|
+
updateBodyScroll();
|
|
54
|
+
}
|
|
55
|
+
return { id, zIndex };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function setTitle(id: string, title: string) {
|
|
59
|
+
const entry = registry.value.get(id);
|
|
60
|
+
if (!entry) return;
|
|
61
|
+
entry.title = title;
|
|
62
|
+
registry.value = new Map(registry.value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function unregister(id: string) {
|
|
66
|
+
const entry = registry.value.get(id);
|
|
67
|
+
if (!entry) return;
|
|
68
|
+
registry.value.delete(id);
|
|
69
|
+
if (entry.locksScroll) {
|
|
70
|
+
scrollLockCount = Math.max(0, scrollLockCount - 1);
|
|
71
|
+
updateBodyScroll();
|
|
72
|
+
}
|
|
73
|
+
if (registry.value.size === 0) {
|
|
74
|
+
nextZ = Z_BASE;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isOpen(id: string): boolean {
|
|
79
|
+
return registry.value.has(id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
registry: readonly(registry),
|
|
84
|
+
register,
|
|
85
|
+
setTitle,
|
|
86
|
+
unregister,
|
|
87
|
+
isOpen,
|
|
88
|
+
};
|
|
89
|
+
}
|