@apptegy/nuxt-navigation 0.1.121 → 0.1.122

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.0.0"
5
5
  },
6
6
  "configKey": "@apptegy/nuxt-navigation",
7
- "version": "0.1.121",
7
+ "version": "0.1.122",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.1",
10
10
  "unbuild": "unknown"
@@ -9,11 +9,13 @@
9
9
  </div>
10
10
  <slot name="org-select" />
11
11
  <slot name="user-controls" />
12
- <div class="user-control-actions">
13
- <div
14
- v-if="showNotifications"
15
- class="header__notifications"
16
- >
12
+ <div class="header__end">
13
+ <slot name="searchbar" />
14
+ <div class="user-control-actions">
15
+ <div
16
+ v-if="showNotifications"
17
+ class="header__notifications"
18
+ >
17
19
  <Button
18
20
  type="button"
19
21
  severity="secondary"
@@ -136,6 +138,7 @@
136
138
  />
137
139
  </NuxtLink>
138
140
  </template>
141
+ </div>
139
142
  </div>
140
143
  </header>
141
144
  </template>
@@ -313,10 +316,18 @@ header.header {
313
316
  background-color: var(--primary-white);
314
317
  font: var(--font-regular);
315
318
  }
319
+ header.header .header__end {
320
+ display: flex;
321
+ align-items: center;
322
+ justify-content: flex-end;
323
+ gap: 24px;
324
+ min-width: 0;
325
+ }
316
326
  header.header .user-control-actions {
317
327
  display: flex;
318
328
  align-items: center;
319
329
  gap: 8px;
330
+ flex-shrink: 0;
320
331
  }
