@ramathibodi/nuxt-commons 0.1.72 → 0.1.73

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.1.72",
7
+ "version": "0.1.73",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -13,6 +13,11 @@ const props = withDefaults(defineProps<Props & MasterLikeProps>(), {
13
13
  waitForFilter: false,
14
14
  meilisearch: false,
15
15
  cache: false,
16
+
17
+ statusField: 'statusCode',
18
+ activeValue: 'active',
19
+ hideInactiveInList: true,
20
+ preventSelectingInactive: true,
16
21
  })
17
22
 
18
23
  const {
@@ -43,6 +48,11 @@ const {
43
48
  model-selected-item="masterItemByGroupKeyAndItemCodeIn"
44
49
  :model-selected-item-by="computedModelSelectedItemBy"
45
50
  model-selected-item-key="itemCodes"
51
+
52
+ :status-field="props.statusField"
53
+ :active-value="props.activeValue"
54
+ :hide-inactive-in-list="props.hideInactiveInList"
55
+ :prevent-selecting-inactive="props.preventSelectingInactive"
46
56
  >
47
57
  <!-- pass-through slots -->
48
58
  <!-- @ts-ignore -->
@@ -13,6 +13,11 @@ const props = withDefaults(defineProps<Props & MasterLikeProps>(), {
13
13
  waitForFilter: false,
14
14
  meilisearch: false,
15
15
  cache: false,
16
+
17
+ statusField: 'statusCode',
18
+ activeValue: 'active',
19
+ hideInactiveInList: true,
20
+ preventSelectingInactive: true,
16
21
  })
17
22
 
18
23
  const {
@@ -43,6 +48,11 @@ const {
43
48
  model-selected-item="masterItemByGroupKeyAndItemCodeIn"
44
49
  :model-selected-item-by="computedModelSelectedItemBy"
45
50
  model-selected-item-key="itemCodes"
51
+
52
+ :status-field="props.statusField"
53
+ :active-value="props.activeValue"
54
+ :hide-inactive-in-list="props.hideInactiveInList"
55
+ :prevent-selecting-inactive="props.preventSelectingInactive"
46
56
  >
47
57
  <!-- pass-through slots -->
48
58
  <!-- @ts-ignore -->
@@ -70,4 +80,4 @@ const {
70
80
 
71
81
  <style>
72
82
  .meilisearch-item em{font-weight:700}
73
- </style>
83
+ </style>
@@ -12,6 +12,11 @@ const props = withDefaults(defineProps<Props & StaticMasterLikeProps>(), {
12
12
  noDataText: 'ไม่พบข้อมูล',
13
13
  waitForFilter: false,
14
14
  cache: false,
15
+
16
+ statusField: 'statusCode',
17
+ activeValue: 'active',
18
+ hideInactiveInList: true,
19
+ preventSelectingInactive: true,
15
20
  })
16
21
 
17
22
  const {
@@ -36,6 +41,10 @@ const {
36
41
  :no-data-text="computedNoDataText"
37
42
  :show-code="props.showCode"
38
43
  :cache="props.cache"
44
+ :status-field="props.statusField"
45
+ :active-value="props.activeValue"
46
+ :hide-inactive-in-list="props.hideInactiveInList"
47
+ :prevent-selecting-inactive="props.preventSelectingInactive"
39
48
  >
40
49
  <!-- pass-through slots -->
41
50
  <!-- @ts-ignore -->
@@ -51,4 +60,4 @@ const {
51
60
  {{ formatItemTitle(item) }}
52
61
  </template>
53
62
  </model-combobox>
54
- </template>
63
+ </template>
@@ -10,11 +10,17 @@ const props = withDefaults(defineProps<Props & LookupProps & PersistSlimProps>()
10
10
  fuzzy: false,
11
11
  showCode: false,
12
12
  cache: false,
13
+
13
14
  serverSearch: false,
14
15
  searchSearchSort: false,
15
16
  serverSearchText: 'Type to search…',
16
17
  serverSearchLoadingText: 'Searching…',
17
18
  serverSearchDebounce: 500,
19
+
20
+ statusField: 'statusCode',
21
+ activeValue: 'active',
22
+ hideInactiveInList: false,
23
+ preventSelectingInactive: false,
18
24
  })
19
25
 
20
26
  const emit = defineEmits<{
@@ -22,8 +28,7 @@ const emit = defineEmits<{
22
28
  }>()
23
29
 
24
30
  const selectedItems = defineModel<any>()
25
-
26
- useLocalStorageModel(selectedItems,props)
31
+ useLocalStorageModel(selectedItems, props)
27
32
 
28
33
  const {
29
34
  searchData,
@@ -33,12 +38,14 @@ const {
33
38
  computedNoDataText,
34
39
  isLoading,
35
40
  isErrorLoading,
36
- } = useLookupList(props, emit, selectedItems)
41
+ onUpdateModelValue, // moved into composable
42
+ } = useLookupList(props as any, emit, selectedItems)
37
43
  </script>
38
44
 
39
45
  <template>
40
46
  <v-autocomplete
41
- v-model="selectedItems"
47
+ :model-value="selectedItems"
48
+ @update:model-value="onUpdateModelValue"
42
49
  v-model:search="searchData"
43
50
  v-bind="$attrs"
44
51
  :items="computedItems"
@@ -15,6 +15,11 @@ const props = withDefaults(defineProps<Props & LookupProps & PersistSlimProps>()
15
15
  serverSearchText: 'Type to search…',
16
16
  serverSearchLoadingText: 'Searching…',
17
17
  serverSearchDebounce: 500,
18
+
19
+ statusField: 'statusCode',
20
+ activeValue: 'active',
21
+ hideInactiveInList: false,
22
+ preventSelectingInactive: false,
18
23
  })
19
24
 
20
25
  const emit = defineEmits<{
@@ -22,8 +27,7 @@ const emit = defineEmits<{
22
27
  }>()
23
28
 
24
29
  const selectedItems = defineModel<any>()
25
-
26
- useLocalStorageModel(selectedItems,props)
30
+ useLocalStorageModel(selectedItems, props)
27
31
 
28
32
  const {
29
33
  searchData,
@@ -33,12 +37,14 @@ const {
33
37
  computedNoDataText,
34
38
  isLoading,
35
39
  isErrorLoading,
36
- } = useLookupList(props, emit, selectedItems)
40
+ onUpdateModelValue, // from composable
41
+ } = useLookupList(props as any, emit, selectedItems)
37
42
  </script>
38
43
 
39
44
  <template>
40
45
  <v-combobox
41
- v-model="selectedItems"
46
+ :model-value="selectedItems"
47
+ @update:model-value="onUpdateModelValue"
42
48
  v-model:search="searchData"
43
49
  v-bind="$attrs"
44
50
  :items="computedItems"
@@ -10,6 +10,11 @@ const props = withDefaults(defineProps<Props & StaticLookupProps & PersistSlimPr
10
10
  fuzzy: false,
11
11
  showCode: false,
12
12
  cache: false,
13
+
14
+ statusField: 'statusCode',
15
+ activeValue: 'active',
16
+ hideInactiveInList: false,
17
+ preventSelectingInactive: false,
13
18
  })
14
19
 
15
20
  const emit = defineEmits<{
@@ -17,8 +22,7 @@ const emit = defineEmits<{
17
22
  }>()
18
23
 
19
24
  const selectedItems = defineModel<any>()
20
-
21
- useLocalStorageModel(selectedItems,props)
25
+ useLocalStorageModel(selectedItems, props)
22
26
 
23
27
  const {
24
28
  computedItems,
@@ -27,12 +31,16 @@ const {
27
31
  computedNoDataText,
28
32
  isLoading,
29
33
  isErrorLoading,
30
- } = useLookupList(props, emit, selectedItems)
34
+ searchData,
35
+ onUpdateModelValue, // ✅ from composable
36
+ } = useLookupList(props as any, emit, selectedItems)
31
37
  </script>
32
38
 
33
39
  <template>
34
40
  <v-combobox
35
- v-model="selectedItems"
41
+ :model-value="selectedItems"
42
+ @update:model-value="onUpdateModelValue"
43
+ v-model:search="searchData"
36
44
  v-bind="$attrs"
37
45
  :items="computedItems"
38
46
  :filter-keys="computedFilterKeys"
@@ -13,6 +13,10 @@ export interface StaticLookupProps {
13
13
  noDataText?: string;
14
14
  placeholder?: string;
15
15
  multiple?: boolean;
16
+ statusField?: string;
17
+ activeValue?: any;
18
+ hideInactiveInList?: boolean;
19
+ preventSelectingInactive?: boolean;
16
20
  }
17
21
  export interface LookupProps extends StaticLookupProps {
18
22
  serverSearch?: boolean;
@@ -28,9 +32,9 @@ export interface LookupProps extends StaticLookupProps {
28
32
  export interface LookupEmits {
29
33
  (e: 'update:selectedObject', payload: any | any[]): void;
30
34
  }
31
- export declare function useLookupList(props: LookupProps, emit: LookupEmits, selectedItems: Ref<any>): {
32
- modelItems: Ref<any[], any[]>;
33
- items: Ref<any[], any[]>;
35
+ export declare function useLookupList<T extends Record<string, any>>(props: LookupProps, emit: LookupEmits, selectedItems: Ref<any>): {
36
+ modelItems: import("vue").ShallowRef<T[], T[]>;
37
+ items: import("vue").ShallowRef<T[], T[]>;
34
38
  searchData: Ref<string, string>;
35
39
  isLoading: Ref<boolean, boolean>;
36
40
  isErrorLoading: Ref<boolean, boolean>;
@@ -38,9 +42,12 @@ export declare function useLookupList(props: LookupProps, emit: LookupEmits, sel
38
42
  computedFilterKeys: import("vue").ComputedRef<string | string[]>;
39
43
  computedPlaceholder: import("vue").ComputedRef<string | undefined>;
40
44
  computedNoDataText: import("vue").ComputedRef<string | undefined>;
41
- computedItems: import("vue").ComputedRef<any[]>;
42
- selectedItemsObject: Ref<any[], any[]>;
45
+ computedItems: import("vue").ComputedRef<T[]>;
46
+ selectedItemsObject: import("vue").ShallowRef<T[], T[]>;
43
47
  syncSelectedFromModelValue: () => Promise<void>;
48
+ isSelectableValue: (value: any) => boolean;
49
+ setModelValueGuarded: (val: any) => void;
50
+ onUpdateModelValue: (val: any) => void;
44
51
  loadItems: () => Promise<void>;
45
52
  applyFuzzy: () => void;
46
53
  };
@@ -1,17 +1,24 @@
1
- import { computed, ref, watch } from "vue";
1
+ import { computed, ref, shallowRef, watch } from "vue";
2
2
  import { watchDebounced } from "@vueuse/core";
3
3
  import { union, isEmpty, isArray, sortBy, castArray } from "lodash-es";
4
4
  import { useFuzzy } from "./utils/fuzzy.js";
5
5
  import { useGraphQlOperation } from "./graphqlOperation.js";
6
6
  export function useLookupList(props, emit, selectedItems) {
7
- const modelItems = ref([]);
8
- const items = ref([]);
7
+ const modelItems = shallowRef([]);
8
+ const items = shallowRef([]);
9
+ const selectedItemsObject = shallowRef([]);
9
10
  const searchData = ref("");
10
11
  const isLoading = ref(false);
11
12
  const isErrorLoading = ref(false);
13
+ const statusField = computed(() => props.statusField ?? "statusCode");
14
+ const activeValue = computed(() => props.activeValue ?? "active");
15
+ const hideInactiveInList = computed(() => props.hideInactiveInList ?? false);
12
16
  const queryFields = computed(() => {
13
17
  let fieldsArray = [props.itemTitle, props.itemValue];
14
18
  if (props.fields) fieldsArray = union(fieldsArray, props.fields);
19
+ if (hideInactiveInList.value && statusField.value) {
20
+ fieldsArray = union(fieldsArray, [statusField.value]);
21
+ }
15
22
  return fieldsArray;
16
23
  });
17
24
  const computedFilterKeys = computed(() => {
@@ -59,7 +66,7 @@ export function useLookupList(props, emit, selectedItems) {
59
66
  modelItems.value = [];
60
67
  items.value = [];
61
68
  }
62
- } catch (e) {
69
+ } catch {
63
70
  if (id !== requestId) return;
64
71
  isErrorLoading.value = true;
65
72
  modelItems.value = [];
@@ -84,7 +91,13 @@ export function useLookupList(props, emit, selectedItems) {
84
91
  }
85
92
  items.value = output;
86
93
  }
87
- const selectedItemsObject = ref([]);
94
+ const itemMap = computed(() => {
95
+ const m = /* @__PURE__ */ new Map();
96
+ for (const it of modelItems.value) {
97
+ m.set(it?.[props.itemValue], it);
98
+ }
99
+ return m;
100
+ });
88
101
  async function syncSelectedFromModelValue() {
89
102
  const modelValueData = selectedItems.value;
90
103
  const values = castArray(modelValueData).filter(
@@ -95,24 +108,24 @@ export function useLookupList(props, emit, selectedItems) {
95
108
  selectedItemsObject.value = [];
96
109
  return;
97
110
  }
98
- let alreadyInObject = selectedItemsObject.value?.filter((item) => values.includes(item?.[props.itemValue])) || [];
99
- const haveSet = /* @__PURE__ */ new Set([...alreadyInObject.map((item) => item?.[props.itemValue])]);
100
- const missingValues = values.filter((value) => !haveSet.has(value));
111
+ const alreadyInObject = selectedItemsObject.value?.filter((it) => values.includes(it?.[props.itemValue])) || [];
112
+ const haveSet = new Set(alreadyInObject.map((it) => it?.[props.itemValue]));
113
+ const missingValues = values.filter((v) => !haveSet.has(v));
101
114
  const stillMissing = [];
102
- for (const value of missingValues) {
103
- const localHit = modelItems.value.find((item) => item?.[props.itemValue] === value);
115
+ for (const v of missingValues) {
116
+ const localHit = itemMap.value.get(v);
104
117
  if (localHit) {
105
118
  alreadyInObject.push(localHit);
106
- haveSet.add(value);
119
+ haveSet.add(v);
107
120
  } else {
108
- stillMissing.push(value);
121
+ stillMissing.push(v);
109
122
  }
110
123
  }
111
124
  if (stillMissing.length && props.modelSelectedItem) {
112
125
  try {
113
126
  const key = props.modelSelectedItemKey || "id";
114
127
  const variables = { ...props.modelSelectedItemBy || {} };
115
- variables[key] = values;
128
+ variables[key] = stillMissing;
116
129
  const result = await useGraphQlOperation(
117
130
  "Query",
118
131
  props.modelSelectedItem,
@@ -120,13 +133,19 @@ export function useLookupList(props, emit, selectedItems) {
120
133
  variables,
121
134
  props.cache
122
135
  );
123
- selectedItemsObject.value = castArray(result);
136
+ const resolved = castArray(result);
137
+ for (const obj of resolved) {
138
+ const v = obj?.[props.itemValue];
139
+ if (!haveSet.has(v)) {
140
+ alreadyInObject.push(obj);
141
+ haveSet.add(v);
142
+ }
143
+ }
124
144
  } catch {
125
145
  }
126
- } else {
127
- selectedItemsObject.value = alreadyInObject;
128
146
  }
129
- emit("update:selectedObject", props.multiple ? selectedItemsObject.value : selectedItemsObject.value[0]);
147
+ selectedItemsObject.value = alreadyInObject;
148
+ emit("update:selectedObject", props.multiple ? alreadyInObject : alreadyInObject[0]);
130
149
  }
131
150
  watchDebounced(
132
151
  () => [
@@ -159,18 +178,44 @@ export function useLookupList(props, emit, selectedItems) {
159
178
  () => {
160
179
  syncSelectedFromModelValue();
161
180
  },
162
- { immediate: true, deep: true }
181
+ { immediate: true }
163
182
  );
164
183
  const computedItems = computed(() => {
165
184
  const sortByField = !props.sortBy || typeof props.sortBy === "string" ? [props.sortBy || (props.showCode ? props.itemValue : props.itemTitle)] : props.sortBy;
166
- const baseItems = (props.fuzzy || props.serverSearch && !props.searchSearchSort) && !isEmpty(searchData.value) ? items.value : sortBy(items.value, sortByField);
185
+ const baseSource = (props.fuzzy || props.serverSearch && !props.searchSearchSort) && !isEmpty(searchData.value) ? items.value : sortBy(items.value, sortByField);
186
+ let baseItems = [...baseSource];
187
+ if (hideInactiveInList.value) {
188
+ baseItems = baseItems.filter((it) => it?.[statusField.value] === activeValue.value);
189
+ }
167
190
  for (const selectedItem of selectedItemsObject.value || []) {
168
- if (!baseItems.find((existingItem) => existingItem[props.itemValue] === selectedItem[props.itemValue])) {
191
+ const v = selectedItem?.[props.itemValue];
192
+ if (!baseItems.find((x) => x?.[props.itemValue] === v)) {
169
193
  baseItems.push(selectedItem);
170
194
  }
171
195
  }
172
196
  return baseItems;
173
197
  });
198
+ function isSelectableValue(value) {
199
+ if (!hideInactiveInList.value) return true;
200
+ const local = itemMap.value.get(value);
201
+ const obj = local ?? selectedItemsObject.value.find((it) => it?.[props.itemValue] === value) ?? modelItems.value.find((it) => it?.[props.itemValue] === value);
202
+ if (!obj) return true;
203
+ return obj?.[statusField.value] === activeValue.value;
204
+ }
205
+ function setModelValueGuarded(val) {
206
+ if (!(props.preventSelectingInactive ?? false)) {
207
+ selectedItems.value = val;
208
+ return;
209
+ }
210
+ if (!props.multiple) {
211
+ if (val != null && !isSelectableValue(val)) return;
212
+ selectedItems.value = val;
213
+ return;
214
+ }
215
+ const arr = Array.isArray(val) ? val : [];
216
+ selectedItems.value = arr.filter((v) => isSelectableValue(v));
217
+ }
218
+ const onUpdateModelValue = (val) => setModelValueGuarded(val);
174
219
  return {
175
220
  // state
176
221
  modelItems,
@@ -187,7 +232,12 @@ export function useLookupList(props, emit, selectedItems) {
187
232
  // selection
188
233
  selectedItemsObject,
189
234
  syncSelectedFromModelValue,
190
- // actions (exposed for testing if needed)
235
+ // selection policy helper
236
+ isSelectableValue,
237
+ // guarded setter for v-model updates
238
+ setModelValueGuarded,
239
+ onUpdateModelValue,
240
+ // actions
191
241
  loadItems,
192
242
  applyFuzzy
193
243
  };
@@ -12,6 +12,10 @@ export interface StaticMasterLikeProps {
12
12
  waitForFilter?: boolean;
13
13
  waitForFilterText?: string;
14
14
  cache?: boolean | number;
15
+ statusField?: string;
16
+ activeValue?: any;
17
+ hideInactiveInList?: boolean;
18
+ preventSelectingInactive?: boolean;
15
19
  }
16
20
  export interface MasterLikeProps extends StaticMasterLikeProps {
17
21
  meilisearch?: boolean;
@@ -16,13 +16,20 @@ export function useLookupListMaster(props) {
16
16
  return modelBy;
17
17
  });
18
18
  const computedModelSelectedItemBy = computed(() => ({ groupKey: props.groupKey }));
19
+ const itemTitleField = computed(() => props.lang === "TH" ? "itemValue" : "itemValueAlternative");
19
20
  const computedFields = computed(() => {
20
21
  const extra = [];
21
22
  if (props.filterText) extra.push("filterText");
22
23
  if (props.meilisearch) extra.push("_formatted");
23
- return union(["itemCode", "itemValue", "itemValueAlternative"], extra, props.fields || []);
24
+ const statusField = props.statusField ?? "statusCode";
25
+ const includeStatus = (props.hideInactiveInList ?? true) && statusField;
26
+ return union(
27
+ ["itemCode", "itemValue", "itemValueAlternative"],
28
+ includeStatus ? [statusField] : [],
29
+ extra,
30
+ props.fields || []
31
+ );
24
32
  });
25
- const itemTitleField = computed(() => props.lang === "TH" ? "itemValue" : "itemValueAlternative");
26
33
  const formatItemTitle = (item) => {
27
34
  if (props.meilisearch) {
28
35
  const raw = item.raw?._formatted ?? {};
@@ -47,7 +54,6 @@ export function useLookupListMaster(props) {
47
54
  return [props.sortBy ?? "itemValue"];
48
55
  });
49
56
  return {
50
- // for <model-*> props
51
57
  computedModelName,
52
58
  computedModelBy,
53
59
  computedModelSelectedItemBy,
@@ -55,7 +61,6 @@ export function useLookupListMaster(props) {
55
61
  itemTitleField,
56
62
  computedNoDataText,
57
63
  computedSortBy,
58
- // helpers
59
64
  formatItemTitle
60
65
  };
61
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramathibodi/nuxt-commons",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "Ramathibodi Nuxt modules for common components",
5
5
  "repository": {
6
6
  "type": "git",