@merkaly/nuxt 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.3.0",
7
+ "version": "0.3.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,10 +1,15 @@
1
1
  <script setup>
2
2
  import { useRoute } from "#app";
3
- import { useAuth, watchOnce } from "#imports";
3
+ import { useAuth, watchOnce, addRouteMiddleware, useNuxtApp } from "#imports";
4
4
  import AuthMiddleware from "../middleware/auth";
5
+ import { useNavigation } from "../composables/useNavigation";
5
6
  const $route = useRoute();
6
7
  const { isLoading } = useAuth();
8
+ const { hook } = useNuxtApp();
7
9
  watchOnce(isLoading, () => AuthMiddleware($route, $route));
10
+ const { defer, regenerate } = useNavigation();
11
+ addRouteMiddleware("navigation", (to) => defer(to), { global: true });
12
+ hook("page:finish", () => regenerate());
8
13
  </script>
9
14
 
10
15
  <template>
@@ -60,10 +60,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
60
60
  code: string;
61
61
  city: string;
62
62
  country: string;
63
- locality: string;
64
- state: string;
65
63
  line1: string;
66
64
  line2: string;
65
+ locality: string;
66
+ state: string;
67
67
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
68
68
  declare const _default: typeof __VLS_export;
69
69
  export default _default;
@@ -60,10 +60,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
60
60
  code: string;
61
61
  city: string;
62
62
  country: string;
63
- locality: string;
64
- state: string;
65
63
  line1: string;
66
64
  line2: string;
65
+ locality: string;
66
+ state: string;
67
67
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
68
68
  declare const _default: typeof __VLS_export;
69
69
  export default _default;