321
332
  header.header .header__notifications {
322
333
  position: relative;
@@ -17,7 +17,7 @@ interface IUserDropdown {
17
17
  userDropdownOptions: Array<unknown>;
18
18
  }
19
19
  type __VLS_Props = IHeaderProps & IUserDropdown;
20
- declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_15: {};
20
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_17: {};
21
21
  type __VLS_Slots = {} & {
22
22
  breadcrumbs?: (props: typeof __VLS_1) => any;
23
23
  } & {
@@ -25,9 +25,27 @@ type __VLS_Slots = {} & {
25
25
  } & {
26
26
  'user-controls'?: (props: typeof __VLS_5) => any;
27
27
  } & {
28
- notifications?: (props: typeof __VLS_15) => any;
28
+ searchbar?: (props: typeof __VLS_7) => any;
29
+ } & {
30
+ notifications?: (props: typeof __VLS_17) => any;
29
31
  };
30
- declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, any, string, import("vue").PublicProps, any, {
32
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
33
+ "selected-option": (...args: any[]) => void;
34
+ "user-logout": (...args: any[]) => void;
35
+ "update:notifications": (...args: any[]) => void;
36
+ "notifications-clear-all": (...args: any[]) => void;
37
+ "notifications-dismiss": (...args: any[]) => void;
38
+ "notifications-select": (...args: any[]) => void;
39
+ "notification-click": (...args: any[]) => void;
40
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
41
+ "onSelected-option"?: ((...args: any[]) => any) | undefined;
42
+ "onUser-logout"?: ((...args: any[]) => any) | undefined;
43
+ "onUpdate:notifications"?: ((...args: any[]) => any) | undefined;
44
+ "onNotifications-clear-all"?: ((...args: any[]) => any) | undefined;
45
+ "onNotifications-dismiss"?: ((...args: any[]) => any) | undefined;
46
+ "onNotifications-select"?: ((...args: any[]) => any) | undefined;
47
+ "onNotification-click"?: ((...args: any[]) => any) | undefined;
48
+ }>, {
31
49
  notifications: INotificationsHeaderModel | null;
32
50
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
51
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
@@ -3,95 +3,124 @@
3
3
  class="notification-item"
4
4
  :class="{ 'notification-item--in-badge': showBadgeActions }"
5
5
  >
6
- <div
7
- class="notification-item__main"
8
- role="button"
9
- tabindex="0"
10
- @click="handleClick"
11
- @keydown.enter.prevent="handleClick"
12
- >
6
+ <div class="notification-item__header">
13
7
  <div
14
- class="notification-item__avatar"
15
- :style="{ '--type-accent': typeAccent }"
8
+ class="notification-item__main"
9
+ role="button"
10
+ tabindex="0"
11
+ @click="handleClick"
12
+ @keydown.enter.prevent="handleClick"
16
13
  >
17
- <div class="notification-item__avatar-ring">
18
- <NavAvatar
19
- :name="primaryActor.name"
20
- :src="primaryActor.avatarUrl"
21
- class="notification-item__avatar-image"
14
+ <div
15
+ class="notification-item__avatar"
16
+ :style="{ '--type-accent': typeAccent }"
17
+ >
18
+ <div class="notification-item__avatar-ring">
19
+ <NavAvatar
20
+ :name="primaryActor.name"
21
+ :src="primaryActor.avatarUrl"
22
+ class="notification-item__avatar-image"
23
+ />
24
+ </div>
25
+ <span
26
+ v-if="groupBadgeLabel"
27
+ class="notification-item__group-badge"
28
+ aria-hidden="true"
29
+ >
30
+ {{ groupBadgeLabel }}
31
+ </span>
32
+ <span
33
+ v-else
34
+ class="notification-item__type-badge"
35
+ aria-hidden="true"
36
+ >
37
+ <component :is="typeIcon" />
38
+ </span>
39
+ </div>
40
+
41
+ <div class="notification-item__content">
42
+ <p class="notification-item__message">
43
+ {{ item.actionText }}
44
+ </p>
45
+ <p class="notification-item__meta">
46
+ <span>{{ item.timestampLabel }}</span>
47
+ <template v-if="item.contextLabel">
48
+ <span
49
+ class="notification-item__meta-separator"
50
+ aria-hidden="true"
51
+ >·</span
52
+ >
53
+ <span>{{ item.contextLabel }}</span>
54
+ </template>
55
+ </p>
56
+ <Tag
57
+ v-if="item.stateLabel"
58
+ :value="item.stateLabel"
59
+ severity="secondary"
60
+ class="notification-item__state-chip"
22
61
  />
23
62
  </div>
24
- <span
25
- v-if="groupBadgeLabel"
26
- class="notification-item__group-badge"
27
- aria-hidden="true"
28
- >
29
- {{ groupBadgeLabel }}
30
- </span>
31
- <span
32
- class="notification-item__type-badge"
33
- aria-hidden="true"
34
- >
35
- <component :is="typeIcon" />
36
- </span>
37
63
  </div>
38
64
 
39
- <div class="notification-item__content">
40
- <p class="notification-item__message">
41
- {{ item.actionText }}
42
- </p>
43
- <p class="notification-item__meta">
44
- <span>{{ item.timestampLabel }}</span>
45
- <template v-if="item.contextLabel">
46
- <span
47
- class="notification-item__meta-separator"
48
- aria-hidden="true"
49
- >·</span
50
- >
51
- <span>{{ item.contextLabel }}</span>
52
- </template>
53
- </p>
54
- <Tag
55
- v-if="item.stateLabel"
56
- :value="item.stateLabel"
57
- severity="secondary"
58
- class="notification-item__state-chip"
65
+ <div class="notification-item__actions">
66
+ <span
67
+ v-if="showBadgeActions"
68
+ class="notification-item__unread-dot"
69
+ aria-hidden="true"
59
70
  />
71
+ <Button
72
+ v-if="showBadgeActions"
73
+ type="button"
74
+ severity="secondary"
75
+ text
76
+ rounded
77
+ class="notification-item__dismiss"
78
+ :aria-label="$t('navigation.notificationsPanel.dismiss')"
79
+ @click="handleDismiss"
80
+ >
81
+ <svg
82
+ xmlns="http://www.w3.org/2000/svg"
83
+ width="16"
84
+ height="16"
85
+ viewBox="0 0 24 24"
86
+ fill="none"
87
+ stroke="currentColor"
88
+ stroke-width="2"
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ aria-hidden="true"
92
+ >
93
+ <path d="M18 6 6 18" />
94
+ <path d="m6 6 12 12" />
95
+ </svg>
96
+ </Button>
60
97
  </div>
61
98
  </div>
62
99
 
63
- <div class="notification-item__actions">
64
- <span
65
- v-if="showBadgeActions"
66
- class="notification-item__unread-dot"
67
- aria-hidden="true"
68
- />
69
- <Button
70
- v-if="showBadgeActions"
71
- type="button"
72
- severity="secondary"
73
- text
74
- rounded
75
- class="notification-item__dismiss"
76
- :aria-label="$t('navigation.notificationsPanel.dismiss')"
77
- @click="handleDismiss"
78
- >
79
- <svg
80
- xmlns="http://www.w3.org/2000/svg"
81
- width="16"
82
- height="16"
83
- viewBox="0 0 24 24"
84
- fill="none"
85
- stroke="currentColor"
86
- stroke-width="2"
87
- stroke-linecap="round"
88
- stroke-linejoin="round"
100
+ <div
101
+ v-if="item.summary"
102
+ class="notification-item__summary"
103
+ role="button"
104
+ tabindex="0"
105
+ @click="handleClick"
106
+ @keydown.enter.prevent="handleClick"
107
+ >
108
+ <template v-if="reactionEmojis.length">
109
+ <span
110
+ class="notification-item__reactions"
89
111
  aria-hidden="true"
90
112
  >
91
- <path d="M18 6 6 18" />
92
- <path d="m6 6 12 12" />
93
- </svg>
94
- </Button>
113
+ <span
114
+ v-for="(emoji, index) in reactionEmojis"
115
+ :key="`${emoji}-${index}`"
116
+ class="notification-item__reaction"
117
+ >{{ emoji }}</span>
118
+ </span>
119
+ <span class="notification-item__summary-on">
120
+ {{ $t("navigation.notificationsPanel.summaryOn") }}
121
+ </span>
122
+ </template>
123
+ <span class="notification-item__summary-text">"{{ item.summary }}"</span>
95
124
  </div>
96
125
  </div>
97
126
  </template>
@@ -108,6 +137,7 @@ const emit = defineEmits(["dismiss", "select"]);
108
137
  const primaryActor = computed(() => props.item.actors[0] || { name: "" });
109
138
  const typeAccent = computed(() => getTypeAccent(props.item.type));
110
139
  const showBadgeActions = computed(() => isInBadge(props.item));
140
+ const reactionEmojis = computed(() => props.item.reactionEmojis ?? []);
111
141
  const groupBadgeLabel = computed(() => {
112
142
  const count = props.item.groupCount ?? Math.max(props.item.actors.length - 1, 0);
113
143
  return count > 0 ? `+${count}` : "";
@@ -189,10 +219,8 @@ function handleDismiss(event) {
189
219
 
190
220
  <style scoped>
191
221
  .notification-item {
192
- display: grid;
193
- grid-template-columns: 1fr auto;
194
- gap: 12px;
195
- align-items: start;
222
+ display: flex;
223
+ flex-direction: column;
196
224
  width: 100%;
197
225
  padding: 12px;
198
226
  border: 1px solid rgb(232, 232, 236);
@@ -203,6 +231,12 @@ function handleDismiss(event) {
203
231
  box-shadow: none;
204
232
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
205
233
  }
234
+ .notification-item__header {
235
+ display: grid;
236
+ grid-template-columns: 1fr auto;
237
+ gap: 12px;
238
+ align-items: start;
239
+ }
206
240
  .notification-item__main {
207
241
  display: grid;
208
242
  grid-template-columns: auto 1fr;
@@ -308,6 +342,42 @@ function handleDismiss(event) {
308
342
  .notification-item__state-chip {
309
343
  margin-top: 8px;
310
344
  }
345
+ .notification-item__summary {
346
+ display: flex;
347
+ flex-wrap: wrap;
348
+ align-items: baseline;
349
+ gap: 6px;
350
+ margin-top: 12px;
351
+ padding-top: 12px;
352
+ border-top: 1px solid rgb(232, 232, 236);
353
+ cursor: pointer;
354
+ font-size: 13px;
355
+ line-height: 18px;
356
+ color: var(--grey-70, #4b5563);
357
+ }
358
+ .notification-item__summary:focus-visible {
359
+ outline: 2px solid var(--purple-60, #662e80);
360
+ outline-offset: 2px;
361
+ border-radius: 4px;
362
+ }
363
+ .notification-item__reactions {
364
+ display: inline-flex;
365
+ flex-wrap: wrap;
366
+ gap: 2px;
367
+ flex-shrink: 0;
368
+ }
369
+ .notification-item__reaction {
370
+ font-size: 14px;
371
+ line-height: 1;
372
+ }
373
+ .notification-item__summary-on {
374
+ color: var(--grey-50, #747474);
375
+ }
376
+ .notification-item__summary-text {
377
+ min-width: 0;
378
+ font-style: italic;
379
+ color: var(--grey-70, #4b5563);
380
+ }
311
381
  .notification-item__actions {
312
382
  display: flex;
313
383
  align-items: center;
@@ -3,10 +3,10 @@ type __VLS_Props = {
3
3
  item: INotificationItem;
4
4
  };
5
5
  declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
- dismiss: (id: string) => any;
7
6
  select: (item: INotificationItem) => any;
7
+ dismiss: (id: string) => any;
8
8
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
- onDismiss?: ((id: string) => any) | undefined;
10
9
  onSelect?: ((item: INotificationItem) => any) | undefined;
10
+ onDismiss?: ((id: string) => any) | undefined;
11
11
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
12
  export default _default;
@@ -13,6 +13,11 @@ type __VLS_Props = {
13
13
  mobile?: boolean;
14
14
  };
15
15
  declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ select: (payload: {
17
+ item: INotificationsPanelData["groups"][number]["notifications"][number];
18
+ tabId: string;
19
+ filterId: string;
20
+ }) => any;
16
21
  "update:activeTabId": (id: string) => any;
17
22
  "update:activeFilterId": (id: string) => any;
18
23
  "update:filterCatalog": (filters: INotificationFilter[]) => any;
@@ -26,13 +31,13 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
26
31
  tabId: string;
27
32
  filterId: string;
28
33
  }) => any;
29
- select: (payload: {
34
+ close: () => any;
35
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
36
+ onSelect?: ((payload: {
30
37
  item: INotificationsPanelData["groups"][number]["notifications"][number];
31
38
  tabId: string;
32
39
  filterId: string;
33
- }) => any;
34
- close: () => any;
35
- }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
40
+ }) => any) | undefined;
36
41
  "onUpdate:activeTabId"?: ((id: string) => any) | undefined;
37
42
  "onUpdate:activeFilterId"?: ((id: string) => any) | undefined;
38
43
  "onUpdate:filterCatalog"?: ((filters: INotificationFilter[]) => any) | undefined;
@@ -46,11 +51,6 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
46
51
  tabId: string;
47
52
  filterId: string;
48
53
  }) => any) | undefined;
49
- onSelect?: ((payload: {
50
- item: INotificationsPanelData["groups"][number]["notifications"][number];
51
- tabId: string;
52
- filterId: string;
53
- }) => any) | undefined;
54
54
  onClose?: (() => any) | undefined;
55
55
  }>, {
56
56
  loading: boolean;
@@ -12,4 +12,4 @@ export interface IUser {
12
12
  client: string;
13
13
  admin_email: string;
14
14
  }
15
- export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
15
+ export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRoom, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
@@ -5,6 +5,11 @@ export interface INotificationApiActor {
5
5
  first_name: string;
6
6
  last_name: string;
7
7
  }
8
+ export interface INotificationApiRoom {
9
+ id?: string;
10
+ sis_id?: string;
11
+ name: string;
12
+ }
8
13
  export interface INotificationApiRecord {
9
14
  id: number | string;
10
15
  /**
@@ -14,7 +19,15 @@ export interface INotificationApiRecord {
14
19
  kind?: string | null;
15
20
  source_record_type: string;
16
21
  title: string;
17
- summary?: string;
22
+ /** Post/body preview shown in the card summary row. */
23
+ summary?: string | null;
24
+ /**
25
+ * Reaction tallies for aggregate reaction notifications (e.g. `{ CLAP: 2, LIKE: 1 }`).
26
+ * When present, corresponding emojis are shown beside the summary.
27
+ */
28
+ reaction_counts?: Record<string, number> | null;
29
+ /** Room associated with the notification; `name` is shown in the meta line. */
30
+ room?: INotificationApiRoom | null;
18
31
  actors?: INotificationApiActor[] | null;
19
32
  detail_link?: string | null;
20
33
  notification_state: NotificationApiState | string;
@@ -58,7 +71,12 @@ export interface INotificationItem {
58
71
  * Derived from API `last_activity_at`, falling back to `created_at`.
59
72
  */
60
73
  createdAt?: string;
74
+ /** Room name shown after the relative timestamp (e.g. "Staff Lounge"). */
61
75
  contextLabel?: string;
76
+ /** Post/body preview shown in the card summary row. */
77
+ summary?: string;
78
+ /** Emoji list derived from API `reaction_counts`, shown before the summary when present. */
79
+ reactionEmojis?: string[];
62
80
  /** When false, the notification counts toward the bell badge and appears in the New tab. */
63
81
  dismissed?: boolean;
64
82
  /** Read receipt — announcement and message types only. */
@@ -1,5 +1,10 @@
1
1
  import type { INotificationApiActor, INotificationApiRecord, INotificationGroup, INotificationItem, INotificationsApiResponse, INotificationsPanelData, INotificationTab, NotificationType } from '../types/index.js';
2
2
  import { type INotificationDateLabels } from './notification-dates.js';
3
+ /**
4
+ * Expand `reaction_counts` into a list of emojis (one entry per count).
5
+ * Unknown reaction keys are skipped.
6
+ */
7
+ export declare function mapReactionCountsToEmojis(reactionCounts: Record<string, number> | null | undefined): string[];
3
8
  export declare const DEFAULT_NOTIFICATION_TABS: INotificationTab[];
4
9
  /** Normalize kind values for filter matching and `filter[kind][]` query params. */
5
10
  export declare function normalizeNotificationKind(value: string): string;
@@ -15,6 +15,31 @@ const SOURCE_RECORD_TYPE_MAP = {
15
15
  "school-post": "school-post",
16
16
  system: "system"
17
17
  };
18
+ const REACTION_EMOJI_MAP = {
19
+ clap: "\u{1F44F}",
20
+ like: "\u{1F44D}",
21
+ heart: "\u2764\uFE0F",
22
+ dislike: "\u{1F44E}",
23
+ laugh: "\u{1F602}",
24
+ wow: "\u{1F62E}"
25
+ };
26
+ export function mapReactionCountsToEmojis(reactionCounts) {
27
+ if (!reactionCounts) {
28
+ return [];
29
+ }
30
+ const emojis = [];
31
+ for (const [key, count] of Object.entries(reactionCounts)) {
32
+ const emoji = REACTION_EMOJI_MAP[key.toLowerCase()];
33
+ const safeCount = Number(count);
34
+ if (!emoji || !Number.isFinite(safeCount) || safeCount < 1) {
35
+ continue;
36
+ }
37
+ for (let i = 0; i < Math.floor(safeCount); i += 1) {
38
+ emojis.push(emoji);
39
+ }
40
+ }
41
+ return emojis;
42
+ }
18
43
  export const DEFAULT_NOTIFICATION_TABS = [
19
44
  { id: "new", label: "New", unreadOnly: true },
20
45
  { id: "all", label: "All" }
@@ -46,6 +71,8 @@ export function mapNotificationApiRecord(record, options) {
46
71
  const state = record.notification_state;
47
72
  const type = mapSourceRecordType(record.source_record_type);
48
73
  const activityAt = resolveNotificationActivityAt(record);
74
+ const summary = record.summary?.trim() || void 0;
75
+ const reactionEmojis = mapReactionCountsToEmojis(record.reaction_counts);
49
76
  return {
50
77
  id: String(record.id),
51
78
  type,
@@ -61,7 +88,9 @@ export function mapNotificationApiRecord(record, options) {
61
88
  }
62
89
  }),
63
90
  createdAt: activityAt,
64
- contextLabel: record.summary,
91
+ contextLabel: record.room?.name?.trim() || void 0,
92
+ summary,
93
+ reactionEmojis: reactionEmojis.length > 0 ? reactionEmojis : void 0,
65
94
  dismissed: state === "dismissed",
66
95
  read: state === "read",
67
96
  unread: state === "unread",
package/locale/en.json CHANGED
@@ -30,6 +30,7 @@
30
30
  "close": "Close notifications",
31
31
  "loading": "Loading notifications...",
32
32
  "empty": "You're all caught up.",
33
+ "summaryOn": "on",
33
34
  "justNow": "just now",
34
35
  "yesterday": "Yesterday",
35
36
  "dateGroups": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptegy/nuxt-navigation",
3
- "version": "0.1.121",
3
+ "version": "0.1.122",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -36,20 +36,19 @@
36
36
  "primevue": "^4.2.5"
37
37
  },
38
38
  "devDependencies": {
39
- "@nuxt/devtools": "^2.6.2",
40
- "@nuxt/eslint-config": "^1.6.0",
41
- "@nuxt/module-builder": "^1.0.1",
42
- "@nuxt/schema": "^3.17.0",
43
- "@nuxt/test-utils": "^3.17.0",
44
- "@types/node": "^22.0.0",
45
- "eslint": "^9.31.0",
46
- "nuxt": "^3.17.0",
39
+ "@nuxt/devtools": "catalog:",
40
+ "@nuxt/eslint-config": "catalog:",
41
+ "@nuxt/module-builder": "catalog:",
42
+ "@nuxt/schema": "catalog:",
43
+ "@nuxt/test-utils": "catalog:",
44
+ "@types/node": "catalog:",
45
+ "eslint": "catalog:",
46
+ "nuxt": "catalog:",
47
47
  "primevue": "^4.5.4",
48
- "sass": "^1.89.2",
49
- "typescript": "~5.8.0",
50
- "vitest": "^3.2.4",
51
- "vue": "^3.5.13",
52
- "vue-tsc": "^3.0.3"
53
- },
54
- "gitHead": "82ad905c86fddf3e4037ae98571588d4915209d9"
48
+ "sass": "catalog:",
49
+ "typescript": "catalog:",
50
+ "vitest": "catalog:",
51
+ "vue": "catalog:",
52
+ "vue-tsc": "catalog:"
53
+ }
55
54
  }