pukaad-ui-lib 1.418.0 → 1.420.0

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 (26) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/card/card-content/card-content-menu.vue +57 -1
  3. package/dist/runtime/components/display/display-video.d.vue.ts +4 -4
  4. package/dist/runtime/components/display/display-video.vue.d.ts +4 -4
  5. package/dist/runtime/components/image/image-cropper.d.vue.ts +2 -2
  6. package/dist/runtime/components/image/image-cropper.vue.d.ts +2 -2
  7. package/dist/runtime/components/input/input-autocomplete.d.vue.ts +1 -1
  8. package/dist/runtime/components/input/input-autocomplete.vue.d.ts +1 -1
  9. package/dist/runtime/components/input/input-combobox.d.vue.ts +1 -1
  10. package/dist/runtime/components/input/input-combobox.vue.d.ts +1 -1
  11. package/dist/runtime/components/input/input-localized-name.d.vue.ts +1 -1
  12. package/dist/runtime/components/input/input-localized-name.vue.d.ts +1 -1
  13. package/dist/runtime/components/input/input-suggest.d.vue.ts +1 -1
  14. package/dist/runtime/components/input/input-suggest.vue.d.ts +1 -1
  15. package/dist/runtime/components/input/input-tag-modal.d.vue.ts +52 -0
  16. package/dist/runtime/components/input/input-tag-modal.vue +307 -0
  17. package/dist/runtime/components/input/input-tag-modal.vue.d.ts +52 -0
  18. package/dist/runtime/components/input/input-tag.d.vue.ts +1 -1
  19. package/dist/runtime/components/input/input-tag.vue.d.ts +1 -1
  20. package/dist/runtime/components/input/input-textarea.d.vue.ts +1 -1
  21. package/dist/runtime/components/input/input-textarea.vue.d.ts +1 -1
  22. package/dist/runtime/components/picker/picker-option-menu/picker-option-menu-user.vue +49 -1
  23. package/package.json +1 -1
  24. package/dist/runtime/components/input/input-select-tag.d.vue.ts +0 -3
  25. package/dist/runtime/components/input/input-select-tag.vue +0 -57
  26. package/dist/runtime/components/input/input-select-tag.vue.d.ts +0 -3
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.418.0",
4
+ "version": "1.420.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -35,9 +35,24 @@ const onActionClick = (action) => {
35
35
  };
36
36
  const isPinned = computed(() => props.item?.pinned ?? props.isPinned);
37
37
  const isHidden = computed(() => props.item?.hidden ?? props.isHidden);
38
- const isBookmarked = computed(() => props.item?.bookmarked ?? props.isBookmarked);
38
+ const isBookmarked = computed(
39
+ () => props.item?.bookmarked ?? props.isBookmarked
40
+ );
39
41
  const actions = computed(() => {
40
42
  if (props.state === "my-content") {
43
+ const pinAction = props.hidePin ? [] : [{ name: "pin", icon: "lucide:pin", isActive: isPinned.value }];
44
+ return [
45
+ ...pinAction,
46
+ ...props.hidePreview ? [] : [{ name: "preview", icon: "lucide:tv-minimal-play" }],
47
+ {
48
+ name: "delete",
49
+ icon: "lucide:trash-2",
50
+ variant: "default",
51
+ color: "destructive"
52
+ }
53
+ ];
54
+ }
55
+ if (props.state === "business") {
41
56
  const result = [];
42
57
  if (!props.hidePin && !isHidden.value) {
43
58
  result.push({
@@ -93,6 +108,47 @@ const actions = computed(() => {
93
108
  }
94
109
  return result;
95
110
  }
111
+ if (props.state === "expired") {
112
+ return [
113
+ ...props.hidePreview ? [] : [{ name: "preview", icon: "lucide:tv-minimal-play" }],
114
+ { name: "renew", icon: "lucide:history" },
115
+ {
116
+ name: "delete",
117
+ icon: "lucide:trash-2",
118
+ variant: "default",
119
+ color: "destructive"
120
+ }
121
+ ];
122
+ }
123
+ if (props.state === "trash") {
124
+ return [
125
+ { name: "recover", icon: "lucide:rotate-ccw" },
126
+ {
127
+ name: "delete",
128
+ icon: "lucide:trash-2",
129
+ variant: "default",
130
+ color: "destructive"
131
+ }
132
+ ];
133
+ }
134
+ if (props.state === "draft") {
135
+ return [
136
+ {
137
+ name: "preview",
138
+ icon: "lucide:tv-minimal-play"
139
+ },
140
+ {
141
+ name: "edit",
142
+ icon: "lucide:square-pen"
143
+ },
144
+ {
145
+ name: "delete",
146
+ icon: "lucide:trash-2",
147
+ variant: "default",
148
+ color: "destructive"
149
+ }
150
+ ];
151
+ }
96
152
  return [];
97
153
  });
98
154
  const visibleActions = computed(
@@ -81,16 +81,16 @@ declare const __VLS_base: import("vue").DefineComponent<DisplayVideoProps, {
81
81
  toggle: () => void;
82
82
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
83
83
  close: (payload: DisplayVideoAction) => any;
84
- prev: (payload: DisplayVideoAction) => any;
85
- next: (payload: DisplayVideoAction) => any;
86
84
  fullscreen: (payload: DisplayVideoAction) => any;
87
85
  "play-click": (payload: DisplayVideoAction) => any;
86
+ prev: (payload: DisplayVideoAction) => any;
87
+ next: (payload: DisplayVideoAction) => any;
88
88
  }, string, import("vue").PublicProps, Readonly<DisplayVideoProps> & Readonly<{
89
89
  onClose?: ((payload: DisplayVideoAction) => any) | undefined;
90
- onPrev?: ((payload: DisplayVideoAction) => any) | undefined;
91
- onNext?: ((payload: DisplayVideoAction) => any) | undefined;
92
90
  onFullscreen?: ((payload: DisplayVideoAction) => any) | undefined;
93
91
  "onPlay-click"?: ((payload: DisplayVideoAction) => any) | undefined;
92
+ onPrev?: ((payload: DisplayVideoAction) => any) | undefined;
93
+ onNext?: ((payload: DisplayVideoAction) => any) | undefined;
94
94
  }>, {
95
95
  src: string;
96
96
  fullHeight: boolean;
@@ -81,16 +81,16 @@ declare const __VLS_base: import("vue").DefineComponent<DisplayVideoProps, {
81
81
  toggle: () => void;
82
82
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
83
83
  close: (payload: DisplayVideoAction) => any;
84
- prev: (payload: DisplayVideoAction) => any;
85
- next: (payload: DisplayVideoAction) => any;
86
84
  fullscreen: (payload: DisplayVideoAction) => any;
87
85
  "play-click": (payload: DisplayVideoAction) => any;
86
+ prev: (payload: DisplayVideoAction) => any;
87
+ next: (payload: DisplayVideoAction) => any;
88
88
  }, string, import("vue").PublicProps, Readonly<DisplayVideoProps> & Readonly<{
89
89
  onClose?: ((payload: DisplayVideoAction) => any) | undefined;
90
- onPrev?: ((payload: DisplayVideoAction) => any) | undefined;
91
- onNext?: ((payload: DisplayVideoAction) => any) | undefined;
92
90
  onFullscreen?: ((payload: DisplayVideoAction) => any) | undefined;
93
91
  "onPlay-click"?: ((payload: DisplayVideoAction) => any) | undefined;
92
+ onPrev?: ((payload: DisplayVideoAction) => any) | undefined;
93
+ onNext?: ((payload: DisplayVideoAction) => any) | undefined;
94
94
  }>, {
95
95
  src: string;
96
96
  fullHeight: boolean;
@@ -65,14 +65,14 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
65
65
  src: string;
66
66
  center: boolean;
67
67
  crossorigin: "" | "anonymous" | "use-credentials";
68
+ background: boolean;
69
+ modal: boolean;
68
70
  responsive: boolean;
69
71
  restore: boolean;
70
72
  checkCrossOrigin: boolean;
71
73
  checkOrientation: boolean;
72
- modal: boolean;
73
74
  guides: boolean;
74
75
  highlight: boolean;
75
- background: boolean;
76
76
  autoCrop: boolean;
77
77
  movable: boolean;
78
78
  rotatable: boolean;
@@ -65,14 +65,14 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
65
65
  src: string;
66
66
  center: boolean;
67
67
  crossorigin: "" | "anonymous" | "use-credentials";
68
+ background: boolean;
69
+ modal: boolean;
68
70
  responsive: boolean;
69
71
  restore: boolean;
70
72
  checkCrossOrigin: boolean;
71
73
  checkOrientation: boolean;
72
- modal: boolean;
73
74
  guides: boolean;
74
75
  highlight: boolean;
75
- background: boolean;
76
76
  autoCrop: boolean;
77
77
  movable: boolean;
78
78
  rotatable: boolean;
@@ -70,9 +70,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
70
70
  required: boolean;
71
71
  description: string;
72
72
  options: AutocompleteOption[] | string[] | number[];
73
+ limit: number;
73
74
  labelKey: string;
74
75
  placeholder: string;
75
- limit: number;
76
76
  disabledErrorMessage: boolean;
77
77
  disabledBorder: boolean;
78
78
  showCounter: boolean;
@@ -70,9 +70,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
70
70
  required: boolean;
71
71
  description: string;
72
72
  options: AutocompleteOption[] | string[] | number[];
73
+ limit: number;
73
74
  labelKey: string;
74
75
  placeholder: string;
75
- limit: number;
76
76
  disabledErrorMessage: boolean;
77
77
  disabledBorder: boolean;
78
78
  showCounter: boolean;
@@ -59,8 +59,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
59
59
  }>, {
60
60
  pageSize: number;
61
61
  name: string;
62
- labelKey: string;
63
62
  limit: number;
63
+ labelKey: string;
64
64
  valueKey: string;
65
65
  multiple: boolean;
66
66
  placeholderSearch: string;
@@ -59,8 +59,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
59
59
  }>, {
60
60
  pageSize: number;
61
61
  name: string;
62
- labelKey: string;
63
62
  limit: number;
63
+ labelKey: string;
64
64
  valueKey: string;
65
65
  multiple: boolean;
66
66
  placeholderSearch: string;
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
12
12
  name: string;
13
13
  modelValue: LocalizedNameItem[];
14
14
  options: InputSelectItem[];
15
- placeholder: string;
16
15
  limit: number;
16
+ placeholder: string;
17
17
  showCounter: boolean;
18
18
  addLabel: string;
19
19
  removable: boolean;
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
12
12
  name: string;
13
13
  modelValue: LocalizedNameItem[];
14
14
  options: InputSelectItem[];
15
- placeholder: string;
16
15
  limit: number;
16
+ placeholder: string;
17
17
  showCounter: boolean;
18
18
  addLabel: string;
19
19
  removable: boolean;
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
20
20
  name: string;
21
21
  required: boolean;
22
22
  description: string;
23
+ limit: number;
23
24
  labelKey: string;
24
25
  placeholder: string;
25
- limit: number;
26
26
  disabledErrorMessage: boolean;
27
27
  disabledBorder: boolean;
28
28
  showCounter: boolean;
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
20
20
  name: string;
21
21
  required: boolean;
22
22
  description: string;
23
+ limit: number;
23
24
  labelKey: string;
24
25
  placeholder: string;
25
- limit: number;
26
26
  disabledErrorMessage: boolean;
27
27
  disabledBorder: boolean;
28
28
  showCounter: boolean;
@@ -0,0 +1,52 @@
1
+ import type { InputTagModalOption, InputTagModalProps } from "../../../types/components/input/input-tag-modal.js";
2
+ interface InputTextFieldExpose {
3
+ setErrors?: (errors: string[]) => void;
4
+ validate?: () => Promise<unknown>;
5
+ }
6
+ type __VLS_Props = InputTagModalProps;
7
+ type __VLS_ModelProps = {
8
+ modelValue?: string | number | null;
9
+ };
10
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
11
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
12
+ setErrors: (errors: string[]) => void;
13
+ validate: () => Promise<unknown>;
14
+ reload: () => Promise<void>;
15
+ fieldRef: import("vue").Ref<{
16
+ setErrors?: ((errors: string[]) => void) | undefined;
17
+ validate?: (() => Promise<unknown>) | undefined;
18
+ } | null, InputTextFieldExpose | {
19
+ setErrors?: ((errors: string[]) => void) | undefined;
20
+ validate?: (() => Promise<unknown>) | undefined;
21
+ } | null>;
22
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ "update:modelValue": (value: string | number | null) => any;
24
+ } & {
25
+ select: (option: InputTagModalOption) => any;
26
+ clear: () => any;
27
+ open: () => any;
28
+ "fetch-error": (error: unknown) => any;
29
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
30
+ onSelect?: ((option: InputTagModalOption) => any) | undefined;
31
+ onClear?: (() => any) | undefined;
32
+ "onUpdate:modelValue"?: ((value: string | number | null) => any) | undefined;
33
+ onOpen?: (() => any) | undefined;
34
+ "onFetch-error"?: ((error: unknown) => any) | undefined;
35
+ }>, {
36
+ title: string;
37
+ loading: boolean;
38
+ disabled: boolean;
39
+ pageSize: number;
40
+ id: string;
41
+ name: string;
42
+ required: boolean;
43
+ options: InputTagModalOption[];
44
+ labelKey: string;
45
+ placeholder: string;
46
+ disabledErrorMessage: boolean;
47
+ valueKey: string;
48
+ searchPlaceholder: string;
49
+ clearable: boolean;
50
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
51
+ declare const _default: typeof __VLS_export;
52
+ export default _default;
@@ -0,0 +1,307 @@
1
+ <template>
2
+ <div :class="cn('w-full', props.class)">
3
+ <InputTextField
4
+ ref="fieldRef"
5
+ v-model="displayValue"
6
+ :id="props.id"
7
+ :name="props.name"
8
+ :label="props.label"
9
+ :placeholder="props.placeholder"
10
+ :description="props.description"
11
+ :rules="props.rules"
12
+ :required="props.required"
13
+ :disabled="props.disabled"
14
+ :disabled-error-message="props.disabledErrorMessage"
15
+ :icon-append="appendIcon"
16
+ class="cursor-pointer"
17
+ readonly
18
+ @click="onOpenModal"
19
+ @click-icon-append="onAppendClick"
20
+ />
21
+
22
+ <Modal v-model="isOpenModal" :title="props.title">
23
+ <template #title>
24
+ <span class="font-title-medium-prominent text-black">
25
+ {{ props.title }}
26
+ </span>
27
+ </template>
28
+
29
+ <div class="flex w-full flex-col gap-[16px]">
30
+ <InputSearch
31
+ v-model="searchKeyword"
32
+ name="tag-modal-search"
33
+ :placeholder="props.searchPlaceholder"
34
+ icon="lucide:search"
35
+ icon-position="right"
36
+ disabled-error-message
37
+ />
38
+
39
+ <div
40
+ v-if="isInitialLoading"
41
+ class="flex h-[392px] items-center justify-center"
42
+ >
43
+ <Icon
44
+ name="lucide:loader-circle"
45
+ :size="24"
46
+ class="animate-spin text-primary"
47
+ />
48
+ </div>
49
+
50
+ <div
51
+ v-else-if="filteredOptions.length > 0"
52
+ class="flex max-h-[592px] flex-wrap content-start gap-[8px] overflow-y-auto pr-[4px]"
53
+ @scroll.passive="onListScroll"
54
+ >
55
+ <Chip
56
+ v-for="option in filteredOptions"
57
+ :key="optionKey(option)"
58
+ :color="isSelected(option) ? 'primary' : 'bright'"
59
+ class="cursor-pointer transition-colors"
60
+ @click="onSelectOption(option)"
61
+ >
62
+ {{ option.label }}
63
+ </Chip>
64
+
65
+ <div
66
+ v-if="isLoadingMore"
67
+ class="flex w-full items-center justify-center py-[8px]"
68
+ >
69
+ <Icon
70
+ name="lucide:loader-circle"
71
+ :size="20"
72
+ class="animate-spin text-primary"
73
+ />
74
+ </div>
75
+ </div>
76
+
77
+ <div
78
+ v-else
79
+ class="flex h-[392px] flex-col items-center justify-center gap-[8px]"
80
+ >
81
+ <Icon name="pukaad:page-not-found" class="h-[100px] w-[121px]" />
82
+ <div class="font-body-large text-gray">ไม่พบข้อมูล</div>
83
+ </div>
84
+ </div>
85
+ </Modal>
86
+ </div>
87
+ </template>
88
+
89
+ <script setup>
90
+ import { computed, nextTick, reactive, ref, watch } from "vue";
91
+ import { useDebounceFn } from "@vueuse/core";
92
+ import { cn } from "#pukaad-ui/runtime/plugins/shadcn";
93
+ defineOptions({ inheritAttrs: false });
94
+ const props = defineProps({
95
+ class: { type: null, required: false },
96
+ id: { type: String, required: false, default: "input-tag-modal" },
97
+ name: { type: String, required: false, default: "input-tag-modal" },
98
+ label: { type: String, required: false },
99
+ placeholder: { type: String, required: false, default: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E41\u0E17\u0E47\u0E01" },
100
+ searchPlaceholder: { type: String, required: false, default: "\u0E04\u0E49\u0E19\u0E2B\u0E32\u0E41\u0E17\u0E47\u0E01" },
101
+ title: { type: String, required: false, default: "\u0E41\u0E17\u0E47\u0E01\u0E17\u0E35\u0E48\u0E40\u0E01\u0E35\u0E48\u0E22\u0E27\u0E02\u0E49\u0E2D\u0E07" },
102
+ description: { type: String, required: false },
103
+ rules: { type: [Object, String, Function], required: false },
104
+ options: { type: Array, required: false, default: () => [] },
105
+ fetchFn: { type: Function, required: false },
106
+ valueKey: { type: String, required: false, default: "value" },
107
+ labelKey: { type: String, required: false, default: "label" },
108
+ pageSize: { type: Number, required: false, default: 30 },
109
+ required: { type: Boolean, required: false, default: false },
110
+ disabled: { type: Boolean, required: false, default: false },
111
+ disabledErrorMessage: { type: Boolean, required: false, default: false },
112
+ clearable: { type: Boolean, required: false, default: true },
113
+ loading: { type: Boolean, required: false, default: false }
114
+ });
115
+ const emit = defineEmits(["open", "select", "clear", "fetch-error"]);
116
+ const modelValue = defineModel({ type: [String, Number, null], ...{ default: null } });
117
+ const fieldRef = ref(null);
118
+ const isOpenModal = ref(false);
119
+ const searchKeyword = ref("");
120
+ const selectedOptionCache = ref(null);
121
+ const suppressSearchWatch = ref(false);
122
+ const remoteState = reactive({
123
+ data: [],
124
+ page: 1,
125
+ hasMore: true,
126
+ loading: false,
127
+ lastSearch: "",
128
+ generation: 0
129
+ });
130
+ const hasValue = computed(
131
+ () => modelValue.value !== null && modelValue.value !== void 0 && modelValue.value !== ""
132
+ );
133
+ const optionKey = (option) => `${typeof option.value}:${String(option.value)}`;
134
+ const mergeOptions = (current, incoming) => {
135
+ const map = new Map(current.map((option) => [optionKey(option), option]));
136
+ incoming.forEach((option) => map.set(optionKey(option), option));
137
+ return Array.from(map.values());
138
+ };
139
+ const mapRemoteOption = (item) => {
140
+ if (typeof item !== "object" || item === null) return null;
141
+ const record = item;
142
+ const rawValue = record[props.valueKey];
143
+ const rawLabel = record[props.labelKey];
144
+ if (rawValue === void 0 || rawValue === null || rawLabel === void 0) {
145
+ return null;
146
+ }
147
+ return {
148
+ value: typeof rawValue === "number" ? rawValue : String(rawValue),
149
+ label: String(rawLabel)
150
+ };
151
+ };
152
+ const resolvedOptions = computed(
153
+ () => props.fetchFn ? remoteState.data : props.options
154
+ );
155
+ const filteredOptions = computed(() => {
156
+ if (props.fetchFn) return resolvedOptions.value;
157
+ const query = searchKeyword.value.trim().toLocaleLowerCase("th");
158
+ if (!query) return resolvedOptions.value;
159
+ return resolvedOptions.value.filter(
160
+ (option) => option.label.toLocaleLowerCase("th").includes(query)
161
+ );
162
+ });
163
+ const knownOptions = computed(
164
+ () => mergeOptions(props.options, remoteState.data)
165
+ );
166
+ watch(
167
+ [modelValue, knownOptions],
168
+ ([value, options]) => {
169
+ if (value === null || value === void 0 || value === "") {
170
+ selectedOptionCache.value = null;
171
+ return;
172
+ }
173
+ const found = options.find((option) => option.value === value);
174
+ if (found) {
175
+ selectedOptionCache.value = found;
176
+ return;
177
+ }
178
+ if (selectedOptionCache.value?.value !== value) {
179
+ selectedOptionCache.value = null;
180
+ }
181
+ },
182
+ { immediate: true, deep: true }
183
+ );
184
+ const displayValue = computed({
185
+ get: () => selectedOptionCache.value?.label ?? "",
186
+ set: () => void 0
187
+ });
188
+ const appendIcon = computed(
189
+ () => hasValue.value && props.clearable ? "lucide:x" : "lucide:chevron-down"
190
+ );
191
+ const isInitialLoading = computed(
192
+ () => (props.loading || remoteState.loading) && filteredOptions.value.length === 0
193
+ );
194
+ const isLoadingMore = computed(
195
+ () => !!props.fetchFn && remoteState.loading && remoteState.data.length > 0
196
+ );
197
+ const fetchRemote = async (reset = false, search = searchKeyword.value.trim()) => {
198
+ if (!props.fetchFn) return;
199
+ if (!reset && (remoteState.loading || !remoteState.hasMore)) return;
200
+ let generation = remoteState.generation;
201
+ if (reset) {
202
+ generation = remoteState.generation + 1;
203
+ remoteState.generation = generation;
204
+ remoteState.page = 1;
205
+ remoteState.data = [];
206
+ remoteState.hasMore = true;
207
+ remoteState.loading = false;
208
+ remoteState.lastSearch = search;
209
+ }
210
+ if (remoteState.loading) return;
211
+ const page = remoteState.page;
212
+ remoteState.loading = true;
213
+ try {
214
+ const response = await props.fetchFn(
215
+ page,
216
+ props.pageSize,
217
+ search || void 0
218
+ );
219
+ if (generation !== remoteState.generation) return;
220
+ const mapped = (response.data ?? []).map(mapRemoteOption).filter((option) => option !== null);
221
+ remoteState.data = reset ? mergeOptions([], mapped) : mergeOptions(remoteState.data, mapped);
222
+ const totalPages = Number(response.meta?.total_pages ?? 1);
223
+ remoteState.page = page + 1;
224
+ remoteState.hasMore = page < totalPages;
225
+ } catch (error) {
226
+ if (generation === remoteState.generation) {
227
+ remoteState.hasMore = false;
228
+ emit("fetch-error", error);
229
+ console.error("InputTagModal: Failed to fetch options:", error);
230
+ }
231
+ } finally {
232
+ if (generation === remoteState.generation) {
233
+ remoteState.loading = false;
234
+ }
235
+ }
236
+ };
237
+ const debouncedRemoteSearch = useDebounceFn(() => {
238
+ void fetchRemote(true);
239
+ }, 300);
240
+ watch(searchKeyword, () => {
241
+ if (!props.fetchFn || !isOpenModal.value || suppressSearchWatch.value) return;
242
+ debouncedRemoteSearch();
243
+ });
244
+ watch(
245
+ () => props.fetchFn,
246
+ () => {
247
+ remoteState.generation += 1;
248
+ remoteState.data = [];
249
+ remoteState.page = 1;
250
+ remoteState.hasMore = true;
251
+ remoteState.loading = false;
252
+ remoteState.lastSearch = "";
253
+ }
254
+ );
255
+ const onOpenModal = async () => {
256
+ if (props.disabled) return;
257
+ suppressSearchWatch.value = true;
258
+ searchKeyword.value = "";
259
+ isOpenModal.value = true;
260
+ emit("open");
261
+ await nextTick();
262
+ suppressSearchWatch.value = false;
263
+ if (props.fetchFn && (remoteState.data.length === 0 || remoteState.lastSearch !== "")) {
264
+ await fetchRemote(true, "");
265
+ }
266
+ };
267
+ const onAppendClick = () => {
268
+ if (props.disabled) return;
269
+ if (hasValue.value && props.clearable) {
270
+ modelValue.value = null;
271
+ selectedOptionCache.value = null;
272
+ emit("clear");
273
+ return;
274
+ }
275
+ void onOpenModal();
276
+ };
277
+ const onSelectOption = (option) => {
278
+ modelValue.value = option.value;
279
+ selectedOptionCache.value = option;
280
+ emit("select", option);
281
+ isOpenModal.value = false;
282
+ };
283
+ const isSelected = (option) => option.value === modelValue.value;
284
+ const onListScroll = (event) => {
285
+ if (!props.fetchFn || remoteState.loading || !remoteState.hasMore) return;
286
+ const target = event.currentTarget;
287
+ if (!target) return;
288
+ const distanceFromBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
289
+ if (distanceFromBottom < 48) {
290
+ void fetchRemote();
291
+ }
292
+ };
293
+ const reload = async () => {
294
+ if (!props.fetchFn) return;
295
+ await fetchRemote(true);
296
+ };
297
+ const setErrors = (errors) => {
298
+ fieldRef.value?.setErrors?.(errors);
299
+ };
300
+ const validate = async () => await fieldRef.value?.validate?.();
301
+ defineExpose({
302
+ setErrors,
303
+ validate,
304
+ reload,
305
+ fieldRef
306
+ });
307
+ </script>
@@ -0,0 +1,52 @@
1
+ import type { InputTagModalOption, InputTagModalProps } from "../../../types/components/input/input-tag-modal.js";
2
+ interface InputTextFieldExpose {
3
+ setErrors?: (errors: string[]) => void;
4
+ validate?: () => Promise<unknown>;
5
+ }
6
+ type __VLS_Props = InputTagModalProps;
7
+ type __VLS_ModelProps = {
8
+ modelValue?: string | number | null;
9
+ };
10
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
11
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
12
+ setErrors: (errors: string[]) => void;
13
+ validate: () => Promise<unknown>;
14
+ reload: () => Promise<void>;
15
+ fieldRef: import("vue").Ref<{
16
+ setErrors?: ((errors: string[]) => void) | undefined;
17
+ validate?: (() => Promise<unknown>) | undefined;
18
+ } | null, InputTextFieldExpose | {
19
+ setErrors?: ((errors: string[]) => void) | undefined;
20
+ validate?: (() => Promise<unknown>) | undefined;
21
+ } | null>;
22
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ "update:modelValue": (value: string | number | null) => any;
24
+ } & {
25
+ select: (option: InputTagModalOption) => any;
26
+ clear: () => any;
27
+ open: () => any;
28
+ "fetch-error": (error: unknown) => any;
29
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
30
+ onSelect?: ((option: InputTagModalOption) => any) | undefined;
31
+ onClear?: (() => any) | undefined;
32
+ "onUpdate:modelValue"?: ((value: string | number | null) => any) | undefined;
33
+ onOpen?: (() => any) | undefined;
34
+ "onFetch-error"?: ((error: unknown) => any) | undefined;
35
+ }>, {
36
+ title: string;
37
+ loading: boolean;
38
+ disabled: boolean;
39
+ pageSize: number;
40
+ id: string;
41
+ name: string;
42
+ required: boolean;
43
+ options: InputTagModalOption[];
44
+ labelKey: string;
45
+ placeholder: string;
46
+ disabledErrorMessage: boolean;
47
+ valueKey: string;
48
+ searchPlaceholder: string;
49
+ clearable: boolean;
50
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
51
+ declare const _default: typeof __VLS_export;
52
+ export default _default;
@@ -26,8 +26,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
26
26
  }>, {
27
27
  name: string;
28
28
  state: "user" | "admin";
29
- placeholder: string;
30
29
  limit: number;
30
+ placeholder: string;
31
31
  ignore: string[];
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
@@ -26,8 +26,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
26
26
  }>, {
27
27
  name: string;
28
28
  state: "user" | "admin";
29
- placeholder: string;
30
29
  limit: number;
30
+ placeholder: string;
31
31
  ignore: string[];
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
@@ -46,8 +46,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
46
46
  resize: "none" | "both" | "horizontal" | "vertical";
47
47
  fullHeight: boolean;
48
48
  fullWidth: boolean;
49
- rows: number;
50
49
  limit: number;
50
+ rows: number;
51
51
  readonly: boolean;
52
52
  disabledErrorMessage: boolean;
53
53
  disabledBorder: boolean;
@@ -46,8 +46,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
46
46
  resize: "none" | "both" | "horizontal" | "vertical";
47
47
  fullHeight: boolean;
48
48
  fullWidth: boolean;
49
- rows: number;
50
49
  limit: number;
50
+ rows: number;
51
51
  readonly: boolean;
52
52
  disabledErrorMessage: boolean;
53
53
  disabledBorder: boolean;
@@ -25,7 +25,7 @@
25
25
 
26
26
  <script setup>
27
27
  import { computed, ref } from "vue";
28
- const emit = defineEmits(["profile-edit", "profile-share", "profile-follower-delete", "profile-block", "blog-pin", "blog-unpin", "blog-edit", "blog-delete", "report-blog", "report-place", "report-profile", "report-content", "delete-content", "content-delete", "delete", "report", "report-review", "place-close-duplicate", "review-edit", "review-delete", "review-reply", "reply-edit", "reply-delete", "archive", "profile-name-updated", "report-submit"]);
28
+ const emit = defineEmits(["profile-edit", "profile-share", "profile-follower-delete", "profile-block", "blog-pin", "blog-unpin", "blog-edit", "blog-delete", "report-blog", "report-place", "report-profile", "report-content", "report-spotlight", "report-tag", "report-label", "delete-content", "content-delete", "delete", "report", "report-review", "place-close-duplicate", "review-edit", "review-delete", "review-reply", "reply-edit", "reply-delete", "archive", "profile-name-updated", "report-submit"]);
29
29
  const props = defineProps({
30
30
  state: { type: String, required: false },
31
31
  profileDetail: { type: Object, required: false },
@@ -173,6 +173,39 @@ const menu = [
173
173
  emit("report");
174
174
  }
175
175
  },
176
+ {
177
+ label: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19\u0E2B\u0E31\u0E27\u0E02\u0E49\u0E2D\u0E41\u0E19\u0E30\u0E19\u0E33\u0E19\u0E35\u0E49",
178
+ name: "report-spotlight",
179
+ icon: "lucide:flag",
180
+ action: () => {
181
+ reportState.value = "spotlight";
182
+ isOpenModalReport.value = true;
183
+ emit("report-spotlight");
184
+ emit("report");
185
+ }
186
+ },
187
+ {
188
+ label: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19\u0E41\u0E17\u0E47\u0E01\u0E19\u0E35\u0E49",
189
+ name: "report-tag",
190
+ icon: "lucide:flag",
191
+ action: () => {
192
+ reportState.value = "tag";
193
+ isOpenModalReport.value = true;
194
+ emit("report-tag");
195
+ emit("report");
196
+ }
197
+ },
198
+ {
199
+ label: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19\u0E2B\u0E21\u0E27\u0E14\u0E2B\u0E21\u0E39\u0E48\u0E19\u0E35\u0E49",
200
+ name: "report-label",
201
+ icon: "lucide:flag",
202
+ action: () => {
203
+ reportState.value = "label";
204
+ isOpenModalReport.value = true;
205
+ emit("report-label");
206
+ emit("report");
207
+ }
208
+ },
176
209
  {
177
210
  label: "\u0E25\u0E1A\u0E40\u0E19\u0E37\u0E49\u0E2D\u0E2B\u0E32\u0E19\u0E35\u0E49",
178
211
  name: "delete-content",
@@ -302,6 +335,21 @@ const items = computed(() => {
302
335
  (item) => ["report-content"].includes(item.name)
303
336
  );
304
337
  }
338
+ if (props.state === "spotlight") {
339
+ return menu.filter(
340
+ (item) => ["report-spotlight"].includes(item.name)
341
+ );
342
+ }
343
+ if (props.state === "tag") {
344
+ return menu.filter(
345
+ (item) => ["report-tag"].includes(item.name)
346
+ );
347
+ }
348
+ if (props.state === "label") {
349
+ return menu.filter(
350
+ (item) => ["report-label"].includes(item.name)
351
+ );
352
+ }
305
353
  if (props.state === "delete-content") {
306
354
  return menu.filter(
307
355
  (item) => ["delete-content", "content-delete"].includes(item.name)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.418.0",
3
+ "version": "1.420.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +0,0 @@
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>;
2
- declare const _default: typeof __VLS_export;
3
- export default _default;
@@ -1,57 +0,0 @@
1
- <template>
2
- <!-- <InputTextField
3
- :name="props.name"
4
- :label="props.label"
5
- :placeholder="props.placeholder"
6
- :full-width="props.fullWidth"
7
- :width="props.width"
8
- :height="props.height"
9
- :required="props.required"
10
- :disabled="props.disabled"
11
- :readonly="props.readonly"
12
- :append-icon="props.appendIcon"
13
- @click="isOpenModal = true"
14
- @clickAppendIcon="isOpenModal = true"
15
- @reset="isOpenModal = true"
16
- reset
17
- v-model="selectedTag"
18
- />
19
- <Modal
20
- v-model="isOpenModal"
21
- title="แท็กที่เกี่ยวข้อง"
22
- :gap="16"
23
- :width="604"
24
- :height="670"
25
- >
26
- <div class="space-y-[16px]">
27
- <InputSearch v-model="search" placeholder="ค้นหาแท็ก" full-width />
28
- <Divider :height="0" />
29
- <div
30
- v-if="filteredTags.length > 0"
31
- class="flex flex-wrap gap-[8px] h-[526px] overflow-y-auto"
32
- >
33
- <Chip
34
- v-for="tag in filteredTags"
35
- :key="tag"
36
- size="small"
37
- :color="selectedTag === tag ? 'primary' : 'default'"
38
- class="cursor-pointer"
39
- @click="onSelectTag(tag)"
40
- >
41
- {{ tag }}
42
- </Chip>
43
- </div>
44
- <div
45
- v-else
46
- class="flex flex-col gap-[8px] items-center justify-center h-[526px]"
47
- >
48
- <Icon name="pukaad:page-not-found" size="100" />
49
- <div class="text-gray font-body-large">ไม่พบข้อมูล</div>
50
- </div>
51
- </div>
52
- </Modal> -->
53
- </template>
54
-
55
- <script setup>
56
-
57
- </script>
@@ -1,3 +0,0 @@
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>;
2
- declare const _default: typeof __VLS_export;
3
- export default _default;