nuance-ui 0.1.51 → 0.1.53

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
  "compatibility": {
5
5
  "nuxt": "^4.0.0"
6
6
  },
7
- "version": "0.1.51",
7
+ "version": "0.1.53",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,5 +1,5 @@
1
1
  import type { NuanceRadius, NuanceShadow, NuanceSize, NuanceSpacing } from '@nui/types';
2
- import type { CSSProperties } from 'vue';
2
+ import type { CSSProperties, RendererElement } from 'vue';
3
3
  import type { BoxProps } from '../../box.vue.js';
4
4
  import type { TransitionName } from '../../transition/index.js';
5
5
  import type { DialogModel } from '../model.js';
@@ -25,6 +25,8 @@ export interface DialogRootProps extends BoxProps {
25
25
  /** Passes a class to root element */
26
26
  rootClass?: string;
27
27
  transition?: TransitionName;
28
+ /** Portal target to render element @default 'body' */
29
+ portalTarget?: string | RendererElement | null;
28
30
  }
29
31
  type __VLS_Props = DialogRootProps;
30
32
  type __VLS_ModelProps = {
@@ -6,8 +6,9 @@ import Box from "../../box.vue";
6
6
  import NTransition from "../../transition/transition.vue";
7
7
  import { useProvideDialogState } from "../lib";
8
8
  import css from "./dialog.module.css";
9
+ defineOptions({ inheritAttrs: false });
9
10
  const {
10
- withinPortal = true,
11
+ withinPortal = false,
11
12
  closeOnClickOutside = true,
12
13
  mod,
13
14
  xOffset,
@@ -18,7 +19,8 @@ const {
18
19
  shadow,
19
20
  rootClass,
20
21
  transition = "fade-down",
21
- withoutOverlay = false
22
+ withoutOverlay = false,
23
+ portalTarget = "body"
22
24
  } = defineProps({
23
25
  closeOnClickOutside: { type: Boolean, required: false },
24
26
  withinPortal: { type: Boolean, required: false },
@@ -31,6 +33,7 @@ const {
31
33
  padding: { type: [String, Number], required: false },
32
34
  rootClass: { type: String, required: false },
33
35
  transition: { type: String, required: false },
36
+ portalTarget: { type: [String, Object, null], required: false },
34
37
  is: { type: null, required: false },
35
38
  mod: { type: [Object, Array, null], required: false }
36
39
  });
@@ -43,7 +46,10 @@ function overlayClick(event) {
43
46
  }
44
47
  function open(dialog) {
45
48
  emit("open");
46
- dialog?.showModal();
49
+ if (withoutOverlay)
50
+ dialog?.show();
51
+ else
52
+ dialog?.showModal();
47
53
  }
48
54
  const dialogRef = shallowRef(null);
49
55
  if (closeOnClickOutside) {
@@ -56,7 +62,7 @@ watch(opened, (isOpen) => {
56
62
  if (isOpen)
57
63
  open(dialog);
58
64
  else
59
- dialog?.close();
65
+ dialog.close();
60
66
  }, { flush: "post" });
61
67
  onMounted(() => {
62
68
  if (!opened.value)
@@ -74,7 +80,7 @@ const style = computed(() => ({
74
80
  </script>
75
81
 
76
82
  <template>
77
- <Teleport :disabled='!withinPortal' to='body'>
83
+ <Teleport :disabled='!withinPortal' :to='portalTarget'>
78
84
  <Box
79
85
  is='dialog'
80
86
  ref='dialogRef'
@@ -83,7 +89,7 @@ const style = computed(() => ({
83
89
  :style
84
90
  @click='overlayClick'
85
91
  @close='$emit("close")'
86
- @cancel.prevent='() => closeOnClickOutside && (opened = false)'
92
+ @cancel.prevent='opened = false'
87
93
  >
88
94
  <NTransition :name='transition'>
89
95
  <Box is='section' v-if='opened' :class='css.content' v-bind='$attrs'>
@@ -1,5 +1,5 @@
1
1
  import type { NuanceRadius, NuanceShadow, NuanceSize, NuanceSpacing } from '@nui/types';
2
- import type { CSSProperties } from 'vue';
2
+ import type { CSSProperties, RendererElement } from 'vue';
3
3
  import type { BoxProps } from '../../box.vue.js';
4
4
  import type { TransitionName } from '../../transition/index.js';
5
5
  import type { DialogModel } from '../model.js';
@@ -25,6 +25,8 @@ export interface DialogRootProps extends BoxProps {
25
25
  /** Passes a class to root element */
26
26
  rootClass?: string;
27
27
  transition?: TransitionName;
28
+ /** Portal target to render element @default 'body' */
29
+ portalTarget?: string | RendererElement | null;
28
30
  }
29
31
  type __VLS_Props = DialogRootProps;
30
32
  type __VLS_ModelProps = {
@@ -1 +1 @@
1
- .root{--dialog-size-xs:rem(320px);--dialog-size-sm:rem(380px);--dialog-size-md:rem(440px);--dialog-size-lg:rem(620px);--dialog-size-xl:rem(780px);--dialog-size:var(--dialog-size-md);--dialog-y-offset:0;--dialog-x-offset:0;--dialog-shadow:var(--shadow-md);--dialog-radius:var(--radius-default);--overlay-bg:rgba(0,0,0,.4);--overlay-filter:blur(.1rem);background:none;border:none;box-shadow:var(--dialog-shadow);display:grid;left:var(--dialog-x-offset);max-height:100%;max-width:100%;min-height:0;opacity:0;overflow:hidden;padding:0;top:var(--dialog-y-offset);transition:opacity .3s ease,overlay allow-discrete .3s ease,display allow-discrete .3s ease;width:var(--dialog-size)}.root::backdrop{backdrop-filter:var(--overlay-filter);background:var(--overlay-bg,rgba(0,0,0,.6));opacity:0;transition:opacity .3s ease,overlay allow-discrete .3s ease,display allow-discrete .3s ease}.root[open]{opacity:1}@starting-style{.root[open]{opacity:0}}.root[open]::backdrop{opacity:1}@starting-style{.root[open]::backdrop{opacity:0}}.root:not([open]){opacity:0;pointer-events:none}.root:not([open])::backdrop{opacity:0}.root[data-without-overlay]::backdrop{display:none}.content{background-color:var(--color-body);border-radius:var(--dialog-radius);color:var(--color-text);height:100%;min-height:0;padding:var(--spacing-sm);width:100%}.header{align-items:center;display:flex;justify-content:space-between}.title{font-weight:400}
1
+ .root{--dialog-size-xs:rem(320px);--dialog-size-sm:rem(380px);--dialog-size-md:rem(440px);--dialog-size-lg:rem(620px);--dialog-size-xl:rem(780px);--dialog-size:var(--dialog-size-md);--dialog-y-offset:0;--dialog-x-offset:0;--dialog-shadow:var(--shadow-md);--dialog-radius:var(--radius-default);--overlay-bg:rgba(0,0,0,.4);--overlay-filter:blur(.1rem);background:none;border:none;box-shadow:var(--dialog-shadow);display:grid;left:var(--dialog-x-offset);max-height:100%;max-width:100%;min-height:0;opacity:0;overflow:hidden;padding:0;top:var(--dialog-y-offset);transition:opacity .3s ease,overlay allow-discrete .3s ease,display allow-discrete .3s ease;width:var(--dialog-size);z-index:2}.root::backdrop{backdrop-filter:var(--overlay-filter);background:var(--overlay-bg,rgba(0,0,0,.6));opacity:0;transition:opacity .3s ease,overlay allow-discrete .3s ease,display allow-discrete .3s ease}.root[open]{opacity:1}@starting-style{.root[open]{opacity:0}}.root[open]::backdrop{opacity:1}@starting-style{.root[open]::backdrop{opacity:0}}.root:not([open]){opacity:0;pointer-events:none}.root:not([open])::backdrop{opacity:0}.root[data-without-overlay]::backdrop{display:none}.content{background-color:var(--color-body);border-radius:var(--dialog-radius);color:var(--color-text);height:100%;min-height:0;padding:var(--spacing-sm);width:100%}.header{align-items:center;display:flex;justify-content:space-between}.title{font-weight:400}
@@ -1,5 +1,5 @@
1
1
  import type { DialogModel, DialogRootProps } from '../dialog/index.js';
2
- type OmittedDialogRootProps = Omit<DialogRootProps, 'modal' | 'closeOnClickOutside' | 'rootClass' | 'transition'>;
2
+ type OmittedDialogRootProps = Omit<DialogRootProps, 'rootClass' | 'transition'>;
3
3
  export interface DrawerRootProps extends OmittedDialogRootProps {
4
4
  /** Side of the screen on which drawer will be opened @default `'left'` */
5
5
  position?: 'bottom' | 'left' | 'right' | 'top';
@@ -4,12 +4,14 @@ import DialogRoot from "../dialog/ui/dialog-root.vue";
4
4
  import css from "./drawer.module.css";
5
5
  const {
6
6
  position = "left",
7
- withoutOverlay = true,
7
+ withoutOverlay = false,
8
+ withinPortal = false,
9
+ closeOnClickOutside = true,
8
10
  mod,
9
- withinPortal = true,
10
11
  ...rest
11
12
  } = defineProps({
12
13
  position: { type: String, required: false },
14
+ closeOnClickOutside: { type: Boolean, required: false },
13
15
  withinPortal: { type: Boolean, required: false },
14
16
  withoutOverlay: { type: Boolean, required: false },
15
17
  yOffset: { type: void 0, required: false },
@@ -18,6 +20,7 @@ const {
18
20
  size: { type: String, required: false },
19
21
  shadow: { type: String, required: false },
20
22
  padding: { type: [String, Number], required: false },
23
+ portalTarget: { type: [String, Object, null], required: false },
21
24
  is: { type: null, required: false },
22
25
  mod: { type: [Object, Array, null], required: false }
23
26
  });
@@ -45,6 +48,7 @@ const transition = computed(() => {
45
48
  :class='css.content'
46
49
  :within-portal
47
50
  :without-overlay
51
+ :close-on-click-outside
48
52
  :transition
49
53
  v-bind='rest'
50
54
  @open='$emit("open")'
@@ -1,5 +1,5 @@
1
1
  import type { DialogModel, DialogRootProps } from '../dialog/index.js';
2
- type OmittedDialogRootProps = Omit<DialogRootProps, 'modal' | 'closeOnClickOutside' | 'rootClass' | 'transition'>;
2
+ type OmittedDialogRootProps = Omit<DialogRootProps, 'rootClass' | 'transition'>;
3
3
  export interface DrawerRootProps extends OmittedDialogRootProps {
4
4
  /** Side of the screen on which drawer will be opened @default `'left'` */
5
5
  position?: 'bottom' | 'left' | 'right' | 'top';
@@ -1,5 +1,5 @@
1
1
  import type { DialogModel, DialogRootProps } from '../dialog/index.js';
2
- type OmittedDialogRootProps = Omit<DialogRootProps, 'modal' | 'closeOnClickOutside' | 'rootClass' | 'withoutOverlay'>;
2
+ type OmittedDialogRootProps = Omit<DialogRootProps, 'rootClass'>;
3
3
  export interface ModalRootProps extends OmittedDialogRootProps {
4
4
  /** If set, the modal is centered vertically @default `false` */
5
5
  centered?: boolean;
@@ -5,12 +5,16 @@ const {
5
5
  centered,
6
6
  fullScreen,
7
7
  mod,
8
- withinPortal = true,
8
+ withoutOverlay = false,
9
+ withinPortal = false,
10
+ closeOnClickOutside = true,
9
11
  ...rest
10
12
  } = defineProps({
11
13
  centered: { type: Boolean, required: false },
12
14
  fullScreen: { type: Boolean, required: false },
15
+ closeOnClickOutside: { type: Boolean, required: false },
13
16
  withinPortal: { type: Boolean, required: false },
17
+ withoutOverlay: { type: Boolean, required: false },
14
18
  yOffset: { type: void 0, required: false },
15
19
  xOffset: { type: void 0, required: false },
16
20
  radius: { type: [String, Number], required: false },
@@ -18,6 +22,7 @@ const {
18
22
  shadow: { type: String, required: false },
19
23
  padding: { type: [String, Number], required: false },
20
24
  transition: { type: String, required: false },
25
+ portalTarget: { type: [String, Object, null], required: false },
21
26
  is: { type: null, required: false },
22
27
  mod: { type: [Object, Array, null], required: false }
23
28
  });
@@ -31,6 +36,8 @@ const opened = defineModel("open", { type: Boolean, ...{ default: false } });
31
36
  :mod='[{ centered, "full-screen": fullScreen }, mod]'
32
37
  :root-class='css.root'
33
38
  :within-portal
39
+ :close-on-click-outside
40
+ :without-overlay
34
41
  v-bind='rest'
35
42
  @open='$emit("open")'
36
43
  @close='$emit("close")'
@@ -1,5 +1,5 @@
1
1
  import type { DialogModel, DialogRootProps } from '../dialog/index.js';
2
- type OmittedDialogRootProps = Omit<DialogRootProps, 'modal' | 'closeOnClickOutside' | 'rootClass' | 'withoutOverlay'>;
2
+ type OmittedDialogRootProps = Omit<DialogRootProps, 'rootClass'>;
3
3
  export interface ModalRootProps extends OmittedDialogRootProps {
4
4
  /** If set, the modal is centered vertically @default `false` */
5
5
  centered?: boolean;
@@ -15,6 +15,7 @@ const {
15
15
  confirmProps,
16
16
  onConfirm,
17
17
  onCancel,
18
+ closeOnClickOutside = true,
18
19
  ...rest
19
20
  } = defineProps({
20
21
  title: { type: String, required: true },
@@ -27,7 +28,9 @@ const {
27
28
  confirmProps: { type: Object, required: false },
28
29
  centered: { type: Boolean, required: false },
29
30
  fullScreen: { type: Boolean, required: false },
31
+ closeOnClickOutside: { type: Boolean, required: false },
30
32
  withinPortal: { type: Boolean, required: false },
33
+ withoutOverlay: { type: Boolean, required: false },
31
34
  yOffset: { type: void 0, required: false },
32
35
  xOffset: { type: void 0, required: false },
33
36
  radius: { type: [String, Number], required: false },
@@ -35,6 +38,7 @@ const {
35
38
  shadow: { type: String, required: false },
36
39
  padding: { type: [String, Number], required: false },
37
40
  transition: { type: String, required: false },
41
+ portalTarget: { type: [String, Object, null], required: false },
38
42
  is: { type: null, required: false },
39
43
  mod: { type: [Object, Array, null], required: false }
40
44
  });
@@ -56,7 +60,7 @@ async function hanleConfirm() {
56
60
  </script>
57
61
 
58
62
  <template>
59
- <ModalRoot v-model:open='opened' v-bind='rest' size='sm'>
63
+ <ModalRoot v-model:open='opened' v-bind='rest' :close-on-click-outside size='sm'>
60
64
  <ModalHeader>
61
65
  <Title order='4'>
62
66
  {{ title }}
@@ -1,13 +1,12 @@
1
1
  <script setup>
2
2
  import { $modals } from "@nui/composals";
3
- const activeModals = $modals.modals;
4
3
  </script>
5
4
 
6
5
  <template>
7
6
  <div id='nui-modals-root'>
8
7
  <component
9
8
  :is='entry.component'
10
- v-for='[id, entry] in activeModals'
9
+ v-for='[id, entry] in $modals.modals'
11
10
  :key='id'
12
11
  v-bind='entry.props'
13
12
  />
@@ -35,7 +35,7 @@ export declare class ModalManager {
35
35
  /** Returns the singleton instance (creates on first access) */
36
36
  static get instance(): ModalManager;
37
37
  /** Reactive map of all active modals. Used by `ModalProvider` for rendering */
38
- get modals(): import("vue").ShallowRef<Map<string, ModalState<object, unknown, unknown>>, Map<string, ModalState<object, unknown, unknown>>>;
38
+ get modals(): import("vue").Reactive<Map<string, ModalState<object, unknown, unknown>>>;
39
39
  /**
40
40
  * Registers a modal component and returns a typed function to open it.
41
41
  */
@@ -64,7 +64,7 @@ export declare class ModalManager {
64
64
  * @param id — modal identifier
65
65
  * @param result — value the promise resolves with
66
66
  */
67
- hide(id: string, result?: any): void;
67
+ resolve(id: string, result?: any): void;
68
68
  /**
69
69
  * Closes the modal and rejects its promise with the given reason.
70
70
  *
@@ -77,7 +77,7 @@ export declare class ModalManager {
77
77
  *
78
78
  * Used inside a modal component (via {@link useModal}).
79
79
  */
80
- state<Props extends object = object, Resolve = unknown, Reject = unknown>(id: string): import("vue").ComputedRef<ModalState<Props, Resolve, Reject>>;
80
+ state<Props extends object = object, Resolve = unknown, Reject = unknown>(id: string): ModalState<Props, Resolve, Reject>;
81
81
  }
82
82
  /** Global {@link ModalManager} instance for use throughout the application */
83
83
  export declare const $modals: ModalManager;
@@ -1,8 +1,8 @@
1
- import { computed, nextTick, shallowRef, triggerRef } from "vue";
1
+ import { markRaw, reactive } from "vue";
2
2
  export class ModalManager {
3
3
  static #instance = null;
4
4
  /** Reactive map of active modals */
5
- #modals = shallowRef(/* @__PURE__ */ new Map());
5
+ #modals = reactive(/* @__PURE__ */ new Map());
6
6
  /** Eagerly registered components (id → Component) */
7
7
  #registered = /* @__PURE__ */ new Map();
8
8
  /** Lazily registered loaders (id → loader) */
@@ -46,8 +46,8 @@ export class ModalManager {
46
46
  * @param id — modal identifier
47
47
  * @param result — value the promise resolves with
48
48
  */
49
- hide(id, result) {
50
- this.#hide(id, result);
49
+ resolve(id, result) {
50
+ this.#resolve(id, result);
51
51
  }
52
52
  /**
53
53
  * Closes the modal and rejects its promise with the given reason.
@@ -64,7 +64,7 @@ export class ModalManager {
64
64
  * Used inside a modal component (via {@link useModal}).
65
65
  */
66
66
  state(id) {
67
- return computed(() => this.#modals.value.get(id));
67
+ return this.#modals.get(id);
68
68
  }
69
69
  // ── Private implementation ──
70
70
  async #show(id, props = {}) {
@@ -80,7 +80,7 @@ export class ModalManager {
80
80
  const component = this.#registered.get(id);
81
81
  if (!component)
82
82
  return Promise.reject(new Error(`Modal "${id}" is not registered`));
83
- const existing = this.#modals.value.get(id);
83
+ const existing = this.#modals.get(id);
84
84
  return new Promise((resolve, reject) => {
85
85
  if (existing) {
86
86
  existing.props = props;
@@ -88,47 +88,30 @@ export class ModalManager {
88
88
  existing.resolve = resolve;
89
89
  existing.reject = reject;
90
90
  } else {
91
- this.#modals.value.set(id, {
91
+ this.#modals.set(id, {
92
92
  id,
93
- component,
93
+ component: markRaw(component),
94
94
  props,
95
95
  opened: true,
96
96
  resolve,
97
97
  reject
98
98
  });
99
99
  }
100
- triggerRef(this.#modals);
101
100
  });
102
101
  }
103
- #hide(id, result) {
104
- const modal = this.#modals.value.get(id);
102
+ #resolve(id, result) {
103
+ const modal = this.#modals.get(id);
105
104
  if (!modal)
106
105
  return;
107
106
  modal.opened = false;
108
107
  modal.resolve?.(result);
109
- triggerRef(this.#modals);
110
- nextTick(() => {
111
- const current = this.#modals.value.get(id);
112
- if (current && !current.opened) {
113
- this.#modals.value.delete(id);
114
- triggerRef(this.#modals);
115
- }
116
- });
117
108
  }
118
109
  #reject(id, reason) {
119
- const modal = this.#modals.value.get(id);
110
+ const modal = this.#modals.get(id);
120
111
  if (!modal)
121
112
  return;
122
113
  modal.opened = false;
123
114
  modal.reject?.(reason);
124
- triggerRef(this.#modals);
125
- nextTick(() => {
126
- const current = this.#modals.value.get(id);
127
- if (current && !current.opened) {
128
- this.#modals.value.delete(id);
129
- triggerRef(this.#modals);
130
- }
131
- });
132
115
  }
133
116
  }
134
117
  export const $modals = ModalManager.instance;
@@ -24,4 +24,5 @@ id: string): {
24
24
  opened: import("vue").WritableComputedRef<boolean, boolean>;
25
25
  /** closes the modal and resolves the promise */
26
26
  resolve: (reason: Resolve) => void;
27
+ reject: () => void;
27
28
  };
@@ -1,10 +1,10 @@
1
1
  import { computed } from "vue";
2
2
  import { $modals } from "./modal-manager.js";
3
3
  export function useModal(id) {
4
- const state = $modals.state(id);
4
+ const state = computed(() => $modals.state(id));
5
5
  const opened = computed({
6
- get: () => state.value.opened,
7
- set: (opened2) => opened2 ? $modals.show(id, state.value.props) : $modals.reject(id)
6
+ get: () => state.value?.opened,
7
+ set: (opened2) => opened2 ? $modals.show(id, state.value.props) : $modals.reject(id, "cancel")
8
8
  });
9
9
  if (!state)
10
10
  throw new Error(`Modal ${id} is not exist`);
@@ -16,6 +16,7 @@ export function useModal(id) {
16
16
  */
17
17
  opened,
18
18
  /** closes the modal and resolves the promise */
19
- resolve: (reason) => $modals.hide(id, reason)
19
+ resolve: (reason) => $modals.resolve(id, reason),
20
+ reject: () => $modals.reject(id)
20
21
  };
21
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuance-ui",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "description": "A UI Library for Modern Web Apps, powered by Vue.",
5
5
  "repository": {
6
6
  "type": "git",