@vc-shell/framework 1.0.147 → 1.0.149

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 (88) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/core/composables/index.ts +1 -1
  3. package/core/composables/useLanguages/index.ts +52 -0
  4. package/core/plugins/i18n/index.ts +1 -1
  5. package/core/plugins/modularity/index.ts +10 -1
  6. package/core/plugins/validation/index.ts +0 -11
  7. package/core/plugins/validation/rules.ts +7 -6
  8. package/dist/core/composables/index.d.ts +1 -1
  9. package/dist/core/composables/index.d.ts.map +1 -1
  10. package/dist/core/composables/useLanguages/index.d.ts +12 -0
  11. package/dist/core/composables/useLanguages/index.d.ts.map +1 -0
  12. package/dist/core/plugins/modularity/index.d.ts.map +1 -1
  13. package/dist/core/plugins/validation/index.d.ts +0 -3
  14. package/dist/core/plugins/validation/index.d.ts.map +1 -1
  15. package/dist/core/plugins/validation/rules.d.ts +1 -1
  16. package/dist/core/plugins/validation/rules.d.ts.map +1 -1
  17. package/dist/framework.js +11732 -10993
  18. package/dist/index.css +1 -1
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/shared/components/app-switcher/composables/useAppSwitcher/index.d.ts.map +1 -1
  22. package/dist/shared/components/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue.d.ts.map +1 -1
  23. package/dist/shared/components/blade-navigation/components/vc-blade-view/vc-blade-view.d.ts +0 -12
  24. package/dist/shared/components/blade-navigation/components/vc-blade-view/vc-blade-view.d.ts.map +1 -1
  25. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts +6 -3
  26. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts.map +1 -1
  27. package/dist/shared/components/blade-navigation/plugin.d.ts.map +1 -1
  28. package/dist/shared/components/blade-navigation/types/index.d.ts +6 -8
  29. package/dist/shared/components/blade-navigation/types/index.d.ts.map +1 -1
  30. package/dist/shared/components/language-selector/language-selector.vue.d.ts.map +1 -1
  31. package/dist/shared/components/user-dropdown-button/user-dropdown-button.vue.d.ts.map +1 -1
  32. package/dist/shared/modules/dynamic/components/SchemaRender.d.ts +3 -3
  33. package/dist/shared/modules/dynamic/components/fields/Button.d.ts +1 -1
  34. package/dist/shared/modules/dynamic/components/fields/Card.d.ts +1 -1
  35. package/dist/shared/modules/dynamic/components/fields/Checkbox.d.ts +1 -1
  36. package/dist/shared/modules/dynamic/components/fields/ContentField.d.ts +1 -1
  37. package/dist/shared/modules/dynamic/components/fields/DynamicProperty.d.ts +1 -1
  38. package/dist/shared/modules/dynamic/components/fields/EditorField.d.ts +1 -1
  39. package/dist/shared/modules/dynamic/components/fields/Fieldset.d.ts +1 -1
  40. package/dist/shared/modules/dynamic/components/fields/GalleryField.d.ts +1 -1
  41. package/dist/shared/modules/dynamic/components/fields/ImageField.d.ts +1 -1
  42. package/dist/shared/modules/dynamic/components/fields/InputCurrency.d.ts +1 -1
  43. package/dist/shared/modules/dynamic/components/fields/InputField.d.ts +1 -1
  44. package/dist/shared/modules/dynamic/components/fields/MultivalueField.d.ts +1 -1
  45. package/dist/shared/modules/dynamic/components/fields/SelectField.d.ts +1 -1
  46. package/dist/shared/modules/dynamic/components/fields/StatusField.d.ts +1 -1
  47. package/dist/shared/modules/dynamic/components/fields/TextareaField.d.ts +1 -1
  48. package/dist/shared/modules/dynamic/components/fields/VideoField.d.ts +1 -1
  49. package/dist/shared/modules/dynamic/components/fields/props.d.ts +1 -1
  50. package/dist/shared/modules/dynamic/factories/types/index.d.ts +1 -1
  51. package/dist/shared/modules/dynamic/factories/types/index.d.ts.map +1 -1
  52. package/dist/shared/modules/dynamic/helpers/nodeBuilder.d.ts.map +1 -1
  53. package/dist/shared/modules/dynamic/helpers/override.d.ts.map +1 -1
  54. package/dist/shared/modules/dynamic/index.d.ts.map +1 -1
  55. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts +3 -1
  56. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
  57. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts +2 -0
  58. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts.map +1 -1
  59. package/dist/shared/modules/dynamic/types/index.d.ts +13 -4
  60. package/dist/shared/modules/dynamic/types/index.d.ts.map +1 -1
  61. package/dist/tsconfig.tsbuildinfo +1 -1
  62. package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue.d.ts.map +1 -1
  63. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  64. package/package.json +6 -5
  65. package/shared/components/app-switcher/composables/useAppSwitcher/index.ts +2 -1
  66. package/shared/components/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue +12 -26
  67. package/shared/components/blade-navigation/components/vc-blade-view/vc-blade-view.ts +18 -11
  68. package/shared/components/blade-navigation/composables/useBladeNavigation/index.ts +231 -337
  69. package/shared/components/blade-navigation/plugin.ts +2 -1
  70. package/shared/components/blade-navigation/types/index.ts +5 -11
  71. package/shared/components/language-selector/language-selector.vue +12 -10
  72. package/shared/components/notification-dropdown/notification-dropdown.vue +1 -1
  73. package/shared/components/user-dropdown-button/user-dropdown-button.vue +55 -40
  74. package/shared/modules/dynamic/factories/types/index.ts +1 -1
  75. package/shared/modules/dynamic/helpers/nodeBuilder.ts +6 -3
  76. package/shared/modules/dynamic/helpers/override.ts +29 -11
  77. package/shared/modules/dynamic/index.ts +1 -0
  78. package/shared/modules/dynamic/pages/dynamic-blade-form.vue +47 -17
  79. package/shared/modules/dynamic/pages/dynamic-blade-list.vue +11 -1
  80. package/shared/modules/dynamic/types/index.ts +13 -4
  81. package/ui/components/atoms/vc-label/vc-label.vue +18 -19
  82. package/ui/components/molecules/vc-multivalue/vc-multivalue.vue +1 -0
  83. package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +5 -19
  84. package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +1 -1
  85. package/ui/components/organisms/vc-app/vc-app.vue +2 -8
  86. package/core/composables/useI18n/index.ts +0 -7
  87. package/dist/core/composables/useI18n/index.d.ts +0 -3
  88. package/dist/core/composables/useI18n/index.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Router } from "vue-router";
