@sfxcode/formkit-primevue 4.1.2 → 4.1.3

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.
@@ -26,6 +26,11 @@ export interface FormKitPrimeListboxProps {
26
26
  tabindex?: ListboxProps['tabindex'];
27
27
  ariaLabel?: ListboxProps['ariaLabel'];
28
28
  ariaLabelledby?: ListboxProps['ariaLabelledby'];
29
+ displayMode?: 'single' | 'transfer';
30
+ transferLeftHeaderText?: string;
31
+ transferRightHeaderText?: string;
32
+ transferHeaderClass?: string;
33
+ transferAll?: boolean;
29
34
  }
30
35
  declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
31
36
  context: {
@@ -2,6 +2,7 @@
2
2
  import type { FormKitFrameworkContext } from '@formkit/core'
3
3
  import type { ListboxProps } from 'primevue/listbox'
4
4
  import type { PropType } from 'vue'
5
+ import { computed, ref, watch } from 'vue'
5
6
  import { useFormKitInput } from '../composables'
6
7
 
7
8
  export interface FormKitPrimeListboxProps {
@@ -29,6 +30,11 @@ export interface FormKitPrimeListboxProps {
29
30
  tabindex?: ListboxProps['tabindex']
30
31
  ariaLabel?: ListboxProps['ariaLabel']
31
32
  ariaLabelledby?: ListboxProps['ariaLabelledby']
33
+ displayMode?: 'single' | 'transfer'
34
+ transferLeftHeaderText?: string
35
+ transferRightHeaderText?: string
36
+ transferHeaderClass?: string
37
+ transferAll?: boolean
32
38
  }
33
39
 
34
40
  const props = defineProps({
@@ -39,10 +45,97 @@ const props = defineProps({
39
45
  })
40
46
 
41
47
  const { validSlotNames, unstyled, isInvalid, handleInput, handleBlur, modelValue } = useFormKitInput(props.context)
48
+
49
+ // Transfer List functionality
50
+ const optionValueKey = computed(() => {
51
+ const key = props.context.optionValue
52
+ return typeof key === 'string' ? key : null
53
+ })
54
+
55
+ // Helper function to convert values to full objects
56
+ function valuesToObjects(values: any[]): any[] {
57
+ if (!optionValueKey.value)
58
+ return values
59
+ const options = props.context?.options || []
60
+ return values.map((val: any) => {
61
+ if (typeof val === 'object')
62
+ return val
63
+ return options.find((opt: any) => opt[optionValueKey.value!] === val) || val
64
+ })
65
+ }
66
+
67
+ // Initialize targetItems by converting any values to full objects
68
+ const initialTargetItems = Array.isArray(modelValue.value) ? valuesToObjects(modelValue.value) : []
69
+ const targetItems = ref<any[]>(initialTargetItems)
70
+ const sourceSelection = ref<any[]>([])
71
+ const targetSelection = ref<any[]>([])
72
+
73
+ const transferHeaderClass = computed(() => {
74
+ return props.context.transferHeaderClass || 'text-base font-semibold'
75
+ })
76
+
77
+ const sourceItems = computed(() => {
78
+ const options = props.context?.options || []
79
+ if (!Array.isArray(options))
80
+ return []
81
+
82
+ return options.filter((item: any) => {
83
+ const itemValue = optionValueKey.value && typeof item === 'object' ? item[optionValueKey.value] : item
84
+ return !targetItems.value.some((t: any) => {
85
+ const targetValue = optionValueKey.value && typeof t === 'object' ? t[optionValueKey.value] : t
86
+ return targetValue === itemValue
87
+ })
88
+ })
89
+ })
90
+
91
+ function transferSelected() {
92
+ // sourceSelection now contains full objects since we removed optionValue
93
+ targetItems.value = [...targetItems.value, ...sourceSelection.value]
94
+ sourceSelection.value = []
95
+ }
96
+
97
+ function transferAll() {
98
+ targetItems.value = [...targetItems.value, ...sourceItems.value]
99
+ sourceSelection.value = []
100
+ }
101
+
102
+ function removeSelected() {
103
+ // targetSelection now contains full objects since we removed optionValue
104
+ targetItems.value = targetItems.value.filter((item: any) => {
105
+ const itemValue = optionValueKey.value && typeof item === 'object' ? item[optionValueKey.value] : item
106
+ return !targetSelection.value.some((sel: any) => {
107
+ const selValue = optionValueKey.value && typeof sel === 'object' ? sel[optionValueKey.value] : sel
108
+ return selValue === itemValue
109
+ })
110
+ })
111
+ targetSelection.value = []
112
+ }
113
+
114
+ function removeAll() {
115
+ targetItems.value = []
116
+ targetSelection.value = []
117
+ }
118
+
119
+ watch(targetItems, (newVal) => {
120
+ modelValue.value = newVal
121
+ handleInput(newVal)
122
+ }, { deep: true })
123
+
124
+ // Watch for external changes to modelValue and update targetItems
125
+ watch(() => modelValue.value, (newVal) => {
126
+ if (props.context.displayMode === 'transfer' && Array.isArray(newVal)) {
127
+ const converted = valuesToObjects(newVal)
128
+ // Only update if the values are actually different to avoid infinite loops
129
+ if (JSON.stringify(converted) !== JSON.stringify(targetItems.value)) {
130
+ targetItems.value = converted
131
+ }
132
+ }
133
+ }, { deep: true })
42
134
  </script>
43
135
 
44
136
  <template>
45
- <div class="p-formkit">
137
+ <!-- Single Mode (default) -->
138
+ <div v-if="!context.displayMode || context.displayMode === 'single'" class="p-formkit">
46
139
  <Listbox
47
140
  :id="context.id"
48
141
  v-model="modelValue"
@@ -83,4 +176,154 @@ const { validSlotNames, unstyled, isInvalid, handleInput, handleBlur, modelValue
83
176
  </template>
84
177
  </Listbox>
85
178
  </div>
179
+
180
+ <!-- Transfer Mode -->
181
+ <div
182
+ v-if="context.displayMode === 'transfer'"
183
+ class="p-formkit-transfer"
184
+ style="display: flex; align-items: stretch; gap: 1rem; width: 100%;"
185
+ >
186
+ <!-- Source List -->
187
+ <div style="display: flex; flex-direction: column; flex: 1; gap: 0.5rem; min-width: 0;">
188
+ <span
189
+ v-if="context.transferLeftHeaderText"
190
+ :class="transferHeaderClass"
191
+ >{{ context.transferLeftHeaderText }}</span>
192
+ <Listbox
193
+ :id="`${context.id}-transfer-source`"
194
+ v-model="sourceSelection"
195
+ v-bind="context?.attrs"
196
+ :disabled="!!context?.disabled"
197
+ :readonly="context?.attrs.readonly ?? false"
198
+ :list-style="context?.attrs.style"
199
+ :class="context?.attrs?.class"
200
+ :invalid="isInvalid"
201
+ :tabindex="context?.attrs.tabindex"
202
+ :aria-label="context?.attrs.ariaLabel"
203
+ :aria-labelledby="context?.attrs.ariaLabelledby"
204
+ :options="sourceItems"
205
+ :option-label="context.optionLabel"
206
+ :option-disabled="context.optionDisabled"
207
+ :option-group-label="context.optionGroupLabel"
208
+ :option-group-children="context.optionGroupChildren"
209
+ :data-key="context.dataKey"
210
+ :multiple="true"
211
+ :filter="context.filter ?? false"
212
+ :filter-icon="context.filterIcon"
213
+ :filter-placeholder="context.filterPlaceholder"
214
+ :filter-locale="context.filterLocale"
215
+ :filter-match-mode="context.filterMatchMode"
216
+ :auto-option-focus="context.autoOptionFocus ?? true"
217
+ :select-on-focus="context.selectOnFocus ?? false"
218
+ :meta-key-selection="context.metaKeySelection ?? false"
219
+ :virtual-scroller-options="context.virtualScrollerOptions"
220
+ :pt="context.pt"
221
+ :pt-options="context.ptOptions"
222
+ :unstyled="unstyled"
223
+ @blur="handleBlur"
224
+ >
225
+ <template v-for="slotName in validSlotNames" :key="slotName" #[slotName]="slotProps">
226
+ <component :is="context?.slots[slotName]" v-bind="{ ...context, ...slotProps }" />
227
+ </template>
228
+ </Listbox>
229
+ </div>
230
+
231
+ <!-- Transfer Buttons -->
232
+ <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.5rem;">
233
+ <Button
234
+ icon="pi pi-angle-right"
235
+ severity="secondary"
236
+ outlined
237
+ :disabled="sourceSelection.length === 0"
238
+ aria-label="Move selected to target"
239
+ @click="transferSelected"
240
+ />
241
+ <Button
242
+ v-if="context.transferAll"
243
+ icon="pi pi-angle-double-right"
244
+ severity="secondary"
245
+ outlined
246
+ :disabled="sourceItems.length === 0"
247
+ aria-label="Move all to target"
248
+ @click="transferAll"
249
+ />
250
+ <Button
251
+ icon="pi pi-angle-left"
252
+ severity="secondary"
253
+ outlined
254
+ :disabled="targetSelection.length === 0"
255
+ aria-label="Move selected to source"
256
+ @click="removeSelected"
257
+ />
258
+ <Button
259
+ v-if="context.transferAll"
260
+ icon="pi pi-angle-double-left"
261
+ severity="secondary"
262
+ outlined
263
+ :disabled="targetItems.length === 0"
264
+ aria-label="Move all to source"
265
+ @click="removeAll"
266
+ />
267
+ </div>
268
+
269
+ <!-- Target List -->
270
+ <div style="display: flex; flex-direction: column; flex: 1; gap: 0.5rem; min-width: 0;">
271
+ <span
272
+ v-if="context.transferRightHeaderText"
273
+ :class="transferHeaderClass"
274
+ >{{ context.transferRightHeaderText }}</span>
275
+ <Listbox
276
+ :id="`${context.id}-transfer-target`"
277
+ v-model="targetSelection"
278
+ v-bind="context?.attrs"
279
+ :disabled="!!context?.disabled"
280
+ :readonly="context?.attrs.readonly ?? false"
281
+ :list-style="context?.attrs.style"
282
+ :class="context?.attrs?.class"
283
+ :invalid="isInvalid"
284
+ :tabindex="context?.attrs.tabindex"
285
+ :aria-label="context?.attrs.ariaLabel"
286
+ :aria-labelledby="context?.attrs.ariaLabelledby"
287
+ :options="targetItems"
288
+ :option-label="context.optionLabel"
289
+ :option-disabled="context.optionDisabled"
290
+ :option-group-label="context.optionGroupLabel"
291
+ :option-group-children="context.optionGroupChildren"
292
+ :data-key="context.dataKey"
293
+ :multiple="true"
294
+ :filter="context.filter ?? false"
295
+ :filter-icon="context.filterIcon"
296
+ :filter-placeholder="context.filterPlaceholder"
297
+ :filter-locale="context.filterLocale"
298
+ :filter-match-mode="context.filterMatchMode"
299
+ :auto-option-focus="context.autoOptionFocus ?? true"
300
+ :select-on-focus="context.selectOnFocus ?? false"
301
+ :meta-key-selection="context.metaKeySelection ?? false"
302
+ :virtual-scroller-options="context.virtualScrollerOptions"
303
+ :pt="context.pt"
304
+ :pt-options="context.ptOptions"
305
+ :unstyled="unstyled"
306
+ @blur="handleBlur"
307
+ >
308
+ <template v-for="slotName in validSlotNames" :key="slotName" #[slotName]="slotProps">
309
+ <component :is="context?.slots[slotName]" v-bind="{ ...context, ...slotProps }" />
310
+ </template>
311
+ </Listbox>
312
+ </div>
313
+ </div>
86
314
  </template>
315
+
316
+ <style scoped>
317
+ .p-formkit-transfer {
318
+ min-width: 600px;
319
+ }
320
+
321
+ .p-formkit-transfer :deep(.p-listbox) {
322
+ width: 100%;
323
+ height: 100%;
324
+ }
325
+
326
+ .p-formkit-transfer :deep(.p-listbox-list-container) {
327
+ max-height: none;
328
+ }
329
+ </style>
@@ -26,6 +26,11 @@ export interface FormKitPrimeListboxProps {
26
26
  tabindex?: ListboxProps['tabindex'];
27
27
  ariaLabel?: ListboxProps['ariaLabel'];
28
28
  ariaLabelledby?: ListboxProps['ariaLabelledby'];
29
+ displayMode?: 'single' | 'transfer';
30
+ transferLeftHeaderText?: string;
31
+ transferRightHeaderText?: string;
32
+ transferHeaderClass?: string;
33
+ transferAll?: boolean;
29
34
  }
30
35
  declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
31
36
  context: {
@@ -74,7 +74,7 @@ const primeMultiSelectDefinition = exports.primeMultiSelectDefinition = (0, _vue
74
74
  family: "PrimeInput"
75
75
  });
76
76
  const primeListboxDefinition = exports.primeListboxDefinition = (0, _vue.createInput)(_PrimeListbox.default, {
77
- props: ["pt", "ptOptions", "unstyled", "options", "optionLabel", "optionValue", "multiple", "filter", "filterIcon", "filterPlaceholder", "filterLocale", "filterMatchMode", "autoOptionFocus", "selectOnFocus", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "dataKey", "metaKeySelection", "virtualScrollerOptions"],
77
+ props: ["pt", "ptOptions", "unstyled", "options", "optionLabel", "optionValue", "multiple", "filter", "filterIcon", "filterPlaceholder", "filterLocale", "filterMatchMode", "autoOptionFocus", "selectOnFocus", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "dataKey", "metaKeySelection", "virtualScrollerOptions", "displayMode", "transferLeftHeaderText", "transferRightHeaderText", "transferHeaderClass", "transferAll"],
78
78
  family: "PrimeInput"
79
79
  });
80
80
  const primeDatePickerDefinition = exports.primeDatePickerDefinition = (0, _vue.createInput)(_PrimeDatePicker.default, {
@@ -67,7 +67,7 @@ export const primeMultiSelectDefinition = createInput(PrimeMultiSelect, {
67
67
  family: "PrimeInput"
68
68
  });
69
69
  export const primeListboxDefinition = createInput(PrimeListbox, {
70
- props: ["pt", "ptOptions", "unstyled", "options", "optionLabel", "optionValue", "multiple", "filter", "filterIcon", "filterPlaceholder", "filterLocale", "filterMatchMode", "autoOptionFocus", "selectOnFocus", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "dataKey", "metaKeySelection", "virtualScrollerOptions"],
70
+ props: ["pt", "ptOptions", "unstyled", "options", "optionLabel", "optionValue", "multiple", "filter", "filterIcon", "filterPlaceholder", "filterLocale", "filterMatchMode", "autoOptionFocus", "selectOnFocus", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "dataKey", "metaKeySelection", "virtualScrollerOptions", "displayMode", "transferLeftHeaderText", "transferRightHeaderText", "transferHeaderClass", "transferAll"],
71
71
  family: "PrimeInput"
72
72
  });
73
73
  export const primeDatePickerDefinition = createInput(PrimeDatePicker, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sfxcode/formkit-primevue",
3
3
  "type": "module",
4
- "version": "4.1.2",
4
+ "version": "4.1.3",
5
5
  "packageManager": "pnpm@11.0.9+sha512.34ce82e6780233cf9cad8685029a8f81d2e06196c5a9bad98879f7424940c6817c4e4524fb7d38b8553ceed48b9758b8ebaf1abd3600c232c4c8cf7366086f38",
6
6
  "author": {
7
7
  "name": "Tom",