@v1nt1248/3nclient-lib 0.3.28 → 0.3.30

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 (111) hide show
  1. package/dist/chunks/ui3n-notification-Dr_3Td9N.js +279 -0
  2. package/dist/components/index.d.ts +12 -12
  3. package/dist/components/ui3n-autocomplete/types.d.ts +13 -0
  4. package/dist/components/ui3n-autocomplete/ui3n-autocomplete.d.ts +2 -2
  5. package/dist/components/ui3n-badge/types.d.ts +12 -0
  6. package/dist/components/ui3n-badge/ui3n-badge-simple.d.ts +1 -1
  7. package/dist/components/ui3n-badge/ui3n-badge.d.ts +1 -1
  8. package/dist/components/ui3n-breadcrumbs/types.d.ts +1 -1
  9. package/dist/components/ui3n-breadcrumbs/ui3n-breadcrumb.d.ts +2 -2
  10. package/dist/components/ui3n-button/types.d.ts +4 -2
  11. package/dist/components/ui3n-checkbox/types.d.ts +11 -0
  12. package/dist/components/ui3n-checkbox/ui3n-checkbox.d.ts +4 -1
  13. package/dist/components/ui3n-chip/types.d.ts +6 -1
  14. package/dist/components/ui3n-chip/ui3n-chip.d.ts +2 -0
  15. package/dist/components/ui3n-drop-files/types.d.ts +7 -0
  16. package/dist/components/ui3n-drop-files/ui3n-drop-files.d.ts +7 -1
  17. package/dist/components/ui3n-editable/types.d.ts +11 -0
  18. package/dist/components/ui3n-editable/ui3n-editable.d.ts +4 -2
  19. package/dist/components/ui3n-icon/types.d.ts +5 -1
  20. package/dist/components/ui3n-icon/ui3n-icon.d.ts +2 -2
  21. package/dist/components/ui3n-input/types.d.ts +4 -0
  22. package/dist/components/ui3n-input/ui3n-input.d.ts +4 -1
  23. package/dist/components/ui3n-input-file/types.d.ts +11 -0
  24. package/dist/components/ui3n-input-file/ui3n-input-file.d.ts +4 -1
  25. package/dist/components/ui3n-list/types.d.ts +4 -0
  26. package/dist/components/ui3n-menu/types.d.ts +9 -0
  27. package/dist/components/ui3n-menu/ui3n-menu.d.ts +2 -1
  28. package/dist/components/ui3n-notification/ui3n-notification.d.ts +2 -2
  29. package/dist/components/ui3n-progress/ui3n-progress-circular.d.ts +1 -1
  30. package/dist/components/ui3n-progress/ui3n-progress-linear.d.ts +1 -1
  31. package/dist/components/ui3n-radio-group/types.d.ts +18 -0
  32. package/dist/components/ui3n-radio-group/ui3n-radio-group.d.ts +3 -1
  33. package/dist/components/ui3n-radio-group/ui3n-radio.d.ts +6 -7
  34. package/dist/components/ui3n-scrollbar-vertical/types.d.ts +2 -0
  35. package/dist/components/ui3n-scrollbar-vertical/ui3n-scrollbar-vertical.d.ts +2 -0
  36. package/dist/components/ui3n-selector/types.d.ts +11 -0
  37. package/dist/components/ui3n-selector/ui3n-selector.d.ts +2 -2
  38. package/dist/components/ui3n-slider/types.d.ts +11 -0
  39. package/dist/components/ui3n-slider/ui3n-slider.d.ts +4 -1
  40. package/dist/components/ui3n-step-line-bar/types.d.ts +4 -0
  41. package/dist/components/ui3n-switch/types.d.ts +11 -0
  42. package/dist/components/ui3n-switch/ui3n-switch.d.ts +4 -1
  43. package/dist/components/ui3n-table/composables/useTable.d.ts +1 -0
  44. package/dist/components/ui3n-table/types.d.ts +4 -0
  45. package/dist/components/ui3n-table/ui3n-table-sort-icon.d.ts +4 -1
  46. package/dist/components/ui3n-tabs/types.d.ts +4 -0
  47. package/dist/components/ui3n-text/types.d.ts +11 -0
  48. package/dist/components/ui3n-text/ui3n-text.d.ts +4 -1
  49. package/dist/components/ui3n-tooltip/types.d.ts +4 -0
  50. package/dist/components/ui3n-tooltip/ui3n-tooltip.d.ts +4 -2
  51. package/dist/style.css +1 -1
  52. package/dist/ui3n-components.js +2121 -1696
  53. package/dist/ui3n-plugins.js +1 -1
  54. package/package.json +1 -1
  55. package/src/components/index.ts +29 -7
  56. package/src/components/ui3n-autocomplete/types.ts +14 -1
  57. package/src/components/ui3n-autocomplete/ui3n-autocomplete.vue +189 -55
  58. package/src/components/ui3n-badge/types.ts +10 -0
  59. package/src/components/ui3n-badge/ui3n-badge-simple.vue +54 -55
  60. package/src/components/ui3n-badge/ui3n-badge.vue +4 -0
  61. package/src/components/ui3n-breadcrumbs/types.ts +1 -1
  62. package/src/components/ui3n-breadcrumbs/ui3n-breadcrumb.vue +7 -5
  63. package/src/components/ui3n-button/types.ts +4 -2
  64. package/src/components/ui3n-button/ui3n-button.vue +24 -19
  65. package/src/components/ui3n-checkbox/types.ts +12 -0
  66. package/src/components/ui3n-checkbox/ui3n-checkbox.vue +79 -29
  67. package/src/components/ui3n-chip/types.ts +6 -1
  68. package/src/components/ui3n-chip/ui3n-chip.vue +64 -61
  69. package/src/components/ui3n-dialog/ui3n-dialog.vue +3 -3
  70. package/src/components/ui3n-drop-files/types.ts +8 -0
  71. package/src/components/ui3n-drop-files/ui3n-drop-files.vue +73 -29
  72. package/src/components/ui3n-editable/types.ts +12 -0
  73. package/src/components/ui3n-editable/ui3n-editable.vue +56 -24
  74. package/src/components/ui3n-icon/types.ts +5 -1
  75. package/src/components/ui3n-icon/ui3n-icon.vue +45 -50
  76. package/src/components/ui3n-input/types.ts +4 -0
  77. package/src/components/ui3n-input/ui3n-input.vue +14 -1
  78. package/src/components/ui3n-input-file/types.ts +12 -0
  79. package/src/components/ui3n-input-file/ui3n-input-file.vue +33 -16
  80. package/src/components/ui3n-list/types.ts +4 -0
  81. package/src/components/ui3n-list/ui3n-list.vue +3 -1
  82. package/src/components/ui3n-menu/types.ts +9 -0
  83. package/src/components/ui3n-menu/ui3n-menu.vue +169 -39
  84. package/src/components/ui3n-notification/ui3n-notification.vue +190 -175
  85. package/src/components/ui3n-progress/ui3n-progress-circular.vue +40 -22
  86. package/src/components/ui3n-progress/ui3n-progress-linear.vue +14 -7
  87. package/src/components/ui3n-radio-group/types.ts +20 -0
  88. package/src/components/ui3n-radio-group/ui3n-radio-group.vue +61 -48
  89. package/src/components/ui3n-radio-group/ui3n-radio.vue +60 -65
  90. package/src/components/ui3n-scrollbar-vertical/types.ts +2 -0
  91. package/src/components/ui3n-scrollbar-vertical/ui3n-scrollbar-vertical.vue +4 -4
  92. package/src/components/ui3n-selector/types.ts +12 -0
  93. package/src/components/ui3n-selector/ui3n-selector.vue +63 -40
  94. package/src/components/ui3n-slider/types.ts +12 -0
  95. package/src/components/ui3n-slider/ui3n-slider.vue +88 -39
  96. package/src/components/ui3n-step-line-bar/types.ts +4 -0
  97. package/src/components/ui3n-step-line-bar/ui3n-step-line-bar.vue +8 -10
  98. package/src/components/ui3n-switch/types.ts +12 -0
  99. package/src/components/ui3n-switch/ui3n-switch.vue +57 -25
  100. package/src/components/ui3n-table/composables/useTable.ts +17 -0
  101. package/src/components/ui3n-table/types.ts +4 -0
  102. package/src/components/ui3n-table/ui3n-table-sort-icon.vue +9 -8
  103. package/src/components/ui3n-table/ui3n-table.vue +48 -17
  104. package/src/components/ui3n-tabs/types.ts +4 -0
  105. package/src/components/ui3n-tabs/ui3n-tabs.vue +146 -108
  106. package/src/components/ui3n-text/types.ts +12 -0
  107. package/src/components/ui3n-text/ui3n-text.vue +56 -15
  108. package/src/components/ui3n-tooltip/types.ts +4 -0
  109. package/src/components/ui3n-tooltip/ui3n-tooltip.vue +61 -43
  110. package/src/components/ui3n-virtual-scroll/ui3n-virtual-scroll.vue +201 -17
  111. package/dist/chunks/ui3n-notification-DlDM-u-R.js +0 -276