2
- import { App, inject } from "vue";
2
+ import { App, inject, ref } from "vue";
3
3
  import * as components from "./components";
4
4
  import { BladeNavigationPlugin, BladeRoutesRecord } from "./types";
5
5
 
@@ -25,6 +25,7 @@ export const VcBladeNavigationComponent = {
25
25
  const bladeNavigationPlugin: BladeNavigationPlugin = {
26
26
  router: args.router,
27
27
  internalRoutes,
28
+ blades: ref([]),
28
29
  };
29
30
 
30
31
  app.config.globalProperties.$bladeNavigationPlugin = bladeNavigationPlugin;
@@ -8,7 +8,7 @@ import {
8
8
  VNode,
9
9
  ComponentInternalInstance,
10
10
  VNodeTypes,
11
- ComponentPublicInstance,
11
+ Ref,
12
12
  } from "vue";
13
13
  import { ComponentPublicInstanceConstructor } from "../../../utilities/vueUtils";
14
14
  import { MenuItemConfig } from "../../../../core/types";
@@ -27,6 +27,7 @@ export type CoreDynamicBladeComponentProps = {
27
27
 
28
28
  export type CoreBladeAdditionalSettings = {
29
29
  url?: `/${string}`;
30
+ routable?: boolean;
30
31
  permissions?: string | string[];
31
32
  isWorkspace?: boolean;
32
33
  name?: string;
@@ -75,6 +76,7 @@ export interface IBladeEvent<T extends Component = Component> {
75
76
  export interface BladeNavigationPlugin {
76
77
  router: Router;
77
78
  internalRoutes: BladeRoutesRecord[];
79
+ blades: Ref<BladeVNode[]>;
78
80
  }
79
81
 
80
82
  export interface BladeRoutesRecord {
@@ -90,10 +92,10 @@ export interface BladeVNode extends VNode {
90
92
  navigation: {
91
93
  onOpen?: () => void;
92
94
  onClose?: () => void;
95
+ onBeforeClose?: () => Promise<boolean | undefined>;
96
+ instance: Ref<CoreBladeExposed | undefined | null>;
93
97
  fullPath: string;
94
- bladePath?: string;
95
98
  idx: number;
96
- uniqueRouteKey: string;
97
99
  };
98
100
  onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[];
99
101
  onVnodeMounted?: VNodeMountHook | VNodeMountHook[];
@@ -101,11 +103,3 @@ export interface BladeVNode extends VNode {
101
103
  CoreBladeComponentProps;
102
104
  type: VNodeTypes & BladeInstanceConstructor;
103
105
  }
104
-
105
- export interface BladeRouteRecordLocationNormalized extends RouteRecordNormalized {
106
- components: Record<string, BladeVNode>;
107
- instances: Record<
108
- string,
109
- ComponentPublicInstance<CoreBladeExposed, any, CoreBladeExposed, any, CoreBladeExposed, any, any, any, any, any>
110
- >;
111
- }
@@ -44,17 +44,19 @@ import { VcIcon } from "./../../../ui/components";
44
44
  import { ref } from "vue";
45
45
  import { vOnClickOutside } from "@vueuse/components";
46
46
  import { useI18n } from "vue-i18n";
47
+ import { useLanguages } from "../../../core/composables";
47
48
 
48
- const { locale: currentLocale, availableLocales, getLocaleMessage } = useI18n({ useScope: "global" });
49
-
49
+ const { availableLocales, getLocaleMessage } = useI18n({ useScope: "global" });
50
+ const { setLocale } = useLanguages();
50
51
  const isDropActive = ref(false);
51
52
 
52
- const languageItems = availableLocales.map((locale: string) => ({
53
- lang: locale,
54
- title: (getLocaleMessage(locale) as { language_name: string }).language_name,
55
- clickHandler(lang: string) {
56
- currentLocale.value = lang;
57
- localStorage.setItem("VC_LANGUAGE_SETTINGS", lang);
58
- },
59
- }));
53
+ const languageItems = availableLocales
54
+ .map((locale: string) => ({
55
+ lang: locale,
56
+ title: (getLocaleMessage(locale) as { language_name: string }).language_name,
57
+ clickHandler(lang: string) {
58
+ setLocale(lang);
59
+ },
60
+ }))
61
+ .filter((item) => item.title);
60
62
  </script>
@@ -45,7 +45,7 @@
45
45
  >
46
46
  <div
47
47
  v-if="$isMobile.value"
48
- class="tw-text-[#319ed4] tw-flex tw-justify-end tw-items-center tw-p-4"
48
+ class="tw-text-[#319ed4] tw-flex tw-justify-end tw-items-center tw-p-4 tw-cursor-pointer"
49
49
  >
50
50
  <VcIcon
51
51
  icon="fas fa-times"
@@ -4,40 +4,42 @@
4
4
  class="user-dropdown-button"
5
5
  :class="{
6
6
  'user-dropdown-button_active': accountMenuVisible,
7
- 'user-dropdown-button_no-pointer': $isMobile.value,
8
7
  }"
9
8
  @click.stop="toggleAccountMenuVisible"
10
9
  >
11
- <div
12
- v-if="avatarUrl"
13
- class="user-dropdown-button__avatar"
14
- :style="imageHandler"
15
- ></div>
16
- <VcIcon
17
- v-else
18
- icon="fas fa-user-circle"
19
- size="xxl"
20
- class="tw-text-[color:var(--app-bar-button-color)]"
21
- />
22
- <div class="tw-grow tw-basis-0 tw-ml-3 tw-overflow-hidden">
23
- <div class="user-dropdown-button__name tw-truncate">
24
- {{ name || user?.userName }}
10
+ <div class="user-dropdown-button__wrap tw-flex tw-justify-between tw-items-center tw-flex-auto">
11
+ <div
12
+ v-if="avatarUrl"
13
+ class="user-dropdown-button__avatar"
14
+ :style="imageHandler"
15
+ ></div>
16
+ <VcIcon
17
+ v-else
18
+ icon="fas fa-user-circle"
19
+ size="xxl"
20
+ class="tw-text-[color:var(--app-bar-button-color)]"
21
+ />
22
+ <div class="tw-grow tw-basis-0 tw-ml-3 tw-overflow-hidden">
23
+ <div class="user-dropdown-button__name tw-truncate">
24
+ {{ name || user?.userName }}
25
+ </div>
26
+ <div class="user-dropdown-button__role">
27
+ {{
28
+ (role && $t(`SHELL.USER.ROLE.${role}`)) ||
29
+ (user?.isAdministrator ? $t("SHELL.USER.ROLE.ADMINISTRATOR") : "")
30
+ }}
31
+ </div>
25
32
  </div>
26
- <div class="user-dropdown-button__role">
27
- {{
28
- (role && $t(`SHELL.USER.ROLE.${role}`)) || (user?.isAdministrator ? $t("SHELL.USER.ROLE.ADMINISTRATOR") : "")
29
- }}
33
+ <div
34
+ v-if="menu && menu.length"
35
+ class="user-dropdown-button__chevron"
36
+ >
37
+ <VcIcon
38
+ icon="fas fa-chevron-down"
39
+ size="xl"
40
+ ></VcIcon>
30
41
  </div>
31
42
  </div>
32
- <div
33
- v-if="menu && menu.length && !$isMobile.value"
34
- class="user-dropdown-button__chevron"
35
- >
36
- <VcIcon
37
- icon="fas fa-chevron-down"
38
- size="xl"
39
- ></VcIcon>
40
- </div>
41
43
  <div
42
44
  v-if="menu && accountMenuVisible"
43
45
  class="user-dropdown-button__menu"
@@ -56,7 +58,7 @@
56
58
  </template>
57
59
 
58
60
  <script lang="ts" setup>
59
- import { Ref, computed, inject, ref } from "vue";
61
+ import { computed, ref } from "vue";
60
62
  import { vOnClickOutside } from "@vueuse/components";
61
63
  import { useUser } from "../../../core/composables";
62
64
  import { useRouter } from "vue-router";
@@ -65,6 +67,7 @@ import { VcIcon } from "../../../ui/components";
65
67
  import { BladeMenu } from "../../../core/types";
66
68
  import { usePopup } from "../popup-handler/composables/usePopup";
67
69
  import { ChangePassword } from "../change-password";
70
+ import { useBladeNavigation } from "..";
68
71
 
69
72
  export interface Props {
70
73
  avatarUrl?: string | undefined;
@@ -76,8 +79,6 @@ const props = withDefaults(defineProps<Props>(), {
76
79
  menuItems: () => [],
77
80
  });
78
81
 
79
- const isMobile = inject("isMobile") as Ref<boolean>;
80
-
81
82
  const { user, signOut } = useUser();
82
83
  const router = useRouter();
83
84
  const { t } = useI18n({ useScope: "global" });
@@ -85,7 +86,7 @@ const { t } = useI18n({ useScope: "global" });
85
86
  const { open } = usePopup({
86
87
  component: ChangePassword,
87
88
  });
88
-
89
+ const { closeBlade } = useBladeNavigation();
89
90
  const accountMenuVisible = ref(false);
90
91
  const menu = computed(() => [
91
92
  ...props.menuItems,
@@ -98,14 +99,18 @@ const menu = computed(() => [
98
99
  {
99
100
  title: t("SHELL.ACCOUNT.LOGOUT"),
100
101
  async clickHandler() {
101
- await signOut();
102
- router.push({ name: "Login" });
102
+ const isPrevented = await closeBlade(0);
103
+
104
+ if (!isPrevented) {
105
+ await signOut();
106
+ router.push({ name: "Login" });
107
+ }
103
108
  },
104
109
  },
105
110
  ]);
106
111
 
107
112
  const toggleAccountMenuVisible = () => {
108
- if (menu.value && menu.value.length && !isMobile.value) {
113
+ if (menu.value && menu.value.length) {
109
114
  accountMenuVisible.value = !accountMenuVisible.value;
110
115
  }
111
116
  };
@@ -125,11 +130,7 @@ const imageHandler = computed(() => {
125
130
  <style lang="scss">
126
131
  .user-dropdown-button {
127
132
  @apply tw-w-[240px] tw-border-l tw-border-solid tw-border-l-[color:var(--app-bar-divider-color)] tw-px-4 tw-cursor-pointer
128
- tw-relative tw-flex tw-justify-between tw-items-center tw-h-full;
129
-
130
- &_no-pointer {
131
- @apply tw-cursor-default;
132
- }
133
+ tw-relative tw-flex tw-h-full tw-flex-col tw-select-none;
133
134
 
134
135
  &:hover,
135
136
  &_active {
@@ -168,5 +169,19 @@ const imageHandler = computed(() => {
168
169
  @apply tw-p-3 tw-text-lg tw-text-black tw-border-l tw-border-solid tw-border-l-[#eef0f2] tw-border-b tw-border-b-[#eef0f2] tw-bg-white hover:tw-bg-[#eff7fc];
169
170
  }
170
171
  }
172
+
173
+ .vc-app_mobile & {
174
+ &__wrap {
175
+ height: var(--app-bar-height);
176
+ }
177
+
178
+ &__menu {
179
+ @apply tw-static tw-shadow-none #{!important};
180
+ }
181
+
182
+ &__menu-item {
183
+ @apply tw-border-none #{!important};
184
+ }
185
+ }
171
186
  }
172
187
  </style>
@@ -79,7 +79,7 @@ export interface DetailsBaseBladeScope extends BaseBladeScope {
79
79
  currentLocale: Ref<string>;
80
80
  languages: Ref<string[]>;
81
81
  setLocale: (locale: string) => void;
82
- localesOptions: Ref<{ label: string; value: string }[]>;
82
+ localesOptions: Ref<{ label: string | undefined; value: string }[]>;
83
83
  getLanguages: AsyncAction<void, void>;
84
84
  };
85
85
  dynamicProperties?: {
@@ -7,6 +7,7 @@ import { setModel } from "./setters";
7
7
  import { unwrapInterpolation } from "./unwrapInterpolation";
8
8
  import { DetailsBladeContext } from "../factories";
9
9
  import { safeIn } from "./safeIn";
10
+ import { i18n } from "./../../../../core/plugins/i18n";
10
11
 
11
12
  function disabledHandler(
12
13
  disabled: { method?: string } | boolean,
@@ -41,6 +42,8 @@ function nodeBuilder<
41
42
  }): VNode {
42
43
  if (!controlSchema) throw new Error("There is no controlSchema provided");
43
44
 
45
+ const { t } = i18n.global;
46
+
44
47
  const name = controlSchema.id;
45
48
  const rules = (safeIn("rules", controlSchema) && controlSchema.rules) || undefined;
46
49
  const placeholder = (safeIn("placeholder", controlSchema) && controlSchema.placeholder) || undefined;
@@ -99,15 +102,15 @@ function nodeBuilder<
99
102
 
100
103
  const baseProps: IControlBaseProps = reactive({
101
104
  key: `${parentId}`,
102
- label,
105
+ label: unref(computed(() => (label ? t(label) : undefined))),
103
106
  disabled,
104
107
  name,
105
108
  rules,
106
- placeholder,
109
+ placeholder: unref(computed(() => (placeholder ? t(placeholder) : undefined))),
107
110
  required,
108
111
  modelValue,
109
112
  "onUpdate:modelValue": onUpdateModelValue,
110
- tooltip,
113
+ tooltip: unref(computed(() => (tooltip ? t(tooltip) : undefined))),
111
114
  multilanguage,
112
115
  });
113
116
 
@@ -1,5 +1,5 @@
1
1
  import * as _ from "lodash-es";
2
- import { DynamicSchema, OverridesSchema } from "../types";
2
+ import { DynamicSchema, OverridesRemove, OverridesSchema } from "../types";
3
3
  import "core-js/actual/array/to-spliced";
4
4
 
5
5
  export const handleOverrides = (overrides: OverridesSchema, schemaCopy: { [key: string]: DynamicSchema }) => {
@@ -43,22 +43,40 @@ const upsertHelper = (overrides: OverridesSchema, schemaCopy: { [key: string]: D
43
43
  );
44
44
  };
45
45
 
46
- const removeHelper = (overrides: OverridesSchema, schemaCopy: { [key: string]: DynamicSchema }) => {
46
+ const removeHelper = (overrides: OverridesSchema, schemaCopy: Record<string, DynamicSchema>) => {
47
47
  return Object.entries(schemaCopy).reduce(
48
48
  (obj, [name, schema]) => {
49
49
  const clonedSchema = _.cloneDeep(schema);
50
- overrides.remove
51
- ?.filter((x) => clonedSchema.settings.id === x.id)
52
- .forEach(({ path }) => {
53
- if (path) {
54
- const parentPath = path.slice(0, path.lastIndexOf("["));
55
- _.unset(clonedSchema, path);
56
- _.update(clonedSchema, parentPath, _.compact);
57
- }
58
- }, {});
50
+ sortByMaxIndexPath(overrides.remove?.filter((x) => schema.settings.id === x.id)).forEach(({ path }) => {
51
+ if (path) {
52
+ removePath(clonedSchema, path);
53
+ }
54
+ });
59
55
  obj[name] = clonedSchema;
60
56
  return obj;
61
57
  },
62
58
  {} as Record<string, DynamicSchema>,
63
59
  );
64
60
  };
61
+
62
+ function removePath(obj: DynamicSchema, path: string) {
63
+ const parentPath = path.slice(0, path.lastIndexOf("["));
64
+ _.unset(obj, path);
65
+ _.update(obj, parentPath, _.compact);
66
+ }
67
+
68
+ // this part sorts paths with indexes in descending order to avoid deleting items with already changed indexes
69
+ function sortByMaxIndexPath(items: { path: string }[] | undefined) {
70
+ return (items || []).sort((a, b) => getMaxIndexFromPath(b.path) - getMaxIndexFromPath(a.path));
71
+ }
72
+
73
+ function getMaxIndexFromPath(path: string): number {
74
+ const matches = path.match(/\[(\d+)\]/g);
75
+
76
+ if (matches) {
77
+ const indexes = matches.map((match) => parseInt(match.slice(1, -1), 10));
78
+ return Math.max(...indexes);
79
+ }
80
+
81
+ return Infinity;
82
+ }
@@ -72,6 +72,7 @@ const register = (
72
72
  name: bladeName,
73
73
  isWorkspace: "isWorkspace" in json.settings && json.settings.isWorkspace,
74
74
  menuItem: ("menuItem" in json.settings && json.settings.menuItem) ?? undefined,
75
+ routable: json.settings.routable ?? true,
75
76
  setup: (props: ComponentProps<typeof bladeComponent>, ctx) =>
76
77
  (bladeComponent?.setup &&
77
78
  bladeComponent.setup(
@@ -50,7 +50,9 @@
50
50
  >
51
51
  <component
52
52
  :is="item"
53
+ :ref="(el: HTMLElement) => widgetsRefs.set({ component: item, el })"
53
54
  v-model="bladeContext"
55
+ @click="setActiveWidget(item)"
54
56
  ></component>
55
57
  </div>
56
58
  </div>
@@ -75,9 +77,10 @@ import {
75
77
  onBeforeMount,
76
78
  ComputedRef,
77
79
  type Component,
80
+ ConcreteComponent,
78
81
  } from "vue";
79
82
  import { DynamicDetailsSchema, FormContentSchema, SettingsSchema } from "../types";
80
- import { reactiveComputed, useMounted } from "@vueuse/core";
83
+ import { reactiveComputed, useMounted, useTemplateRefsList } from "@vueuse/core";
81
84
  import {
82
85
  DetailsBladeContext,
83
86
  DetailsBaseBladeScope,
@@ -85,11 +88,11 @@ import {
85
88
  UseDetails,
86
89
  usePopup,
87
90
  useBladeNavigation,
91
+ CoreBladeExposed,
88
92
  } from "../../../index";
89
93
  import SchemaRender from "../components/SchemaRender";
90
94
  import { VcSelect } from "../../../../ui/components";
91
95
  import { toolbarReducer } from "../helpers/toolbarReducer";
92
- import { onBeforeRouteLeave } from "vue-router";
93
96
  import { useBeforeUnload } from "../../../../core/composables/useBeforeUnload";
94
97
  import * as _ from "lodash-es";
95
98
  import { IBladeToolbar } from "../../../../core/types";
@@ -123,7 +126,9 @@ const { t } = useI18n({ useScope: "global" });
123
126
 
124
127
  const { showConfirmation } = usePopup();
125
128
 
126
- const { currentBladeNavigationData } = useBladeNavigation();
129
+ const { onBeforeClose } = useBladeNavigation();
130
+
131
+ const widgetsRefs = useTemplateRefsList<{ el: HTMLDivElement; component: ConcreteComponent }>();
127
132
 
128
133
  const { loading, item, validationState, scope, load, remove, saveChanges, bladeTitle } = props.composables
129
134
  ? (props.composables?.[props.model?.settings?.composable ?? ""]({ emit, props, mounted: useMounted() }) as UseDetails<
@@ -143,6 +148,7 @@ const { loading, item, validationState, scope, load, remove, saveChanges, bladeT
143
148
 
144
149
  const title = ref();
145
150
  const isReady = ref(false);
151
+ const activeWidgetExposed = ref<CoreBladeExposed>();
146
152
 
147
153
  watch(
148
154
  () => bladeTitle?.value,
@@ -245,6 +251,11 @@ const toolbarComputed =
245
251
  emit("parent:call", {
246
252
  method: "reload",
247
253
  });
254
+
255
+ emit("parent:call", {
256
+ method: "updateActiveWidgetCount",
257
+ });
258
+
248
259
  if (!props.param) {
249
260
  emit("close:blade");
250
261
  }
@@ -264,6 +275,10 @@ const toolbarComputed =
264
275
  emit("parent:call", {
265
276
  method: "reload",
266
277
  });
278
+ emit("parent:call", {
279
+ method: "updateActiveWidgetCount",
280
+ });
281
+
267
282
  emit("close:blade");
268
283
  }
269
284
  }
@@ -275,24 +290,24 @@ const toolbarComputed =
275
290
  context: bladeContext.value,
276
291
  });
277
292
 
278
- onBeforeMount(async () => {
279
- if (props.composables) await init();
280
- });
293
+ async function setActiveWidget(widget: string | ConcreteComponent) {
294
+ const component = typeof widget === "string" ? resolveComponent(widget) : widget;
295
+
296
+ if (typeof component !== "string") {
297
+ await nextTick(
298
+ () => (activeWidgetExposed.value = widgetsRefs.value.find((x) => _.isEqual(x.component, component))?.el),
299
+ );
300
+ }
301
+ }
281
302
 
282
- onBeforeRouteLeave(async (to) => {
303
+ async function updateActiveWidgetCount() {
283
304
  if (
284
- (settings.value?.url
285
- ? currentBladeNavigationData.value?.fullPath && !to.path.includes(currentBladeNavigationData.value?.fullPath)
286
- : true) &&
287
- unref(validated)
305
+ activeWidgetExposed.value?.updateActiveWidgetCount &&
306
+ typeof activeWidgetExposed.value?.updateActiveWidgetCount === "function"
288
307
  ) {
289
- return await showConfirmation(
290
- unref(
291
- computed(() => t(`${settings.value?.localizationPrefix.trim().toUpperCase()}.PAGES.ALERTS.CLOSE_CONFIRMATION`)),
292
- ),
293
- );
308
+ await activeWidgetExposed.value.updateActiveWidgetCount();
294
309
  }
295
- });
310
+ }
296
311
 
297
312
  async function init() {
298
313
  if (props.param) {
@@ -304,8 +319,23 @@ async function init() {
304
319
  });
305
320
  }
306
321
 
322
+ onBeforeMount(async () => {
323
+ if (props.composables) await init();
324
+ });
325
+
326
+ onBeforeClose(async () => {
327
+ if (unref(validated)) {
328
+ return await showConfirmation(
329
+ unref(
330
+ computed(() => t(`${settings.value?.localizationPrefix.trim().toUpperCase()}.PAGES.ALERTS.CLOSE_CONFIRMATION`)),
331
+ ),
332
+ );
333
+ }
334
+ });
335
+
307
336
  defineExpose({
308
337
  title: bladeTitle ?? "",
338
+ updateActiveWidgetCount,
309
339
  ...scope?.value,
310
340
  });
311
341
  </script>
@@ -345,7 +345,7 @@ const toolbarComputed =
345
345
  },
346
346
  removeItems: {
347
347
  async clickHandler() {
348
- removeItems();
348
+ await removeItems();
349
349
  },
350
350
  disabled: computed(() => !selectedIds.value?.length),
351
351
  isVisible: isDesktop.value,
@@ -472,6 +472,9 @@ async function removeItems() {
472
472
  }
473
473
  }
474
474
  }
475
+ emit("parent:call", {
476
+ method: "updateActiveWidgetCount",
477
+ });
475
478
  await reload();
476
479
  }
477
480
  }
@@ -590,9 +593,16 @@ function sortRows(event: { dragIndex: number; dropIndex: number; value: any[] })
590
593
  }
591
594
  }
592
595
 
596
+ function updateActiveWidgetCount() {
597
+ emit("parent:call", {
598
+ method: "updateActiveWidgetCount",
599
+ });
600
+ }
601
+
593
602
  defineExpose({
594
603
  reload,
595
604
  title,
605
+ updateActiveWidgetCount,
596
606
  ...scope?.value,
597
607
  });
598
608
  </script>
@@ -42,6 +42,10 @@ export type SettingsSchema = SettingsGrid | SettingsDetails;
42
42
 
43
43
  export interface SettingsGrid extends SettingsBase {
44
44
  component: "DynamicBladeList";
45
+ /**
46
+ * Blade default header title
47
+ */
48
+ titleTemplate: string;
45
49
  }
46
50
 
47
51
  export interface SettingsDetails extends SettingsBase {
@@ -58,6 +62,15 @@ export interface SettingsBase {
58
62
  * Blade url
59
63
  */
60
64
  url?: string;
65
+ /**
66
+ * Determines whether you can navigate to the blade directly
67
+ * without parameters or options.
68
+ *
69
+ * @example Blade is not workspace and use context (passed `options` or `params` in openBlade method) to load data - then set `routable` to `false`
70
+ *
71
+ * @default true
72
+ */
73
+ routable?: boolean;
61
74
  /**
62
75
  * Locale key for VueI18n locale files
63
76
  */
@@ -66,10 +79,6 @@ export interface SettingsBase {
66
79
  * Required component id
67
80
  */
68
81
  id: string;
69
- /**
70
- * Blade default header title
71
- */
72
- titleTemplate: string;
73
82
  /**
74
83
  * Composable to use at {@link SettingsBase.model } blade component view
75
84
  */
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="tw-flex tw-flex-row tw-justify-between tw-items-center">
3
- <div class="tw-flex-nowrap tw-font-bold tw-relative tw-truncate">
2
+ <div class="tw-flex tw-flex-row tw-justify-between tw-items-center tw-relative">
3
+ <div class="tw-flex-nowrap tw-font-bold tw-truncate">
4
4
  <span class="tw-truncate">
5
5
  <slot></slot>
6
6
  </span>
@@ -9,26 +9,25 @@
9
9
  class="tw-text-[color:var(--label-required-color)] tw-ml-1"
10
10
  >*</span
11
11
  >
12
-
12
+ </div>
13
+ <span
14
+ v-if="$slots['tooltip']"
15
+ class="tw-grow tw-basis-0 tw-ml-1"
16
+ >
17
+ <VcIcon
18
+ class="tw-text-[color:var(--label-tooltip-color)]"
19
+ :icon="tooltipIcon"
20
+ size="s"
21
+ @mouseenter="tooltipVisible = true"
22
+ @mouseleave="tooltipVisible = false"
23
+ ></VcIcon>
13
24
  <span
14
- v-if="$slots['tooltip']"
15
- class="tw-grow tw-basis-0 tw-ml-1"
25
+ v-if="tooltipVisible"
26
+ class="tw-absolute tw-z-10 tw-bg-white tw-border tw-border-solid tw-border-[color:#eef0f2] tw-shadow-[1px_1px_8px_rgba(126,142,157,0.25)] tw-rounded-[3px] tw-text-[color:#8e9daa] tw-font-normal tw-py-1 tw-px-2 tw-ml-4"
16
27
  >
17
- <VcIcon
18
- class="tw-text-[color:var(--label-tooltip-color)]"
19
- :icon="tooltipIcon"
20
- size="s"
21
- @mouseenter="tooltipVisible = true"
22
- @mouseleave="tooltipVisible = false"
23
- ></VcIcon>
24
- <span
25
- v-if="tooltipVisible"
26
- class="tw-absolute tw-z-10 tw-bg-white tw-border tw-border-solid tw-border-[color:#eef0f2] tw-shadow-[1px_1px_8px_rgba(126,142,157,0.25)] tw-rounded-[3px] tw-text-[color:#8e9daa] tw-font-normal tw-py-1 tw-px-2 tw-ml-4"
27
- >
28
- <slot name="tooltip"></slot>
29
- </span>
28
+ <slot name="tooltip"></slot>
30
29
  </span>
31
- </div>
30
+ </span>
32
31
  <div
33
32
  v-if="multilanguage"
34
33
  class="tw-text-[color:var(--app-menu-item-icon-color)] tw-shrink-0"
@@ -59,6 +59,7 @@
59
59
  <div class="vc-multivalue__field vc-multivalue__field_dictionary tw-grow tw-basis-0 tw-p-2">
60
60
  <VcButton
61
61
  small
62
+ :disabled="disabled"
62
63
  @click.stop="toggleDropdown"
63
64
  >Add +</VcButton
64
65
  >