@apptegy/nuxt-navigation 0.1.115 → 0.1.116

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.115",
7
+ "version": "0.1.116",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.1",
10
10
  "unbuild": "unknown"
@@ -148,12 +148,7 @@ import {
148
148
  } from "../../utils/notification-api";
149
149
  const props = defineProps({
150
150
  sticky: { type: Boolean, required: false },
151
- showNotifications: { type: Boolean, required: false, default: false },
152
- notificationsResponse: { type: [Object, null], required: false, default: null },
153
- notificationsLoading: { type: Boolean, required: false, default: false },
154
- notificationPanelOpen: { type: Boolean, required: false, default: false },
155
- activeTabId: { type: String, required: false, default: "" },
156
- activeFilterId: { type: String, required: false, default: "" },
151
+ notifications: { type: [Object, null], required: false, default: null },
157
152
  user: { type: Object, required: true },
158
153
  impersonatedUser: { type: Object, required: false },
159
154
  useLogoutEmit: { type: Boolean, required: false },
@@ -163,9 +158,7 @@ const nuxtApp = useNuxtApp();
163
158
  const emit = defineEmits([
164
159
  "selected-option",
165
160
  "user-logout",
166
- "update:notificationPanelOpen",
167
- "update:activeTabId",
168
- "update:activeFilterId",
161
+ "update:notifications",
169
162
  "notifications-clear-all",
170
163
  "notifications-dismiss",
171
164
  "notifications-select",
@@ -173,8 +166,13 @@ const emit = defineEmits([
173
166
  ]);
174
167
  const popoverRef = ref(null);
175
168
  const isMobilePanel = useMediaQuery("(max-width: 960px)");
176
- const panelOpen = ref(props.notificationPanelOpen);
177
169
  const isBellShaking = ref(false);
170
+ const showNotifications = computed(() => props.notifications != null);
171
+ const notificationsResponse = computed(() => props.notifications?.response ?? null);
172
+ const notificationsLoading = computed(() => props.notifications?.loading ?? false);
173
+ const panelOpen = ref(props.notifications?.panelOpen ?? false);
174
+ const activeTabId = ref(props.notifications?.activeTabId || "new");
175
+ const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
178
176
  const popoverPt = {
179
177
  content: {
180
178
  class: "notifications-popover__content",
@@ -188,10 +186,10 @@ const notificationDateLabels = computed(() => ({
188
186
  justNow: nuxtApp.$t("navigation.notificationsPanel.justNow")
189
187
  }));
190
188
  const notificationsPanel = computed(() => {
191
- if (!props.notificationsResponse) {
189
+ if (!notificationsResponse.value) {
192
190
  return null;
193
191
  }
194
- return mapNotificationsApiResponse(props.notificationsResponse, {
192
+ return mapNotificationsApiResponse(notificationsResponse.value, {
195
193
  locale: nuxtApp.$intl.locale,
196
194
  dateLabels: notificationDateLabels.value,
197
195
  retentionLabel: nuxtApp.$t("navigation.notificationsPanel.retentionLabel"),
@@ -202,10 +200,17 @@ const notificationsPanel = computed(() => {
202
200
  });
203
201
  });
204
202
  const notificationCount = computed(
205
- () => getNotificationsTotalCount(props.notificationsResponse)
203
+ () => getNotificationsTotalCount(notificationsResponse.value)
206
204
  );
207
- const activeTabId = ref(props.activeTabId || notificationsPanel.value?.tabs[0]?.id || "");
208
- const activeFilterId = ref(props.activeFilterId || notificationsPanel.value?.filters?.[0]?.id || "all-types");
205
+ function patchNotifications(patch) {
206
+ if (!props.notifications) {
207
+ return;
208
+ }
209
+ emit("update:notifications", {
210
+ ...props.notifications,
211
+ ...patch
212
+ });
213
+ }
209
214
  watch(
210
215
  notificationCount,
211
216
  (newCount, oldCount) => {
@@ -223,19 +228,21 @@ function onBellShakeEnd() {
223
228
  isBellShaking.value = false;
224
229
  }
225
230
  watch(
226
- () => props.notificationPanelOpen,
231
+ () => props.notifications?.panelOpen,
227
232
  (value) => {
228
- panelOpen.value = value;
233
+ if (value !== void 0) {
234
+ panelOpen.value = value;
235
+ }
229
236
  }
230
237
  );
231
238
  watch(panelOpen, (value) => {
232
- emit("update:notificationPanelOpen", value);
239
+ patchNotifications({ panelOpen: value });
233
240
  if (!value && !isMobilePanel.value) {
234
241
  popoverRef.value?.hide();
235
242
  }
236
243
  });
237
244
  watch(
238
- () => props.activeTabId,
245
+ () => props.notifications?.activeTabId,
239
246
  (value) => {
240
247
  if (value) {
241
248
  activeTabId.value = value;
@@ -243,7 +250,7 @@ watch(
243
250
  }
244
251
  );
245
252
  watch(
246
- () => props.activeFilterId,
253
+ () => props.notifications?.activeFilterId,
247
254
  (value) => {
248
255
  if (value) {
249
256
  activeFilterId.value = value;
@@ -279,11 +286,11 @@ function handleNotificationSelect(payload) {
279
286
  }
280
287
  function handleTabChange(id) {
281
288
  activeTabId.value = id;
282
- emit("update:activeTabId", id);
289
+ patchNotifications({ activeTabId: id });
283
290
  }
284
291
  function handleFilterChange(id) {
285
292
  activeFilterId.value = id;
286
- emit("update:activeFilterId", id);
293
+ patchNotifications({ activeFilterId: id });
287
294
  }
288
295
  </script>
289
296
 
@@ -1,12 +1,10 @@
1
- import type { INotificationsApiResponse, IUser } from '../../types/index.js';
1
+ import type { INotificationsHeaderModel, IUser } from '../../types/index.js';
2
2
  interface IHeaderProps {
3
3
  sticky?: boolean;
4
- showNotifications?: boolean;
5
- notificationsResponse?: INotificationsApiResponse | null;
6
- notificationsLoading?: boolean;
7
- notificationPanelOpen?: boolean;
8
- activeTabId?: string;
9
- activeFilterId?: string;
4
+ /**
5
+ * Host-owned notification center state. Pass `null`/`undefined` to hide the bell.
6
+ */
7
+ notifications?: INotificationsHeaderModel | null;
10
8
  }
11
9
  interface IUserDropdown {
12
10
  user: {
@@ -32,9 +30,7 @@ type __VLS_Slots = {} & {
32
30
  declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
33
31
  "selected-option": (...args: any[]) => void;
34
32
  "user-logout": (...args: any[]) => void;
35
- "update:notificationPanelOpen": (...args: any[]) => void;
36
- "update:activeTabId": (...args: any[]) => void;
37
- "update:activeFilterId": (...args: any[]) => void;
33
+ "update:notifications": (...args: any[]) => void;
38
34
  "notifications-clear-all": (...args: any[]) => void;
39
35
  "notifications-dismiss": (...args: any[]) => void;
40
36
  "notifications-select": (...args: any[]) => void;
@@ -42,20 +38,13 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void,
42
38
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
43
39
  "onSelected-option"?: ((...args: any[]) => any) | undefined;
44
40
  "onUser-logout"?: ((...args: any[]) => any) | undefined;
45
- "onUpdate:notificationPanelOpen"?: ((...args: any[]) => any) | undefined;
46
- "onUpdate:activeTabId"?: ((...args: any[]) => any) | undefined;
47
- "onUpdate:activeFilterId"?: ((...args: any[]) => any) | undefined;
41
+ "onUpdate:notifications"?: ((...args: any[]) => any) | undefined;
48
42
  "onNotifications-clear-all"?: ((...args: any[]) => any) | undefined;
49
43
  "onNotifications-dismiss"?: ((...args: any[]) => any) | undefined;
50
44
  "onNotifications-select"?: ((...args: any[]) => any) | undefined;
51
45
  "onNotification-click"?: ((...args: any[]) => any) | undefined;
52
46
  }>, {
53
- showNotifications: boolean;
54
- notificationsResponse: INotificationsApiResponse | null;
55
- notificationsLoading: boolean;
56
- notificationPanelOpen: boolean;
57
- activeTabId: string;
58
- activeFilterId: string;
47
+ notifications: INotificationsHeaderModel | null;
59
48
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
60
49
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
61
50
  export default _default;
@@ -185,7 +185,7 @@ import {
185
185
  getVisibleFilters,
186
186
  isInBadge,
187
187
  notificationMatchesFilter,
188
- resolveNotificationFilters,
188
+ resolveStickyNotificationFilters,
189
189
  shouldShowFilterChips
190
190
  } from "../../utils/notifications";
191
191
  const props = defineProps({
@@ -200,6 +200,7 @@ const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "clear-
200
200
  const filtersRef = ref();
201
201
  const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
202
202
  const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
203
+ const stickyFilters = ref([]);
203
204
  watch(
204
205
  () => props.activeTabId,
205
206
  (value) => {
@@ -230,19 +231,44 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
230
231
  const activeTab = computed(
231
232
  () => props.data.tabs.find((tab) => tab.id === activeTabId.value)
232
233
  );
233
- const resolvedFilters = computed(
234
- () => resolveNotificationFilters(allNotifications.value, activeTab.value, props.data.filters)
234
+ function filterCatalogKey(filters) {
235
+ return filters.map((filter) => filter.id).join("|");
236
+ }
237
+ watch(
238
+ [allNotifications, activeTab, activeFilterId, () => props.data.filters],
239
+ () => {
240
+ const { nextStickyFilters } = resolveStickyNotificationFilters({
241
+ notifications: allNotifications.value,
242
+ tab: activeTab.value,
243
+ providedFilters: props.data.filters,
244
+ activeFilterId: activeFilterId.value,
245
+ stickyFilters: stickyFilters.value
246
+ });
247
+ if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
248
+ stickyFilters.value = nextStickyFilters;
249
+ }
250
+ },
251
+ { immediate: true, deep: true }
252
+ );
253
+ const displayFilters = computed(
254
+ () => resolveStickyNotificationFilters({
255
+ notifications: allNotifications.value,
256
+ tab: activeTab.value,
257
+ providedFilters: props.data.filters,
258
+ activeFilterId: activeFilterId.value,
259
+ stickyFilters: stickyFilters.value
260
+ }).filters
235
261
  );
236
262
  const visibleFilters = computed(() => {
237
263
  if (usesDynamicFilters.value) {
238
- return resolvedFilters.value;
264
+ return displayFilters.value;
239
265
  }
240
- return getVisibleFilters(allNotifications.value, resolvedFilters.value, activeTab.value);
266
+ return getVisibleFilters(allNotifications.value, displayFilters.value, activeTab.value);
241
267
  });
242
268
  const showFilterChips = computed(
243
269
  () => shouldShowFilterChips(
244
270
  allNotifications.value,
245
- resolvedFilters.value,
271
+ displayFilters.value,
246
272
  activeTab.value,
247
273
  usesDynamicFilters.value
248
274
  )
@@ -269,7 +295,7 @@ function formatCount(count) {
269
295
  return formatNotificationCount(count);
270
296
  }
271
297
  const activeFilter = computed(
272
- () => visibleFilters.value.find((filter) => filter.id === activeFilterId.value) || resolvedFilters.value.find((filter) => filter.id === activeFilterId.value)
298
+ () => visibleFilters.value.find((filter) => filter.id === activeFilterId.value) || displayFilters.value.find((filter) => filter.id === activeFilterId.value)
273
299
  );
274
300
  function showClearAll(groupIndex) {
275
301
  return groupIndex === 0 && (activeTabId.value === "new" || activeTab.value?.unreadOnly);
@@ -17,3 +17,5 @@ export declare const DEFAULT_NOTIFICATION_FILTERS: ({
17
17
  id: string;
18
18
  label: string;
19
19
  })[];
20
+ /** Resolve `filter[kind][]` values for a notification center filter pill id. */
21
+ export declare function getKindsForNotificationFilter(filterId: string | null | undefined): string[];
@@ -46,3 +46,10 @@ export const DEFAULT_NOTIFICATION_FILTERS = [
46
46
  { id: "all-types", label: "All types" },
47
47
  ...NOTIFICATION_FILTER_CATEGORIES
48
48
  ];
49
+ export function getKindsForNotificationFilter(filterId) {
50
+ if (!filterId || filterId === "all-types") {
51
+ return [];
52
+ }
53
+ const category = NOTIFICATION_FILTER_CATEGORIES.find((filter) => filter.id === filterId);
54
+ return category?.kinds ? [...category.kinds] : [];
55
+ }
@@ -11,4 +11,4 @@ export interface IUser {
11
11
  client: string;
12
12
  admin_email: string;
13
13
  }
14
- export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, } from './notifications.js';
14
+ export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
@@ -96,3 +96,11 @@ export interface INotificationsPanelData {
96
96
  filters?: INotificationFilter[];
97
97
  groups: INotificationGroup[];
98
98
  }
99
+ /** Host-owned notification center state bound to NavHeader via `v-model:notifications`. */
100
+ export interface INotificationsHeaderModel {
101
+ response: INotificationsApiResponse | null;
102
+ loading?: boolean;
103
+ panelOpen?: boolean;
104
+ activeTabId?: string;
105
+ activeFilterId?: string;
106
+ }
@@ -14,6 +14,20 @@ export declare function getScopedNotifications(notifications: INotificationItem[
14
14
  */
15
15
  export declare function buildFiltersFromNotifications(notifications: INotificationItem[], tab: INotificationTab | undefined): INotificationFilter[];
16
16
  export declare function resolveNotificationFilters(notifications: INotificationItem[], tab: INotificationTab | undefined, filters?: INotificationFilter[]): INotificationFilter[];
17
+ /**
18
+ * Keeps the last "all types" pill catalog while a kind filter is active, so chips
19
+ * don't collapse when results are already filtered (e.g. via `filter[kind][]`).
20
+ */
21
+ export declare function resolveStickyNotificationFilters({ notifications, tab, providedFilters, activeFilterId, stickyFilters, }: {
22
+ notifications: INotificationItem[];
23
+ tab: INotificationTab | undefined;
24
+ providedFilters?: INotificationFilter[];
25
+ activeFilterId: string;
26
+ stickyFilters: INotificationFilter[];
27
+ }): {
28
+ filters: INotificationFilter[];
29
+ nextStickyFilters: INotificationFilter[];
30
+ };
17
31
  export declare function filterHasContent(notifications: INotificationItem[], filter: INotificationFilter, tab: INotificationTab | undefined): boolean;
18
32
  export declare function getVisibleFilters(notifications: INotificationItem[], filters: INotificationFilter[], tab: INotificationTab | undefined): INotificationFilter[];
19
33
  export declare function shouldShowFilterChips(notifications: INotificationItem[], filters: INotificationFilter[], tab: INotificationTab | undefined, usesDynamicFilters?: boolean): boolean;
@@ -1,4 +1,7 @@
1
- import { NOTIFICATION_FILTER_CATEGORIES } from "../constants/notification-types.js";
1
+ import {
2
+ DEFAULT_NOTIFICATION_FILTERS,
3
+ NOTIFICATION_FILTER_CATEGORIES
4
+ } from "../constants/notification-types.js";
2
5
  import { normalizeNotificationKind } from "./notification-api.js";
3
6
  export function flattenNotifications(groups) {
4
7
  return groups.flatMap((group) => group.notifications);
@@ -82,6 +85,37 @@ export function resolveNotificationFilters(notifications, tab, filters) {
82
85
  }
83
86
  return buildFiltersFromNotifications(notifications, tab);
84
87
  }
88
+ export function resolveStickyNotificationFilters({
89
+ notifications,
90
+ tab,
91
+ providedFilters,
92
+ activeFilterId,
93
+ stickyFilters
94
+ }) {
95
+ if (providedFilters?.length) {
96
+ return {
97
+ filters: providedFilters,
98
+ nextStickyFilters: stickyFilters
99
+ };
100
+ }
101
+ const dynamicFilters = buildFiltersFromNotifications(notifications, tab);
102
+ let nextStickyFilters = stickyFilters;
103
+ if (activeFilterId === "all-types") {
104
+ nextStickyFilters = dynamicFilters;
105
+ } else if (!stickyFilters.length) {
106
+ nextStickyFilters = dynamicFilters.length ? dynamicFilters : [...DEFAULT_NOTIFICATION_FILTERS];
107
+ }
108
+ if (activeFilterId !== "all-types" && nextStickyFilters.length) {
109
+ return {
110
+ filters: nextStickyFilters,
111
+ nextStickyFilters
112
+ };
113
+ }
114
+ return {
115
+ filters: dynamicFilters.length ? dynamicFilters : nextStickyFilters,
116
+ nextStickyFilters
117
+ };
118
+ }
85
119
  export function filterHasContent(notifications, filter, tab) {
86
120
  const scoped = getScopedNotifications(notifications, tab);
87
121
  return scoped.some((notification) => notificationMatchesFilter(notification, filter));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptegy/nuxt-navigation",
3
- "version": "0.1.115",
3
+ "version": "0.1.116",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {