nuance-ui 0.3.1 → 0.3.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.
Files changed (49) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +6 -1
  3. package/dist/runtime/components/app-shell/app-shell.vue +15 -12
  4. package/dist/runtime/components/app-shell/page-meta.d.ts +16 -0
  5. package/dist/runtime/components/dialog/ui/dialog-root.d.vue.ts +1 -1
  6. package/dist/runtime/components/dialog/ui/dialog-root.vue +1 -1
  7. package/dist/runtime/components/dialog/ui/dialog-root.vue.d.ts +1 -1
  8. package/dist/runtime/components/index.d.ts +3 -1
  9. package/dist/runtime/components/index.js +1 -0
  10. package/dist/runtime/components/kbd.d.vue.ts +25 -0
  11. package/dist/runtime/components/kbd.vue +28 -0
  12. package/dist/runtime/components/kbd.vue.d.ts +25 -0
  13. package/dist/runtime/components/notification.d.vue.ts +82 -0
  14. package/dist/runtime/components/notification.vue +144 -0
  15. package/dist/runtime/components/notification.vue.d.ts +82 -0
  16. package/dist/runtime/components/transition/index.d.ts +2 -0
  17. package/dist/runtime/components/transition/index.js +0 -0
  18. package/dist/runtime/components/transition/transition-group.d.vue.ts +35 -0
  19. package/dist/runtime/components/transition/transition-group.vue +32 -0
  20. package/dist/runtime/components/transition/transition-group.vue.d.ts +35 -0
  21. package/dist/runtime/components/transition/transition.vue +33 -0
  22. package/dist/runtime/components/tree/_ui/tree-item.vue +3 -3
  23. package/dist/runtime/composables/index.d.ts +1 -0
  24. package/dist/runtime/composables/index.js +1 -0
  25. package/dist/runtime/composables/use-countdown.d.ts +19 -0
  26. package/dist/runtime/composables/use-countdown.js +30 -0
  27. package/dist/runtime/notifications/index.d.ts +4 -0
  28. package/dist/runtime/notifications/index.js +3 -0
  29. package/dist/runtime/notifications/lib.d.ts +4 -0
  30. package/dist/runtime/notifications/lib.js +14 -0
  31. package/dist/runtime/notifications/notification-container.d.vue.ts +20 -0
  32. package/dist/runtime/notifications/notification-container.vue +35 -0
  33. package/dist/runtime/notifications/notification-container.vue.d.ts +20 -0
  34. package/dist/runtime/notifications/notifications-provider.d.vue.ts +22 -0
  35. package/dist/runtime/notifications/notifications-provider.vue +117 -0
  36. package/dist/runtime/notifications/notifications-provider.vue.d.ts +22 -0
  37. package/dist/runtime/notifications/notifications-store.d.ts +25 -0
  38. package/dist/runtime/notifications/notifications-store.js +80 -0
  39. package/dist/runtime/notifications/types.d.ts +50 -0
  40. package/dist/runtime/notifications/types.js +0 -0
  41. package/dist/runtime/notifications/use-notifications.d.ts +7 -0
  42. package/dist/runtime/notifications/use-notifications.js +10 -0
  43. package/dist/runtime/styles/global.css +1 -1
  44. package/dist/runtime/styles/transitions.css +1 -0
  45. package/dist/runtime/types/index.d.ts +2 -0
  46. package/package.json +9 -5
  47. package/dist/runtime/components/transition.vue +0 -42
  48. /package/dist/runtime/components/{transition.d.vue.ts → transition/transition.d.vue.ts} +0 -0
  49. /package/dist/runtime/components/{transition.vue.d.ts → transition/transition.vue.d.ts} +0 -0
@@ -0,0 +1,33 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ const {
4
+ name = "pop-bottom-left",
5
+ duration = 250,
6
+ delay,
7
+ appear,
8
+ mode
9
+ } = defineProps({
10
+ name: { type: String, required: false },
11
+ duration: { type: Number, required: false },
12
+ delay: { type: Number, required: false },
13
+ mode: { type: String, required: false },
14
+ appear: { type: Boolean, required: false }
15
+ });
16
+ const emit = defineEmits(["afterLeave"]);
17
+ const style = computed(() => ({
18
+ "--transition-duration": `${duration}ms`,
19
+ "--transition-delay": delay ? `${delay}ms` : void 0
20
+ }));
21
+ </script>
22
+
23
+ <template>
24
+ <Transition
25
+ :style
26
+ :name
27
+ :mode
28
+ :appear
29
+ @after-leave='() => emit("afterLeave")'
30
+ >
31
+ <slot />
32
+ </Transition>
33
+ </template>
@@ -6,7 +6,7 @@ import ActionIcon from "../../action-icon/action-icon.vue";
6
6
  import Button from "../../button/button.vue";
7
7
  import Loader from "../../loader/loader.vue";
8
8
  import RovingFocusItem from "../../roving-focus/roving-focus-item.vue";
9
- import UTransition from "../../transition.vue";
9
+ import NTransition from "../../transition/transition.vue";
10
10
  import { useTreeState } from "../lib/context";
11
11
  import { filterTreeItems } from "../lib/filter-tree-items";
12
12
  import { useTreeItemHandlers } from "../lib/item-handlers";
@@ -106,7 +106,7 @@ const { handleClick, handleKeyDown } = useTreeItemHandlers(path, isFolder, expan
106
106
  </Button>
107
107
  </RovingFocusItem>
108
108
 
109
- <UTransition name='scale-y'>
109
+ <NTransition name='scale-y'>
110
110
  <ul
111
111
  v-if='expanded && data && data.length > 0'
112
112
  :class='$style.list'
@@ -120,7 +120,7 @@ const { handleClick, handleKeyDown } = useTreeItemHandlers(path, isFolder, expan
120
120
  :level='level + 1'
121
121
  />
122
122
  </ul>
123
- </UTransition>
123
+ </NTransition>
124
124
  </li>
125
125
  </template>
126
126
 
@@ -1,6 +1,7 @@
1
1
  export * from './create-strict-injection.js';
2
2
  export * from './use-active-link.js';
3
3
  export * from './use-config.js';
4
+ export * from './use-countdown.js';
4
5
  export * from './use-date-config.js';
5
6
  export * from './use-floating-indicator.js';
6
7
  export * from './use-selectable-group.js';
@@ -1,6 +1,7 @@
1
1
  export * from "./create-strict-injection.js";
2
2
  export * from "./use-active-link.js";
3
3
  export * from "./use-config.js";
4
+ export * from "./use-countdown.js";
4
5
  export * from "./use-date-config.js";
5
6
  export * from "./use-floating-indicator.js";
6
7
  export * from "./use-selectable-group.js";
@@ -0,0 +1,19 @@
1
+ import type { Ref } from 'vue';
2
+ export interface UseCountdownOptions {
3
+ /** Countdown duration in ms, `0` disables the timer */
4
+ duration: number;
5
+ /** Interval step in ms @default 16 */
6
+ step?: number;
7
+ /** Whether the timer is currently paused */
8
+ paused?: Ref<boolean>;
9
+ /** Called when the countdown reaches 0 */
10
+ onComplete: () => void;
11
+ }
12
+ export interface UseCountdownReturn {
13
+ /** Remaining time in ms */
14
+ remaining: Ref<number>;
15
+ /** Remaining time as a percentage of the total duration, `0` if duration is `0` */
16
+ progress: Ref<number>;
17
+ }
18
+ /** Countdown timer driven by `useIntervalFn`, pausable via an external ref. */
19
+ export declare function useCountdown({ duration, step, paused, onComplete, }: UseCountdownOptions): UseCountdownReturn;
@@ -0,0 +1,30 @@
1
+ import { useIntervalFn } from "@vueuse/core";
2
+ import { computed, ref, watch } from "vue";
3
+ export function useCountdown({
4
+ duration,
5
+ step = 16,
6
+ paused,
7
+ onComplete
8
+ }) {
9
+ const active = duration > 0;
10
+ const remaining = ref(duration);
11
+ const progress = computed(() => active ? remaining.value / duration * 100 : 0);
12
+ const { pause, resume } = useIntervalFn(() => {
13
+ remaining.value -= step;
14
+ if (remaining.value <= 0) {
15
+ pause();
16
+ onComplete();
17
+ }
18
+ }, step, { immediate: active && !paused?.value });
19
+ if (paused) {
20
+ watch(paused, (isPaused) => {
21
+ if (!active)
22
+ return;
23
+ if (isPaused)
24
+ pause();
25
+ else
26
+ resume();
27
+ });
28
+ }
29
+ return { remaining, progress };
30
+ }
@@ -0,0 +1,4 @@
1
+ export { default as NNotificationsProvider } from './notifications-provider.vue.js';
2
+ export { $notifications } from './notifications-store.js';
3
+ export type * from './types';
4
+ export { useNotifications } from './use-notifications.js';
@@ -0,0 +1,3 @@
1
+ export { default as NNotificationsProvider } from "./notifications-provider.vue";
2
+ export { $notifications } from "./notifications-store.js";
3
+ export { useNotifications } from "./use-notifications.js";
@@ -0,0 +1,4 @@
1
+ /** Unique id for a notification. */
2
+ export declare function createId(): string;
3
+ /** Resolves the effective auto-close: a notification number/false wins, else the global value. */
4
+ export declare function getAutoClose(global: number | false, notification: number | false | undefined): number | false;
@@ -0,0 +1,14 @@
1
+ let counter = 0;
2
+ export function createId() {
3
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto)
4
+ return crypto.randomUUID();
5
+ counter += 1;
6
+ return `notification-${Date.now()}-${counter}`;
7
+ }
8
+ export function getAutoClose(global, notification) {
9
+ if (typeof notification === "number")
10
+ return notification;
11
+ if (notification === false || global === false)
12
+ return false;
13
+ return global;
14
+ }
@@ -0,0 +1,20 @@
1
+ import type { NotificationData } from './types.js';
2
+ type __VLS_Props = {
3
+ /** Notification data object */
4
+ data: NotificationData;
5
+ /** Global auto-close duration in ms, or false to disable */
6
+ autoClose: number | false;
7
+ /** Whether the timer is currently paused */
8
+ paused: boolean;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ hide: (id: string) => any;
12
+ hoverStart: () => any;
13
+ hoverEnd: () => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onHide?: ((id: string) => any) | undefined;
16
+ onHoverStart?: (() => any) | undefined;
17
+ onHoverEnd?: (() => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,35 @@
1
+ <script setup>
2
+ import { onBeforeUnmount, onMounted, toRef } from "vue";
3
+ import Notification from "../components/notification.vue";
4
+ import { useCountdown } from "../composables";
5
+ import { getAutoClose } from "./lib";
6
+ const props = defineProps({
7
+ data: { type: Object, required: true },
8
+ autoClose: { type: [Number, Boolean], required: true },
9
+ paused: { type: Boolean, required: true }
10
+ });
11
+ const emit = defineEmits(["hide", "hoverStart", "hoverEnd"]);
12
+ const duration = getAutoClose(props.autoClose, props.data.autoClose);
13
+ const { progress } = useCountdown({
14
+ duration: typeof duration === "number" ? duration : 0,
15
+ paused: toRef(props, "paused"),
16
+ onComplete: () => emit("hide", props.data.id)
17
+ });
18
+ onMounted(() => {
19
+ props.data.onOpen?.(props.data.id);
20
+ });
21
+ onBeforeUnmount(() => {
22
+ props.data.onClose?.(props.data.id);
23
+ });
24
+ </script>
25
+
26
+ <template>
27
+ <Notification
28
+ v-bind='props.data'
29
+ v-model:progress='progress'
30
+ :with-progress='props.data.withProgress && !!duration'
31
+ @close="emit('hide', props.data.id)"
32
+ @mouseenter='$emit("hoverStart")'
33
+ @mouseleave='$emit("hoverEnd")'
34
+ />
35
+ </template>
@@ -0,0 +1,20 @@
1
+ import type { NotificationData } from './types.js';
2
+ type __VLS_Props = {
3
+ /** Notification data object */
4
+ data: NotificationData;
5
+ /** Global auto-close duration in ms, or false to disable */
6
+ autoClose: number | false;
7
+ /** Whether the timer is currently paused */
8
+ paused: boolean;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ hide: (id: string) => any;
12
+ hoverStart: () => any;
13
+ hoverEnd: () => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onHide?: ((id: string) => any) | undefined;
16
+ onHoverStart?: (() => any) | undefined;
17
+ onHoverEnd?: (() => any) | undefined;
18
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: typeof __VLS_export;
20
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import type { Classes } from '@nui/types';
2
+ import type { NotificationPosition } from './types.js';
3
+ export interface NotificationProviderProps {
4
+ /** Default notification position @default 'bottom-right' */
5
+ position?: NotificationPosition;
6
+ /** Auto-close in ms, or `false` to disable @default 4000 */
7
+ autoClose?: number | false;
8
+ /** Max visible at once @default 5 */
9
+ limit?: number;
10
+ /** Viewport z-index @default 1 */
11
+ zIndex?: number | string;
12
+ /** Container width @default 440 */
13
+ containerWidth?: number | string;
14
+ /** Transition duration in ms @default 250 */
15
+ transitionDuration?: number;
16
+ /** Which notifications pause auto-close on hover @default 'all' */
17
+ pauseOnHover?: 'all' | 'notification';
18
+ classes?: Classes<'root' | 'notification'>;
19
+ }
20
+ declare const __VLS_export: import("vue").DefineComponent<NotificationProviderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NotificationProviderProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,117 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref, useTemplateRef, watch } from "vue";
3
+ import { useVarsResolver } from "#imports";
4
+ import NotificationContainer from "./notification-container.vue";
5
+ import { $notifications } from "./notifications-store";
6
+ import { useNotifications } from "./use-notifications";
7
+ const {
8
+ position = "bottom-right",
9
+ autoClose = 4e3,
10
+ limit = 5,
11
+ zIndex = 1,
12
+ containerWidth = 440,
13
+ transitionDuration = 250,
14
+ pauseOnHover = "all"
15
+ } = defineProps({
16
+ position: { type: String, required: false },
17
+ autoClose: { type: [Number, Boolean], required: false },
18
+ limit: { type: Number, required: false },
19
+ zIndex: { type: [Number, String], required: false },
20
+ containerWidth: { type: [Number, String], required: false },
21
+ transitionDuration: { type: Number, required: false },
22
+ pauseOnHover: { type: String, required: false },
23
+ classes: { type: Object, required: false }
24
+ });
25
+ const POSITIONS = [
26
+ "top-left",
27
+ "top-center",
28
+ "top-right",
29
+ "bottom-left",
30
+ "bottom-center",
31
+ "bottom-right"
32
+ ];
33
+ const { notifications } = useNotifications();
34
+ const hoveredId = ref(null);
35
+ const grouped = computed(() => {
36
+ const acc = Object.fromEntries(
37
+ POSITIONS.map((p) => [p, []])
38
+ );
39
+ for (const n of notifications.value)
40
+ acc[n.position ?? position].push(n);
41
+ return acc;
42
+ });
43
+ function isPaused(id) {
44
+ if (pauseOnHover === "all")
45
+ return hoveredId.value !== null;
46
+ return hoveredId.value === id;
47
+ }
48
+ function transitionName(position2) {
49
+ if (position2.startsWith("top"))
50
+ return "slide-down";
51
+ return "slide-up";
52
+ }
53
+ const style = useVarsResolver(() => ({
54
+ root: {
55
+ "--notifications-z-index": String(zIndex),
56
+ "--notifications-width": typeof containerWidth === "number" ? `${containerWidth}px` : containerWidth,
57
+ "--transition-duration": `${transitionDuration}ms`
58
+ }
59
+ }));
60
+ const viewportRefs = useTemplateRef("viewports");
61
+ function showPopovers() {
62
+ for (const el of viewportRefs.value ?? []) {
63
+ if (el && "showPopover" in el && !el.matches(":popover-open")) {
64
+ try {
65
+ el.showPopover();
66
+ } catch {
67
+ }
68
+ }
69
+ }
70
+ }
71
+ onMounted(() => {
72
+ $notifications.updateState({
73
+ limit,
74
+ defaultPosition: position
75
+ });
76
+ showPopovers();
77
+ });
78
+ watch(() => limit, (limit2) => $notifications.updateState({ limit: limit2 }));
79
+ watch(() => position, (position2) => $notifications.updateState({ defaultPosition: position2 }));
80
+ </script>
81
+
82
+ <template>
83
+ <div
84
+ v-for='viewportPosition in POSITIONS'
85
+ :key='viewportPosition'
86
+ ref='viewports'
87
+ popover='manual'
88
+ role='region'
89
+ aria-label='Notifications'
90
+ aria-live='polite'
91
+ aria-atomic='false'
92
+ :data-position='viewportPosition'
93
+ :class='[$style.viewport, classes?.root]'
94
+ :style='style.root'
95
+ >
96
+ <TransitionGroup
97
+ :name='transitionName(viewportPosition)'
98
+ :duration='transitionDuration'
99
+ >
100
+ <NotificationContainer
101
+ v-for='n in grouped[viewportPosition]'
102
+ :key='n.id'
103
+ :data='n'
104
+ :auto-close
105
+ :paused='isPaused(n.id)'
106
+ :class='classes?.notification'
107
+ @hide='$notifications.hide($event)'
108
+ @hover-start='hoveredId = n.id'
109
+ @hover-end='hoveredId = null'
110
+ />
111
+ </TransitionGroup>
112
+ </div>
113
+ </template>
114
+
115
+ <style module>
116
+ .viewport{background:transparent;border:0;display:flex;flex-direction:column-reverse;gap:var(--spacing-sm);inset:auto;margin:0;max-width:100%;overflow:visible;padding:var(--spacing-md);position:fixed;width:var(--notifications-width);z-index:var(--notifications-z-index)}.viewport[data-position=top-left]{left:0;top:0}.viewport[data-position=top-center]{left:50%;top:0;transform:translateX(-50%)}.viewport[data-position=top-right]{right:0;top:0}.viewport[data-position=bottom-left]{bottom:0;flex-direction:column;left:0}.viewport[data-position=bottom-center]{bottom:0;flex-direction:column;left:50%;transform:translateX(-50%)}.viewport[data-position=bottom-right]{bottom:0;flex-direction:column;right:0}.viewport:not(:popover-open){display:none}
117
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { Classes } from '@nui/types';
2
+ import type { NotificationPosition } from './types.js';
3
+ export interface NotificationProviderProps {
4
+ /** Default notification position @default 'bottom-right' */
5
+ position?: NotificationPosition;
6
+ /** Auto-close in ms, or `false` to disable @default 4000 */
7
+ autoClose?: number | false;
8
+ /** Max visible at once @default 5 */
9
+ limit?: number;
10
+ /** Viewport z-index @default 1 */
11
+ zIndex?: number | string;
12
+ /** Container width @default 440 */
13
+ containerWidth?: number | string;
14
+ /** Transition duration in ms @default 250 */
15
+ transitionDuration?: number;
16
+ /** Which notifications pause auto-close on hover @default 'all' */
17
+ pauseOnHover?: 'all' | 'notification';
18
+ classes?: Classes<'root' | 'notification'>;
19
+ }
20
+ declare const __VLS_export: import("vue").DefineComponent<NotificationProviderProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NotificationProviderProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,25 @@
1
+ import type { NotificationInput, NotificationsState } from './types.js';
2
+ /**
3
+ * Global notifications store.
4
+ *
5
+ * Holds a reactive list of visible notifications plus an overflow queue.
6
+ * Shows, updates, and removes notifications from any context.
7
+ */
8
+ export declare class NotificationsStore {
9
+ readonly state: NotificationsState;
10
+ private constructor();
11
+ static get instance(): NotificationsStore;
12
+ /** Shows a notification, or queues it past the limit. Returns its id. */
13
+ show(data: NotificationInput): string;
14
+ /** Removes a notification and promotes the next queued one. */
15
+ hide(id: string): void;
16
+ /** Patches an existing notification in place. Noop if absent. */
17
+ update(id: string, data: Partial<NotificationInput>): void;
18
+ /** Removes everything. */
19
+ clean(): void;
20
+ /** Removes only queued notifications. */
21
+ cleanQueue(): void;
22
+ /** Patches store configuration (limit / defaultPosition). */
23
+ updateState(patch: Partial<Pick<NotificationsState, 'limit' | 'defaultPosition'>>): void;
24
+ }
25
+ export declare const $notifications: import("vue").Raw<NotificationsStore>;
@@ -0,0 +1,80 @@
1
+ import { markRaw, reactive } from "vue";
2
+ import { createId } from "./lib.js";
3
+ const GLOBAL_KEY = "__nui_notifications_store__";
4
+ export class NotificationsStore {
5
+ state = reactive({
6
+ notifications: [],
7
+ queue: [],
8
+ limit: 5,
9
+ defaultPosition: "bottom-right"
10
+ });
11
+ constructor() {
12
+ }
13
+ static get instance() {
14
+ if (!globalThis[GLOBAL_KEY])
15
+ globalThis[GLOBAL_KEY] = new NotificationsStore();
16
+ return globalThis[GLOBAL_KEY];
17
+ }
18
+ /** Shows a notification, or queues it past the limit. Returns its id. */
19
+ show(data) {
20
+ const id = data.id ?? createId();
21
+ const visible = this.state.notifications.find((n) => n.id === id);
22
+ if (visible) {
23
+ Object.assign(visible, data, { id });
24
+ return id;
25
+ }
26
+ const queued = this.state.queue.find((n) => n.id === id);
27
+ if (queued) {
28
+ Object.assign(queued, data, { id });
29
+ return id;
30
+ }
31
+ const next = { ...data, id };
32
+ if (this.state.notifications.length < this.state.limit)
33
+ this.state.notifications.push(next);
34
+ else
35
+ this.state.queue.push(next);
36
+ return id;
37
+ }
38
+ /** Removes a notification and promotes the next queued one. */
39
+ hide(id) {
40
+ const index = this.state.notifications.findIndex((n) => n.id === id);
41
+ if (index === -1) {
42
+ const queuedIndex = this.state.queue.findIndex((n) => n.id === id);
43
+ if (queuedIndex !== -1)
44
+ this.state.queue.splice(queuedIndex, 1);
45
+ return;
46
+ }
47
+ this.state.notifications.splice(index, 1);
48
+ const next = this.state.queue.shift();
49
+ if (next)
50
+ this.state.notifications.push(next);
51
+ }
52
+ /** Patches an existing notification in place. Noop if absent. */
53
+ update(id, data) {
54
+ const visible = this.state.notifications.find((n) => n.id === id);
55
+ if (visible) {
56
+ Object.assign(visible, data, { id });
57
+ return;
58
+ }
59
+ const queued = this.state.queue.find((n) => n.id === id);
60
+ if (queued)
61
+ Object.assign(queued, data, { id });
62
+ }
63
+ /** Removes everything. */
64
+ clean() {
65
+ this.state.notifications = [];
66
+ this.state.queue = [];
67
+ }
68
+ /** Removes only queued notifications. */
69
+ cleanQueue() {
70
+ this.state.queue = [];
71
+ }
72
+ /** Patches store configuration (limit / defaultPosition). */
73
+ updateState(patch) {
74
+ if (patch.limit !== void 0)
75
+ this.state.limit = patch.limit;
76
+ if (patch.defaultPosition !== void 0)
77
+ this.state.defaultPosition = patch.defaultPosition;
78
+ }
79
+ }
80
+ export const $notifications = markRaw(NotificationsStore.instance);
@@ -0,0 +1,50 @@
1
+ import type { StringOrVNode } from '@nui/types';
2
+ import type { ButtonProps } from '../components/button/button.vue.js';
3
+ import type { NotificationProps } from '../components/notification.vue.js';
4
+ /** Notification placement on screen. */
5
+ export type NotificationPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
6
+ /** Actions/close layout. */
7
+ export type NotificationOrientation = 'vertical' | 'horizontal';
8
+ /** Action button rendered inside a notification. */
9
+ export interface NotificationAction extends ButtonProps {
10
+ /** Button label */
11
+ label: string;
12
+ /** Click handler */
13
+ onClick?: (event: MouseEvent) => void;
14
+ }
15
+ /** Argument of `$notifications.show()`. */
16
+ export interface NotificationInput extends Omit<NotificationProps, 'title' | 'message'> {
17
+ /** Stable id. Reusing an existing id updates that notification in place. */
18
+ id?: string;
19
+ /** Notification title */
20
+ title?: StringOrVNode;
21
+ /** Notification body */
22
+ message?: StringOrVNode;
23
+ /** Action buttons */
24
+ actions?: NotificationAction[];
25
+ /** Actions/close layout @default 'vertical' */
26
+ orientation?: NotificationOrientation;
27
+ /** Overrides the provider position */
28
+ position?: NotificationPosition;
29
+ /** Auto-close in ms, or `false` to keep open. Overrides the provider value */
30
+ autoClose?: number | false;
31
+ /** Called after the notification is removed */
32
+ onClose?: (id: string) => void;
33
+ /** Called once when the notification mounts */
34
+ onOpen?: (id: string) => void;
35
+ }
36
+ /** Stored notification — always has an id. */
37
+ export interface NotificationData extends NotificationInput {
38
+ id: string;
39
+ }
40
+ /** Reactive store state. */
41
+ export interface NotificationsState {
42
+ /** Currently visible notifications (<= limit) */
43
+ notifications: NotificationData[];
44
+ /** Notifications waiting for a free slot */
45
+ queue: NotificationData[];
46
+ /** Max visible at once */
47
+ limit: number;
48
+ /** Position used when a notification has none */
49
+ defaultPosition: NotificationPosition;
50
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ /** Reactive access to the global notifications state. */
2
+ export declare function useNotifications(): {
3
+ /** Currently visible notifications. */
4
+ notifications: import("vue").ComputedRef<import("./types.js").NotificationData[]>;
5
+ /** Queued notifications waiting for a free slot. */
6
+ queue: import("vue").ComputedRef<import("./types.js").NotificationData[]>;
7
+ };
@@ -0,0 +1,10 @@
1
+ import { computed } from "vue";
2
+ import { $notifications } from "./notifications-store.js";
3
+ export function useNotifications() {
4
+ return {
5
+ /** Currently visible notifications. */
6
+ notifications: computed(() => $notifications.state.notifications),
7
+ /** Queued notifications waiting for a free slot. */
8
+ queue: computed(() => $notifications.state.queue)
9
+ };
10
+ }
@@ -1 +1 @@
1
- @import url(./baseline.css);@import url(./const.css);@import url(./colors.css);@import url(./dark-theme.css);@import url(./light-theme.css);.light-hidden{@mixin light{display:none}}.dark-hidden{@mixin dark{display:none}}.focus-auto:focus-visible{outline:2px solid var(--color-primary-filled);outline-offset:2px}.focus-always:focus{outline:2px solid var(--color-primary-filled);outline-offset:2px}.focus-never:focus{outline:none}.active:active{transform:translateY(rem(1px))}fieldset:disabled .active:active{transform:none}.rotate-rtl{@mixin where-rtl{transform:rotate(180deg)}}[data-v-hidden]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}
1
+ @import url(./baseline.css);@import url(./const.css);@import url(./colors.css);@import url(./dark-theme.css);@import url(./light-theme.css);@import url(./transitions.css);.light-hidden{@mixin light{display:none}}.dark-hidden{@mixin dark{display:none}}.focus-auto:focus-visible{outline:2px solid var(--color-primary-filled);outline-offset:2px}.focus-always:focus{outline:2px solid var(--color-primary-filled);outline-offset:2px}.focus-never:focus{outline:none}.active:active{transform:translateY(rem(1px))}fieldset:disabled .active:active{transform:none}.rotate-rtl{@mixin where-rtl{transform:rotate(180deg)}}[data-v-hidden]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}
@@ -0,0 +1 @@
1
+ :root{--transition-delay:0ms;--transition-duration:250ms}.transition-move{transition:transform var(--transition-duration) var(--transition-delay) ease}.fade-enter-active,.fade-leave-active{transition:opacity var(--transition-duration) var(--transition-delay) ease}.fade-enter-from,.fade-leave-to{opacity:0}.fade-up-enter-active,.fade-up-leave-active{transform:translateY(0);transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.fade-up-enter-from,.fade-up-leave-to{opacity:0;transform:translateY(30px)}.fade-down-enter-active,.fade-down-leave-active{transform:translateY(0);transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.fade-down-enter-from,.fade-down-leave-to{opacity:0;transform:translateY(-30px)}.fade-left-enter-active,.fade-left-leave-active{transform:translateX(0);transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.fade-left-enter-from,.fade-left-leave-to{opacity:0;transform:translateX(30px)}.fade-right-enter-active,.fade-right-leave-active{transform:translateX(0);transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.fade-right-enter-from,.fade-right-leave-to{opacity:0;transform:translateX(-30px)}.scale-enter-active,.scale-leave-active{transform:scale(1);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.scale-enter-from,.scale-leave-to{opacity:0;transform:scale(0)}.scale-y-enter-active,.scale-y-leave-active{transform:scaleY(1);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.scale-y-enter-from,.scale-y-leave-to{opacity:0;transform:scaleY(0)}.scale-x-enter-active,.scale-x-leave-active{transform:scaleX(1);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.scale-x-enter-from,.scale-x-leave-to{opacity:0;transform:scaleX(0)}.skew-up-enter-active,.skew-up-leave-active{transform:translateY(0) skew(0deg,0deg);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.skew-up-enter-from,.skew-up-leave-to{opacity:0;transform:translateY(-20px) skew(-10deg,-5deg)}.skew-down-enter-active,.skew-down-leave-active{transform:translateY(0) skew(0deg,0deg);transform-origin:bottom;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.skew-down-enter-from,.skew-down-leave-to{opacity:0;transform:translateY(20px) skew(-10deg,-5deg)}.rotate-left-enter-active,.rotate-left-leave-active{transform:translateY(0) rotate(0deg);transform-origin:bottom;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.rotate-left-enter-from,.rotate-left-leave-to{opacity:0;transform:translateY(20px) rotate(-5deg)}.rotate-right-enter-active,.rotate-right-leave-active{transform:translateY(0) rotate(0deg);transform-origin:bottom;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.rotate-right-enter-from,.rotate-right-leave-to{opacity:0;transform:translateY(20px) rotate(5deg)}.slide-down-enter-active,.slide-down-leave-active{transform:translateY(0);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.slide-down-enter-from,.slide-down-leave-to{opacity:0;transform:translateY(-100%)}.slide-up-enter-active,.slide-up-leave-active{transform:translateY(0);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.slide-up-enter-from,.slide-up-leave-to{opacity:0;transform:translateY(100%)}.slide-left-enter-active,.slide-left-leave-active{transform:translateX(0);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.slide-left-enter-from,.slide-left-leave-to{opacity:0;transform:translateX(100%)}.slide-right-enter-active,.slide-right-leave-active{transform:translateX(0);transform-origin:top;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.slide-right-enter-from,.slide-right-leave-to{opacity:0;transform:translateX(-100%)}.pop-enter-active,.pop-leave-active{transform:scale(1);transform-origin:center center;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.pop-enter-from,.pop-leave-to{opacity:0;transform:scale(.9) translateY(10px);transform-origin:center center}.pop-bottom-left-enter-active,.pop-bottom-left-leave-active{transform:scale(1);transform-origin:bottom left;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.pop-bottom-left-enter-from,.pop-bottom-left-leave-to{opacity:0;transform:scale(.9) translateY(10px);transform-origin:bottom left}.pop-bottom-right-enter-active,.pop-bottom-right-leave-active{transform:scale(1);transform-origin:bottom right;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.pop-bottom-right-enter-from,.pop-bottom-right-leave-to{opacity:0;transform:scale(.9) translateY(10px);transform-origin:bottom right}.pop-top-left-enter-active,.pop-top-left-leave-active{transform:scale(1);transform-origin:top left;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.pop-top-left-enter-from,.pop-top-left-leave-to{opacity:0;transform:scale(.9) translateY(-10px);transform-origin:top left}.pop-top-right-enter-active,.pop-top-right-leave-active{transform:scale(1);transform-origin:top right;transition:opacity var(--transition-duration) var(--transition-delay) ease,transform var(--transition-duration) ease}.pop-top-right-enter-from,.pop-top-right-leave-to{opacity:0;transform:scale(.9) translateY(-10px);transform-origin:top right}
@@ -4,6 +4,8 @@ export type * from './styling';
4
4
  export type * from './theme';
5
5
  /** Value that may be synchronous or wrapped in a promise. */
6
6
  export type MaybePromise<T = unknown> = T | Promise<T>;
7
+ /** String, VNode, or a render function producing a VNode. */
8
+ export type StringOrVNode = string | VNode | (() => VNode);
7
9
  export type AnyString = string & {};
8
10
  export type DynamicSlotsKeys<Name extends string | undefined, Suffix extends string | undefined = undefined> = (Name extends string ? Suffix extends string ? Name | `${Name}-${Suffix}` : Name : never);
9
11
  export type SlotProps<T extends {