@flux-ui/application 3.0.0-next.39

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 (59) hide show
  1. package/README.md +16 -0
  2. package/dist/component/FluxApplication.vue.d.ts +28 -0
  3. package/dist/component/FluxApplicationContent.vue.d.ts +24 -0
  4. package/dist/component/FluxApplicationHero.vue.d.ts +26 -0
  5. package/dist/component/FluxApplicationMenu.vue.d.ts +29 -0
  6. package/dist/component/FluxApplicationMenuAccount.vue.d.ts +29 -0
  7. package/dist/component/FluxApplicationMenuContext.vue.d.ts +15 -0
  8. package/dist/component/FluxApplicationMenuContextStack.vue.d.ts +6 -0
  9. package/dist/component/FluxApplicationMenuPromo.vue.d.ts +22 -0
  10. package/dist/component/FluxApplicationMenuToggle.vue.d.ts +3 -0
  11. package/dist/component/FluxApplicationSection.vue.d.ts +26 -0
  12. package/dist/component/FluxApplicationSide.vue.d.ts +20 -0
  13. package/dist/component/FluxApplicationTop.vue.d.ts +29 -0
  14. package/dist/component/index.d.ts +12 -0
  15. package/dist/composable/index.d.ts +3 -0
  16. package/dist/composable/useApplicationContextMenu.d.ts +7 -0
  17. package/dist/composable/useApplicationContextRegistration.d.ts +2 -0
  18. package/dist/composable/useApplicationInjection.d.ts +2 -0
  19. package/dist/composable/useApplicationMenu.d.ts +26 -0
  20. package/dist/data/index.d.ts +29 -0
  21. package/dist/index.css +746 -0
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.js +4331 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/routing/useNamedRoutes.d.ts +14 -0
  26. package/dist/routing/useRoute.d.ts +8 -0
  27. package/dist/types.d.ts +13 -0
  28. package/package.json +71 -0
  29. package/src/component/FluxApplication.vue +144 -0
  30. package/src/component/FluxApplicationContent.vue +37 -0
  31. package/src/component/FluxApplicationHero.vue +34 -0
  32. package/src/component/FluxApplicationMenu.vue +73 -0
  33. package/src/component/FluxApplicationMenuAccount.vue +47 -0
  34. package/src/component/FluxApplicationMenuContext.vue +78 -0
  35. package/src/component/FluxApplicationMenuContextStack.vue +53 -0
  36. package/src/component/FluxApplicationMenuPromo.vue +23 -0
  37. package/src/component/FluxApplicationMenuToggle.vue +30 -0
  38. package/src/component/FluxApplicationSection.vue +40 -0
  39. package/src/component/FluxApplicationSide.vue +16 -0
  40. package/src/component/FluxApplicationTop.vue +74 -0
  41. package/src/component/index.ts +12 -0
  42. package/src/composable/index.ts +3 -0
  43. package/src/composable/useApplicationContextMenu.ts +19 -0
  44. package/src/composable/useApplicationContextRegistration.ts +25 -0
  45. package/src/composable/useApplicationInjection.ts +12 -0
  46. package/src/composable/useApplicationMenu.ts +79 -0
  47. package/src/css/component/Application.module.scss +88 -0
  48. package/src/css/component/ApplicationContent.module.scss +119 -0
  49. package/src/css/component/ApplicationHero.module.scss +17 -0
  50. package/src/css/component/ApplicationMenu.module.scss +421 -0
  51. package/src/css/component/ApplicationSection.module.scss +43 -0
  52. package/src/css/component/ApplicationSide.module.scss +21 -0
  53. package/src/css/component/ApplicationTop.module.scss +111 -0
  54. package/src/data/index.ts +38 -0
  55. package/src/index.ts +3 -0
  56. package/src/routing/useNamedRoutes.ts +34 -0
  57. package/src/routing/useRoute.ts +11 -0
  58. package/src/types.d.ts +13 -0
  59. package/tsconfig.json +7 -0
