jobdone-shared-files 1.0.13 → 1.0.14

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 (32) hide show
  1. package/ProjectManagement/projectNavbar.vue +363 -363
  2. package/autocompleteSelect.vue +461 -461
  3. package/common/directives/collapse.js +12 -12
  4. package/common/directives/popovers.js +10 -10
  5. package/common/directives/selectPlaceholder.js +52 -52
  6. package/common/directives/textareaAutoHeight.js +10 -10
  7. package/common/directives/tooltip.js +10 -10
  8. package/common/format.js +26 -26
  9. package/index.js +14 -14
  10. package/lightboxWithOverview.vue +156 -156
  11. package/package.json +19 -19
  12. package/paginate.vue +141 -141
  13. package/style/css/vue-loading-overlay/index.css +40 -40
  14. package/style/scss/Common/Animation.scss +9 -9
  15. package/style/scss/Common/SelectableTable.scss +34 -34
  16. package/style/scss/Common/filepond.scss +31 -31
  17. package/style/scss/Common/thumbnail-group.scss +14 -14
  18. package/style/scss/Layout/LayoutBase.scss +1031 -1031
  19. package/style/scss/Layout/LayoutMobile.scss +206 -206
  20. package/style/scss/Layout/LayoutProject.scss +126 -126
  21. package/style/scss/Layout/LayoutSinglePage.scss +17 -17
  22. package/style/scss/Layout/LayoutTwoColumn.scss +60 -60
  23. package/style/scss/Settings/_Mixins.scss +232 -232
  24. package/style/scss/Settings/_MobileVariables.scss +11 -11
  25. package/style/scss/Settings/_bs-variables-dark.scss +70 -70
  26. package/style/scss/Settings/_bs-variables.scss +1743 -1743
  27. package/style/scss/Settings/_color-mode.scss +122 -122
  28. package/style/scss/Settings/_custom-variables.scss +10 -10
  29. package/tagEditor.vue +249 -249
  30. package/tree.vue +69 -69
  31. package/treeItem.vue +355 -371
  32. package/vueLoadingOverlay.vue +74 -74