@@ -1,67 +1,62 @@
1
1
  <script lang="ts" setup>
2
- import { computed } from 'vue';
3
- import type { Ui3nIconEmits, Ui3nIconProps } from './types';
2
+ import { computed } from 'vue';
3
+ import type { Ui3nIconEmits, Ui3nIconProps } from './types';
4
4
 
5
- const props = withDefaults(
6
- defineProps<Ui3nIconProps>(),
7
- {
5
+ const props = withDefaults(defineProps<Ui3nIconProps>(), {
8
6
  title: '',
9
7
  rotate: 0,
10
8
  color: 'var(--color-icon-control-primary-default)',
11
- },
12
- );
13
- const emits = defineEmits<Ui3nIconEmits>();
14
-
15
- const widthVal = computed(() => props.size || props.width || 16);
16
- const heightVal = computed(() => props.size || props.height || 16);
17
- const widthCss = computed(() => `${widthVal.value}px`);
18
- const heightCss = computed(() => `${heightVal.value}px`);
19
-
20
- const iconStyle = computed(() => {
21
- const value: Record<string, string> = {};
22
- const transformData = [];
23
- props.horizontalFlip && transformData.push('rotateY(180deg)');
24
- props.verticalFlip && transformData.push('rotateX(180deg)');
25
- props.rotate && transformData.push(`rotate(${props.rotate}deg)`);
26
- if (transformData.length) {
27
- value.transform = transformData.join(' ');
28
- }
29
-
30
- return value;
31
- });
32
-
33
- function onClick(ev: Event) {
34
- emits('click', ev);
35
- }
9
+ });
10
+ const emits = defineEmits<Ui3nIconEmits>();
11
+
12
+ const widthVal = computed(() => props.size || props.width || 16);
13
+ const heightVal = computed(() => props.size || props.height || 16);
14
+
15
+ const iconStyle = computed(() => {
16
+ const value: Record<string, string> = {
17
+ '--ui3n-icon-width': `${widthVal.value}px`,
18
+ '--ui3n-icon-height': `${heightVal.value}px`,
19
+ '--ui3n-icon-color': props.color,
20
+ };
21
+ const transformData = [];
22
+ props.horizontalFlip && transformData.push('rotateY(180deg)');
23
+ props.verticalFlip && transformData.push('rotateX(180deg)');
24
+ props.rotate && transformData.push(`rotate(${props.rotate}deg)`);
25
+ if (transformData.length) {
26
+ value.transform = transformData.join(' ');
27
+ }
28
+
29
+ return value;
30
+ });
36
31
  </script>
