@v1nt1248/3nclient-lib 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@v1nt1248/3nclient-lib",
3
3
  "license": "AGPL-3.0-or-later",
4
4
  "author": "v1nt1248",
5
- "version": "0.1.2",
5
+ "version": "0.1.4",
6
6
  "description": "Library for 3NWeb clients",
7
7
  "type": "module",
8
8
  "files": [
@@ -13,7 +13,8 @@
13
13
  "types": "./dist/index.d.ts",
14
14
  "exports": {
15
15
  ".": {
16
- "import": "./dist/ui-3n-lib.js"
16
+ "import": "./dist/ui-3n-lib.js",
17
+ "types": "./dist/index.d.ts"
17
18
  },
18
19
  "./style.css": "./dist/style.css"
19
20
  },
@@ -34,6 +35,7 @@
34
35
  "docs:preview": "vitepress preview docs"
35
36
  },
36
37
  "dependencies": {
38
+ "@floating-ui/vue": "^1.1.4",
37
39
  "@iconify-icons/ic": "1.2.13",
38
40
  "@iconify/vue": "4.1.2",
39
41
  "dayjs": "1.11.12",
@@ -40,8 +40,8 @@ import type {
40
40
  Ui3nDialogProps,
41
41
  Ui3nDialogEvent,
42
42
  } from './ui3n-dialog.vue';
43
- import Ui3nMenu from './ui3n-menu.vue';
44
- import type { Ui3nMenuProps, Ui3nMenuEmits, Ui3nMenuSlots } from './ui3n-menu.vue';
43
+ import Ui3nMenu from './ui3n-menu/ui3n-menu.vue';
44
+ import type { Ui3nMenuProps, Ui3nMenuEmits, Ui3nMenuSlots } from './ui3n-menu/types';
45
45
  import Ui3nList from './ui3n-list.vue';
46
46
  import type { Ui3nListProps, Ui3nListEmits, Ui3nListSlots } from './ui3n-list.vue';
47
47
  import Ui3nVirtualScroll from './ui3n-virtual-scroll.vue';
@@ -0,0 +1,23 @@
1
+ import { VNode } from 'vue';
2
+
3
+ export interface Ui3nMenuProps {
4
+ positionStrategy?: 'absolute' | 'fixed';
5
+ offsetX?: number;
6
+ offsetY?: number;
7
+ closeOnClick?: boolean;
8
+ closeOnClickOutside?: boolean;
9
+ disabled?: boolean;
10
+ }
11
+
12
+ export interface Ui3nMenuEmits {
13
+ (ev: 'open'): void;
14
+ (ev: 'opened'): void;
15
+ (ev: 'close'): void;
16
+ (ev: 'closed'): void;
17
+ (ev: 'click-outside'): void;
18
+ }
19
+
20
+ export interface Ui3nMenuSlots {
21
+ default: () => VNode;
22
+ menu?: () => VNode;
23
+ }
@@ -0,0 +1,117 @@
1
+ <script lang="ts" setup>
2
+ import { ref, watch } from 'vue';
3
+ import { autoUpdate, flip, useFloating, offset, shift } from '@floating-ui/vue';
4
+ import { default as vClickOutside } from '../../directives/ui3n-click-outside';
5
+ import type { Ui3nMenuEmits, Ui3nMenuProps, Ui3nMenuSlots } from './types';
6
+
7
+ const props = withDefaults(defineProps<Ui3nMenuProps>(), {
8
+ positionStrategy: 'absolute',
9
+ offsetX: 0,
10
+ offsetY: 0,
11
+ closeOnClick: true,
12
+ closeOnClickOutside: true,
13
+ disabled: false,
14
+ });
15
+ const emits = defineEmits<Ui3nMenuEmits>();
16
+ defineSlots<Ui3nMenuSlots>();
17
+
18
+ const menuElement = ref<HTMLDivElement | null>(null);
19
+ const menuTriggerElement = ref<HTMLDivElement | null>(null);
20
+ const menuContentElement = ref<HTMLDivElement | null>(null);
21
+ const isShow = ref(false);
22
+
23
+ const { floatingStyles, isPositioned } = useFloating(
24
+ menuTriggerElement,
25
+ menuContentElement,
26
+ {
27
+ placement: 'bottom-start',
28
+ strategy: props.positionStrategy,
29
+ middleware: [
30
+ offset({
31
+ mainAxis: props.offsetY,
32
+ crossAxis: props.offsetX + 2,
33
+ }),
34
+ flip({
35
+ fallbackAxisSideDirection: 'end',
36
+ flipAlignment: false,
37
+ fallbackPlacements: ['bottom-end'],
38
+ }),
39
+ shift(),
40
+ ],
41
+ whileElementsMounted: props.positionStrategy === 'fixed' ? autoUpdate : undefined,
42
+ },
43
+ );
44
+
45
+ function toggleMenu() {
46
+ isShow.value = !isShow.value;
47
+ isShow.value ? emits('open') : emits('close');
48
+ }
49
+
50
+ function onContentClick() {
51
+ isShow.value = false;
52
+ emits('close');
53
+ }
54
+
55
+ function onClickOutside() {
56
+ emits('click-outside');
57
+ if (props.closeOnClickOutside) {
58
+ isShow.value = false;
59
+ emits('close');
60
+ }
61
+ }
62
+
63
+ watch(
64
+ isPositioned,
65
+ (val) => {
66
+ val ? emits('opened') : emits('closed');
67
+ },
68
+ );
69
+ </script>
70
+
71
+ <template>
72
+ <div ref="menuElement" :class="$style.menu">
73
+ <div
74
+ ref="menuTriggerElement"
75
+ :class="$style.trigger"
76
+ @click.stop="toggleMenu"
77
+ >
78
+ <slot />
79
+ </div>
80
+
81
+ <div
82
+ v-if="isShow"
83
+ ref="menuContentElement"
84
+ v-click-outside="onClickOutside"
85
+ :style="floatingStyles"
86
+ :class="$style.content"
87
+ v-on="props.closeOnClick ? { click: onContentClick } : {}"
88
+ >
89
+ <slot name="menu" />
90
+ </div>
91
+ </div>
92
+ </template>
93
+
94
+ <style lang="scss" module>
95
+ @import "../../assets/styles/mixins";
96
+
97
+ .menu {
98
+ --ui3n-menu-content-bg: var(--color-bg-control-secondary-default);
99
+
100
+ position: relative;
101
+ max-width: max-content;
102
+ overflow: visible;
103
+ }
104
+
105
+ .trigger {
106
+ position: relative;
107
+ max-width: max-content;
108
+ }
109
+
110
+ .content {
111
+ position: absolute;
112
+ border-radius: 4px;
113
+ background-color: var(--ui3n-menu-content-bg);
114
+ z-index: 1000;
115
+ @include elevation(3);
116
+ }
117
+ </style>
@@ -13,8 +13,8 @@ export type Ui3nTableConfig<T extends Ui3nTableBodyBaseItem, K extends keyof T =
13
13
  tableName?: string;
14
14
  sortOrder?: Ui3nTableSort<T>;
15
15
  selectable?: 'single' | 'multiple';
16
- // draggebleRows?: boolean; // ToDo this will be done in the next version
17
- // draggebleColumns?: boolean; // ToDo this will be done in the next version
16
+ // draggableRows?: boolean; // ToDo this will be done in the next version
17
+ // draggableColumns?: boolean; // ToDo this will be done in the next version
18
18
  columnStyle?: { [P in Omit<K, 'id'> as string | number]: Record<string, string> };
19
19
  fieldAsRowKey?: keyof T;
20
20
  };
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts" generic="T extends Ui3nTableBodyBaseItem">
2
2
  import { useTable } from './composables/useTable';
