@antelopejs/dms-frontend 0.9.1 → 0.9.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.
- package/README.md +9 -1
- package/dist/cancellation.js +2 -1
- package/dist/config.js +11 -7
- package/dist/derived-outputs.js +6 -113
- package/dist/dev-modules.js +3 -2
- package/dist/discovery.js +19 -12
- package/dist/fs-sync.js +1 -2
- package/dist/internal/guards.d.ts +5 -0
- package/dist/internal/guards.js +22 -0
- package/dist/internal/locale-catalogs.d.ts +6 -0
- package/dist/internal/locale-catalogs.js +115 -0
- package/dist/layer-aliases.js +2 -1
- package/dist/layers.js +2 -1
- package/dist/locale-files.js +9 -4
- package/dist/manifest-secrets.js +11 -4
- package/dist/manifest.js +11 -5
- package/dist/materialize.js +6 -5
- package/dist/module-set.js +2 -4
- package/dist/ports.js +8 -1
- package/dist/private-values.js +26 -17
- package/dist/project-modules.js +17 -11
- package/dist/renderer-range.js +2 -1
- package/dist/server-process.js +17 -16
- package/dist/update-check.js +23 -16
- package/dist/verify-source-runner.js +2 -2
- package/dist/workspace-setup.js +1 -2
- package/dist/workspace.js +11 -10
- package/package.json +7 -4
- package/skills/dms-frontend/SKILL.md +1 -1
- package/skills/dms-frontend/cases.json +77 -0
- package/templates/vue/app-runtime.ts +88 -7
- package/templates/vue/client-chunking.mjs +0 -5
- package/templates/vue/client-server-guard.mjs +10 -13
- package/templates/vue/entry-weight.mjs +12 -14
- package/templates/vue/frontend-module.ts +159 -3563
- package/templates/vue/frontend-runtime/async-data-entry.ts +320 -0
- package/templates/vue/frontend-runtime/async-data.ts +404 -0
- package/templates/vue/frontend-runtime/components.ts +386 -0
- package/templates/vue/frontend-runtime/cookies.ts +125 -0
- package/templates/vue/frontend-runtime/data-access.ts +132 -0
- package/templates/vue/frontend-runtime/dms-error.ts +11 -0
- package/templates/vue/frontend-runtime/errors.ts +145 -0
- package/templates/vue/frontend-runtime/fetch.ts +306 -0
- package/templates/vue/frontend-runtime/module-registration.ts +247 -0
- package/templates/vue/frontend-runtime/paged-data.ts +191 -0
- package/templates/vue/frontend-runtime/payload.ts +172 -0
- package/templates/vue/frontend-runtime/route.ts +328 -0
- package/templates/vue/frontend-runtime/sdk.ts +300 -0
- package/templates/vue/frontend-runtime/state.ts +167 -0
- package/templates/vue/frontend-runtime/types.ts +361 -0
- package/templates/vue/frontend-runtime/ui.ts +247 -0
- package/templates/vue/globals.d.ts +5 -0
- package/templates/vue/head-order.mjs +11 -13
- package/templates/vue/icon-bundle.ts +31 -0
- package/templates/vue/message-subset.ts +65 -0
- package/templates/vue/network-error.ts +4 -0
- package/templates/vue/prepare-types.mjs +0 -8
- package/templates/vue/server/auth/backend.mjs +4 -2
- package/templates/vue/server/auth/establish-endpoints.mjs +14 -9
- package/templates/vue/server/auth/session.mjs +4 -3
- package/templates/vue/server/auth-hooks.mjs +6 -6
- package/templates/vue/server/backend-proxies.mjs +6 -6
- package/templates/vue/server/backend-response.mjs +15 -14
- package/templates/vue/server/built-files.mjs +10 -8
- package/templates/vue/server/cli-report.mjs +7 -5
- package/templates/vue/server/client-error.mjs +11 -8
- package/templates/vue/server/client-manifest.mjs +9 -8
- package/templates/vue/server/content.mjs +2 -1
- package/templates/vue/server/dev-private-files.mjs +20 -15
- package/templates/vue/server/dev-styles.mjs +22 -25
- package/templates/vue/server/email-bundle-watch.mjs +0 -12
- package/templates/vue/server/email.mjs +20 -15
- package/templates/vue/server/favicon.mjs +12 -11
- package/templates/vue/server/homepage.mjs +8 -10
- package/templates/vue/server/icons.mjs +18 -9
- package/templates/vue/server/in-place-render.mjs +6 -8
- package/templates/vue/server/listen-host.mjs +2 -2
- package/templates/vue/server/module-event.mjs +5 -5
- package/templates/vue/server/module-routes.mjs +11 -7
- package/templates/vue/server/module-server.mjs +8 -7
- package/templates/vue/server/module-testing.mjs +7 -7
- package/templates/vue/server/page-props.mjs +14 -14
- package/templates/vue/server/path-patterns.mjs +4 -4
- package/templates/vue/server/proxy-compression.mjs +21 -13
- package/templates/vue/server/proxy-headers.mjs +5 -3
- package/templates/vue/server/proxy-redirects.mjs +7 -6
- package/templates/vue/server/proxy-transfers.mjs +15 -11
- package/templates/vue/server/public-endpoints.mjs +27 -19
- package/templates/vue/server/public-options.mjs +9 -7
- package/templates/vue/server/request-failure.mjs +4 -6
- package/templates/vue/server/request-routing.mjs +8 -11
- package/templates/vue/server/request-target.mjs +6 -6
- package/templates/vue/server/route-rules.mjs +7 -5
- package/templates/vue/server/static-files.mjs +19 -10
- package/templates/vue/server/web-body.mjs +3 -4
- package/templates/vue/server.mjs +32 -19
- package/templates/vue/sfc-global-types.mjs +27 -24
- package/templates/vue/ssr-renderer.ts +10 -1
- package/templates/vue/ui-inertia-page.mjs +7 -7
- package/templates/vue/ui-kbd.mjs +10 -10
- package/templates/vue/ui-lazy-icons.mjs +44 -0
- package/templates/vue/ui-request-state.mjs +9 -9
- package/templates/vue/vite.config.ts +2 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { type InertiaLinkProps } from "@inertiajs/vue3";
|
|
2
|
+
import { type FetchOptions, ofetch } from "ofetch";
|
|
3
|
+
import {
|
|
4
|
+
type App,
|
|
5
|
+
type ComputedRef,
|
|
6
|
+
type MaybeRefOrGetter,
|
|
7
|
+
type Ref,
|
|
8
|
+
watch,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import type { Composer } from "vue-i18n";
|
|
11
|
+
import { type $fetch } from "./fetch";
|
|
12
|
+
|
|
13
|
+
export interface DmsPageRoute {
|
|
14
|
+
displayName?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
fullSlug?: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
layoutUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DmsPagePayload {
|
|
21
|
+
route?: DmsPageRoute;
|
|
22
|
+
componentName?: string;
|
|
23
|
+
layoutUrl?: string;
|
|
24
|
+
layout?: DmsPageLayout;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
export interface DmsSharedPagePayload {
|
|
28
|
+
siteLayout?: unknown;
|
|
29
|
+
siteLayoutTree?: unknown;
|
|
30
|
+
quickActions?: unknown;
|
|
31
|
+
modules?: unknown;
|
|
32
|
+
isOwner?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface DmsLayoutDefinition {
|
|
35
|
+
componentName?: string;
|
|
36
|
+
options?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export interface DmsPageLayout {
|
|
39
|
+
componentName?: string;
|
|
40
|
+
layout?: DmsLayoutDefinition;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
export interface DmsPageProps {
|
|
44
|
+
path: string;
|
|
45
|
+
page: DmsPagePayload;
|
|
46
|
+
/**
|
|
47
|
+
* The site layout. An Inertia once prop: a visit carries it over from the
|
|
48
|
+
* previous page until it expires, unless the server sends a newer one.
|
|
49
|
+
*/
|
|
50
|
+
shared?: DmsSharedPagePayload;
|
|
51
|
+
user?: DmsUser;
|
|
52
|
+
session?: DmsSession;
|
|
53
|
+
error?: DmsErrorData;
|
|
54
|
+
}
|
|
55
|
+
export interface PublicRuntimeConfig extends Record<string, unknown> {}
|
|
56
|
+
export interface RuntimeConfig extends Record<string, unknown> {}
|
|
57
|
+
export interface DmsAppConfig extends Record<string, unknown> {}
|
|
58
|
+
export interface DmsUser extends Record<string, unknown> {}
|
|
59
|
+
export interface DmsSession {
|
|
60
|
+
accountId: string;
|
|
61
|
+
activeTenantId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* When the frontend server last wrote this session, in milliseconds since
|
|
64
|
+
* the epoch. A page restored from history or from the prefetch cache
|
|
65
|
+
* carries an older session than the one the app already holds, and does not
|
|
66
|
+
* replace it.
|
|
67
|
+
*/
|
|
68
|
+
updatedAt?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface DmsRuntimeConfig extends RuntimeConfig {
|
|
71
|
+
public: PublicRuntimeConfig;
|
|
72
|
+
}
|
|
73
|
+
export interface DmsModuleOptions {
|
|
74
|
+
public: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
export type DmsLocaleLoader = (
|
|
77
|
+
locale: string,
|
|
78
|
+
) => Promise<Record<string, unknown>>;
|
|
79
|
+
export type DmsNavigationOptions = Pick<
|
|
80
|
+
InertiaLinkProps,
|
|
81
|
+
| "async"
|
|
82
|
+
| "component"
|
|
83
|
+
| "data"
|
|
84
|
+
| "except"
|
|
85
|
+
| "headers"
|
|
86
|
+
| "method"
|
|
87
|
+
| "onBefore"
|
|
88
|
+
| "onCancel"
|
|
89
|
+
| "onCancelToken"
|
|
90
|
+
| "onError"
|
|
91
|
+
| "onFinish"
|
|
92
|
+
| "onProgress"
|
|
93
|
+
| "onStart"
|
|
94
|
+
| "onSuccess"
|
|
95
|
+
| "only"
|
|
96
|
+
| "preserveScroll"
|
|
97
|
+
| "preserveState"
|
|
98
|
+
| "preserveUrl"
|
|
99
|
+
| "queryStringArrayFormat"
|
|
100
|
+
| "replace"
|
|
101
|
+
| "viewTransition"
|
|
102
|
+
>;
|
|
103
|
+
export interface RouteLocationObject {
|
|
104
|
+
path?: string;
|
|
105
|
+
query?: Record<string, unknown>;
|
|
106
|
+
hash?: string;
|
|
107
|
+
}
|
|
108
|
+
export type LocationQueryValue = string | null;
|
|
109
|
+
export type LocationQuery = Record<
|
|
110
|
+
string,
|
|
111
|
+
LocationQueryValue | LocationQueryValue[]
|
|
112
|
+
>;
|
|
113
|
+
export type LocationQueryRaw = Record<
|
|
114
|
+
string,
|
|
115
|
+
| LocationQueryValue
|
|
116
|
+
| number
|
|
117
|
+
| undefined
|
|
118
|
+
| (LocationQueryValue | number | undefined)[]
|
|
119
|
+
>;
|
|
120
|
+
export type RouteLocationRaw = string | RouteLocationObject;
|
|
121
|
+
export interface DmsRoute {
|
|
122
|
+
name?: string;
|
|
123
|
+
fullPath: string;
|
|
124
|
+
path: string;
|
|
125
|
+
query: Record<string, string>;
|
|
126
|
+
params: Record<string, string>;
|
|
127
|
+
meta: Record<string, unknown>;
|
|
128
|
+
matched: unknown[];
|
|
129
|
+
}
|
|
130
|
+
export interface DmsRouter {
|
|
131
|
+
currentRoute: Ref<DmsRoute>;
|
|
132
|
+
push(to: RouteLocationRaw, options?: DmsNavigationOptions): Promise<void>;
|
|
133
|
+
replace(to: RouteLocationRaw, options?: DmsNavigationOptions): Promise<void>;
|
|
134
|
+
back(): void;
|
|
135
|
+
}
|
|
136
|
+
export type DmsAsyncDataStatus = "idle" | "pending" | "success" | "error";
|
|
137
|
+
/**
|
|
138
|
+
* What a new run does with the one still in flight: `cancel` aborts it and
|
|
139
|
+
* starts over, `defer` waits for it instead.
|
|
140
|
+
*/
|
|
141
|
+
export type DmsAsyncDataDedupe = "cancel" | "defer";
|
|
142
|
+
export interface DmsAsyncDataRefreshOptions {
|
|
143
|
+
dedupe?: DmsAsyncDataDedupe;
|
|
144
|
+
}
|
|
145
|
+
export interface DmsAsyncData<T> {
|
|
146
|
+
data: Ref<T | null>;
|
|
147
|
+
/**
|
|
148
|
+
* The key the data is loaded under, a reactive key's current one; none for
|
|
149
|
+
* a call without a key.
|
|
150
|
+
*/
|
|
151
|
+
key: Readonly<Ref<string | undefined>>;
|
|
152
|
+
/**
|
|
153
|
+
* `true` while `data` is still the previous key's, shown in place of the
|
|
154
|
+
* default until the current key's first answer (a reactive key only).
|
|
155
|
+
*/
|
|
156
|
+
placeholder: Readonly<Ref<boolean>>;
|
|
157
|
+
/**
|
|
158
|
+
* The last run's failure. It stays while the next run is in flight, and
|
|
159
|
+
* goes back to `null` once a run answers.
|
|
160
|
+
*/
|
|
161
|
+
error: Ref<unknown>;
|
|
162
|
+
pending: Ref<boolean>;
|
|
163
|
+
status: Ref<DmsAsyncDataStatus>;
|
|
164
|
+
execute(options?: DmsAsyncDataRefreshOptions): Promise<void>;
|
|
165
|
+
refresh(options?: DmsAsyncDataRefreshOptions): Promise<void>;
|
|
166
|
+
/** Aborts the run in flight and puts the data back to its default. */
|
|
167
|
+
clear(): void;
|
|
168
|
+
}
|
|
169
|
+
/** Usable at once, or awaited until its data is there. */
|
|
170
|
+
export type DmsAsyncDataResult<T> = DmsAsyncData<T> & Promise<DmsAsyncData<T>>;
|
|
171
|
+
export interface DmsAsyncDataContext {
|
|
172
|
+
/**
|
|
173
|
+
* Aborted once the result is no longer wanted: a newer run replaced it, it
|
|
174
|
+
* was cleared, or no component shows its key any more.
|
|
175
|
+
*/
|
|
176
|
+
signal: AbortSignal;
|
|
177
|
+
}
|
|
178
|
+
export type DmsAsyncDataHandler<T> = (
|
|
179
|
+
context: DmsAsyncDataContext,
|
|
180
|
+
) => Promise<T>;
|
|
181
|
+
/** A key, or `null` (or `undefined`) while there is nothing to load. */
|
|
182
|
+
export type DmsAsyncDataKey = string | null | undefined;
|
|
183
|
+
/** What `useDmsData` gives of a key's data. */
|
|
184
|
+
export interface DmsData<T> {
|
|
185
|
+
/**
|
|
186
|
+
* The data loaded under the key, shared with every caller showing it:
|
|
187
|
+
* `undefined` while the key has none of its own (no caller loaded it yet,
|
|
188
|
+
* or it shows the previous key's data meanwhile). Assigning it replaces the
|
|
189
|
+
* key's data; a key nobody loaded keeps none.
|
|
190
|
+
*/
|
|
191
|
+
data: Ref<T | undefined>;
|
|
192
|
+
}
|
|
193
|
+
/** What `transform` knows of the run whose result it transforms. */
|
|
194
|
+
export interface DmsAsyncDataTransformContext {
|
|
195
|
+
/** The key the data is loaded under; none for a call without a key. */
|
|
196
|
+
key: string | undefined;
|
|
197
|
+
}
|
|
198
|
+
/** What `useDmsFetch`'s `transform` knows of the request it answers. */
|
|
199
|
+
export interface DmsFetchTransformContext extends DmsAsyncDataTransformContext {
|
|
200
|
+
/**
|
|
201
|
+
* The URL requested, as given (before the base URL and the query), even
|
|
202
|
+
* when a reactive URL changed since.
|
|
203
|
+
*/
|
|
204
|
+
url: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The request options a ref or a getter may stand for: read again for each
|
|
208
|
+
* request, and part of the key the response is shared under.
|
|
209
|
+
*/
|
|
210
|
+
type DmsFetchRequestSources = "method" | "query" | "params" | "body";
|
|
211
|
+
export interface UseAsyncDataOptions<T> {
|
|
212
|
+
default?: () => T;
|
|
213
|
+
/** `false` waits for `execute()` (a dialog, a drawer) before the first run. */
|
|
214
|
+
immediate?: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* A client navigation does not wait for the data. A server render still
|
|
217
|
+
* does: its HTML shows the data, and the browser takes it over.
|
|
218
|
+
*/
|
|
219
|
+
lazy?: boolean;
|
|
220
|
+
/** `false` leaves the data to the browser: the server renders the default. */
|
|
221
|
+
server?: boolean;
|
|
222
|
+
watch?: false | unknown[];
|
|
223
|
+
transform?: (
|
|
224
|
+
value: unknown,
|
|
225
|
+
context: DmsAsyncDataTransformContext,
|
|
226
|
+
) => T | Promise<T>;
|
|
227
|
+
pick?: string[];
|
|
228
|
+
/**
|
|
229
|
+
* `true` tracks changes inside the value too. By default only the
|
|
230
|
+
* replacement of the whole value triggers, as in Nuxt 4.
|
|
231
|
+
*/
|
|
232
|
+
deep?: boolean;
|
|
233
|
+
dedupe?: DmsAsyncDataDedupe;
|
|
234
|
+
/**
|
|
235
|
+
* `false` leaves the data out of `refreshDmsPageData()`, which reads again
|
|
236
|
+
* the data of the components on screen once the page changed (a save, a
|
|
237
|
+
* realtime event).
|
|
238
|
+
*/
|
|
239
|
+
refreshOnPageRefresh?: boolean;
|
|
240
|
+
/**
|
|
241
|
+
* Reads again every this many milliseconds, in the browser, while the
|
|
242
|
+
* caller shows the data and the page is visible. A ref or a getter changes
|
|
243
|
+
* the interval as it changes; `0`, `null` or `undefined` stops the polling
|
|
244
|
+
* until it has a value again. A call outside a component (a plugin, a
|
|
245
|
+
* callback) polls for as long as the application runs.
|
|
246
|
+
*/
|
|
247
|
+
refreshInterval?: MaybeRefOrGetter<number | null | undefined>;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* `useDmsFetch`'s options: ofetch's request options, of which the method, the
|
|
251
|
+
* query, the params and the body may be refs or getters, and the async data
|
|
252
|
+
* options.
|
|
253
|
+
*/
|
|
254
|
+
export interface UseFetchOptions<T>
|
|
255
|
+
extends
|
|
256
|
+
Omit<FetchOptions, DmsFetchRequestSources>,
|
|
257
|
+
Omit<UseAsyncDataOptions<T>, "transform"> {
|
|
258
|
+
/**
|
|
259
|
+
* The key the response is shared and transferred under, in place of the
|
|
260
|
+
* one the request gives: its method, base URL, URL, query and body. A ref
|
|
261
|
+
* or a getter moves the caller to the data of each key it takes; `null` or
|
|
262
|
+
* `undefined` requests nothing until it has a value.
|
|
263
|
+
*/
|
|
264
|
+
key?: MaybeRefOrGetter<DmsAsyncDataKey>;
|
|
265
|
+
method?: MaybeRefOrGetter<FetchOptions["method"]>;
|
|
266
|
+
query?: MaybeRefOrGetter<FetchOptions["query"]>;
|
|
267
|
+
params?: MaybeRefOrGetter<FetchOptions["params"]>;
|
|
268
|
+
body?: MaybeRefOrGetter<FetchOptions["body"]>;
|
|
269
|
+
transform?: (
|
|
270
|
+
value: unknown,
|
|
271
|
+
context: DmsFetchTransformContext,
|
|
272
|
+
) => T | Promise<T>;
|
|
273
|
+
/** Replaces the fetch function; it receives the run's signal. */
|
|
274
|
+
$fetch?: typeof ofetch;
|
|
275
|
+
}
|
|
276
|
+
export interface DmsErrorData {
|
|
277
|
+
statusCode?: number;
|
|
278
|
+
statusMessage?: string;
|
|
279
|
+
message?: string;
|
|
280
|
+
data?: unknown;
|
|
281
|
+
fatal?: boolean;
|
|
282
|
+
}
|
|
283
|
+
/** What a server render hands the browser along with its HTML. */
|
|
284
|
+
export interface DmsPayload {
|
|
285
|
+
data: Record<string, unknown>;
|
|
286
|
+
errors: Record<string, DmsErrorData>;
|
|
287
|
+
state: Record<string, unknown>;
|
|
288
|
+
}
|
|
289
|
+
export interface DmsStateOptions {
|
|
290
|
+
/**
|
|
291
|
+
* Hands the server's value to the browser, which then starts from it
|
|
292
|
+
* instead of calling `init` again. Never for tokens or secrets: the value
|
|
293
|
+
* is written into the page.
|
|
294
|
+
*/
|
|
295
|
+
transfer?: boolean;
|
|
296
|
+
}
|
|
297
|
+
export interface DmsUserSession<TUser = DmsUser, TSession = DmsSession> {
|
|
298
|
+
user: Ref<TUser | null>;
|
|
299
|
+
session: Ref<TSession | null>;
|
|
300
|
+
loggedIn: Ref<boolean>;
|
|
301
|
+
fetch(): Promise<void>;
|
|
302
|
+
clear(): Promise<void>;
|
|
303
|
+
}
|
|
304
|
+
export interface DmsCookieOptions<T> {
|
|
305
|
+
default?: () => T;
|
|
306
|
+
maxAge?: number;
|
|
307
|
+
path?: string;
|
|
308
|
+
sameSite?: "strict" | "lax" | "none";
|
|
309
|
+
secure?: boolean;
|
|
310
|
+
}
|
|
311
|
+
type DmsLinkPrefetchMode = "mount" | "hover" | "click";
|
|
312
|
+
export type DmsLinkPrefetch =
|
|
313
|
+
| boolean
|
|
314
|
+
| DmsLinkPrefetchMode
|
|
315
|
+
| DmsLinkPrefetchMode[];
|
|
316
|
+
export interface DmsAppContext {
|
|
317
|
+
provide<T>(key: string, value: T): void;
|
|
318
|
+
vueApp: App;
|
|
319
|
+
runWithContext<T>(callback: () => T): T;
|
|
320
|
+
hook(name: string, callback: () => void | Promise<void>): void;
|
|
321
|
+
$i18n: DmsI18n;
|
|
322
|
+
}
|
|
323
|
+
export interface DmsLocale {
|
|
324
|
+
code: string;
|
|
325
|
+
name?: string;
|
|
326
|
+
}
|
|
327
|
+
export type DmsI18n = Composer & {
|
|
328
|
+
locales: ComputedRef<DmsLocale[]>;
|
|
329
|
+
setLocale(locale: string): Promise<void>;
|
|
330
|
+
};
|
|
331
|
+
export type DmsPluginSetup = (context: DmsAppContext) => void | Promise<void>;
|
|
332
|
+
export interface DmsMiddlewareRegistrationOptions {
|
|
333
|
+
global?: boolean;
|
|
334
|
+
/**
|
|
335
|
+
* Runs at the same time as the parallel middlewares next to it, for one
|
|
336
|
+
* that reads nothing another middleware sets (a check against the backend,
|
|
337
|
+
* say). Their results still apply in registration order: the first that
|
|
338
|
+
* redirects or aborts wins, and the next middlewares wait for all of them.
|
|
339
|
+
*/
|
|
340
|
+
parallel?: boolean;
|
|
341
|
+
}
|
|
342
|
+
export interface DmsPluginRegistrationOptions {
|
|
343
|
+
clientOnly?: boolean;
|
|
344
|
+
/**
|
|
345
|
+
* When the plugin runs, as Nuxt's `enforce`: every module's `"pre"`
|
|
346
|
+
* plugins first, then the `"default"` ones, then the `"post"` ones, each
|
|
347
|
+
* group in the modules' priority order. A plugin that the others depend on
|
|
348
|
+
* whatever their module's priority, such as the one setting the async data
|
|
349
|
+
* scope (`setDmsAsyncDataScope`), is `"pre"`.
|
|
350
|
+
*/
|
|
351
|
+
enforce?: "pre" | "default" | "post";
|
|
352
|
+
}
|
|
353
|
+
export type DmsComponentPreloader = () => Promise<unknown>;
|
|
354
|
+
export interface DmsComponentRegistrationOptions {
|
|
355
|
+
/**
|
|
356
|
+
* Registers the component under `name` as given, without the module's
|
|
357
|
+
* `componentPrefix`: the way to replace a component another module
|
|
358
|
+
* registers, from a module of higher priority. Defaults to `true`.
|
|
359
|
+
*/
|
|
360
|
+
prefix?: boolean;
|
|
361
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Link as InertiaLink } from "@inertiajs/vue3";
|
|
2
|
+
import {
|
|
3
|
+
useHead as useUnhead,
|
|
4
|
+
useSeoMeta as useUnheadSeoMeta,
|
|
5
|
+
} from "@unhead/vue";
|
|
6
|
+
import {
|
|
7
|
+
type App,
|
|
8
|
+
type Component,
|
|
9
|
+
type ComponentInternalInstance,
|
|
10
|
+
type ComputedRef,
|
|
11
|
+
defineComponent,
|
|
12
|
+
getCurrentInstance,
|
|
13
|
+
h,
|
|
14
|
+
type InjectionKey,
|
|
15
|
+
inject,
|
|
16
|
+
onMounted,
|
|
17
|
+
type PropType,
|
|
18
|
+
type Ref,
|
|
19
|
+
ref,
|
|
20
|
+
} from "vue";
|
|
21
|
+
import { useI18n as useVueI18n } from "vue-i18n";
|
|
22
|
+
import { type DmsFrontendRuntime } from "./module-registration";
|
|
23
|
+
import {
|
|
24
|
+
type DmsAppContext,
|
|
25
|
+
type DmsI18n,
|
|
26
|
+
type DmsLinkPrefetch,
|
|
27
|
+
type DmsNavigationOptions,
|
|
28
|
+
type DmsPluginSetup,
|
|
29
|
+
type RouteLocationRaw,
|
|
30
|
+
} from "./types";
|
|
31
|
+
import { useDmsRuntime } from "./state";
|
|
32
|
+
import { locationToUrl, visit } from "./route";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* What Vue's development build passes `errorHandler` for an error thrown by a
|
|
36
|
+
* component's `setup`, sync or async (production passes a link instead; it has
|
|
37
|
+
* no warnings to scope either).
|
|
38
|
+
*/
|
|
39
|
+
export const SETUP_FUNCTION_ERROR = "setup function";
|
|
40
|
+
/**
|
|
41
|
+
* Whether a Vue warning comes from a component whose `setup` failed, or from
|
|
42
|
+
* under it. Vue still renders such a component, against the setup state that
|
|
43
|
+
* never came: in development, where a single-file component's template is
|
|
44
|
+
* compiled apart from its setup, the template reads the missing bindings, and
|
|
45
|
+
* every component it uses becomes `undefined` ("Invalid vnode type when
|
|
46
|
+
* creating vnode: undefined", then "Component <Anonymous> is missing template
|
|
47
|
+
* or render function" for the comment Vue renders in its place). The error
|
|
48
|
+
* page replaces that output (a server renders the page again, the browser
|
|
49
|
+
* swaps it), and the failure itself is already reported.
|
|
50
|
+
*
|
|
51
|
+
* Walks internal instances: a `<script setup>` component exposes itself, so
|
|
52
|
+
* its children's `$parent` is an expose proxy, not the proxy `errorHandler`
|
|
53
|
+
* received.
|
|
54
|
+
*/
|
|
55
|
+
export function withinFailedSetup(
|
|
56
|
+
instance: ComponentInternalInstance | null,
|
|
57
|
+
failedSetups: WeakSet<ComponentInternalInstance>,
|
|
58
|
+
): boolean {
|
|
59
|
+
for (let current = instance; current; current = current.parent)
|
|
60
|
+
if (failedSetups.has(current)) return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/** Shows the error page in place of the page, keeping the first failure. */
|
|
64
|
+
export function failPage(runtime: DmsFrontendRuntime, reason: unknown): void {
|
|
65
|
+
runtime.currentError.value ??=
|
|
66
|
+
reason instanceof Error ? reason : new Error(String(reason));
|
|
67
|
+
}
|
|
68
|
+
export function abortDmsNavigation(): false {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
export const useHead = useUnhead;
|
|
72
|
+
export const useSeoMeta = useUnheadSeoMeta;
|
|
73
|
+
export function useI18n(): DmsI18n {
|
|
74
|
+
return useVueI18n() as DmsI18n;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function shouldNavigateDmsLink(event: MouseEvent): boolean {
|
|
78
|
+
const element = event.currentTarget;
|
|
79
|
+
return !(
|
|
80
|
+
event.defaultPrevented ||
|
|
81
|
+
event.button !== 0 ||
|
|
82
|
+
event.altKey ||
|
|
83
|
+
event.ctrlKey ||
|
|
84
|
+
event.metaKey ||
|
|
85
|
+
event.shiftKey ||
|
|
86
|
+
(element instanceof HTMLAnchorElement &&
|
|
87
|
+
Boolean(element.target) &&
|
|
88
|
+
element.target !== "_self")
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function dmsLinkNavigationOptions(
|
|
93
|
+
attributes: Record<string, unknown>,
|
|
94
|
+
): DmsNavigationOptions {
|
|
95
|
+
const options = attributes as DmsNavigationOptions;
|
|
96
|
+
return {
|
|
97
|
+
async: options.async,
|
|
98
|
+
component: options.component,
|
|
99
|
+
data: options.data,
|
|
100
|
+
except: options.except,
|
|
101
|
+
headers: options.headers,
|
|
102
|
+
method: options.method,
|
|
103
|
+
onBefore: options.onBefore,
|
|
104
|
+
onCancel: options.onCancel,
|
|
105
|
+
onCancelToken: options.onCancelToken,
|
|
106
|
+
onError: options.onError,
|
|
107
|
+
onFinish: options.onFinish,
|
|
108
|
+
onProgress: options.onProgress,
|
|
109
|
+
onStart: options.onStart,
|
|
110
|
+
onSuccess: options.onSuccess,
|
|
111
|
+
only: options.only,
|
|
112
|
+
preserveScroll: options.preserveScroll,
|
|
113
|
+
preserveState: options.preserveState,
|
|
114
|
+
preserveUrl: options.preserveUrl,
|
|
115
|
+
queryStringArrayFormat: options.queryStringArrayFormat,
|
|
116
|
+
replace: options.replace,
|
|
117
|
+
viewTransition: options.viewTransition,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const DmsLink: Component = defineComponent({
|
|
122
|
+
name: "DmsLink",
|
|
123
|
+
inheritAttrs: false,
|
|
124
|
+
props: {
|
|
125
|
+
to: {
|
|
126
|
+
type: [String, Object] as PropType<RouteLocationRaw>,
|
|
127
|
+
required: true,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
setup(props, { attrs, slots }) {
|
|
131
|
+
const runtime = useDmsRuntime();
|
|
132
|
+
const prefetch = attrs.prefetch as DmsLinkPrefetch | undefined;
|
|
133
|
+
const onClick = (event: MouseEvent) => {
|
|
134
|
+
if (typeof attrs.onClick === "function") attrs.onClick(event);
|
|
135
|
+
if (!shouldNavigateDmsLink(event)) return;
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
void visit(runtime, props.to, dmsLinkNavigationOptions(attrs));
|
|
138
|
+
};
|
|
139
|
+
return (): ReturnType<typeof h> =>
|
|
140
|
+
h(
|
|
141
|
+
InertiaLink,
|
|
142
|
+
{
|
|
143
|
+
...attrs,
|
|
144
|
+
href: locationToUrl(runtime, props.to),
|
|
145
|
+
onClick,
|
|
146
|
+
prefetch: prefetch ?? "hover",
|
|
147
|
+
},
|
|
148
|
+
slots,
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/** How a keyboard hint prints its modifiers: macOS symbols, or PC key names. */
|
|
154
|
+
export type DmsKbdPlatform = "mac" | "other";
|
|
155
|
+
/**
|
|
156
|
+
* Where Nuxt UI's key hints read the platform: called in the setup of each
|
|
157
|
+
* component that prints one, it returns the platform that component renders.
|
|
158
|
+
* It must read on the server the platform it renders there, and keep it while
|
|
159
|
+
* its caller hydrates, so the browser's first render matches the server's.
|
|
160
|
+
*/
|
|
161
|
+
export type DmsKbdPlatformSource = () => Readonly<Ref<DmsKbdPlatform>>;
|
|
162
|
+
|
|
163
|
+
const KBD_PLATFORM_KEY: InjectionKey<DmsKbdPlatformSource> =
|
|
164
|
+
Symbol("dms:kbd-platform");
|
|
165
|
+
const MODIFIER_KBD_KEYS: Record<
|
|
166
|
+
DmsKbdPlatform,
|
|
167
|
+
Record<"meta" | "ctrl" | "alt", string>
|
|
168
|
+
> = {
|
|
169
|
+
mac: { meta: "⌘", ctrl: "⌃", alt: "⌥" },
|
|
170
|
+
other: { meta: "Ctrl", ctrl: "Ctrl", alt: "Alt" },
|
|
171
|
+
};
|
|
172
|
+
const isModifierKbdKey = (value: string): value is "meta" | "ctrl" | "alt" =>
|
|
173
|
+
value === "meta" || value === "ctrl" || value === "alt";
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Gives Nuxt UI's key hints (`UKbd`, the `kbds` of its menus, the dashboard
|
|
177
|
+
* search button) the application's keyboard platform, so the server prints
|
|
178
|
+
* `meta` as "⌘" or "Ctrl" from the first render instead of a blank the
|
|
179
|
+
* browser fills in after mounting.
|
|
180
|
+
*/
|
|
181
|
+
export function provideDmsKbdPlatform(
|
|
182
|
+
app: App,
|
|
183
|
+
source: DmsKbdPlatformSource,
|
|
184
|
+
): void {
|
|
185
|
+
app.provide(KBD_PLATFORM_KEY, source);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface NuxtUiKbd {
|
|
189
|
+
macOS: ComputedRef<unknown>;
|
|
190
|
+
getKbdKey: (value?: string) => string | undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Nuxt UI's `useKbd` as the DMS runs it (`ui-kbd.mjs` routes it here): a hint
|
|
195
|
+
* renders the same on the server and in the hydrating browser. Nuxt UI prints
|
|
196
|
+
* `meta`, `ctrl` and `alt` from a map shared by every hint and filled by the
|
|
197
|
+
* first one's `onMounted`, so a hint hydrating after that (in an async
|
|
198
|
+
* component) printed "⌘" over the server's blank. Each hint now keeps the
|
|
199
|
+
* server's value until its own `onMounted`; with a platform provided
|
|
200
|
+
* (`provideDmsKbdPlatform`), that value is the platform's from the start.
|
|
201
|
+
*
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
export function useDmsKbd(
|
|
205
|
+
shared: () => NuxtUiKbd,
|
|
206
|
+
keys: Readonly<Record<string, string>>,
|
|
207
|
+
): NuxtUiKbd {
|
|
208
|
+
const { macOS } = shared();
|
|
209
|
+
const instance = getCurrentInstance();
|
|
210
|
+
const source = instance ? inject(KBD_PLATFORM_KEY, null) : null;
|
|
211
|
+
const platform = source ? source() : null;
|
|
212
|
+
// A hydrating component's vnode is bound to its server node before setup.
|
|
213
|
+
const hydrating = ref(!!instance?.vnode.el);
|
|
214
|
+
if (hydrating.value) onMounted(() => (hydrating.value = false));
|
|
215
|
+
return {
|
|
216
|
+
macOS,
|
|
217
|
+
getKbdKey(value) {
|
|
218
|
+
if (!value) return undefined;
|
|
219
|
+
if (!isModifierKbdKey(value)) return keys[value] || value;
|
|
220
|
+
if (platform) return MODIFIER_KBD_KEYS[platform.value][value];
|
|
221
|
+
if (typeof window === "undefined" || hydrating.value) return " ";
|
|
222
|
+
return MODIFIER_KBD_KEYS[macOS.value ? "mac" : "other"][value];
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const DmsClientOnly = defineComponent({
|
|
228
|
+
name: "DmsClientOnly",
|
|
229
|
+
setup(_, { slots }) {
|
|
230
|
+
const isMounted = ref(false);
|
|
231
|
+
onMounted(() => {
|
|
232
|
+
isMounted.value = true;
|
|
233
|
+
});
|
|
234
|
+
return () =>
|
|
235
|
+
isMounted.value ? slots.default?.() : (slots.fallback?.() ?? null);
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export function defineDmsPlugin(setup: DmsPluginSetup): DmsPluginSetup {
|
|
240
|
+
return setup;
|
|
241
|
+
}
|
|
242
|
+
export function useDmsApp(): DmsAppContext {
|
|
243
|
+
const instance = getCurrentInstance();
|
|
244
|
+
const contextual = instance?.appContext.config.globalProperties
|
|
245
|
+
.$dms as DmsAppContext;
|
|
246
|
+
return contextual ?? (useDmsRuntime().appContext as DmsAppContext);
|
|
247
|
+
}
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
// Restoring "paint before hydrate" in the built document.
|
|
2
|
-
//
|
|
3
|
-
// The source index.html marks the entry script `fetchpriority="low"` so the
|
|
4
|
-
// browser spends its bandwidth on the render-blocking stylesheet first. Vite
|
|
5
|
-
// rewrites that script tag when it builds and drops the attribute, and it emits
|
|
6
|
-
// the script *before* the stylesheet — so the production document did the
|
|
7
|
-
// opposite of what the template asked: it discovered a 250 kB hydration bundle
|
|
8
|
-
// first and let it compete with the 27 kB the first paint actually waits on,
|
|
9
|
-
// over the plain HTTP/1.1 the generated server speaks.
|
|
10
|
-
//
|
|
11
|
-
// Kept beside vite.config.ts rather than inside it so the transform can be
|
|
12
|
-
// exercised on its own, the way client-manifest.mjs is.
|
|
13
|
-
|
|
14
1
|
const SCRIPT = /<script[^>]*\stype="module"[^>]*><\/script>/;
|
|
15
2
|
const STYLESHEET = /[ \t]*<link rel="stylesheet"[^>]*>\n?/;
|
|
16
3
|
|
|
@@ -26,6 +13,17 @@ function deprioritize(html) {
|
|
|
26
13
|
* Give the entry script back its low fetch priority and move the stylesheet
|
|
27
14
|
* ahead of it. A document that already has them in that order is returned
|
|
28
15
|
* unchanged, so the transform is safe to run twice.
|
|
16
|
+
*
|
|
17
|
+
* Restores "paint before hydrate" in the built document. The source index.html
|
|
18
|
+
* marks the entry script `fetchpriority="low"` so the browser spends its
|
|
19
|
+
* bandwidth on the render-blocking stylesheet first. Vite rewrites that script
|
|
20
|
+
* tag when it builds and drops the attribute, and it emits the script before
|
|
21
|
+
* the stylesheet, so the production document did the opposite of what the
|
|
22
|
+
* template asked: it discovered a 250 kB hydration bundle first and let it
|
|
23
|
+
* compete with the 27 kB the first paint actually waits on, over the plain
|
|
24
|
+
* HTTP/1.1 the generated server speaks. Kept beside vite.config.ts rather than
|
|
25
|
+
* inside it so the transform can be exercised on its own, the way
|
|
26
|
+
* client-manifest.mjs is.
|
|
29
27
|
*/
|
|
30
28
|
export function orderHeadForFirstPaint(html) {
|
|
31
29
|
const prioritized = deprioritize(html);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { addIcon, type IconifyIcon } from "@iconify/vue";
|
|
2
|
+
|
|
3
|
+
function register(name: string, data: IconifyIcon): boolean {
|
|
4
|
+
const colon = name.indexOf(":");
|
|
5
|
+
if (colon !== -1 && name.slice(0, colon).includes("-")) {
|
|
6
|
+
addIcon(name.replace(":", "-"), data);
|
|
7
|
+
}
|
|
8
|
+
return addIcon(name, data);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let loading: Promise<void> | undefined;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads the icons Nuxt UI bundles for the client, once: every icon the
|
|
15
|
+
* application's sources name. A server render has them from the start (Nuxt
|
|
16
|
+
* UI's own plugin, left in place by `ui-lazy-icons.mjs`), so there is nothing
|
|
17
|
+
* to load.
|
|
18
|
+
*
|
|
19
|
+
* A failed load is forgotten and resolves: an icon the bundle would have
|
|
20
|
+
* held is then fetched from the icon API, as one outside the bundle always is.
|
|
21
|
+
*/
|
|
22
|
+
export function loadIconBundle(): Promise<void> {
|
|
23
|
+
if (import.meta.env.SSR) return Promise.resolve();
|
|
24
|
+
loading ??= import("virtual:nuxt-ui-icons").then(
|
|
25
|
+
({ init }) => init(register),
|
|
26
|
+
() => {
|
|
27
|
+
loading = undefined;
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
return loading;
|
|
31
|
+
}
|