37
32
 
38
33
  <template>
39
34
  <div
35
+ :id="id"
40
36
  :class="$style.ui3nIcon"
41
- :data-icon="icon"
42
37
  :title="title"
43
38
  :style="iconStyle"
44
- @click="onClick"
39
+ v-bind="{ 'data-icon': icon }"
40
+ @click="emits('click', $event)"
45
41
  />
46
42
  </template>
47
43
 
48
44
  <style lang="scss" module>
49
- .ui3nIcon {
50
- --ui3n-icon-color: v-bind(color);
51
-
52
- display: block;
53
- min-width: v-bind(widthCss);
54
- width: v-bind(widthCss);
55
- max-width: v-bind(widthCss);
56
- min-height: v-bind(heightCss);
57
- height: v-bind(heightCss);
58
- max-height: v-bind(heightCss);
59
- flex-grow: 0;
60
- flex-shrink: 0;
61
- background-color: currentcolor;
62
- color: var(--ui3n-icon-color);
63
- mask-image: var(--svg);
64
- mask-repeat: no-repeat;
65
- mask-size: 100% 100%;
66
- }
45
+ .ui3nIcon {
46
+ display: block;
47
+ min-width: var(--ui3n-icon-width);
48
+ width: var(--ui3n-icon-width);
49
+
50
+ min-height: var(--ui3n-icon-height);
51
+ height: var(--ui3n-icon-height);
52
+ max-height: var(--ui3n-icon-height);
53
+
54
+ flex-grow: 0;
55
+ flex-shrink: 0;
56
+ background-color: currentcolor;
57
+ color: var(--ui3n-icon-color);
58
+ mask-image: var(--svg);
59
+ mask-repeat: no-repeat;
60
+ mask-size: 100% 100%;
61
+ }
67
62
  </style>