3
- import {
3
+ import type {
4
4
  Ui3nTableBodyBaseItem,
5
5
  Ui3nTableEmits,
6
6
  Ui3nTableProps,
@@ -0,0 +1,41 @@
1
+ import { VNode } from 'vue';
2
+
3
+ export type Ui3nTooltipPlacement =
4
+ | 'top'
5
+ | 'top-start'
6
+ | 'top-end'
7
+ | 'bottom'
8
+ | 'bottom-start'
9
+ | 'bottom-end'
10
+ | 'right'
11
+ | 'right-start'
12
+ | 'right-end'
13
+ | 'left'
14
+ | 'left-start'
15
+ | 'left-end';
16
+
17
+ export interface Ui3nTooltipProps {
18
+ modelValue?: boolean;
19
+ content?: string;
20
+ color?: string;
21
+ textColor?: string;
22
+ placement?: Ui3nTooltipPlacement;
23
+ positionStrategy?: 'absolute' | 'fixed';
24
+ offsetX?: string | number;
25
+ offsetY?: string | number;
26
+ trigger?: 'click' | 'hover' | 'manual';
27
+ disabled?: boolean;
28
+ }
29
+
30
+ export interface Ui3nTooltipEmits {
31
+ (ev: 'open'): void;
32
+ (ev: 'opened'): void;
33
+ (ev: 'close'): void;
34
+ (ev: 'closed'): void;
35
+ (ev: 'update:modelValue', value: boolean): void;
36
+ }
37
+
38
+ export interface Ui3nTooltipSlots {
39
+ default: () => VNode;
40
+ content?: () => VNode;
41
+ }
@@ -0,0 +1,199 @@
1
+ <script lang="ts" setup>
2
+ import { computed, ref, watch } from 'vue';
3
+ import { useFloating, offset, autoUpdate, arrow } from '@floating-ui/vue';
4
+ import type { Ui3nTooltipEmits, Ui3nTooltipProps, Ui3nTooltipSlots } from './types';
5
+
6
+ const baseOffset = 5;
7
+
8
+ const props = withDefaults(
9
+ defineProps<Ui3nTooltipProps>(),
10
+ {
11
+ color: 'var(--color-bg-control-secondary-default)',
12
+ textColor: 'var(--color-text-control-primary-default)',
13
+ placement: 'top',
14
+ positionStrategy: 'absolute',
15
+ offsetX: 0,
16
+ offsetY: 0,
17
+ trigger: 'hover',
18
+ },
19
+ );
20
+
21
+ const emits = defineEmits<Ui3nTooltipEmits>();
22
+ defineSlots<Ui3nTooltipSlots>();
23
+
24
+ const referenceElEventHandler = {
25
+ ...(props.trigger === 'hover' && !props.disabled && {
26
+ mouseenter: () => handleMouseEvents(true),
27
+ mouseleave: () => handleMouseEvents(false),
28
+ }),
29
+ ...(props.trigger === 'click' && !props.disabled && {
30
+ click: () => handleMouseEvents(!showTooltip.value),
31
+ }),
32
+ };
33
+
34
+ const showTooltip = ref(false);
35
+ const referenceEl = ref(null);
36
+ const floatingEl = ref(null);
37
+ const floatingArrowEl = ref(null);
38
+
39
+ const baseOffsetCssValue = computed(() => `${-baseOffset}px`);
40
+ const arrowSizeCssValue = computed(() => `${baseOffset}px`);
41
+ const mainPlacement = computed(() => {
42
+ const [part1, _] = props.placement.split('-');
43
+ return part1;
44
+ });
45
+ const offsetOptions = computed(() => {
46
+ const options = {
47
+ mainAxis: 0,
48
+ crossAxis: 0,
49
+ };
50
+ switch (mainPlacement.value) {
51
+ case 'top':
52
+ options.mainAxis = -1 * Number(props.offsetY) + baseOffset;
53
+ options.crossAxis = Number(props.offsetX);
54
+ break;
55
+ case 'bottom':
56
+ options.mainAxis = Number(props.offsetY) + baseOffset;
57
+ options.crossAxis = Number(props.offsetX);
58
+ break;
59
+ case 'left':
60
+ options.mainAxis = -1 * Number(props.offsetX) + baseOffset;
61
+ options.crossAxis = Number(props.offsetY);
62
+ break;
63
+ case 'right':
64
+ options.mainAxis = Number(props.offsetX) + baseOffset;
65
+ options.crossAxis = Number(props.offsetY);
66
+ break;
67
+ }
68
+ return options;
69
+ });
70
+
71
+ const { floatingStyles, isPositioned, middlewareData } = useFloating(referenceEl, floatingEl, {
72
+ open: showTooltip,
73
+ placement: props.placement,
74
+ strategy: props.positionStrategy,
75
+ middleware: [
76
+ offset(offsetOptions.value),
77
+ arrow({ element: floatingArrowEl, padding: 8 }),
78
+ ],
79
+ whileElementsMounted: props.positionStrategy === 'fixed' ? autoUpdate : undefined,
80
+ });
81
+
82
+ function handleMouseEvents(val: boolean) {
83
+ showTooltip.value = val;
84
+ val ? emits('open') : emits('close');
85
+ emits('update:modelValue', val);
86
+ }
87
+
88
+ watch(
89
+ () => props.modelValue,
90
+ (val) => props.trigger === 'manual' && (showTooltip.value = val),
91
+ { immediate: true },
92
+ );
93
+
94
+ watch(
95
+ isPositioned,
96
+ (val) => {
97
+ val ? emits('opened') : emits('closed');
98
+ },
99
+ );
100
+ </script>
101
+
102
+ <template>
103
+ <div :class="$style.tooltip">
104
+ <div
105
+ ref="referenceEl"
106
+ :class="$style.reference"
107
+ v-on="referenceElEventHandler"
108
+ >
109
+ <slot name="default" />
110
+ </div>
111
+
112
+ <div ref="floatingEl" :class="$style.floating" :style="floatingStyles" v-if="showTooltip">
113
+ <slot name="content">
114
+ <div :class="$style.content">
115
+ {{ props.content }}
116
+ <div
117
+ ref="floatingArrowEl"
118
+ :class="[$style.arrow, $style[`arrow-${mainPlacement}`]]"
119
+ :style="{
120
+ left: middlewareData.arrow?.x != null ? `${middlewareData.arrow?.x}px` : '',
121
+ top: middlewareData.arrow?.y != null ? `${middlewareData.arrow?.y}px` : '',
122
+ }"
123
+ />
124
+ </div>
125
+ </slot>
126
+ </div>
127
+ </div>
128
+ </template>
129
+
130
+ <style lang="scss" module>
131
+ .tooltip {
132
+ --ui3n-tooltip-bg-color: v-bind(color);
133
+ --ui3n-tooltip-text-color: v-bind(textColor);
134
+
135
+ position: relative;
136
+ width: max-content;
137
+ }
138
+
139
+ .reference {
140
+ position: relative;
141
+ }
142
+
143
+ .floating {
144
+ width: max-content;
145
+ z-index: 5;
146
+ }
147
+
148
+ .arrow {
149
+ position: absolute;
150
+ width: 0;
151
+ height: 0;
152
+
153
+ &-top,
154
+ &-bottom {
155
+ border-style: solid;
156
+ border-width: 0 v-bind(arrowSizeCssValue) v-bind(arrowSizeCssValue) v-bind(arrowSizeCssValue);
157
+ border-color: transparent transparent var(--ui3n-tooltip-bg-color) transparent;
158
+ }
159
+
160
+ &-top {
161
+ bottom: v-bind(baseOffsetCssValue);
162
+ transform: rotate(180deg);
163
+ }
164
+
165
+ &-bottom {
166
+ top: v-bind(baseOffsetCssValue);
167
+ transform: rotate(0deg);
168
+ }
169
+
170
+ &-left,
171
+ &-right {
172
+ border-style: solid;
173
+ border-width: v-bind(arrowSizeCssValue) 0 v-bind(arrowSizeCssValue) v-bind(arrowSizeCssValue);
174
+ border-color: transparent transparent transparent var(--ui3n-tooltip-bg-color);
175
+ }
176
+
177
+ &-left {
178
+ right: v-bind(baseOffsetCssValue);
179
+ transform: rotate(0deg);
180
+ }
181
+
182
+ &-right {
183
+ left: v-bind(baseOffsetCssValue);
184
+ transform: rotate(180deg);
185
+ }
186
+ }
187
+
188
+ .content {
189
+ position: relative;
190
+ max-width: 400px;
191
+ padding: var(--spacing-xs) var(--spacing-s);
192
+ border-radius: var(--spacing-xs);
193
+ font-size: var(--font-11);
194
+ line-height: var(--font-12);
195
+ font-weight: 400;
196
+ background-color: var(--ui3n-tooltip-bg-color);
197
+ color: var(--ui3n-tooltip-text-color);
198
+ }
199
+ </style>
@@ -1,136 +0,0 @@
1
- <script lang="ts" setup>
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- import { computed, onMounted, ref } from 'vue';
4
- import { default as vClickOutside } from '../directives/ui3n-click-outside';
5
-
6
- export interface Ui3nMenuProps {
7
- offsetX?: number;
8
- offsetY?: number;
9
- closeOnClick?: boolean;
10
- closeOnClickOutside?: boolean;
11
- disabled?: boolean;
12
- }
13
-
14
- export interface Ui3nMenuEmits {
15
- (ev: 'open'): void;
16
- (ev: 'opened'): void;
17
- (ev: 'close'): void;
18
- (ev: 'closed'): void;
19
- (ev: 'click-outside'): void;
20
- }
21
-
22
- export interface Ui3nMenuSlots {
23
- default: () => any;
24
- menu?: () => any;
25
- }
26
-
27
- const props = withDefaults(defineProps<Ui3nMenuProps>(), {
28
- offsetX: 0,
29
- offsetY: 0,
30
- closeOnClick: true,
31
- closeOnClickOutside: true,
32
- disabled: false,
33
- });
34
- const emits = defineEmits<Ui3nMenuEmits>();
35
- defineSlots<Ui3nMenuSlots>();
36
-
37
- const menuElement = ref<HTMLDivElement | null>(null);
38
- const menuTriggerElement = ref<HTMLDivElement | null>(null);
39
- const isShow = ref(false);
40
- const defaultAnimationDuration = 400;
41
- const menuContentStyle = computed(() => {
42
- if (!props.offsetX && !props.offsetY) {
43
- return {};
44
- }
45
- return {
46
- ...(props.offsetX && { left: `${props.offsetX}px` }),
47
- ...(props.offsetY && { top: `${menuTriggerElement.value!.clientHeight + props.offsetY}px` }),
48
- };
49
- });
50
-
51
- onMounted(() => {
52
- menuElement.value!.style.setProperty('--ui3n-menu-animation-duration', `${defaultAnimationDuration}ms`);
53
- });
54
-
55
- function toggleMenu() {
56
- isShow.value = !isShow.value;
57
- }
58
-
59
- function onContentClick() {
60
- isShow.value = false;
61
- }
62
-
63
- function onClickOutside() {
64
- emits('click-outside');
65
- if (props.closeOnClickOutside) {
66
- isShow.value = false;
67
- }
68
- }
69
- </script>
70
-
71
- <template>
72
- <div ref="menuElement" :class="$style.menu">
73
- <div
74
- ref="menuTriggerElement"
75
- :class="$style.trigger"
76
- @click.stop="toggleMenu"
77
- >
78
- <slot />
79
- </div>
80
-
81
- <transition
82
- name="menu"
83
- :duration="defaultAnimationDuration"
84
- @before-enter="emits('open')"
85
- @after-enter="emits('opened')"
86
- @before-leave="emits('close')"
87
- @after-leave="emits('closed')"
88
- >
89
- <div
90
- v-if="isShow"
91
- v-click-outside="onClickOutside"
92
- :style="menuContentStyle"
93
- :class="$style.content"
94
- v-on="props.closeOnClick ? { click: onContentClick } : {}"
95
- >
96
- <slot name="menu" />
97
- </div>
98
- </transition>
99
- </div>
100
- </template>
101
-
102
- <style lang="scss" module>
103
- @import "../assets/styles/mixins";
104
-
105
- .menu {
106
- --ui3n-menu-animation-duration: 400ms;
107
- --ui3n-menu-content-bg: var(--color-bg-control-secondary-default);
108
-
109
- position: relative;
110
- overflow: visible;
111
- }
112
-
113
- .trigger {
114
- position: relative;
115
- }
116
-
117
- .content {
118
- position: absolute;
119
- border-radius: 4px;
120
- background-color: var(--ui3n-menu-content-bg);
121
- z-index: 1000;
122
- @include elevation(3);
123
- }
124
- </style>
125
-
126
- <style lang="scss" scoped>
127
- .menu-enter-active,
128
- .menu-leave-active {
129
- transition: opacity var(--ui3n-menu-animation-duration);
130
- }
131
-
132
- .menu-enter-from,
133
- .menu-leave-to {
134
- opacity: 0;
135
- }
136
- </style>