@@ -1,462 +1,462 @@
1
- <template>
2
- <div class="autocomplete-select-component-trigger-content" ref="componentContentInput">
3
- <!-- preview input -->
4
- <button type="button" class='form-control autocomplete-select-component-display-selecting' :disabled="disabled"
5
- :class="[triggerClass, previewInput]">{{
6
- selectedPreviewText
7
- }}</button>
8
- <!-- search input -->
9
- <input class='form-control autocomplete-select-component-keyword-filter-input' :class="[triggerClass, searchInput]"
10
- type="text" ref="keywordFilterInput" v-model="keyword"
11
- :placeholder="searchPlaceholder == '' ? selectedPreviewText : searchPlaceholder" maxlength="50"
12
- @keydown.enter="keyboardSelectConfirm($event)" @keydown.up="keyboardSwitch($event, -1)"
13
- @keydown.down="keyboardSwitch($event, 1)" @change="keyboardSwitchIndexReset()">
14
- <Teleport :to="listPut">
15
- <div class="autocomplete-select-component-selector-content" :class="{ 'active': active }"
16
- ref="componentContentList" :style="positionStyle">
17
- <ul class="autocomplete-select-component-opt-list" ref="opt_list">
18
- <li v-if="!listIsOnTop && clearBtn" class="border-bottom" :title="clearPlaceholder">
19
- <button class="autocomplete-select-component-opt-item is-clear" type="button"
20
- @click.stop="clearSelected()">
21
- {{ clearPlaceholder }}
22
- </button>
23
- </li>
24
- <li v-if="filterList?.length == 0">
25
- <div class="autocomplete-select-component-opt-item is-nothing">
26
- 沒有符合條件的選項
27
- </div>
28
- </li>
29
- <template v-if="active">
30
- <li v-for="(opt, idx) in filterList" :key="idx" @click.stop="selectConfirm(opt)" :title="opt.name"
31
- :id="`autocomplete-select-component-opt-item_${idx}`">
32
- <button class="autocomplete-select-component-opt-item" type="button"
33
- :class="idx == keyboardSwitchIndex ? 'active' : ''">
34
- <template v-if="htmlOption">
35
- <slot name="option" :optData="opt"></slot>
36
- </template>
37
- <template v-else>
38
- {{ previewAdjust(opt) }}
39
- </template>
40
- </button>
41
- </li>
42
- </template>
43
- <li v-if="listIsOnTop && clearBtn" class="border-top" :title="clearPlaceholder">
44
- <button class="autocomplete-select-component-opt-item is-clear" type="button"
45
- @click.stop="clearSelected()">
46
- {{ clearPlaceholder }}
47
- </button>
48
- </li>
49
- </ul>
50
- </div>
51
- </Teleport>
52
- </div>
53
- </template>
54
- <script setup>
55
- // enum Data & Functions
56
-
57
- // vue & bootstrap
58
- import { ref, onMounted, onUnmounted, computed, toRaw } from 'vue'
59
-
60
- // plugins
61
-
62
- // vue components
63
-
64
- const props = defineProps({
65
- selectedData: {
66
- required: true
67
- },
68
- opts: {
69
- type: Array,
70
- default: []
71
- },
72
- placeholder: {
73
- type: String,
74
- default: '請選擇'
75
- },
76
- searchPlaceholder: {
77
- type: String,
78
- default: ''
79
- },
80
- triggerClass: {
81
- type: String,
82
- default: ''
83
- },
84
- listPut: {
85
- type: String,
86
- default: 'body'
87
- },
88
-
89
- clearBtn: {
90
- type: Boolean,
91
- default: true
92
- },
93
- clearPlaceholder: {
94
- type: String,
95
- default: '清除選擇'
96
- },
97
- resetValue: {
98
- default: ''
99
- },
100
- disabled: {
101
- type: Boolean,
102
- default: false
103
- },
104
-
105
-
106
- previewKey: {
107
- type: String,
108
- default: '',
109
- },
110
- bindingKey: {
111
- type: String,
112
- default: '',
113
- },
114
- filterKeys: {
115
- type: Array,
116
- default: ['name'],
117
- },
118
- isUndefinedHint: {
119
- type: String,
120
- default: '⚠️ 未找到符合原選擇的選項,可能相關資料曾發生異動',
121
- },
122
- htmlOption: {
123
- type: Boolean,
124
- default: false
125
- },
126
-
127
-
128
- });
129
-
130
- const emit = defineEmits(['select'])
131
-
132
- // Just DOM
133
- const componentContentInput = ref(null)
134
- const componentContentList = ref(null)
135
- const keywordFilterInput = ref(null)
136
-
137
-
138
- // 是否開啟下拉選單
139
- const active = ref(false)
140
- const domPosition = ref({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0 })
141
- function activeSelector() {
142
- if (props.disabled) {
143
- return
144
- }
145
- active.value = true
146
- keywordFilterInput.value.focus()
147
- domPosition.value = componentContentInput.value.getBoundingClientRect()
148
- }
149
- const listIsOnTop = ref(false)
150
- const positionStyle = computed(() => {
151
- let screenHeight = window.innerHeight;
152
- let info = toRaw(domPosition.value)
153
- listIsOnTop.value = info.top >= (screenHeight - info.bottom)
154
- let str = ''
155
- if (listIsOnTop.value) {
156
- str = `bottom:${screenHeight - info.top}px;max-height:calc(${info.top}px - 10%);`
157
- } else {
158
- str = `top:${info.bottom}px;max-height:calc(${screenHeight - info.bottom}px - 10%);`
159
- }
160
- return `${str}left:${info.x}px;max-width:calc(90% - ${info.x}px);min-width:${Math.abs(info.x - info.right)}px;`
161
- })
162
-
163
- function leave() {
164
- listIsOnTop.value = false
165
- active.value = false
166
- keyword.value = ''
167
- // TODO SCROLL CLOSE
168
- keyboardSwitchIndexReset()
169
- keywordFilterInput.value.blur()
170
- }
171
-
172
- // 確認傳入選單列表內的選項Type,只檢查第一個,請內容統一,不要傳奇怪的東西進來
173
- const allowOptItemType = ['string', 'number', 'object']
174
- const optItemType = computed(() => {
175
- if (!Array.isArray(props.opts) || props.opts?.length < 1) {
176
- return ''
177
- }
178
- let sampling = props.opts[0]
179
- let type = typeof sampling
180
- if (!allowOptItemType.includes(type)) {
181
- return ''
182
- }
183
- if ((sampling instanceof Date) && (sampling instanceof RegExp) && Array.isArray(sampling)) {
184
- return ''
185
- }
186
- return type?.toLowerCase()
187
-
188
- })
189
-
190
- //
191
- // 篩選
192
- const keyword = ref("")
193
- const filterList = computed(() => {
194
- if (optItemType.value === '') {
195
- return []
196
- }
197
- if (keyword.value == '') {
198
- return props.opts
199
- }
200
- let kwReplace = keyword.value.replace(/[.*+?^${}()|[\]\\]/g, "");
201
- let regex = new RegExp(kwReplace, "i");
202
- if (optItemType.value === 'object') {
203
- let final = props.opts.filter(i => {
204
- for (var idx = 0; idx < props.filterKeys.length; idx++) {
205
- if (regex.test(i[props.filterKeys[idx].toString()])) {
206
- return i
207
- };
208
- }
209
- })
210
- return final
211
- }
212
- return props.opts.filter(i => {
213
- if (regex.test(i)) {
214
- return i
215
- }
216
- })
217
- })
218
-
219
- // 從選項列表中挖出整個選中的item,因選中的值(可能是某個key)
220
- const selectedItem = computed(() => {
221
- if (!props.selectedData) {
222
- return
223
- }
224
-
225
- // 如果選中的值是某個Key,需自行指定bindingKey
226
- if (props.bindingKey !== '') {
227
- return props.opts.find(i => i[props.bindingKey.toString()] == props.selectedData) ?? props.selectedData
228
- }
229
- return props.selectedData
230
- })
231
-
232
-
233
- // 整理 選項與選中值 的顯示文字
234
- function previewAdjust(itemObj) {
235
- if (props.previewKey !== '') {
236
- let strAry = props.previewKey.split('+')
237
- let isUndefined = false
238
-
239
- let finalStr = strAry.reduce((acc, cur) => {
240
- let v = cur.trim()
241
- if (v.match(/'[^']+'/g)) {
242
- return acc + v.substring(1, v.length - 1)
243
- } else {
244
- if (itemObj[v] === undefined) {
245
- isUndefined = true
246
- }
247
- return acc + itemObj[v]
248
- }
249
- }, '')
250
- if (isUndefined) {
251
- return props.isUndefinedHint
252
- }
253
- return finalStr ? finalStr : props.placeholder
254
- }
255
- return itemObj
256
- }
257
-
258
-
259
- // 選中的值的顯示
260
- const selectedPreviewText = computed(() => {
261
-
262
-
263
- if (!selectedItem.value || Object.keys(selectedItem.value).length === 0) {
264
- return props.placeholder
265
- }
266
- return previewAdjust(selectedItem.value)
267
- })
268
-
269
- // 清除選擇內容
270
- function clearSelected() {
271
- emit('select', props.resetValue)
272
- leave()
273
- }
274
-
275
- // 選擇送出
276
- function selectConfirm(v) {
277
- if (!v) {
278
- return
279
- }
280
- emit('select', JSON.parse(JSON.stringify(v)))
281
- leave()
282
- keyboardSwitchIndexReset()
283
- }
284
-
285
- // 鍵盤送出
286
- function keyboardSelectConfirm(event) {
287
- if (event.isComposing) {
288
- return
289
- }
290
- if (filterList.value?.length) {
291
- selectConfirm(filterList.value[keyboardSwitchIndex.value])
292
- }
293
- }
294
-
295
- // 鍵盤挑選
296
- const keyboardSwitchIndex = ref(0)
297
- function keyboardSwitch(event, v) {
298
- if (event.isComposing) {
299
- return
300
- }
301
- if (filterList.value?.length < 1) {
302
- keyboardSwitchIndexReset()
303
- return
304
- }
305
- let idx = Number(keyboardSwitchIndex.value) + v
306
-
307
- if (idx < 0) {
308
- keyboardSwitchIndex.value = filterList.value.length - 1
309
- document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
310
- return
311
- }
312
- if (idx > filterList.value.length - 1) {
313
- keyboardSwitchIndexReset()
314
- document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
315
- return
316
- }
317
- keyboardSwitchIndex.value = idx
318
- document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
319
- }
320
-
321
- function keyboardSwitchIndexReset() {
322
- keyboardSwitchIndex.value = 0
323
- }
324
-
325
- // 樣式
326
- const previewInput = computed(() => {
327
- if (selectedPreviewText.value == props.placeholder) {
328
- return 'autocomplete-select-component-value-null'
329
- }
330
- return ''
331
- })
332
- const searchInput = computed(() => {
333
- if (active.value) {
334
- return 'active'
335
- }
336
- return ''
337
- })
338
- function initTrigger(event) {
339
- if (componentContentInput.value.contains(event.target) || componentContentList.value.contains(event.target)) {
340
- activeSelector()
341
- } else {
342
- leave()
343
- }
344
- }
345
- onMounted(() => {
346
- window.addEventListener("click", initTrigger);
347
- })
348
-
349
- onUnmounted(() => {
350
- window.removeEventListener("click", initTrigger);
351
- })
352
-
353
- </script>
354
- <style lang="scss" scoped>
355
- * {
356
- word-break: break-word;
357
- overflow-wrap: break-word;
358
- }
359
-
360
- .autocomplete-select-component-trigger-content {
361
- position: relative;
362
- width: 100%;
363
-
364
- .form-control {
365
- background-image: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%232D4155%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
366
- background-repeat: no-repeat;
367
- background-position: right 0.75rem center;
368
- background-size: 16px 12px;
369
- text-align: start;
370
- width: 100%;
371
- }
372
-
373
- .autocomplete-select-component-value-null {
374
- color: var(--bs-secondary-color);
375
- }
376
-
377
- .autocomplete-select-component-display-selecting {
378
- position: absolute;
379
- top: 0;
380
- left: 0;
381
- z-index: 1;
382
- white-space: nowrap;
383
- overflow: hidden;
384
- text-overflow: ellipsis;
385
- }
386
-
387
- .autocomplete-select-component-keyword-filter-input {
388
- opacity: 0;
389
- position: relative;
390
-
391
- &.active {
392
- opacity: 1;
393
- z-index: 2;
394
- }
395
- }
396
- }
397
-
398
- .autocomplete-select-component-selector-content {
399
- visibility: hidden;
400
- display: none;
401
- position: fixed;
402
- z-index: 2000;
403
- margin-top: 0.25rem;
404
- margin-bottom: 0.25rem;
405
- max-height: 76vh;
406
- overflow: auto;
407
-
408
- ul.autocomplete-select-component-opt-list {
409
- background: #fff;
410
- list-style: none;
411
- border: 1px solid var(--gray-400);
412
- border-radius: var(--bs-border-radius);
413
- margin: 0;
414
- padding: 0;
415
- overflow: auto;
416
- padding: .25rem 0;
417
-
418
- >li {
419
- width: 100%;
420
- overflow: auto;
421
- }
422
-
423
- .autocomplete-select-component-opt-item {
424
- background-color: transparent;
425
- border: none;
426
- padding: .25rem .5rem;
427
- word-break: break-word;
428
- width: 100%;
429
- overflow: hidden;
430
- text-align: start;
431
- text-overflow: ellipsis;
432
- color: var(--gray-600);
433
-
434
- &.is-nothing {
435
- color: var(--bs-danger);
436
- }
437
-
438
- &.is-clear {
439
- color: var(--gray-500);
440
- }
441
-
442
- &:hover,
443
- &.is-clear:hover {
444
- background: rgba(var(--bs-primary-rgb), 1) !important;
445
- color: #fff !important;
446
- }
447
-
448
- &.active {
449
- background: rgba(var(--bs-primary-rgb), .1);
450
- color: var(--bs-primary);
451
- }
452
- }
453
- }
454
-
455
- &.active {
456
- display: block;
457
- box-shadow: var(--bs-box-shadow);
458
- visibility: visible;
459
- z-index: 2000;
460
- }
461
- }
1
+ <template>
2
+ <div class="autocomplete-select-component-trigger-content" ref="componentContentInput">
3
+ <!-- preview input -->
4
+ <button type="button" class='form-control autocomplete-select-component-display-selecting' :disabled="disabled"
5
+ :class="[triggerClass, previewInput]">{{
6
+ selectedPreviewText
7
+ }}</button>
8
+ <!-- search input -->
9
+ <input class='form-control autocomplete-select-component-keyword-filter-input' :class="[triggerClass, searchInput]"
10
+ type="text" ref="keywordFilterInput" v-model="keyword"
11
+ :placeholder="searchPlaceholder == '' ? selectedPreviewText : searchPlaceholder" maxlength="50"
12
+ @keydown.enter="keyboardSelectConfirm($event)" @keydown.up="keyboardSwitch($event, -1)"
13
+ @keydown.down="keyboardSwitch($event, 1)" @change="keyboardSwitchIndexReset()">
14
+ <Teleport :to="listPut">
15
+ <div class="autocomplete-select-component-selector-content" :class="{ 'active': active }"
16
+ ref="componentContentList" :style="positionStyle">
17
+ <ul class="autocomplete-select-component-opt-list" ref="opt_list">
18
+ <li v-if="!listIsOnTop && clearBtn" class="border-bottom" :title="clearPlaceholder">
19
+ <button class="autocomplete-select-component-opt-item is-clear" type="button"
20
+ @click.stop="clearSelected()">
21
+ {{ clearPlaceholder }}
22
+ </button>
23
+ </li>
24
+ <li v-if="filterList?.length == 0">
25
+ <div class="autocomplete-select-component-opt-item is-nothing">
26
+ 沒有符合條件的選項
27
+ </div>
28
+ </li>
29
+ <template v-if="active">
30
+ <li v-for="(opt, idx) in filterList" :key="idx" @click.stop="selectConfirm(opt)" :title="opt.name"
31
+ :id="`autocomplete-select-component-opt-item_${idx}`">
32
+ <button class="autocomplete-select-component-opt-item" type="button"
33
+ :class="idx == keyboardSwitchIndex ? 'active' : ''">
34
+ <template v-if="htmlOption">
35
+ <slot name="option" :optData="opt"></slot>
36
+ </template>
37
+ <template v-else>
38
+ {{ previewAdjust(opt) }}
39
+ </template>
40
+ </button>
41
+ </li>
42
+ </template>
43
+ <li v-if="listIsOnTop && clearBtn" class="border-top" :title="clearPlaceholder">
44
+ <button class="autocomplete-select-component-opt-item is-clear" type="button"
45
+ @click.stop="clearSelected()">
46
+ {{ clearPlaceholder }}
47
+ </button>
48
+ </li>
49
+ </ul>
50
+ </div>
51
+ </Teleport>
52
+ </div>
53
+ </template>
54
+ <script setup>
55
+ // enum Data & Functions
56
+
57
+ // vue & bootstrap
58
+ import { ref, onMounted, onUnmounted, computed, toRaw } from 'vue'
59
+
60
+ // plugins
61
+
62
+ // vue components
63
+
64
+ const props = defineProps({
65
+ selectedData: {
66
+ required: true
67
+ },
68
+ opts: {
69
+ type: Array,
70
+ default: []
71
+ },
72
+ placeholder: {
73
+ type: String,
74
+ default: '請選擇'
75
+ },
76
+ searchPlaceholder: {
77
+ type: String,
78
+ default: ''
79
+ },
80
+ triggerClass: {
81
+ type: String,
82
+ default: ''
83
+ },
84
+ listPut: {
85
+ type: String,
86
+ default: 'body'
87
+ },
88
+
89
+ clearBtn: {
90
+ type: Boolean,
91
+ default: true
92
+ },
93
+ clearPlaceholder: {
94
+ type: String,
95
+ default: '清除選擇'
96
+ },
97
+ resetValue: {
98
+ default: ''
99
+ },
100
+ disabled: {
101
+ type: Boolean,
102
+ default: false
103
+ },
104
+
105
+
106
+ previewKey: {
107
+ type: String,
108
+ default: '',
109
+ },
110
+ bindingKey: {
111
+ type: String,
112
+ default: '',
113
+ },
114
+ filterKeys: {
115
+ type: Array,
116
+ default: ['name'],
117
+ },
118
+ isUndefinedHint: {
119
+ type: String,
120
+ default: '⚠️ 未找到符合原選擇的選項,可能相關資料曾發生異動',
121
+ },
122
+ htmlOption: {
123
+ type: Boolean,
124
+ default: false
125
+ },
126
+
127
+
128
+ });
129
+
130
+ const emit = defineEmits(['select'])
131
+
132
+ // Just DOM
133
+ const componentContentInput = ref(null)
134
+ const componentContentList = ref(null)
135
+ const keywordFilterInput = ref(null)
136
+
137
+
138
+ // 是否開啟下拉選單
139
+ const active = ref(false)
140
+ const domPosition = ref({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0 })
141
+ function activeSelector() {
142
+ if (props.disabled) {
143
+ return
144
+ }
145
+ active.value = true
146
+ keywordFilterInput.value.focus()
147
+ domPosition.value = componentContentInput.value.getBoundingClientRect()
148
+ }
149
+ const listIsOnTop = ref(false)
150
+ const positionStyle = computed(() => {
151
+ let screenHeight = window.innerHeight;
152
+ let info = toRaw(domPosition.value)
153
+ listIsOnTop.value = info.top >= (screenHeight - info.bottom)
154
+ let str = ''
155
+ if (listIsOnTop.value) {
156
+ str = `bottom:${screenHeight - info.top}px;max-height:calc(${info.top}px - 10%);`
157
+ } else {
158
+ str = `top:${info.bottom}px;max-height:calc(${screenHeight - info.bottom}px - 10%);`
159
+ }
160
+ return `${str}left:${info.x}px;max-width:calc(90% - ${info.x}px);min-width:${Math.abs(info.x - info.right)}px;`
161
+ })
162
+
163
+ function leave() {
164
+ listIsOnTop.value = false
165
+ active.value = false
166
+ keyword.value = ''
167
+ // TODO SCROLL CLOSE
168
+ keyboardSwitchIndexReset()
169
+ keywordFilterInput.value.blur()
170
+ }
171
+
172
+ // 確認傳入選單列表內的選項Type,只檢查第一個,請內容統一,不要傳奇怪的東西進來
173
+ const allowOptItemType = ['string', 'number', 'object']
174
+ const optItemType = computed(() => {
175
+ if (!Array.isArray(props.opts) || props.opts?.length < 1) {
176
+ return ''
177
+ }
178
+ let sampling = props.opts[0]
179
+ let type = typeof sampling
180
+ if (!allowOptItemType.includes(type)) {
181
+ return ''
182
+ }
183
+ if ((sampling instanceof Date) && (sampling instanceof RegExp) && Array.isArray(sampling)) {
184
+ return ''
185
+ }
186
+ return type?.toLowerCase()
187
+
188
+ })
189
+
190
+ //
191
+ // 篩選
192
+ const keyword = ref("")
193
+ const filterList = computed(() => {
194
+ if (optItemType.value === '') {
195
+ return []
196
+ }
197
+ if (keyword.value == '') {
198
+ return props.opts
199
+ }
200
+ let kwReplace = keyword.value.replace(/[.*+?^${}()|[\]\\]/g, "");
201
+ let regex = new RegExp(kwReplace, "i");
202
+ if (optItemType.value === 'object') {
203
+ let final = props.opts.filter(i => {
204
+ for (var idx = 0; idx < props.filterKeys.length; idx++) {
205
+ if (regex.test(i[props.filterKeys[idx].toString()])) {
206
+ return i
207
+ };
208
+ }
209
+ })
210
+ return final
211
+ }
212
+ return props.opts.filter(i => {
213
+ if (regex.test(i)) {
214
+ return i
215
+ }
216
+ })
217
+ })
218
+
219
+ // 從選項列表中挖出整個選中的item,因選中的值(可能是某個key)
220
+ const selectedItem = computed(() => {
221
+ if (!props.selectedData) {
222
+ return
223
+ }
224
+
225
+ // 如果選中的值是某個Key,需自行指定bindingKey
226
+ if (props.bindingKey !== '') {
227
+ return props.opts.find(i => i[props.bindingKey.toString()] == props.selectedData) ?? props.selectedData
228
+ }
229
+ return props.selectedData
230
+ })
231
+
232
+
233
+ // 整理 選項與選中值 的顯示文字
234
+ function previewAdjust(itemObj) {
235
+ if (props.previewKey !== '') {
236
+ let strAry = props.previewKey.split('+')
237
+ let isUndefined = false
238
+
239
+ let finalStr = strAry.reduce((acc, cur) => {
240
+ let v = cur.trim()
241
+ if (v.match(/'[^']+'/g)) {
242
+ return acc + v.substring(1, v.length - 1)
243
+ } else {
244
+ if (itemObj[v] === undefined) {
245
+ isUndefined = true
246
+ }
247
+ return acc + itemObj[v]
248
+ }
249
+ }, '')
250
+ if (isUndefined) {
251
+ return props.isUndefinedHint
252
+ }
253
+ return finalStr ? finalStr : props.placeholder
254
+ }
255
+ return itemObj
256
+ }
257
+
258
+
259
+ // 選中的值的顯示
260
+ const selectedPreviewText = computed(() => {
261
+
262
+
263
+ if (!selectedItem.value || Object.keys(selectedItem.value).length === 0) {
264
+ return props.placeholder
265
+ }
266
+ return previewAdjust(selectedItem.value)
267
+ })
268
+
269
+ // 清除選擇內容
270
+ function clearSelected() {
271
+ emit('select', props.resetValue)
272
+ leave()
273
+ }
274
+
275
+ // 選擇送出
276
+ function selectConfirm(v) {
277
+ if (!v) {
278
+ return
279
+ }
280
+ emit('select', JSON.parse(JSON.stringify(v)))
281
+ leave()
282
+ keyboardSwitchIndexReset()
283
+ }
284
+
285
+ // 鍵盤送出
286
+ function keyboardSelectConfirm(event) {
287
+ if (event.isComposing) {
288
+ return
289
+ }
290
+ if (filterList.value?.length) {
291
+ selectConfirm(filterList.value[keyboardSwitchIndex.value])
292
+ }
293
+ }
294
+
295
+ // 鍵盤挑選
296
+ const keyboardSwitchIndex = ref(0)
297
+ function keyboardSwitch(event, v) {
298
+ if (event.isComposing) {
299
+ return
300
+ }
301
+ if (filterList.value?.length < 1) {
302
+ keyboardSwitchIndexReset()
303
+ return
304
+ }
305
+ let idx = Number(keyboardSwitchIndex.value) + v
306
+
307
+ if (idx < 0) {
308
+ keyboardSwitchIndex.value = filterList.value.length - 1
309
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
310
+ return
311
+ }
312
+ if (idx > filterList.value.length - 1) {
313
+ keyboardSwitchIndexReset()
314
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
315
+ return
316
+ }
317
+ keyboardSwitchIndex.value = idx
318
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
319
+ }
320
+
321
+ function keyboardSwitchIndexReset() {
322
+ keyboardSwitchIndex.value = 0
323
+ }
324
+
325
+ // 樣式
326
+ const previewInput = computed(() => {
327
+ if (selectedPreviewText.value == props.placeholder) {
328
+ return 'autocomplete-select-component-value-null'
329
+ }
330
+ return ''
331
+ })
332
+ const searchInput = computed(() => {
333
+ if (active.value) {
334
+ return 'active'
335
+ }
336
+ return ''
337
+ })
338
+ function initTrigger(event) {
339
+ if (componentContentInput.value.contains(event.target) || componentContentList.value.contains(event.target)) {
340
+ activeSelector()
341
+ } else {
342
+ leave()
343
+ }
344
+ }
345
+ onMounted(() => {
346
+ window.addEventListener("click", initTrigger);
347
+ })
348
+
349
+ onUnmounted(() => {
350
+ window.removeEventListener("click", initTrigger);
351
+ })
352
+
353
+ </script>
354
+ <style lang="scss" scoped>
355
+ * {
356
+ word-break: break-word;
357
+ overflow-wrap: break-word;
358
+ }
359
+
360
+ .autocomplete-select-component-trigger-content {
361
+ position: relative;
362
+ width: 100%;
363
+
364
+ .form-control {
365
+ background-image: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%232D4155%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
366
+ background-repeat: no-repeat;
367
+ background-position: right 0.75rem center;
368
+ background-size: 16px 12px;
369
+ text-align: start;
370
+ width: 100%;
371
+ }
372
+
373
+ .autocomplete-select-component-value-null {
374
+ color: var(--bs-secondary-color);
375
+ }
376
+
377
+ .autocomplete-select-component-display-selecting {
378
+ position: absolute;
379
+ top: 0;
380
+ left: 0;
381
+ z-index: 1;
382
+ white-space: nowrap;
383
+ overflow: hidden;
384
+ text-overflow: ellipsis;
385
+ }
386
+
387
+ .autocomplete-select-component-keyword-filter-input {
388
+ opacity: 0;
389
+ position: relative;
390
+
391
+ &.active {
392
+ opacity: 1;
393
+ z-index: 2;
394
+ }
395
+ }
396
+ }
397
+
398
+ .autocomplete-select-component-selector-content {
399
+ visibility: hidden;
400
+ display: none;
401
+ position: fixed;
402
+ z-index: 2000;
403
+ margin-top: 0.25rem;
404
+ margin-bottom: 0.25rem;
405
+ max-height: 76vh;
406
+ overflow: auto;
407
+
408
+ ul.autocomplete-select-component-opt-list {
409
+ background: #fff;
410
+ list-style: none;
411
+ border: 1px solid var(--gray-400);
412
+ border-radius: var(--bs-border-radius);
413
+ margin: 0;
414
+ padding: 0;
415
+ overflow: auto;
416
+ padding: .25rem 0;
417
+
418
+ >li {
419
+ width: 100%;
420
+ overflow: auto;
421
+ }
422
+
423
+ .autocomplete-select-component-opt-item {
424
+ background-color: transparent;
425
+ border: none;
426
+ padding: .25rem .5rem;
427
+ word-break: break-word;
428
+ width: 100%;
429
+ overflow: hidden;
430
+ text-align: start;
431
+ text-overflow: ellipsis;
432
+ color: var(--gray-600);
433
+
434
+ &.is-nothing {
435
+ color: var(--bs-danger);
436
+ }
437
+
438
+ &.is-clear {
439
+ color: var(--gray-500);
440
+ }
441
+
442
+ &:hover,
443
+ &.is-clear:hover {
444
+ background: rgba(var(--bs-primary-rgb), 1) !important;
445
+ color: #fff !important;
446
+ }
447
+
448
+ &.active {
449
+ background: rgba(var(--bs-primary-rgb), .1);
450
+ color: var(--bs-primary);
451
+ }
452
+ }
453
+ }
454
+
455
+ &.active {
456
+ display: block;
457
+ box-shadow: var(--bs-box-shadow);
458
+ visibility: visible;
459
+ z-index: 2000;
460
+ }
461
+ }
462
462
  </style>