@@ -1,6 +1,10 @@
1
1
  import type { Ref, VNode } from 'vue';
2
2
 
3
3
  export interface Ui3nInputProps {
4
+ /**
5
+ * Root element id
6
+ */
7
+ id?: string;
4
8
  /**
5
9
  * Input value
6
10
  */
@@ -6,6 +6,9 @@
6
6
 
7
7
  const props = withDefaults(defineProps<Ui3nInputProps>(), {
8
8
  modelValue: '',
9
+ name: undefined,
10
+ maxlength: undefined,
11
+ minlength: undefined,
9
12
  type: 'text',
10
13
  size: 'regular',
11
14
  label: '',
@@ -177,10 +180,20 @@
177
180
  }
178
181
  },
179
182
  );
183
+
184
+ watch(
185
+ () => props.name,
186
+ newName => {
187
+ if (!newName) {
188
+ clearValue();
189
+ }
190
+ },
191
+ );
180
192
  </script>
181
193
 
182
194
  <template>
183
195
  <div
196
+ :id="id"
184
197
  :class="[
185
198
  $style.ui3nInput,
186
199
  size === 'large' && $style.large,
@@ -359,7 +372,7 @@
359
372
  padding: 0 var(--ui3n-input-padding-x);
360
373
  background-color: transparent;
361
374
  font-size: var(--ui3n-input-font-size);
362
- line-height: var(--spacing-m);
375
+ line-height: 1.23;
363
376
  font-weight: 400;
364
377
  color: var(--color-text-control-primary-default);
365
378
  transition: color 0.2s ease-in-out;
@@ -1,6 +1,14 @@
1
1
  import { VNode } from 'vue';
2
2
 
