@v1nt1248/3nclient-lib 0.1.36 → 0.1.38

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.36",
5
+ "version": "0.1.38",
6
6
  "description": "Library for 3NWeb clients",
7
7
  "type": "module",
8
8
  "files": [
@@ -57,7 +57,7 @@
57
57
  "mitt": "3.0.1",
58
58
  "sanitize-html": "2.13.1",
59
59
  "tslib": "2.8.0",
60
- "vuedraggable": "^4.1.0"
60
+ "vuedraggable": "4.1.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "pinia": "2.2.4",
@@ -1,7 +1,9 @@
1
+ import { VNodeProps, AllowedComponentProps } from 'vue';
2
+
1
3
  export type ExtractComponentProps<TComponent> = TComponent extends new () => {
2
4
  $props: infer P;
3
5
  }
4
- ? P
6
+ ? Omit<P, keyof VNodeProps | keyof AllowedComponentProps>
5
7
  : never;
6
8
 
7
9
  export type Nullable<T> = T | null;
@@ -1,11 +1,14 @@
1
1
  import type { Component } from 'vue';
2
+ import type { ExtractComponentProps } from '../types/index';
2
3
 
3
4
  export type Ui3nDialogEvent = 'open' | 'before-close' | 'close' | 'confirm' | 'cancel' | 'click-overlay';
4
5
 
5
6
  export interface Ui3nDialogProps {
7
+ id?: string;
6
8
  teleport?: string;
7
9
  title?: string;
8
10
  width?: string | number;
11
+ draggable?: boolean;
9
12
  cssClass?: string[];
10
13
  cssStyle?: Record<string, string>;
11
14
  contentCssClass?: string[];
@@ -13,8 +16,8 @@ export interface Ui3nDialogProps {
13
16
  confirmButton?: boolean;
14
17
  cancelButton?: boolean;
15
18
  onClose?: () => void;
16
- onConfirm?: (data: any) => void;
17
- onCancel?: (data: any) => void;
19
+ onConfirm?: (data: unknown) => void;
20
+ onCancel?: (data: unknown) => void;
18
21
  confirmButtonText?: string;
19
22
  cancelButtonText?: string;
20
23
  confirmButtonColor?: string;
@@ -24,17 +27,17 @@ export interface Ui3nDialogProps {
24
27
  closeOnClickOverlay?: boolean;
25
28
  }
26
29
 
27
- export interface Ui3nDialogComponentProps<T extends Component, P extends Record<string, unknown>> {
30
+ export interface Ui3nDialogComponentProps<T extends Component> {
28
31
  component: T;
29
- componentProps?: P;
32
+ componentProps?: ExtractComponentProps<T>;
30
33
  dialogProps?: Ui3nDialogProps;
31
34
  }
32
35
 
33
36
  export interface Ui3nDialogComponentEmits {
34
- (ev: 'open', value?: any): void;
35
- (ev: 'before-close', value?: any): void;
36
- (ev: 'close', value?: any): void;
37
- (ev: 'confirm', value?: any): void;
38
- (ev: 'cancel', value?: any): void;
39
- (ev: 'click-overlay', value?: any): void;
37
+ (ev: 'open', value?: unknown): void;
38
+ (ev: 'before-close', value?: unknown): void;
39
+ (ev: 'close', value?: unknown): void;
40
+ (ev: 'confirm', value?: unknown): void;
41
+ (ev: 'cancel', value?: unknown): void;
42
+ (ev: 'click-overlay', value?: unknown): void;
40
43
  }
@@ -1,61 +1,62 @@
1
- <script lang="ts" setup generic="T extends Component, P extends Record<string, unknown>">
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ <script lang="ts" setup generic="T extends Component">
3
2
  import { type Component, computed, nextTick, onMounted, ref } from 'vue';
4
3
  import Ui3nButton from '../ui3n-button/ui3n-button.vue';
5
- import type { Ui3nDialogEvent, Ui3nDialogProps, Ui3nDialogComponentProps, Ui3nDialogComponentEmits } from './types';
4
+ import type { Ui3nDialogEvent, Ui3nDialogComponentProps, Ui3nDialogComponentEmits } from './types';
5
+ import { ExtractComponentProps } from '@/components/types';
6
6
 
7
- const props = defineProps<Ui3nDialogComponentProps<T, P>>();
7
+ const props = defineProps<Ui3nDialogComponentProps<T>>();
8
8
  const emits = defineEmits<Ui3nDialogComponentEmits>();
9
9
 
10
+ const currentDialogProps = computed(() => ({
11
+ teleport: props.dialogProps?.teleport || 'body',
12
+ title: props.dialogProps?.title || '',
13
+ width: props.dialogProps?.width || 380,
14
+ cssClass: props.dialogProps?.cssClass || [],
15
+ cssStyle: props.dialogProps?.cssStyle || {},
16
+ contentCssClass: props.dialogProps?.contentCssClass || [],
17
+ contentCssStyle: props.dialogProps?.contentCssStyle || {},
18
+ confirmButton: props.dialogProps?.confirmButton || true,
19
+ cancelButton: props.dialogProps?.cancelButton || true,
20
+ confirmButtonText: props.dialogProps?.confirmButtonText || 'Done',
21
+ cancelButtonText: props.dialogProps?.cancelButtonText || 'Cancel',
22
+ confirmButtonColor: props.dialogProps?.confirmButtonColor || 'var(--color-text-button-primary-default)',
23
+ cancelButtonColor: props.dialogProps?.cancelButtonColor || 'var(--color-text-button-secondary-default)',
24
+ confirmButtonBackground: props.dialogProps?.confirmButtonBackground || 'var(--color-bg-button-primary-default)',
25
+ cancelButtonBackground: props.dialogProps?.cancelButtonBackground || 'var(--color-bg-button-secondary-default)',
26
+ closeOnClickOverlay: props.dialogProps?.closeOnClickOverlay || true,
27
+ }));
28
+
10
29
  const show = ref(true);
11
- const data = ref(null);
30
+ const data = ref<unknown>(null);
12
31
  const isValid = ref(true);
32
+ const dialogOverlayElement = ref<HTMLElement | null>(null);
13
33
  const dialogElement = ref<HTMLDivElement | null>(null);
34
+ const isMousedown = ref(false);
35
+ const dragData = ref({
36
+ initialLeft: 0,
37
+ initialTop: 0,
38
+ left: 0,
39
+ top: 0,
40
+ shiftX: 0,
41
+ shiftY: 0,
42
+ });
14
43
 
15
- const dialogProps = computed<Required<Omit<Ui3nDialogProps, 'onClose' | 'onConfirm' | 'onCancel'>>>(() => ({
16
- teleport: props.dialogProps?.teleport ?? 'body',
17
- title: props.dialogProps?.title ?? '',
18
- width: props.dialogProps?.width ?? 380,
19
- cssClass: props.dialogProps?.cssClass ?? [],
20
- cssStyle: props.dialogProps?.cssStyle ?? {},
21
- contentCssClass: props.dialogProps?.contentCssClass ?? [],
22
- contentCssStyle: props.dialogProps?.contentCssStyle ?? {},
23
- confirmButton: props.dialogProps?.confirmButton ?? true,
24
- cancelButton: props.dialogProps?.confirmButton ?? true,
25
- confirmButtonText: props.dialogProps?.confirmButtonText ?? 'Done',
26
- cancelButtonText: props.dialogProps?.cancelButtonText ?? 'Cancel',
27
- confirmButtonColor: props.dialogProps?.confirmButtonColor ?? 'var(--color-text-button-primary-default)',
28
- cancelButtonColor: props.dialogProps?.cancelButtonColor ?? 'var(--color-text-button-secondary-default)',
29
- confirmButtonBackground: props.dialogProps?.confirmButtonBackground ?? 'var(--color-bg-button-primary-default)',
30
- cancelButtonBackground: props.dialogProps?.cancelButtonBackground ?? 'var(--color-bg-button-secondary-default)',
31
- closeOnClickOverlay: props.dialogProps?.closeOnClickOverlay ?? true,
44
+ const cssStyle = computed(() => ({
45
+ ...currentDialogProps.value.cssStyle,
46
+ width: `${currentDialogProps.value.width}px`,
47
+ ...(props.dialogProps?.draggable && {
48
+ left: `${dragData.value.left}px`,
49
+ top: `${dragData.value.top}px`,
50
+ }),
32
51
  }));
33
- const cssStyle = computed(() => ({ width: `${dialogProps.value.width}px`, ...dialogProps.value.cssStyle }));
34
-
35
- onMounted(() => {
36
- if (dialogElement.value) {
37
- dialogElement.value.style.setProperty('--dialog-confirm-button-color', dialogProps.value.confirmButtonColor!);
38
- dialogElement.value.style.setProperty('--dialog-cancel-button-color', dialogProps.value.cancelButtonColor!);
39
- dialogElement.value.style.setProperty(
40
- '--dialog-confirm-background-color',
41
- dialogProps.value.confirmButtonBackground!,
42
- );
43
- dialogElement.value.style.setProperty(
44
- '--dialog-cancel-background-color',
45
- dialogProps.value.cancelButtonBackground!,
46
- );
47
- }
48
-
49
- if (dialogElement.value) {
50
- nextTick(() => {
51
- dialogElement.value!.focus();
52
- });
53
- }
54
- });
55
52
 
56
- function selectData(value: any) {
53
+ function selectData(value: unknown) {
57
54
  data.value = value;
58
- if (!dialogProps.value.confirmButton && !dialogProps.value.cancelButton && props.dialogProps?.onConfirm) {
55
+ if (
56
+ !currentDialogProps.value.confirmButton &&
57
+ !currentDialogProps.value.cancelButton &&
58
+ props.dialogProps?.onConfirm
59
+ ) {
59
60
  props.dialogProps?.onConfirm(data.value);
60
61
  closeDialog();
61
62
  }
@@ -81,7 +82,7 @@ function closeDialog(arg?: { ev?: Event; withAction?: boolean }) {
81
82
  function startEmit(event: Ui3nDialogEvent, ev?: Event) {
82
83
  if (event === 'click-overlay') {
83
84
  emits(event);
84
- dialogProps.value.closeOnClickOverlay && closeDialog({ ev });
85
+ currentDialogProps.value.closeOnClickOverlay && closeDialog({ ev });
85
86
  return;
86
87
  }
87
88
 
@@ -109,28 +110,95 @@ function handleEvent(event: Event, eventName: Ui3nDialogEvent) {
109
110
  event.preventDefault();
110
111
  startEmit(eventName);
111
112
  }
113
+
114
+ /* drag */
115
+ function onDragstart() {
116
+ return false;
117
+ }
118
+
119
+ function onMouseup() {
120
+ isMousedown.value = false;
121
+ dragData.value.shiftX = 0;
122
+ dragData.value.shiftY = 0;
123
+ return false;
124
+ }
125
+
126
+ function onMousedown(ev: MouseEvent) {
127
+ isMousedown.value = true;
128
+ const dialogElementRect = dialogElement.value!.getBoundingClientRect();
129
+ dragData.value.shiftX = ev.clientX - dialogElementRect.left;
130
+ dragData.value.shiftY = ev.clientY - dialogElementRect.top;
131
+ }
132
+
133
+ function onMousemove(ev: MouseEvent) {
134
+ if (!isMousedown.value) {
135
+ return;
136
+ }
137
+ dragData.value.left = ev.clientX - dragData.value.shiftX - dragData.value.initialLeft;
138
+ dragData.value.top = ev.clientY - dragData.value.shiftY - dragData.value.initialTop;
139
+ }
140
+
141
+ onMounted(() => {
142
+ if (dialogElement.value) {
143
+ dialogElement.value.style.setProperty(
144
+ '--dialog-confirm-button-color',
145
+ currentDialogProps.value.confirmButtonColor!,
146
+ );
147
+ dialogElement.value.style.setProperty(
148
+ '--dialog-cancel-button-color',
149
+ currentDialogProps.value.cancelButtonColor!,
150
+ );
151
+ dialogElement.value.style.setProperty(
152
+ '--dialog-confirm-background-color',
153
+ currentDialogProps.value.confirmButtonBackground!,
154
+ );
155
+ dialogElement.value.style.setProperty(
156
+ '--dialog-cancel-background-color',
157
+ currentDialogProps.value.cancelButtonBackground!,
158
+ );
159
+
160
+ nextTick(() => {
161
+ dialogElement.value!.focus();
162
+
163
+ setTimeout(() => {
164
+ const dialogClientRect = dialogElement.value!.getBoundingClientRect();
165
+ dragData.value.initialLeft = dialogClientRect.left;
166
+ dragData.value.initialTop = dialogClientRect.top;
167
+ }, 100);
168
+ });
169
+ }
170
+ });
112
171
  </script>
113
172
 
114
173
  <template>
115
174
  <div
116
175
  v-if="show"
176
+ ref="dialogOverlayElement"
117
177
  :class="$style.overlay"
118
178
  @click="startEmit('click-overlay', $event)"
119
179
  >
120
180
  <div
181
+ :id="dialogProps?.id"
121
182
  ref="dialogElement"
122
183
  tabindex="1"
123
- :class="[$style.dialog, ...dialogProps.cssClass]"
184
+ :class="[
185
+ $style.dialog,
186
+ dialogProps?.draggable && isMousedown && $style.draggable,
187
+ ...currentDialogProps.cssClass!,
188
+ ]"
124
189
  :style="cssStyle"
190
+ v-on="dialogProps?.draggable
191
+ ? { dragstart: onDragstart, mousedown: onMousedown, mouseup: onMouseup, mousemove: onMousemove }
192
+ : {}"
125
193
  @keydown.esc.stop="startEmit('cancel')"
126
194
  @keydown.enter.stop="isValid && startEmit('confirm')"
127
195
  >
128
196
  <div
129
- v-if="dialogProps.title"
197
+ v-if="currentDialogProps.title"
130
198
  :class="$style.title"
131
199
  @click.stop
132
200
  >
133
- <span>{{ dialogProps.title }}</span>
201
+ <span>{{ currentDialogProps.title }}</span>
134
202
  </div>
135
203
 
136
204
  <ui3n-button
@@ -146,14 +214,14 @@ function handleEvent(event: Event, eventName: Ui3nDialogEvent) {
146
214
 
147
215
  <div
148
216
  v-if="component"
149
- :class="[$style.content, ...dialogProps.contentCssClass]"
150
- :style="dialogProps.contentCssStyle"
217
+ :class="[$style.content, ...currentDialogProps.contentCssClass!]"
218
+ :style="currentDialogProps.contentCssStyle"
151
219
  @click.stop
152
220
  >
153
221
  <!-- @vue-ignore -->
154
222
  <component
155
223
  :is="component"
156
- v-bind="componentProps as Object"
224
+ v-bind="componentProps as ExtractComponentProps<T>"
157
225
  @select="selectData"
158
226
  @validate="validate"
159
227
  @close="closeDialog({ ev: $event })"
@@ -163,28 +231,31 @@ function handleEvent(event: Event, eventName: Ui3nDialogEvent) {
163
231
  </div>
164
232
 
165
233
  <div
166
- v-if="dialogProps.confirmButton || dialogProps.cancelButton"
167
- :class="[$style.actions, dialogProps.confirmButton && dialogProps.cancelButton && $style.bothBtns]"
234
+ v-if="currentDialogProps.confirmButton || currentDialogProps.cancelButton"
235
+ :class="[
236
+ $style.actions,
237
+ currentDialogProps.confirmButton && currentDialogProps.cancelButton && $style.bothBtns
238
+ ]"
168
239
  @click.stop
169
240
  >
170
241
  <ui3n-button
171
- v-if="dialogProps.cancelButton"
242
+ v-if="currentDialogProps.cancelButton"
172
243
  type="secondary"
173
- :color="dialogProps.cancelButtonBackground"
174
- :text-color="dialogProps.cancelButtonColor"
244
+ :color="currentDialogProps.cancelButtonBackground"
245
+ :text-color="currentDialogProps.cancelButtonColor"
175
246
  @click="handleEvent($event, 'cancel')"
176
247
  >
177
- {{ dialogProps.cancelButtonText }}
248
+ {{ currentDialogProps.cancelButtonText }}
178
249
  </ui3n-button>
179
250
 
180
251
  <ui3n-button
181
- v-if="dialogProps.confirmButton"
182
- :color="dialogProps.confirmButtonBackground"
183
- :text-color="dialogProps.confirmButtonColor"
252
+ v-if="currentDialogProps.confirmButton"
253
+ :color="currentDialogProps.confirmButtonBackground"
254
+ :text-color="currentDialogProps.confirmButtonColor"
184
255
  :disabled="!isValid"
185
256
  @click="handleEvent($event, 'confirm')"
186
257
  >
187
- {{ dialogProps.confirmButtonText }}
258
+ {{ currentDialogProps.confirmButtonText }}
188
259
  </ui3n-button>
189
260
  </div>
190
261
  </div>
@@ -224,6 +295,10 @@ function handleEvent(event: Event, eventName: Ui3nDialogEvent) {
224
295
  outline: none;
225
296
 
226
297
  @include mixins.elevation();
298
+
299
+ &.draggable {
300
+ cursor: move;
301
+ }
227
302
  }
228
303
 
229
304
  .title {
@@ -242,6 +317,12 @@ function handleEvent(event: Event, eventName: Ui3nDialogEvent) {
242
317
  @include mixins.text-overflow-ellipsis();
243
318
  }
244
319
 
320
+ .dragHandleIcon {
321
+ position: absolute;
322
+ left: 0;
323
+ top: 16px;
324
+ }
325
+
245
326
  .closeBtn {
246
327
  position: absolute;
247
328
  right: 4px;
@@ -7,7 +7,7 @@ export interface Ui3nInputProps {
7
7
  autofocus?: boolean;
8
8
  icon?: string;
9
9
  iconColor?: string;
10
- rules?: Array<(v: string) => any>;
10
+ rules?: Array<(v: unknown) => boolean | string>;
11
11
  displayStateMode?: 'error' | 'success';
12
12
  displayStateWithIcon?: boolean;
13
13
  displayStateMessage?: string;
@@ -21,13 +21,16 @@ export interface Ui3nInputEmits {
21
21
  (event: 'blur', value: Event): void;
22
22
  (event: 'clear'): void;
23
23
  (event: 'change', value: string): void;
24
- (event: 'enter', value: {
25
- value: string;
26
- altKey: boolean;
27
- ctrlKey: boolean;
28
- shiftKey: boolean;
29
- metaKey: boolean;
30
- }): void;
24
+ (
25
+ event: 'enter',
26
+ value: {
27
+ value: string;
28
+ altKey: boolean;
29
+ ctrlKey: boolean;
30
+ shiftKey: boolean;
31
+ metaKey: boolean;
32
+ },
33
+ ): void;
31
34
  (event: 'escape', value: Event): void;
32
35
  (event: 'update:modelValue', value: string): void;
33
36
  (event: 'update:valid', value: boolean): void;
@@ -12,6 +12,7 @@ const $css = useCssModule();
12
12
 
13
13
  const inputElement = ref<HTMLInputElement | null>(null);
14
14
  const text = ref<string>('');
15
+ const isDirty = ref(false);
15
16
  const isFocused = ref(false);
16
17
  const errorMessage = ref('');
17
18
 
@@ -24,7 +25,7 @@ const cssIconColor = computed(() => {
24
25
  });
25
26
 
26
27
  const isValid = computed(() => {
27
- if (!text.value) {
28
+ if (!isDirty.value) {
28
29
  return true;
29
30
  }
30
31
 
@@ -37,7 +38,7 @@ const mainCssClasses = computed(() => {
37
38
  props.icon && val.push($css.withIcon);
38
39
  props.clearable && text.value && val.push($css.clearable);
39
40
  props.disabled && val.push($css.disabled);
40
- (!!errorMessage.value || props.displayStateMode === 'error') && val.push($css.error);
41
+ ((!!errorMessage.value && !isValid.value) || props.displayStateMode === 'error') && val.push($css.error);
41
42
  props.displayStateMode === 'success' && val.push($css.success);
42
43
 
43
44
  return val;
@@ -52,9 +53,9 @@ const iconCssClasses = computed(() => {
52
53
 
53
54
  const messageCssClasses = computed(() => {
54
55
  const val = [$css.ui3nInputFieldMessage];
55
- (errorMessage.value || (props.displayStateMode === 'error' && !!props.displayStateMessage))
56
- && val.push($css.ui3nInputErrorMessage);
57
- props.displayStateMode === 'success' && props.displayStateMessage && val.push($css.ui3nInputSuccessMessage);
56
+ (errorMessage.value || (props.displayStateMode === 'error' && !!props.displayStateMessage)) &&
57
+ val.push($css.ui3nInputErrorMessage);
58
+ props.displayStateMode === 'success' && props.displayStateMessage && val.push($css.ui3nInputSuccessMessage);
58
59
 
59
60
  return val;
60
61
  });
@@ -80,6 +81,7 @@ function onChange(event: Event) {
80
81
  function onInput(ev: Event) {
81
82
  const value = (ev.target as HTMLInputElement).value;
82
83
  text.value = value;
84
+ isDirty.value = true;
83
85
  validate(value);
84
86
  emits('update:modelValue', value);
85
87
  emits('input', value);
@@ -88,13 +90,15 @@ function onInput(ev: Event) {
88
90
  function onEnterKeydown(event: KeyboardEvent) {
89
91
  const { altKey, ctrlKey, metaKey, shiftKey, target } = event;
90
92
  const value = (target as HTMLInputElement).value;
93
+ isDirty.value = true;
91
94
  validate(value);
92
95
  emits('update:modelValue', value);
93
- emits('enter', { value , altKey, ctrlKey, metaKey, shiftKey });
96
+ emits('enter', { value, altKey, ctrlKey, metaKey, shiftKey });
94
97
  }
95
98
 
96
99
  function onEscapeKeydown(event: KeyboardEvent) {
97
100
  const value = (event.target as HTMLInputElement).value;
101
+ isDirty.value = true;
98
102
  validate(value);
99
103
  emits('update:modelValue', value);
100
104
  emits('escape', event);
@@ -102,6 +106,7 @@ function onEscapeKeydown(event: KeyboardEvent) {
102
106
 
103
107
  function clearValue() {
104
108
  text.value = '';
109
+ isDirty.value = false;
105
110
  validate('');
106
111
  emits('update:modelValue', '');
107
112
  emits('input', '');
@@ -123,6 +128,12 @@ function validate(text: string) {
123
128
  }
124
129
  }
125
130
 
131
+ defineExpose({
132
+ isDirty,
133
+ isFocused,
134
+ clearValue,
135
+ })
136
+
126
137
  onMounted(() => {
127
138
  if (props.autofocus && inputElement.value) {
128
139
  inputElement.value.focus();
@@ -142,7 +153,9 @@ watch(
142
153
 
143
154
  watch(
144
155
  () => isValid.value,
145
- val => emits('update:valid', val),
156
+ val => {
157
+ emits('update:valid', val);
158
+ },
146
159
  { immediate: true },
147
160
  );
148
161
  </script>
@@ -152,7 +165,10 @@ watch(
152
165
  ref="wrapperElement"
153
166
  :class="mainCssClasses"
154
167
  >
155
- <label v-if="label" :class="$style.ui3nInputLabel">
168
+ <label
169
+ v-if="label"
170
+ :class="$style.ui3nInputLabel"
171
+ >
156
172
  {{ label }}
157
173
  </label>
158
174
 
@@ -170,7 +186,7 @@ watch(
170
186
  @focusin="onFocus"
171
187
  @focusout="onBlur"
172
188
  @change="onChange"
173
- >
189
+ />
174
190
 
175
191
  <ui3n-icon
176
192
  v-if="icon"
@@ -202,7 +218,7 @@ watch(
202
218
  />
203
219
 
204
220
  <div
205
- v-if="errorMessage || displayStateMessage"
221
+ v-if="(isDirty && errorMessage) || (displayStateMode && displayStateMessage)"
206
222
  :class="messageCssClasses"
207
223
  >
208
224
  {{ errorMessage || displayStateMessage }}
@@ -314,7 +330,7 @@ watch(
314
330
  }
315
331
 
316
332
  &.ui3nInputSuccessMessage {
317
- color: var(--success-content-default)
333
+ color: var(--success-content-default);
318
334
  }
319
335
  }
320
336