@@ -32,7 +32,7 @@ const classList = computed(() => [
32
32
  <template v-if="props.text">
33
33
  <span :class="{ 'flex-row-reverse': props.reversed }" class="d-flex align-items-center">
34
34
  <component :is="props.tag" :class="classList" />
35
- <span class="ps-1" v-text="props.text" />
35
+ <span :class="fontColor" class="ps-1" v-text="props.text" />
36
36
  </span>
37
37
  </template>
38
38
 
@@ -61,10 +61,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
61
61
  mode: keyof Intl.NumberFormatOptionsStyleRegistry;
62
62
  base: number;
63
63
  tag: string;
64
- value: number;
65
64
  currency: string;
66
65
  hideDecimal: boolean;
67
66
  locale: string;
67
+ value: number;
68
68
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
69
69
  declare const _default: typeof __VLS_export;
70
70
  export default _default;
@@ -61,10 +61,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
61
61
  mode: keyof Intl.NumberFormatOptionsStyleRegistry;
62
62
  base: number;
63
63
  tag: string;
64
- value: number;
65
64
  currency: string;
66
65
  hideDecimal: boolean;
67
66
  locale: string;
67
+ value: number;
68
68
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
69
69
  declare const _default: typeof __VLS_export;
70
70
  export default _default;
@@ -74,11 +74,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
74
74
  }>> & Readonly<{
75
75
  "onUpdate:modelValue"?: ((value: Numberish) => any) | undefined;
76
76
  }>, {
77
- prefix: string;
78
- placeholder: string;
79
- max: number;
80
77
  min: number;
78
+ max: number;
79
+ prefix: string;
81
80
  decimal: string;
81
+ placeholder: string;
82
82
  precision: number;
83
83
  suffix: string;
84
84
  thousands: string;
@@ -74,11 +74,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
74
74
  }>> & Readonly<{
75
75
  "onUpdate:modelValue"?: ((value: Numberish) => any) | undefined;
76
76
  }>, {
77
- prefix: string;
78
- placeholder: string;
79
- max: number;
80
77
  min: number;
78
+ max: number;
79
+ prefix: string;
81
80
  decimal: string;
81
+ placeholder: string;
82
82
  precision: number;
83
83
  suffix: string;
84
84
  thousands: string;
@@ -1,3 +1,11 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ declare const search: import("vue").ModelRef<string, string, string, string>;
2
+ type __VLS_ModelProps = {
3
+ modelValue?: typeof search['value'];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:modelValue": (value: string) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
8
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
10
  declare const _default: typeof __VLS_export;
3
11
  export default _default;
@@ -1,10 +1,11 @@
1
1
  <script setup>
2
2
  import FormatIcon from "../format/FormatIcon.vue";
3
+ const search = defineModel({ type: String, default: () => String() });
3
4
  </script>
4
5
 
5
6
  <template>
6
7
  <div class="d-flex align-items-center position-relative">
7
- <BFormInput class="form-control-solid border" placeholder="Search..." />
8
+ <BFormInput v-model="search" class="form-control-solid border" placeholder="Search..." />
8
9
 
9
10
  <FormatIcon class="position-absolute me-3 end-0" mode="regular" name="search" size="4" variant="primary" />
10
11
  </div>
@@ -1,3 +1,11 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ declare const search: import("vue").ModelRef<string, string, string, string>;
2
+ type __VLS_ModelProps = {
3
+ modelValue?: typeof search['value'];
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:modelValue": (value: string) => any;
7
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
8
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
10
  declare const _default: typeof __VLS_export;
3
11
  export default _default;
@@ -78,7 +78,11 @@ function toggleDetails(item) {
78
78
  <BCardHeader class="align-items-center p-4">
79
79
  <BCardTitle>
80
80
  <slot name="search">
81
- <InputSearch :disabled="$datagrid.loading" class="w-250px" />
81
+ <InputSearch
82
+ v-model="$datagrid.search"
83
+ :disabled="$datagrid.loading"
84
+ class="w-250px"
85
+ @change="emit('fetch', 'search')" />
82
86
  </slot>
83
87
  </BCardTitle>
84
88
 
@@ -133,7 +137,7 @@ function toggleDetails(item) {
133
137
 
134
138
  <BTableSimple
135
139
  :class="{ 'h-100': !visibleItems.length || $datagrid.loading }"
136
- class="mb-0"
140
+ class="mb-0 overflow-auto"
137
141
  hover
138
142
  responsive="lg"
139
143
  small
@@ -236,29 +240,31 @@ function toggleDetails(item) {
236
240
  <BCardFooter v-if="!props.hideFooter" class="p-4">
237
241
  <slot name="footer" />
238
242
 
239
- <BRow v-if="!props.hidePagination" align-h="between" align-v="center">
240
- <BCol cols="auto">
241
- <select v-model="$datagrid.limit" class="form-select form-select-sm form-select-solid" disabled>
242
- <BFormSelectOption :value="10">10</BFormSelectOption>
243
- </select>
244
- </BCol>
243
+ <template v-if="!props.hidePagination">
244
+ <BRow align-h="between" align-v="center" class="w-100">
245
+ <BCol cols="auto">
246
+ <select v-model="$datagrid.limit" class="form-select form-select-sm form-select-solid" disabled>
247
+ <BFormSelectOption :value="10">10</BFormSelectOption>
248
+ </select>
249
+ </BCol>
245
250
 
246
- <BCol v-show="!$datagrid.loading" cols="auto">
247
- <BPagination
248
- v-model="$datagrid.page"
249
- :disabled="$datagrid.loading"
250
- :per-page="$datagrid.limit"
251
- :total-rows="$datagrid.total"
252
- class="mb-0"
253
- no-goto-end-buttons
254
- @update:model-value="emit('fetch', 'paginate')" />
255
- </BCol>
251
+ <BCol v-show="!$datagrid.loading" cols="auto">
252
+ <BPagination
253
+ v-model="$datagrid.page"
254
+ :disabled="$datagrid.loading"
255
+ :per-page="$datagrid.limit"
256
+ :total-rows="$datagrid.total"
257
+ class="mb-0"
258
+ no-goto-end-buttons
259
+ @update:model-value="emit('fetch', 'paginate')" />
260
+ </BCol>
256
261
 
257
- <BCol class="fs-7 text-muted" cols="auto">
258
- <span v-if="$datagrid.loading">Loading...</span>
259
- <FormatText v-else :template="paginationText.template" :values="paginationText.values" />
260
- </BCol>
261
- </BRow>
262
+ <BCol class="fs-7 text-muted" cols="auto">
263
+ <span v-if="$datagrid.loading">Loading...</span>
264
+ <FormatText v-else :template="paginationText.template" :values="paginationText.values" />
265
+ </BCol>
266
+ </BRow>
267
+ </template>
262
268
  </BCardFooter>
263
269
  </BCard>
264
270
  </template>
@@ -14,6 +14,7 @@ export interface DataGrid<C = unknown> {
14
14
  limit: number;
15
15
  loading: boolean;
16
16
  page: number;
17
+ search: string;
17
18
  total: number;
18
19
  fn: {
19
20
  addItem: (item: C) => DataGridItem<C>[];
@@ -26,6 +27,7 @@ interface OptionArgs<D> {
26
27
  items?: D[];
27
28
  limit?: number;
28
29
  loading?: boolean;
30
+ search?: string;
29
31
  total?: number;
30
32
  }
31
33
  export declare function useDatagrid<D = unknown>(params: OptionArgs<D>): DataGrid<D>;
@@ -4,6 +4,7 @@ export function useDatagrid(params) {
4
4
  columns: params.columns,
5
5
  error: params.error ?? null,
6
6
  items: params.items ?? [],
7
+ search: params.search,
7
8
  limit: params.limit ?? 10,
8
9
  loading: params.loading ?? false,
9
10
  page: 1,
@@ -1,18 +1,23 @@
1
- import type { RouteLocationNormalized } from 'vue-router';
2
- interface INavigationItem {
1
+ interface NavigationItem {
3
2
  text: string | null;
4
3
  path: string;
4
+ loading?: boolean;
5
5
  }
6
- export declare function useNavigation(page?: INavigationItem): {
6
+ type NavigationItemOrGetter = NavigationItem | (() => NavigationItem);
7
+ export declare function useNavigation(page?: NavigationItemOrGetter): {
7
8
  current: import("vue").ComputedRef<{
8
9
  path: string;
9
10
  text: string | null;
11
+ loading: boolean;
10
12
  } | undefined>;
11
13
  items: import("vue").ComputedRef<{
12
14
  path: string;
13
15
  text: string | null;
16
+ loading: boolean;
14
17
  }[]>;
15
- regenerate: (route: RouteLocationNormalized) => void;
16
- setPage: (item: INavigationItem) => number | undefined;
18
+ defer: (route: {
19
+ path: string;
20
+ }) => void;
21
+ regenerate: () => void;
17
22
  };
18
23
  export {};
@@ -2,45 +2,50 @@ import { computed } from "vue";
2
2
  import { useState } from "#imports";
3
3
  export function useNavigation(page) {
4
4
  const list = useState("breadcrumbs", () => []);
5
+ const pendingRoute = useState("breadcrumbs:pending", () => null);
5
6
  function normalizePath(base, path) {
6
- path = String(path);
7
- if (!path.startsWith("/")) {
8
- path = "/" + path;
9
- }
10
- if (base.endsWith("/")) {
11
- base = base.slice(0, -1);
12
- }
13
- return base + path;
7
+ return `${base.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
14
8
  }
15
- const items = computed(() => {
9
+ function resolve(item) {
10
+ return typeof item === "function" ? item() : item;
11
+ }
12
+ const resolved = computed(() => {
16
13
  let uri = "";
17
- return list.value.map(({ text, path }) => {
14
+ return list.value.map((item) => {
15
+ const { text, path = "", loading } = resolve(item);
18
16
  uri = normalizePath(uri, path);
19
- return { path: uri, text };
20
- }).filter(({ text }) => text !== null);
17
+ return { path: uri, text, loading: !!loading };
18
+ });
19
+ });
20
+ const items = computed(() => {
21
+ return resolved.value.filter(({ text, loading }) => text != null && !loading);
21
22
  });
22
23
  const current = computed(() => {
23
- const length = items.value.length;
24
- return items.value.at(length - 1);
24
+ return resolved.value.at(-1);
25
25
  });
26
- const setPage = (item) => {
27
- const newItem = JSON.stringify(item);
28
- const existingItems = list.value.map((item2) => JSON.stringify(item2));
29
- if (existingItems.includes(newItem)) {
30
- return;
31
- }
32
- return list.value.push(item);
33
- };
34
- if (page) {
35
- setPage(page);
36
- }
37
- function regenerate(route) {
38
- const itemIndex = items.value.findLastIndex((value) => route.path.startsWith(value.path));
26
+ function regenerate() {
27
+ if (!pendingRoute.value) return;
28
+ const route = pendingRoute.value;
29
+ pendingRoute.value = null;
30
+ const itemIndex = resolved.value.findLastIndex((value) => route.startsWith(value.path));
39
31
  if (itemIndex >= 0) {
40
32
  list.value = list.value.slice(0, itemIndex + 1);
41
33
  return;
42
34
  }
43
35
  list.value = [];
44
36
  }
45
- return { current, items, regenerate, setPage };
37
+ if (page) {
38
+ regenerate();
39
+ const { path } = resolve(page);
40
+ const existingIndex = list.value.findIndex((i) => resolve(i).path === path);
41
+ if (existingIndex >= 0) {
42
+ list.value[existingIndex] = page;
43
+ } else {
44
+ list.value.push(page);
45
+ }
46
+ }
47
+ function defer(route) {
48
+ pendingRoute.value = route.path;
49
+ }
50
+ return { current, items, defer, regenerate };
46
51
  }
@@ -0,0 +1,16 @@
1
+ type FieldConstraints = {
2
+ maxlength?: number;
3
+ minlength?: number;
4
+ required?: boolean;
5
+ };
6
+ type FieldAttrs = FieldConstraints & {
7
+ state?: boolean | null;
8
+ };
9
+ export declare function useValidator<T extends object>(instance: T): {
10
+ form: T;
11
+ attrs: import("vue").ComputedRef<{ [K in keyof T]?: FieldAttrs | undefined; }>;
12
+ valid: import("vue").ComputedRef<boolean>;
13
+ errors: Record<keyof T, string>;
14
+ validate: () => Promise<boolean>;
15
+ };
16
+ export {};
@@ -0,0 +1,59 @@
1
+ import { reactive, computed, ref } from "vue";
2
+ import { validate as classValidate, getMetadataStorage } from "class-validator";
3
+ const handlers = {
4
+ isLength(entry, [min, max]) {
5
+ if (min > 0) entry.minlength = min;
6
+ if (max != null) entry.maxlength = max;
7
+ },
8
+ isNotEmpty(entry) {
9
+ entry.required = true;
10
+ }
11
+ };
12
+ function extractConstraints(constructor) {
13
+ const metadata = getMetadataStorage().getTargetValidationMetadatas(constructor, "", false, false);
14
+ const constraints = {};
15
+ for (const meta of metadata) {
16
+ const prop = meta.propertyName;
17
+ if (!constraints[prop]) constraints[prop] = {};
18
+ const handler = handlers[meta.name];
19
+ if (handler) handler(constraints[prop], meta.constraints ?? []);
20
+ }
21
+ return constraints;
22
+ }
23
+ function applyErrors(result, errors, state) {
24
+ for (const key of Object.keys(errors)) {
25
+ errors[key] = "";
26
+ state[key] = true;
27
+ }
28
+ for (const error of result) {
29
+ const messages = error.constraints ? Object.values(error.constraints) : [];
30
+ errors[error.property] = messages[0] || "Invalid";
31
+ state[error.property] = false;
32
+ }
33
+ }
34
+ function mergeAttrs(constraints, state) {
35
+ const result = {};
36
+ const keys = /* @__PURE__ */ new Set([...Object.keys(constraints), ...Object.keys(state)]);
37
+ for (const key of keys) {
38
+ const prop = key;
39
+ result[prop] = { ...constraints[prop], state: state[key] ?? null };
40
+ }
41
+ return result;
42
+ }
43
+ export function useValidator(instance) {
44
+ const form = reactive(instance);
45
+ const constraints = extractConstraints(instance.constructor);
46
+ const errors = reactive({});
47
+ const state = reactive({});
48
+ const validated = ref(false);
49
+ const valid = computed(() => validated.value && !Object.values(errors).some((v) => v));
50
+ const attrs = computed(() => mergeAttrs(constraints, state));
51
+ async function validate() {
52
+ const plain = Object.assign(Object.create(Object.getPrototypeOf(instance)), form);
53
+ const result = await classValidate(plain);
54
+ applyErrors(result, errors, state);
55
+ validated.value = true;
56
+ return result.length === 0;
57
+ }
58
+ return { form, attrs, valid, errors, validate };
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merkaly/nuxt",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/merkaly-io/nuxt.git"
@@ -60,6 +60,8 @@
60
60
  "bootstrap": "^5.3.8",
61
61
  "bootstrap-vue-next": "^0.43.0",
62
62
  "change-case": "^5",
63
+ "class-transformer": "^0.5.1",
64
+ "class-validator": "^0.14.3",
63
65
  "eslint": "^9.39.1",
64
66
  "filesize": "^11.0.2",
65
67
  "nuxt": "^4.3.0",