3
3
  export interface Ui3nInputFileProps {
4
+ /**
5
+ * Root element id
6
+ */
7
+ id?: string;
8
+ /**
9
+ * Input name attribute (for use in native forms)
10
+ */
11
+ name?: string;
4
12
  /**
5
13
  * Selected files
6
14
  */
@@ -50,3 +58,7 @@ export interface Ui3nInputFileSlots {
50
58
  */
51
59
  default: () => VNode;
52
60
  }
61
+
62
+ export interface Ui3nInputFileExpose {
63
+ clear: () => void;
64
+ }
@@ -1,13 +1,14 @@
1
1
  <script lang="ts" setup>
2
- import { ref } from 'vue';
2
+ import { ref, watch } from 'vue';
3
3
  import size from 'lodash/size';
4
4
  import last from 'lodash/last';
5
5
  import trim from 'lodash/trim';
6
6
  import { getRandomId, formatFileSize } from '@/utils';
7
- import type { Ui3nInputFileProps, Ui3nInputFileEmits, Ui3nInputFileSlots } from './types';
8
7
  import type { Nullable } from '@/types';
8
+ import type { Ui3nInputFileProps, Ui3nInputFileEmits, Ui3nInputFileSlots, Ui3nInputFileExpose } from './types';
9
9
 
10
10
  const props = withDefaults(defineProps<Ui3nInputFileProps>(), {
11
+ name: undefined,
11
12
  modelValue: () => [],
12
13
  buttonText: 'Select file',
13
14
  allowedFileTypes: '*',
@@ -15,7 +16,7 @@
15
16
  const emits = defineEmits<Ui3nInputFileEmits>();
16
17
  defineSlots<Ui3nInputFileSlots>();
17
18
 
18
- const id = getRandomId(4);
19
+ const id = props.id || getRandomId(4);
19
20
  const fileInput = ref<Nullable<HTMLInputElement>>(null);
20
21
 
21
22
  function selectFiles(ev: MouseEvent) {
@@ -80,15 +81,8 @@
80
81
  return true;
81
82
  }
82
83
 
83
- const total = files.reduce((acc, file) => {
84
- const { size } = file;
85
- if (size) {
86
- acc += size;
87
- }
88
- return acc;
89
- }, 0);
90
-
91
- const isValid = total <= Number(props.maxNumberOfFiles);
84
+ const totalCount = size(props.modelValue) + size(files);
85
+ const isValid = totalCount <= Number(props.maxNumberOfFiles);
92
86
  if (!isValid) {
93
87
  emits('error', `You cannot select more than ${props.maxNumberOfFiles} files.`);
94
88
  }
@@ -116,6 +110,26 @@
116
110
  fileInput.value && fileInput.value.files && processFiles([...fileInput.value.files]);
117
111
  fileInput.value && (fileInput.value!.value = '');
118
112
  }
113
+
114
+ function clear() {
115
+ if (fileInput.value) {
116
+ fileInput.value.value = '';
117
+ }
118
+ emits('update:modelValue', []);
119
+ }
120
+
121
+ defineExpose<Ui3nInputFileExpose>({
122
+ clear,
123
+ });
124
+
125
+ watch(
126
+ () => props.name,
127
+ newName => {
128
+ if (!newName) {
129
+ clear();
130
+ }
131
+ },
132
+ );
119
133
  </script>
120
134
 
121
135
  <template>
@@ -132,7 +146,7 @@
132
146
  :id="id"
133
147
  ref="fileInput"
134
148
  type="file"
135
- name="file"
149
+ :name="name || 'file'"
136
150
  hidden
137
151
  :multiple="multiple"
138
152
  :accept="allowedFileTypes"
@@ -144,16 +158,19 @@
144
158
 
145
159
  <style lang="scss" module>
146
160
  .ui3nInputFile {
161
+ --ui3n-input-file-font-size: 12px;
162
+ --ui3n-input-file-padding-inline: 4px;
163
+
147
164
  position: relative;
148
165
  width: max-content;
149
166
  }
150
167
 
151
168
  .label {
152
- font-size: var(--font-12);
169
+ font-size: var(--ui3n-input-file-font-size);
153
170
  font-weight: 500;
154
- line-height: var(--font-16);
171
+ line-height: 1.33;
155
172
  color: var(--color-text-button-secondary-default);
156
- padding: 0 var(--space-xs);
173
+ padding: 0 var(--ui3n-input-file-padding-inline);
157
174
  cursor: pointer;
158
175
 
159
176
  &:hover {
@@ -4,6 +4,10 @@ import type { VNode } from 'vue';
4
4
  * List component properties
5
5
  */
6
6
  export interface Ui3nListProps<T> {
7
+ /**
8
+ * Root element id
9
+ */
10
+ id?: string;
7
11
  /**
8
12
  * List title
9
13
  */
@@ -15,6 +15,7 @@
15
15
  <template>
16
16
  <div
17
17
  ref="listElement"
18
+ :id="id"
18
19
  :class="$style.ui3nList"
19
20
  >
20
21
  <div :class="[$style.ui3nListTitle, sticky && $style.ui3nListTitleSticky]">
@@ -55,6 +56,7 @@
55
56
  .ui3nList {
56
57
  --ui3n-list-item-height: 28px;
57
58
  --ui3n-list-bg-color: transparent;
59
+ --ui3n-list-padding-block: 4px;
58
60
 
59
61
  position: relative;
60
62
  width: 100%;
@@ -81,7 +83,7 @@
81
83
  position: relative;
82
84
  width: 100%;
83
85
  min-height: var(--ui3n-list-item-height);
84
- padding: 4px 0;
86
+ padding: var(--ui3n-list-padding-block) 0;
85
87
  display: flex;
86
88
  justify-content: flex-start;
87
89
  align-items: center;
@@ -4,6 +4,10 @@ import type { Ref, VNode } from 'vue';
4
4
  * Menu component properties
5
5
  */
6
6
  export interface Ui3nMenuProps {
7
+ /**
8
+ * Root element id
9
+ */
10
+ id?: string;
7
11
  /**
8
12
  * Menu open state
9
13
  */
@@ -27,6 +31,11 @@ export interface Ui3nMenuProps {
27
31
  * @default true
28
32
  */
29
33
  allowFlip?: boolean;
34
+ /**
35
+ * Whether to lock scroll when menu is open
36
+ * @default false
37
+ */
38
+ lockScroll?: boolean;
30
39
  /**
31
40
  * Offset X
32
41
  * @default 0
@@ -1,11 +1,12 @@
1
1
  <script lang="ts" setup>
2
- import { computed, ref, watch, useTemplateRef } from 'vue';
2
+ import { computed, nextTick, onUnmounted, ref, watch, useTemplateRef } from 'vue';
3
3
  import { autoUpdate, flip, useFloating, offset, shift } from '@floating-ui/vue';
4
4
  import { default as vClickOutside } from '../../directives/ui3n-click-outside';
5
- import type { Ui3nMenuEmits, Ui3nMenuProps, Ui3nMenuSlots, Ui3nMenuExpose } from './types';
6
5
  import { Nullable } from '@/types';
6
+ import type { Ui3nMenuEmits, Ui3nMenuProps, Ui3nMenuSlots, Ui3nMenuExpose } from './types';
7
7
 
8
8
  const props = withDefaults(defineProps<Ui3nMenuProps>(), {
9
+ lockScroll: false,
9
10
  positionStrategy: 'absolute',
10
11
  offsetX: 0,
11
12
  offsetY: 0,
@@ -23,37 +24,32 @@
23
24
  const menuContentElement = useTemplateRef<HTMLDivElement>('menu-content-element');
24
25
  const isShow = ref(false);
25
26
 
27
+ let targetScrollElement: HTMLElement | null = null;
28
+
26
29
  const outerTriggerElement = ref<Nullable<HTMLElement>>(props.triggerElement || null);
27
30
 
28
31
  const usedTriggerElement = computed(() => outerTriggerElement.value || menuTriggerElement.value);
29
32
 
30
- const contentBorderRadiusCss = computed(() => {
31
- if (typeof props.contentBorderRadius === 'number') {
32
- return `${props.contentBorderRadius}px`;
33
+ const middleware = computed(() => {
34
+ const list = [
35
+ offset({
36
+ mainAxis: props.offsetY,
37
+ crossAxis: props.offsetX + 2,
38
+ }),
39
+ ];
40
+
41
+ if (props.allowFlip) {
42
+ list.push(
43
+ flip({
44
+ fallbackAxisSideDirection: 'end',
45
+ fallbackPlacements: ['top-end', 'bottom-end'],
46
+ }),
47
+ );
33
48
  }
34
49
 
35
- const [tl, tr, br, bl] = props.contentBorderRadius;
36
- return `${tl}px ${tr}px ${br}px ${bl}px`;
50
+ list.push(shift());
51
+ return list;
37
52
  });
38
- const zIndexCss = computed(() => props.zIndex);
39
-
40
- const middleware = [
41
- offset({
42
- mainAxis: props.offsetY,
43
- crossAxis: props.offsetX + 2,
44
- }),
45
- ];
46
-
47
- if (props.allowFlip) {
48
- middleware.push(
49
- flip({
50
- fallbackAxisSideDirection: 'end',
51
- fallbackPlacements: ['top-end', 'bottom-end'],
52
- }),
53
- );
54
- }
55
-
56
- middleware.push(shift());
57
53
 
58
54
  const { floatingStyles, isPositioned, update } = useFloating(usedTriggerElement, menuContentElement, {
59
55
  placement: 'bottom-start',
@@ -62,6 +58,126 @@
62
58
  whileElementsMounted: props.positionAutoupdate || props.positionStrategy === 'fixed' ? autoUpdate : undefined,
63
59
  });
64
60
 
61
+ const contentStylesComputed = computed(() => {
62
+ let borderRadiusStr = '';
63
+ if (typeof props.contentBorderRadius === 'number') {
64
+ borderRadiusStr = `${props.contentBorderRadius}px`;
65
+ } else if (Array.isArray(props.contentBorderRadius)) {
66
+ const [tl, tr, br, bl] = props.contentBorderRadius;
67
+ borderRadiusStr = `${tl}px ${tr}px ${br}px ${bl}px`;
68
+ }
69
+
70
+ return {
71
+ ...floatingStyles.value,
72
+ '--ui3n-menu-zIndex': String(props.zIndex),
73
+ ...(borderRadiusStr && { '--ui3n-menu-border-radius': borderRadiusStr }),
74
+ ...(props.contentStyles || {}),
75
+ };
76
+ });
77
+
78
+ function preventScrollEvent(e: Event) {
79
+ e.preventDefault();
80
+ }
81
+
82
+ function getVerticalScrollParent(element: HTMLElement | null): HTMLElement {
83
+ if (!element) {
84
+ return document.body;
85
+ }
86
+
87
+ let parent: HTMLElement | null = element.parentElement;
88
+
89
+ while (parent && parent !== document.body && parent !== document.documentElement) {
90
+ const style = window.getComputedStyle(parent);
91
+
92
+ if (/(auto|scroll)/.test(style.overflowY || style.overflow)) {
93
+ if (parent.scrollHeight > parent.clientHeight) {
94
+ return parent;
95
+ }
96
+ }
97
+ parent = parent.parentElement;
98
+ }
99
+
100
+ return document.body;
101
+ }
102
+
103
+ function lockScroll() {
104
+ if (!props.lockScroll) {
105
+ return;
106
+ }
107
+
108
+ const trigger = usedTriggerElement.value;
109
+ targetScrollElement = getVerticalScrollParent(trigger);
110
+
111
+ if (targetScrollElement) {
112
+ const el = targetScrollElement;
113
+
114
+ const currentLocks = Number(el.dataset.scrollLocksCount || '0');
115
+
116
+ // If this is the FIRST menu opened for this container
117
+ if (currentLocks === 0) {
118
+ el.dataset.scrollLocksCount = '1';
119
+
120
+ // We remember the original overscroll-behavior-y in data-original-overscroll
121
+ el.dataset.originalOverscroll = el.style.overscrollBehaviorY || '';
122
+
123
+ // Isolating scrolling
124
+ el.style.overscrollBehaviorY = 'contain';
125
+
126
+ if (targetScrollElement === document.body) {
127
+ document.addEventListener('wheel', preventScrollEvent, { passive: false });
128
+ document.addEventListener('touchmove', preventScrollEvent, { passive: false });
129
+ } else {
130
+ targetScrollElement.addEventListener('wheel', preventScrollEvent, { passive: false });
131
+ targetScrollElement.addEventListener('touchmove', preventScrollEvent, { passive: false });
132
+ }
133
+ } else {
134
+ // If the container is already blocked by another menu, simply increment the counter
135
+ el.dataset.scrollLocksCount = String(currentLocks + 1);
136
+ }
137
+ }
138
+ }
139
+
140
+ function unlockScroll() {
141
+ if (!targetScrollElement) {
142
+ return;
143
+ }
144
+
145
+ const el = targetScrollElement;
146
+ const currentLocks = Number(el.dataset.scrollLocksCount || '0');
147
+
148
+ if (currentLocks > 0) {
149
+ const newLocks = currentLocks - 1;
150
+
151
+ // Release the lock only when the LAST menu holding this container has closed
152
+ if (newLocks === 0) {
153
+ const originalOverscroll = el.dataset.originalOverscroll;
154
+
155
+ if (originalOverscroll) {
156
+ el.style.overscrollBehaviorY = originalOverscroll;
157
+ } else {
158
+ el.style.removeProperty('overscroll-behavior-y');
159
+ }
160
+
161
+ if (targetScrollElement === document.body) {
162
+ document.removeEventListener('wheel', preventScrollEvent);
163
+ document.removeEventListener('touchmove', preventScrollEvent);
164
+ } else {
165
+ targetScrollElement.removeEventListener('wheel', preventScrollEvent);
166
+ targetScrollElement.removeEventListener('touchmove', preventScrollEvent);
167
+ }
168
+
169
+ // We completely remove data attributes from the DOM tree to avoid leaving garbage.
170
+ delete el.dataset.scrollLocksCount;
171
+ delete el.dataset.originalOverscroll;
172
+ } else {
173
+ // If there are still open menus, simply keep the reduced counter
174
+ el.dataset.scrollLocksCount = String(newLocks);
175
+ }
176
+ }
177
+
178
+ targetScrollElement = null;
179
+ }
180
+
65
181
  function toggleMenu() {
66
182
  if (props.disabled) {
67
183
  return;
@@ -78,7 +194,11 @@
78
194
  emits('update:modelValue', isShow.value);
79
195
  }
80
196
 
81
- function onClickOutside() {
197
+ function onClickOutside(event: MouseEvent) {
198
+ if (usedTriggerElement.value?.contains(event.target as Node)) {
199
+ return;
200
+ }
201
+
82
202
  emits('click-outside');
83
203
  if (props.closeOnClickOutside) {
84
204
  isShow.value = false;
@@ -87,13 +207,13 @@
87
207
  }
88
208
  }
89
209
 
90
- watch(isPositioned, val => {
91
- val ? emits('opened') : emits('closed');
92
- });
93
-
94
210
  watch(
95
211
  () => props.modelValue,
96
- val => {
212
+ async val => {
213
+ if (!val) {
214
+ unlockScroll();
215
+ }
216
+
97
217
  if (isShow.value !== val) {
98
218
  isShow.value = val;
99
219
  }
@@ -101,6 +221,15 @@
101
221
  { immediate: true },
102
222
  );
103
223
 
224
+ watch(isPositioned, val => {
225
+ if (val) {
226
+ lockScroll();
227
+ emits('opened');
228
+ } else {
229
+ emits('closed');
230
+ }
231
+ });
232
+
104
233
  watch(
105
234
  () => props.triggerElement,
106
235
  val => {
@@ -117,6 +246,10 @@
117
246
  }
118
247
  });
119
248
 
249
+ onUnmounted(() => {
250
+ unlockScroll();
251
+ });
252
+
120
253
  defineExpose<Ui3nMenuExpose>({
121
254
  isPositioned,
122
255
  update,
@@ -126,6 +259,7 @@
126
259
  <template>
127
260
  <div
128
261
  ref="menu-element"
262
+ :id="id"
129
263
  :class="$style.ui3nMenu"
130
264
  >
131
265
  <div
@@ -140,10 +274,7 @@
140
274
  v-if="isShow"
141
275
  ref="menu-content-element"
142
276
  v-click-outside="onClickOutside"
143
- :style="{
144
- ...floatingStyles,
145
- ...(contentStyles && { ...contentStyles }),
146
- }"
277
+ :style="contentStylesComputed"
147
278
  :class="$style.ui3nMenuContent"
148
279
  v-on="closeOnClick ? { click: onContentClick } : {}"
149
280
  >
@@ -157,7 +288,6 @@
157
288
 
158
289
  .ui3nMenu {
159
290
  --ui3n-menu-content-bg: var(--color-bg-control-secondary-default);
160
- --ui3n-menu-content-border-raduis: v-bind(contentBorderRadiusCss);
161
291
 
162
292
  position: relative;
163
293
  max-width: max-content;
@@ -176,9 +306,9 @@
176
306
 
177
307
  .ui3nMenuContent {
178
308
  position: absolute;
179
- border-radius: var(--ui3n-menu-content-border-raduis);
309
+ border-radius: var(--ui3n-menu-content-border-radius);
180
310
  background-color: var(--ui3n-menu-content-bg);
181
- z-index: v-bind(zIndexCss);
311
+ z-index: var(--ui3n-menu-zIndex, 1000);
182
312
  overflow: hidden;
183
313
  @include mixins.dropShadow();
184
314
  }