@@ -0,0 +1,421 @@
1
+ @use '$flux/css/mixin';
2
+
3
+ .applicationMenu {
4
+ position: fixed;
5
+ display: flex;
6
+ top: 0;
7
+ left: 0;
8
+ height: 100dvh;
9
+ width: var(--application-menu-width);
10
+ flex-flow: column;
11
+ transition: var(--application-duration) var(--swift-out);
12
+ transition-property: translate, width;
13
+ background: var(--application-menu-surface);
14
+ border-right: 1px solid var(--application-menu-surface-stroke);
15
+ overflow: hidden;
16
+ z-index: 500;
17
+ }
18
+
19
+ .applicationMenuFooter,
20
+ .applicationMenuHeader,
21
+ .applicationMenuPanel {
22
+ padding: 12px;
23
+ gap: 12px;
24
+ }
25
+
26
+ .applicationMenuFooter,
27
+ .applicationMenuHeader {
28
+ position: relative;
29
+ background: var(--application-menu-surface);
30
+ z-index: 1;
31
+
32
+ &::before {
33
+ position: absolute;
34
+ left: 0;
35
+ right: 0;
36
+ height: 18px;
37
+ width: 100%;
38
+ content: '';
39
+ pointer-events: none;
40
+ }
41
+ }
42
+
43
+ .applicationMenuFooter {
44
+ max-height: 50vh;
45
+ padding-bottom: max(12px, env(safe-area-inset-bottom));
46
+ transition: var(--application-duration) var(--swift-out);
47
+ transition-property: max-height, opacity, padding-top, padding-bottom;
48
+
49
+ &::before {
50
+ bottom: 100%;
51
+ background: linear-gradient(to top, var(--application-menu-surface), transparent);
52
+ transition: opacity var(--application-duration) var(--swift-out);
53
+ }
54
+
55
+ /**
56
+ * Collapses the footer in sync with the slider track when the
57
+ * user navigates into a context-menu panel. Animating `max-height`
58
+ * is the standard hack to transition a height-auto element; 50vh
59
+ * is far above any realistic footer height yet small enough to
60
+ * animate smoothly without the leading edge appearing to "wait".
61
+ */
62
+ &[data-hidden] {
63
+ max-height: 0;
64
+ padding-top: 0;
65
+ padding-bottom: 0;
66
+ opacity: 0;
67
+ pointer-events: none;
68
+ overflow: hidden;
69
+
70
+ &::before {
71
+ opacity: 0;
72
+ }
73
+ }
74
+ }
75
+
76
+ .applicationMenuHeader {
77
+ padding-top: max(12px, env(safe-area-inset-top));
78
+ border-bottom: 1px solid var(--application-menu-surface-stroke);
79
+
80
+ &::before {
81
+ top: calc(100% + 1px);
82
+ background: linear-gradient(to bottom, var(--application-menu-surface), transparent);
83
+ }
84
+ }
85
+
86
+ .applicationMenuStage {
87
+ position: relative;
88
+ min-height: 0;
89
+ flex: 1 1 auto;
90
+ overflow: hidden;
91
+ }
92
+
93
+ .applicationMenuTrack {
94
+ display: flex;
95
+ flex-flow: row nowrap;
96
+ height: 100%;
97
+ transform: translateX(calc(var(--view-index, 0) * -100%));
98
+ transition: transform var(--application-duration) var(--swift-out);
99
+ will-change: transform;
100
+ }
101
+
102
+ .applicationMenuPanel {
103
+ display: flex;
104
+ flex: 0 0 100%;
105
+ flex-flow: column;
106
+ width: 100%;
107
+ overflow-y: auto;
108
+ }
109
+
110
+ .applicationMenuPageIndicator {
111
+ display: flex;
112
+ flex-flow: row nowrap;
113
+ gap: 6px;
114
+ justify-content: center;
115
+ align-items: center;
116
+ padding: 9px 12px;
117
+ background: var(--application-menu-surface);
118
+ z-index: 1;
119
+ }
120
+
121
+ .applicationMenuPageIndicatorDot {
122
+ height: 6px;
123
+ width: 6px;
124
+ padding: 0;
125
+ cursor: pointer;
126
+ background: transparent;
127
+ border: 1.5px solid var(--application-menu-surface-stroke);
128
+ border-radius: 50%;
129
+ transition: 200ms var(--swift-out);
130
+ transition-property: background, border-color, transform;
131
+
132
+ @include mixin.hover {
133
+ border-color: var(--foreground-secondary);
134
+ transform: scale(1.2);
135
+ }
136
+
137
+ @include mixin.focus-ring;
138
+ }
139
+
140
+ .applicationMenuPageIndicatorDotActive {
141
+ background: var(--foreground-prominent);
142
+ border-color: var(--foreground-prominent);
143
+ }
144
+
145
+ /**
146
+ * No visual animation on enter/leave — just hold the leaving panel in
147
+ * the DOM for the duration of the slider's translate transition so it
148
+ * stays visible while the track slides away from it. Without this the
149
+ * v-for would unmount the panel the moment matches shrinks, leaving
150
+ * the slider sliding over an empty track.
151
+ */
152
+ .applicationMenuPanelEnterActive,
153
+ .applicationMenuPanelEnterFrom,
154
+ .applicationMenuPanelLeaveActive,
155
+ .applicationMenuPanelLeaveTo {
156
+ transition: opacity var(--application-duration) var(--swift-out);
157
+ }
158
+
159
+ .applicationMenuPanelLeaveActive {
160
+ pointer-events: none;
161
+ }
162
+
163
+ .applicationMenu :local(.divider) {
164
+ margin: 3px 12px;
165
+ }
166
+
167
+ .applicationMenu :local(.dividerContent)::before,
168
+ .applicationMenu :local(.dividerContent)::after,
169
+ .applicationMenu :local(.dividerLine) {
170
+ background: var(--application-menu-surface-stroke);
171
+ }
172
+
173
+ .applicationMenu :local(.menuSubHeader) {
174
+ --surface: var(--application-menu-surface);
175
+ }
176
+
177
+ .applicationMenu :local(.menuItem) {
178
+ position: relative;
179
+
180
+ @include mixin.hover {
181
+ background: var(--gray-200);
182
+ }
183
+
184
+ &:active {
185
+ background: var(--gray-300);
186
+ }
187
+
188
+ &::before {
189
+ position: absolute;
190
+ display: block;
191
+ content: '';
192
+ top: 50%;
193
+ right: 0;
194
+ height: 1em;
195
+ width: 3px;
196
+ background: var(--foreground-secondary);
197
+ border-radius: var(--radius);
198
+ opacity: 0;
199
+ transition: 300ms var(--swift-out);
200
+ transition-property: height, opacity;
201
+ translate: 8px -50%;
202
+ }
203
+ }
204
+
205
+ .applicationMenu :local(.menuItemIcon) {
206
+ color: var(--foreground-secondary);
207
+ }
208
+
209
+ .applicationMenu :local(.menuItemLabel) {
210
+ transition: var(--application-duration) var(--swift-out);
211
+ transition-property: filter, opacity, translate;
212
+ white-space: nowrap;
213
+ }
214
+
215
+ .applicationMenu :local(.menuItemActive) {
216
+ background: var(--gray-200);
217
+ color: var(--foreground-prominent);
218
+
219
+ &::before {
220
+ height: 1.6em;
221
+ opacity: 1;
222
+ }
223
+ }
224
+
225
+ .applicationMenu :local(.menuItemActive) :local(.menuItemIcon) {
226
+ color: var(--foreground);
227
+ }
228
+
229
+ .applicationMenu :local(.menuItemActive) :local(.menuItemLabel) {
230
+ font-weight: 500;
231
+ }
232
+
233
+ .applicationMenuAccount {
234
+ position: relative;
235
+
236
+ pointer-events: none;
237
+ }
238
+
239
+ .applicationMenuAccountSwitcher {
240
+ composes: applicationMenuAccount;
241
+
242
+ pointer-events: auto;
243
+ }
244
+
245
+ .applicationMenuAccount :local(.menuItemLabel) {
246
+ font-weight: 600;
247
+ }
248
+
249
+ .applicationMenuAccount :local(.avatar) {
250
+ margin: -5px;
251
+ font-size: 30px;
252
+ }
253
+
254
+ .applicationMenuContext {
255
+ display: flex;
256
+ margin-top: -12px;
257
+ margin-left: -12px;
258
+ margin-right: -12px;
259
+ padding: 15px;
260
+ flex-flow: row nowrap;
261
+ gap: 15px;
262
+ border: 1px solid var(--application-menu-surface-stroke);
263
+ border-top: 0;
264
+ border-left: 0;
265
+ border-right: 0;
266
+ }
267
+
268
+ .applicationMenuContextContent {
269
+ display: flex;
270
+ align-self: center;
271
+ flex-flow: column;
272
+
273
+ :global(strong) {
274
+ color: var(--foreground-prominent);
275
+ }
276
+
277
+ :global(span) {
278
+ color: var(--foreground-secondary);
279
+ font-size: 14px;
280
+ }
281
+ }
282
+
283
+ .applicationMenuContextPill {
284
+ position: relative;
285
+ display: flex;
286
+ flex-flow: row nowrap;
287
+ gap: 9px;
288
+ align-items: center;
289
+ margin: -3px -6px 6px;
290
+ padding: 9px 12px;
291
+ cursor: pointer;
292
+ color: var(--foreground-secondary);
293
+ background: transparent;
294
+ border: 1px solid var(--application-menu-surface-stroke);
295
+ border-radius: var(--radius);
296
+ transition: background 200ms var(--swift-out);
297
+
298
+ @include mixin.hover {
299
+ background: var(--gray-200);
300
+ }
301
+
302
+ &:active {
303
+ background: var(--gray-300);
304
+ }
305
+
306
+ @include mixin.focus-ring;
307
+ }
308
+
309
+ .applicationMenuContextPillContent {
310
+ display: flex;
311
+ flex: 1 1 auto;
312
+ flex-flow: column;
313
+ min-width: 0;
314
+
315
+ :global(strong) {
316
+ font-weight: 500;
317
+ font-size: 14px;
318
+ line-height: 1.2;
319
+ white-space: nowrap;
320
+ overflow: hidden;
321
+ color: var(--foreground-prominent);
322
+ text-overflow: ellipsis;
323
+ transition: var(--application-duration) var(--swift-out);
324
+ transition-property: filter, opacity, translate;
325
+ }
326
+
327
+ :global(span) {
328
+ font-size: 12px;
329
+ line-height: 1.2;
330
+ white-space: nowrap;
331
+ overflow: hidden;
332
+ color: var(--foreground-secondary);
333
+ text-overflow: ellipsis;
334
+ transition: var(--application-duration) var(--swift-out);
335
+ transition-property: filter, opacity, translate;
336
+ }
337
+ }
338
+
339
+ .applicationMenuPromo {
340
+ display: flex;
341
+ padding: 12px;
342
+ flex-flow: row;
343
+ gap: 12px;
344
+ background: var(--gray-25);
345
+ border: 1px solid var(--application-menu-surface-stroke);
346
+ border-radius: var(--radius);
347
+ }
348
+
349
+ .applicationMenuPromo > :local(.icon) {
350
+ margin: 2px;
351
+ color: var(--gray-600);
352
+ font-size: 16px;
353
+ }
354
+
355
+ .applicationMenuPromoContent {
356
+ display: flex;
357
+ flex-flow: column;
358
+ gap: 6px;
359
+ font-size: 14px;
360
+ }
361
+
362
+ .applicationMenuPromoContent :global(a) {
363
+ color: var(--gray-700);
364
+ }
365
+
366
+ .applicationMenuToggle:local(.menuItem) {
367
+ height: min-content;
368
+ align-self: center;
369
+ color: var(--foreground);
370
+ flex-grow: 0;
371
+ flex-shrink: 0;
372
+ }
373
+
374
+ .applicationMenuToggleIcon {
375
+ height: 1em;
376
+ width: 1em;
377
+ flex-shrink: 0;
378
+ font-size: 18px;
379
+ line-height: 1;
380
+ overflow: visible;
381
+
382
+ :global(path) {
383
+ fill: currentColor;
384
+ }
385
+ }
386
+
387
+ @include mixin.breakpoint-up(lg) {
388
+ .applicationMenu[data-collapsed][data-collapsible] {
389
+ width: var(--application-menu-width-collapsed);
390
+
391
+ :local(.menuItemLabel) {
392
+ filter: blur(6px);
393
+ opacity: 0;
394
+ translate: -12px 0;
395
+ }
396
+
397
+ .applicationMenuPromo,
398
+ .applicationMenuContext {
399
+ opacity: 0;
400
+ pointer-events: none;
401
+ }
402
+
403
+ .applicationMenuContextPillContent :global(strong),
404
+ .applicationMenuContextPillContent :global(span) {
405
+ filter: blur(6px);
406
+ opacity: 0;
407
+ translate: -12px 0;
408
+ }
409
+ }
410
+ }
411
+
412
+ @include mixin.breakpoint-down(md) {
413
+ .applicationMenu {
414
+ width: min(300px, calc(100dvw - 42px));
415
+ z-index: 2000;
416
+ }
417
+
418
+ .applicationMenu[data-collapsed] {
419
+ translate: -100% 0;
420
+ }
421
+ }
@@ -0,0 +1,43 @@
1
+ @use '$flux/css/mixin';
2
+
3
+ .applicationSection {
4
+ display: flex;
5
+ flex-flow: column;
6
+ gap: 21px;
7
+ }
8
+
9
+ .applicationSectionHeader {
10
+ display: flex;
11
+ align-items: center;
12
+ flex-flow: row;
13
+ gap: 21px;
14
+
15
+ :global(h2) {
16
+ flex-grow: 1;
17
+ color: var(--foreground-prominent);
18
+ }
19
+ }
20
+
21
+ .applicationSectionInfo {
22
+ font-size: 14px;
23
+ color: var(--foreground-secondary);
24
+ }
25
+
26
+ .applicationSectionContent {
27
+ display: flex;
28
+ flex-flow: column;
29
+ flex-grow: 1;
30
+ gap: 21px;
31
+ }
32
+
33
+ :local(.gridColumn) > .applicationSection {
34
+ min-height: 100%;
35
+ }
36
+
37
+ @include mixin.breakpoint-down(md) {
38
+ .applicationSectionHeader {
39
+ align-items: flex-start;
40
+ flex-flow: column;
41
+ gap: 0;
42
+ }
43
+ }
@@ -0,0 +1,21 @@
1
+ @use '$flux/css/mixin';
2
+
3
+ .applicationSide {
4
+ position: fixed;
5
+ display: flex;
6
+ top: 0;
7
+ right: 0;
8
+ height: 100dvh;
9
+ width: var(--application-side-width);
10
+ flex-flow: column;
11
+ background: var(--surface);
12
+ border-left: 1px solid var(--surface-stroke);
13
+ overflow: hidden;
14
+ z-index: 500;
15
+ }
16
+
17
+ @include mixin.breakpoint-down(lg) {
18
+ .applicationSide {
19
+ display: none;
20
+ }
21
+ }
@@ -0,0 +1,111 @@
1
+ @use '$flux/css/mixin';
2
+
3
+ .applicationTop {
4
+ position: sticky;
5
+ top: 0;
6
+ background: rgb(from var(--surface) r g b / .975);
7
+ background-clip: padding-box;
8
+ backdrop-filter: blur(3px) saturate(180%);
9
+ border-bottom: 1px solid transparent;
10
+ transition: 300ms var(--swift-out);
11
+ transition-property: border-color, box-shadow;
12
+ z-index: 400;
13
+ }
14
+
15
+ .applicationTopScrolled {
16
+ composes: applicationTop;
17
+
18
+ border-bottom-color: var(--surface-stroke-out);
19
+ box-shadow: var(--shadow-lg);
20
+ }
21
+
22
+ .applicationTopBar {
23
+ display: flex;
24
+ height: var(--application-top-height);
25
+ padding-left: 21px;
26
+ padding-right: 21px;
27
+ align-items: center;
28
+ flex-flow: row;
29
+ gap: 15px;
30
+ color: var(--foreground-prominent);
31
+ }
32
+
33
+ .applicationTopBarTitle {
34
+ font-size: 16px;
35
+ font-weight: 700;
36
+ line-height: 1;
37
+ overflow: hidden;
38
+ text-overflow: ellipsis;
39
+ }
40
+
41
+ .applicationTopBar > :local(.separator) {
42
+ height: 18px;
43
+ }
44
+
45
+ @include mixin.breakpoint-up(lg) {
46
+ .applicationTopMenuToggleHidden {
47
+ display: none;
48
+ }
49
+ }
50
+
51
+ .applicationTopTabs {
52
+ position: relative;
53
+ display: flex;
54
+ margin-top: -6px;
55
+ margin-left: auto;
56
+ margin-right: auto;
57
+ flex-flow: row;
58
+ gap: 21px;
59
+
60
+ :local(.tabBar) {
61
+ --tab-padding: 18px;
62
+
63
+ width: 100%;
64
+ box-shadow: unset;
65
+ }
66
+ }
67
+
68
+ .applicationTopTabsDefault,
69
+ .applicationTopTabsDashboard {
70
+ composes: applicationTopTabs;
71
+
72
+ width: min(1620px, 100% - 42px);
73
+ }
74
+
75
+ .applicationTopTabsFull {
76
+ composes: applicationTopTabs;
77
+
78
+ width: calc(100% - 42px);
79
+ }
80
+
81
+ .applicationTopTabsMedium {
82
+ composes: applicationTopTabs;
83
+
84
+ width: min(1280px, 100% - 42px);
85
+ }
86
+
87
+ .applicationTopTabsNarrow {
88
+ composes: applicationTopTabs;
89
+
90
+ width: min(840px, 100% - 42px);
91
+ }
92
+
93
+ @include mixin.breakpoint-down(md) {
94
+ .applicationTop,
95
+ .applicationTopScrolled {
96
+ padding-top: env(safe-area-inset-top);
97
+ }
98
+
99
+ .applicationTopBar > :local(.icon) {
100
+ display: none;
101
+ }
102
+
103
+ .applicationMenuToggle {
104
+ margin-left: -12px;
105
+ margin-right: -15px;
106
+ }
107
+ }
108
+
109
+ .applicationTop:has(+ .applicationContentFull > :local(.table)) {
110
+ border-bottom-color: var(--surface-stroke-out);
111
+ }
@@ -0,0 +1,38 @@
1
+ import type { ComputedRef, InjectionKey, Ref } from 'vue';
2
+ import type { FluxPressableType, FluxTo } from '@flux-ui/types';
3
+
4
+ export const FluxApplicationInjectionKey: InjectionKey<FluxApplicationInjection> = Symbol();
5
+
6
+ export type FluxApplicationContextInfo = {
7
+ readonly id: symbol;
8
+ readonly title: string;
9
+ readonly subtitle?: string;
10
+ readonly to?: FluxTo;
11
+ readonly entryTo?: FluxTo;
12
+ readonly href?: string;
13
+ readonly type?: FluxPressableType;
14
+ };
15
+
16
+ export type FluxApplicationInjection = {
17
+ readonly activeContext: ComputedRef<FluxApplicationContextInfo | undefined>;
18
+ readonly contexts: ComputedRef<readonly FluxApplicationContextInfo[]>;
19
+ readonly isMenuCollapsed: Ref<boolean>;
20
+ readonly layout: Ref<FluxApplicationLayout>;
21
+ readonly showDesktopMenuToggle: Ref<boolean>;
22
+ readonly totalLevels: ComputedRef<number>;
23
+ readonly viewIndex: Ref<number>;
24
+ goToChild(): void;
25
+ goToCurrent(): void;
26
+ goToLevel(index: number): void;
27
+ goToMain(): void;
28
+ goToParent(): void;
29
+ pushContext(info: FluxApplicationContextInfo): void;
30
+ removeContext(id: symbol): void;
31
+ };
32
+
33
+ export type FluxApplicationLayout =
34
+ | 'default'
35
+ | 'dashboard'
36
+ | 'full'
37
+ | 'medium'
38
+ | 'narrow';
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './component';
2
+ export * from './composable';
3
+ export * from './data';
@@ -0,0 +1,34 @@
1
+ import { computed, type ComputedRef, type Ref, unref } from 'vue';
2
+ import type { RouteRecordNormalized } from 'vue-router';
3
+ import useRoute from './useRoute';
4
+
5
+ /**
6
+ * Returns **all** matched route records that expose a named view with
7
+ * the given `name`, paired with their depth in `route.matched`. Powers
8
+ * `<FluxApplicationMenuContextStack>` so every level of a nested route
9
+ * tree can render its own menu component (instead of vue-router's
10
+ * default behaviour of only rendering the deepest one).
11
+ */
12
+ export default function (nameRef: Ref<string> | string): ComputedRef<NamedRouteMatch[]> {
13
+ const route = useRoute();
14
+
15
+ return computed(() => {
16
+ const name = unref(nameRef);
17
+ const matches: NamedRouteMatch[] = [];
18
+
19
+ route.matched.forEach((record, depth) => {
20
+ if (!record.components || !(name in record.components)) {
21
+ return;
22
+ }
23
+
24
+ matches.push({depth, record});
25
+ });
26
+
27
+ return matches;
28
+ });
29
+ }
30
+
31
+ export type NamedRouteMatch = {
32
+ readonly depth: number;
33
+ readonly record: RouteRecordNormalized;
34
+ };
@@ -0,0 +1,11 @@
1
+ import { type RouteLocationNormalizedLoaded, useRoute as useVueRoute } from 'vue-router';
2
+
3
+ /**
4
+ * Internal alias for vue-router's `useRoute`. Exists so the rest of the
5
+ * package can import a single, controlled `useRoute` symbol — making it
6
+ * trivial to swap in a wrapper later (e.g. modal-aware variants) without
7
+ * touching every call site.
8
+ */
9
+ export default function (): RouteLocationNormalizedLoaded {
10
+ return useVueRoute();
11
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ declare module '*.module.css' {
2
+ const content: Record<string, string>;
3
+ export default content;
4
+ }
5
+
6
+ declare module '*.module.scss' {
7
+ const content: Record<string, string>;
8
+ export default content;
9
+ }
10
+
11
+ declare module '*.svg' {
12
+ export = string;
13
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }