quasar-ui-danx 0.0.46 → 0.0.48
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/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +13 -4
- package/src/components/ActionTable/ActionTable.vue +2 -17
- package/src/components/ActionTable/ActionTableColumn.vue +9 -20
- package/src/components/ActionTable/Columns/ColumnListItem.vue +39 -0
- package/src/components/ActionTable/Columns/ColumnSettingsDialog.vue +102 -0
- package/src/components/ActionTable/Columns/TItleColumnFormat.vue +28 -0
- package/src/components/ActionTable/Columns/VisibleColumnsToggleButtons.vue +156 -0
- package/src/components/ActionTable/Columns/index.ts +4 -0
- package/src/components/ActionTable/Form/Fields/NumberField.vue +15 -2
- package/src/components/ActionTable/Form/Fields/SelectField.vue +14 -9
- package/src/components/ActionTable/index.ts +1 -0
- package/src/components/ActionTable/listControls.ts +5 -0
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +7 -4
- package/src/helpers/actions.ts +106 -94
- package/src/svg/CaretDownIcon.svg +3 -0
- package/src/svg/index.ts +1 -0
    
        package/package.json
    CHANGED
    
    
| @@ -4,16 +4,15 @@ | |
| 4 4 | 
             
                  :items="activeActions"
         | 
| 5 5 | 
             
                  :disabled="!hasTarget"
         | 
| 6 6 | 
             
                  :tooltip="!hasTarget ? tooltip : null"
         | 
| 7 | 
            -
                  :loading="loading"
         | 
| 7 | 
            +
                  :loading="isSaving || loading"
         | 
| 8 8 | 
             
                  :loading-component="loadingComponent"
         | 
| 9 | 
            -
                  @action-item=" | 
| 9 | 
            +
                  @action-item="onAction"
         | 
| 10 10 | 
             
              />
         | 
| 11 11 | 
             
            </template>
         | 
| 12 12 | 
             
            <script setup>
         | 
| 13 | 
            -
            import { computed } from 'vue';
         | 
| 13 | 
            +
            import { computed, ref } from 'vue';
         | 
| 14 14 | 
             
            import { PopoverMenu } from '../Utility';
         | 
| 15 15 |  | 
| 16 | 
            -
            const emit = defineEmits(['action']);
         | 
| 17 16 | 
             
            const props = defineProps({
         | 
| 18 17 | 
             
              actions: {
         | 
| 19 18 | 
             
                type: Array,
         | 
| @@ -43,4 +42,14 @@ const activeActions = computed(() => props.actions.filter(action => { | |
| 43 42 |  | 
| 44 43 | 
             
              return action.enabled ? action.enabled(props.target) : true;
         | 
| 45 44 | 
             
            }));
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            const isSaving = ref(false);
         | 
| 47 | 
            +
            async function onAction(action) {
         | 
| 48 | 
            +
              if (!action.trigger) {
         | 
| 49 | 
            +
                throw new Error('Action must have a trigger function! Make sure you are using useActions() or implement your own trigger function.');
         | 
| 50 | 
            +
              }
         | 
| 51 | 
            +
              isSaving.value = true;
         | 
| 52 | 
            +
              await action.trigger(props.target);
         | 
| 53 | 
            +
              isSaving.value = false;
         | 
| 54 | 
            +
            }
         | 
| 46 55 | 
             
            </script>
         | 
| @@ -51,10 +51,8 @@ | |
| 51 51 | 
             
                  <ActionTableColumn
         | 
| 52 52 | 
             
                      :row-props="rowProps"
         | 
| 53 53 | 
             
                      :settings="columnSettings[rowProps.col.name]"
         | 
| 54 | 
            -
                      :is-saving="isSavingRow(rowProps.row)"
         | 
| 55 | 
            -
                      @action="$emit('action', $event, rowProps.row)"
         | 
| 56 54 | 
             
                  >
         | 
| 57 | 
            -
                    <slot />
         | 
| 55 | 
            +
                    <slot :column-name="rowProps.col.name" :row="rowProps.row" :value="rowProps.value" />
         | 
| 58 56 | 
             
                  </ActionTableColumn>
         | 
| 59 57 | 
             
                </template>
         | 
| 60 58 | 
             
                <template #bottom>
         | 
| @@ -71,7 +69,7 @@ import { HandleDraggable } from '../DragAndDrop'; | |
| 71 69 | 
             
            import { ActionVnode, mapSortBy } from '../index';
         | 
| 72 70 | 
             
            import { ActionTableColumn, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
         | 
| 73 71 |  | 
| 74 | 
            -
            defineEmits([' | 
| 72 | 
            +
            defineEmits(['update:quasar-pagination', 'update:selected-rows']);
         | 
| 75 73 | 
             
            const props = defineProps({
         | 
| 76 74 | 
             
              name: {
         | 
| 77 75 | 
             
                type: String,
         | 
| @@ -89,10 +87,6 @@ const props = defineProps({ | |
| 89 87 | 
             
                type: Object,
         | 
| 90 88 | 
             
                required: true
         | 
| 91 89 | 
             
              },
         | 
| 92 | 
            -
              isSavingTarget: {
         | 
| 93 | 
            -
                type: Object,
         | 
| 94 | 
            -
                default: null
         | 
| 95 | 
            -
              },
         | 
| 96 90 | 
             
              isLoadingList: Boolean,
         | 
| 97 91 | 
             
              pagedItems: {
         | 
| 98 92 | 
             
                type: Object,
         | 
| @@ -126,15 +120,6 @@ function onResizeColumn(column, val) { | |
| 126 120 | 
             
              };
         | 
| 127 121 | 
             
              setItem(COLUMN_SETTINGS_KEY, columnSettings.value);
         | 
| 128 122 | 
             
            }
         | 
| 129 | 
            -
             | 
| 130 | 
            -
            function isSavingRow(row) {
         | 
| 131 | 
            -
              if (!props.isSavingTarget) return false;
         | 
| 132 | 
            -
             | 
| 133 | 
            -
              if (Array.isArray(props.isSavingTarget)) {
         | 
| 134 | 
            -
                return !!props.isSavingTarget.find(t => t.id === row.id);
         | 
| 135 | 
            -
              }
         | 
| 136 | 
            -
              return props.isSavingTarget.id === row.id;
         | 
| 137 | 
            -
            }
         | 
| 138 123 | 
             
            </script>
         | 
| 139 124 |  | 
| 140 125 | 
             
            <style lang="scss" scoped>
         | 
| @@ -9,29 +9,18 @@ | |
| 9 9 | 
             
                      class="flex-grow"
         | 
| 10 10 | 
             
                      @click="column.onClick(row)"
         | 
| 11 11 | 
             
                  >
         | 
| 12 | 
            -
                    <RenderVnode
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                        :vnode="column.vnode(row)"
         | 
| 15 | 
            -
                    />
         | 
| 16 | 
            -
                    <slot v-else v-bind="{name: column.name, row, value}">
         | 
| 17 | 
            -
                      {{ value }}
         | 
| 18 | 
            -
                    </slot>
         | 
| 12 | 
            +
                    <RenderVnode v-if="column.vnode" :vnode="column.vnode(row)" />
         | 
| 13 | 
            +
                    <slot v-else>{{ value }}</slot>
         | 
| 19 14 | 
             
                  </a>
         | 
| 20 15 | 
             
                  <div v-else class="flex-grow">
         | 
| 21 | 
            -
                    <RenderVnode
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                        :vnode="column.vnode(row)"
         | 
| 24 | 
            -
                    />
         | 
| 25 | 
            -
                    <slot v-else v-bind="{name: column.name, row, value}">
         | 
| 26 | 
            -
                      {{ value }}
         | 
| 27 | 
            -
                    </slot>
         | 
| 16 | 
            +
                    <RenderVnode v-if="column.vnode" :vnode="column.vnode(row)" />
         | 
| 17 | 
            +
                    <slot v-else>{{ value }}</slot>
         | 
| 28 18 | 
             
                  </div>
         | 
| 29 | 
            -
                  <div v-if="column. | 
| 19 | 
            +
                  <div v-if="column.actionMenu" class="flex flex-shrink-0 pl-2">
         | 
| 30 20 | 
             
                    <ActionMenu
         | 
| 31 | 
            -
                        :actions="column. | 
| 21 | 
            +
                        :actions="column.actionMenu"
         | 
| 32 22 | 
             
                        :target="row"
         | 
| 33 23 | 
             
                        :loading="isSaving"
         | 
| 34 | 
            -
                        @action="$emit('action', $event)"
         | 
| 35 24 | 
             
                    />
         | 
| 36 25 | 
             
                  </div>
         | 
| 37 26 | 
             
                </div>
         | 
| @@ -42,7 +31,6 @@ import { computed } from 'vue'; | |
| 42 31 | 
             
            import { RenderVnode } from '../Utility';
         | 
| 43 32 | 
             
            import { ActionMenu } from './index';
         | 
| 44 33 |  | 
| 45 | 
            -
            defineEmits(['action']);
         | 
| 46 34 | 
             
            const props = defineProps({
         | 
| 47 35 | 
             
              rowProps: {
         | 
| 48 36 | 
             
                type: Object,
         | 
| @@ -51,13 +39,13 @@ const props = defineProps({ | |
| 51 39 | 
             
              settings: {
         | 
| 52 40 | 
             
                type: Object,
         | 
| 53 41 | 
             
                default: null
         | 
| 54 | 
            -
              } | 
| 55 | 
            -
              isSaving: Boolean
         | 
| 42 | 
            +
              }
         | 
| 56 43 | 
             
            });
         | 
| 57 44 |  | 
| 58 45 | 
             
            const row = computed(() => props.rowProps.row);
         | 
| 59 46 | 
             
            const column = computed(() => props.rowProps.col);
         | 
| 60 47 | 
             
            const value = computed(() => props.rowProps.value);
         | 
| 48 | 
            +
            const isSaving = computed(() => column.value.isSaving && column.value.isSaving(row.value));
         | 
| 61 49 |  | 
| 62 50 | 
             
            const columnStyle = computed(() => {
         | 
| 63 51 | 
             
              const width = props.settings?.width || column.value.width;
         | 
| @@ -65,6 +53,7 @@ const columnStyle = computed(() => { | |
| 65 53 | 
             
            });
         | 
| 66 54 |  | 
| 67 55 | 
             
            const columnClass = computed(() => ({
         | 
| 56 | 
            +
              'is-saving': isSaving.value,
         | 
| 68 57 | 
             
              'justify-end': column.value.align === 'right',
         | 
| 69 58 | 
             
              'justify-center': column.value.align === 'center',
         | 
| 70 59 | 
             
              'justify-start': column.value.align === 'left'
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            <template>
         | 
| 2 | 
            +
              <div
         | 
| 3 | 
            +
                class="flex items-center w-full"
         | 
| 4 | 
            +
                :class="{'cursor-not-allowed': locked}"
         | 
| 5 | 
            +
              >
         | 
| 6 | 
            +
                <a v-if="locked" class="text-neutral-on-plus-3 cursor-not-allowed">
         | 
| 7 | 
            +
                  <LockedIcon class="w-4" />
         | 
| 8 | 
            +
                </a>
         | 
| 9 | 
            +
                <div class="font-semibold text-sm ml-5 py-3 flex-grow">{{ column.label }}</div>
         | 
| 10 | 
            +
                <div v-if="!locked" class="flex items-center">
         | 
| 11 | 
            +
                  <a class="py-2 px-1" @click="$emit('visible', !visible)">
         | 
| 12 | 
            +
                    <VisibleIcon v-if="visible" class="w-4" />
         | 
| 13 | 
            +
                    <HiddenIcon v-else class="w-4 text-neutral-on-plus-3" />
         | 
| 14 | 
            +
                  </a>
         | 
| 15 | 
            +
                  <a class="py-2 px-1" @click="$emit('is-title', !isTitle)">
         | 
| 16 | 
            +
                    <IsTitleIcon class="w-4" :class="isTitle ? '' : 'text-neutral-plus-3'" />
         | 
| 17 | 
            +
                    <QTooltip>
         | 
| 18 | 
            +
                      <template v-if="!isTitle">Add to priority list</template>
         | 
| 19 | 
            +
                      <template v-else>Remove from priority list</template>
         | 
| 20 | 
            +
                    </QTooltip>
         | 
| 21 | 
            +
                  </a>
         | 
| 22 | 
            +
                </div>
         | 
| 23 | 
            +
              </div>
         | 
| 24 | 
            +
            </template>
         | 
| 25 | 
            +
            <script setup>
         | 
| 26 | 
            +
            import { EyeIcon as VisibleIcon, EyeOffIcon as HiddenIcon, LockClosedIcon as LockedIcon } from "@heroicons/vue/outline";
         | 
| 27 | 
            +
            import { StarIcon as IsTitleIcon } from "@heroicons/vue/solid";
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            defineEmits(["visible", "is-title"]);
         | 
| 30 | 
            +
            defineProps({
         | 
| 31 | 
            +
              locked: Boolean,
         | 
| 32 | 
            +
              visible: Boolean,
         | 
| 33 | 
            +
              isTitle: Boolean,
         | 
| 34 | 
            +
              column: {
         | 
| 35 | 
            +
                type: Object,
         | 
| 36 | 
            +
                required: true
         | 
| 37 | 
            +
              }
         | 
| 38 | 
            +
            });
         | 
| 39 | 
            +
            </script>
         | 
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            <template>
         | 
| 2 | 
            +
              <InfoDialog
         | 
| 3 | 
            +
                  title="Column Settings"
         | 
| 4 | 
            +
                  @close="$emit('close')"
         | 
| 5 | 
            +
              >
         | 
| 6 | 
            +
                <div class="mb-4 text-sm">
         | 
| 7 | 
            +
                  Customize columns by visibility, order, or priority (maximum 3 additional).
         | 
| 8 | 
            +
                </div>
         | 
| 9 | 
            +
                <ColumnListItem
         | 
| 10 | 
            +
                    v-for="column in lockedColumns" :key="column.name" locked visible :column="column"
         | 
| 11 | 
            +
                    class="px-2.5 border border-neutral-plus-5 bg-white rounded-t-lg"
         | 
| 12 | 
            +
                />
         | 
| 13 | 
            +
                <ListTransition
         | 
| 14 | 
            +
                    name="fade-down-list"
         | 
| 15 | 
            +
                    data-drop-zone="column-list"
         | 
| 16 | 
            +
                >
         | 
| 17 | 
            +
                  <ListItemDraggable
         | 
| 18 | 
            +
                      v-for="(column, index) in sortableColumns"
         | 
| 19 | 
            +
                      :key="column.name"
         | 
| 20 | 
            +
                      :list-items="sortableColumns"
         | 
| 21 | 
            +
                      drop-zone="column-list"
         | 
| 22 | 
            +
                      class="px-2 border border-neutral-plus-5 bg-white"
         | 
| 23 | 
            +
                      :class="{'rounded-b-lg': index === sortableColumns.length - 1}"
         | 
| 24 | 
            +
                      show-handle
         | 
| 25 | 
            +
                      @update:list-items="$emit('update:sortable-columns', $event)"
         | 
| 26 | 
            +
                  >
         | 
| 27 | 
            +
                    <ColumnListItem
         | 
| 28 | 
            +
                        :column="column"
         | 
| 29 | 
            +
                        :visible="isVisible(column)"
         | 
| 30 | 
            +
                        :is-title="isTitleColumn(column)"
         | 
| 31 | 
            +
                        @visible="onVisibilityChange(column, $event)"
         | 
| 32 | 
            +
                        @is-title="onTitleColumnChange(column, $event)"
         | 
| 33 | 
            +
                    />
         | 
| 34 | 
            +
                  </ListItemDraggable>
         | 
| 35 | 
            +
                </ListTransition>
         | 
| 36 | 
            +
              </InfoDialog>
         | 
| 37 | 
            +
            </template>
         | 
| 38 | 
            +
            <script setup>
         | 
| 39 | 
            +
            import { computed } from 'vue';
         | 
| 40 | 
            +
            import { FlashMessages, remove } from '../../../helpers';
         | 
| 41 | 
            +
            import { ListItemDraggable } from '../../DragAndDrop';
         | 
| 42 | 
            +
            import { InfoDialog, ListTransition } from '../../Utility';
         | 
| 43 | 
            +
            import { ColumnListItem } from './index';
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            const emit = defineEmits(['close', 'update:hidden-column-names', 'update:title-column-names', 'update:sortable-columns']);
         | 
| 46 | 
            +
            const props = defineProps({
         | 
| 47 | 
            +
              hiddenColumnNames: {
         | 
| 48 | 
            +
                type: Array,
         | 
| 49 | 
            +
                required: true
         | 
| 50 | 
            +
              },
         | 
| 51 | 
            +
              titleColumnNames: {
         | 
| 52 | 
            +
                type: Array,
         | 
| 53 | 
            +
                required: true
         | 
| 54 | 
            +
              },
         | 
| 55 | 
            +
              lockedColumns: {
         | 
| 56 | 
            +
                type: Array,
         | 
| 57 | 
            +
                required: true
         | 
| 58 | 
            +
              },
         | 
| 59 | 
            +
              sortableColumns: {
         | 
| 60 | 
            +
                type: Array,
         | 
| 61 | 
            +
                required: true
         | 
| 62 | 
            +
              },
         | 
| 63 | 
            +
              titleColumnLimit: {
         | 
| 64 | 
            +
                type: Number,
         | 
| 65 | 
            +
                default: 3
         | 
| 66 | 
            +
              }
         | 
| 67 | 
            +
            });
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            const allowMoreTitleColumns = computed(() => {
         | 
| 70 | 
            +
              return props.titleColumnNames.length < props.titleColumnLimit;
         | 
| 71 | 
            +
            });
         | 
| 72 | 
            +
            function isVisible(column) {
         | 
| 73 | 
            +
              return !props.hiddenColumnNames.includes(column.name);
         | 
| 74 | 
            +
            }
         | 
| 75 | 
            +
            function onVisibilityChange(column, visible) {
         | 
| 76 | 
            +
              let hiddenColumnNames = [...props.hiddenColumnNames];
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              if (visible && hiddenColumnNames.includes(column.name)) {
         | 
| 79 | 
            +
                hiddenColumnNames = remove(hiddenColumnNames, column.name);
         | 
| 80 | 
            +
              } else {
         | 
| 81 | 
            +
                hiddenColumnNames.push(column.name);
         | 
| 82 | 
            +
              }
         | 
| 83 | 
            +
              emit('update:hidden-column-names', [...new Set(hiddenColumnNames)]);
         | 
| 84 | 
            +
            }
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            function isTitleColumn(column) {
         | 
| 87 | 
            +
              return props.titleColumnNames.includes(column.name);
         | 
| 88 | 
            +
            }
         | 
| 89 | 
            +
            function onTitleColumnChange(column, isTitle) {
         | 
| 90 | 
            +
              let titleColumnNames = [...props.titleColumnNames];
         | 
| 91 | 
            +
              if (isTitle && !titleColumnNames.includes(column.name)) {
         | 
| 92 | 
            +
                if (!allowMoreTitleColumns.value) {
         | 
| 93 | 
            +
                  FlashMessages.warning(`You can only have ${props.titleColumnLimit} priority columns.`);
         | 
| 94 | 
            +
                  return;
         | 
| 95 | 
            +
                }
         | 
| 96 | 
            +
                titleColumnNames.push(column.name);
         | 
| 97 | 
            +
              } else {
         | 
| 98 | 
            +
                titleColumnNames = remove(titleColumnNames, column.name);
         | 
| 99 | 
            +
              }
         | 
| 100 | 
            +
              emit('update:title-column-names', [...new Set(titleColumnNames)]);
         | 
| 101 | 
            +
            }
         | 
| 102 | 
            +
            </script>
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            <template>
         | 
| 2 | 
            +
              <div>
         | 
| 3 | 
            +
                <div
         | 
| 4 | 
            +
                    v-for="column in columns" :key="column.name"
         | 
| 5 | 
            +
                    class="overflow-hidden overflow-ellipsis text-xs text-gray-base"
         | 
| 6 | 
            +
                >{{ format(row[column.name], column.format) }}
         | 
| 7 | 
            +
                </div>
         | 
| 8 | 
            +
              </div>
         | 
| 9 | 
            +
            </template>
         | 
| 10 | 
            +
            <script setup>
         | 
| 11 | 
            +
            defineProps({
         | 
| 12 | 
            +
              row: {
         | 
| 13 | 
            +
                type: Object,
         | 
| 14 | 
            +
                required: true
         | 
| 15 | 
            +
              },
         | 
| 16 | 
            +
              columns: {
         | 
| 17 | 
            +
                type: Array,
         | 
| 18 | 
            +
                required: true
         | 
| 19 | 
            +
              }
         | 
| 20 | 
            +
            });
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            function format(value, format) {
         | 
| 23 | 
            +
              if (typeof format === 'function') {
         | 
| 24 | 
            +
                return format(value);
         | 
| 25 | 
            +
              }
         | 
| 26 | 
            +
              return value;
         | 
| 27 | 
            +
            }
         | 
| 28 | 
            +
            </script>
         | 
| @@ -0,0 +1,156 @@ | |
| 1 | 
            +
            <template>
         | 
| 2 | 
            +
              <div class="flex items-center flex-nowrap">
         | 
| 3 | 
            +
                <div
         | 
| 4 | 
            +
                    v-for="category in categories"
         | 
| 5 | 
            +
                    :key="category"
         | 
| 6 | 
            +
                    class="category-toggle"
         | 
| 7 | 
            +
                    :class="{'has-visible-columns': categoryHasVisibleColumns(category)}"
         | 
| 8 | 
            +
                >
         | 
| 9 | 
            +
                  <QCheckbox
         | 
| 10 | 
            +
                      toggle-indeterminate
         | 
| 11 | 
            +
                      size="20px"
         | 
| 12 | 
            +
                      :model-value="getCategoryCheckboxState(category)"
         | 
| 13 | 
            +
                      class="mr-2 cb-white-border"
         | 
| 14 | 
            +
                      @click="toggleColumns(columnsInCategory(category), !categoryHasVisibleColumns(category))"
         | 
| 15 | 
            +
                  />
         | 
| 16 | 
            +
                  <div>
         | 
| 17 | 
            +
                    {{ category }}
         | 
| 18 | 
            +
                  </div>
         | 
| 19 | 
            +
                  <CaretDownIcon
         | 
| 20 | 
            +
                      class="ml-2 w-5 transition-all"
         | 
| 21 | 
            +
                      :class="{'rotate-180' : isShowingColumnToggle === category}"
         | 
| 22 | 
            +
                  />
         | 
| 23 | 
            +
                  <QMenu
         | 
| 24 | 
            +
                      @update:model-value="isShowingColumnToggle = $event ? category : ''"
         | 
| 25 | 
            +
                  >
         | 
| 26 | 
            +
                    <QList>
         | 
| 27 | 
            +
                      <div
         | 
| 28 | 
            +
                          v-for="column in columnsInCategory(category)"
         | 
| 29 | 
            +
                          :key="column"
         | 
| 30 | 
            +
                          class="flex items-center flex-nowrap px-2 py-3 cursor-pointer"
         | 
| 31 | 
            +
                          @click="toggleColumn(column.name)"
         | 
| 32 | 
            +
                      >
         | 
| 33 | 
            +
                        <QCheckbox
         | 
| 34 | 
            +
                            :model-value="!hiddenColumnNames.includes(column.name)"
         | 
| 35 | 
            +
                            class="mr-3 cb-white-border"
         | 
| 36 | 
            +
                            size="20px"
         | 
| 37 | 
            +
                            :color="column.required ? 'gray-base': 'blue-base'"
         | 
| 38 | 
            +
                            :disable="column.required"
         | 
| 39 | 
            +
                            @click="toggleColumn(column.name)"
         | 
| 40 | 
            +
                        />
         | 
| 41 | 
            +
                        <div class="text-xs">{{ column.label }}</div>
         | 
| 42 | 
            +
                      </div>
         | 
| 43 | 
            +
                    </QList>
         | 
| 44 | 
            +
                  </QMenu>
         | 
| 45 | 
            +
                </div>
         | 
| 46 | 
            +
              </div>
         | 
| 47 | 
            +
            </template>
         | 
| 48 | 
            +
            <script setup>
         | 
| 49 | 
            +
            import { computed, ref } from 'vue';
         | 
| 50 | 
            +
            import { remove } from '../../../helpers';
         | 
| 51 | 
            +
            import { CaretDownIcon } from '../../../svg';
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            const emit = defineEmits(['update:hidden-column-names']);
         | 
| 54 | 
            +
            const props = defineProps({
         | 
| 55 | 
            +
              columns: {
         | 
| 56 | 
            +
                type: Array,
         | 
| 57 | 
            +
                required: true
         | 
| 58 | 
            +
              },
         | 
| 59 | 
            +
              hiddenColumnNames: {
         | 
| 60 | 
            +
                type: Array,
         | 
| 61 | 
            +
                required: true
         | 
| 62 | 
            +
              }
         | 
| 63 | 
            +
            });
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            const isShowingColumnToggle = ref('');
         | 
| 66 | 
            +
            const categories = computed(() => [...new Set(props.columns.map(c => c.category)).values()]);
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            /**
         | 
| 69 | 
            +
             * Return a list of column names that belong to the category
         | 
| 70 | 
            +
             * @param category
         | 
| 71 | 
            +
             * @returns {(string|*)[]}
         | 
| 72 | 
            +
             */
         | 
| 73 | 
            +
            function columnsInCategory(category) {
         | 
| 74 | 
            +
              return props.columns.filter(c => c.category === category);
         | 
| 75 | 
            +
            }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            /**
         | 
| 78 | 
            +
             * Return true if any columns in the category are visible
         | 
| 79 | 
            +
             * @param category
         | 
| 80 | 
            +
             * @returns {boolean}
         | 
| 81 | 
            +
             */
         | 
| 82 | 
            +
            function categoryHasVisibleColumns(category) {
         | 
| 83 | 
            +
              // If there are any columns in the category that are not hidden, then the category has visible columns
         | 
| 84 | 
            +
              return columnsInCategory(category).filter(c => !c.required).map(c => c.name).some(c => !props.hiddenColumnNames.includes(c));
         | 
| 85 | 
            +
            }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            /**
         | 
| 88 | 
            +
             * Determines the state of the checkbox as either true, false or null (for the indeterminate state)
         | 
| 89 | 
            +
             * @param category
         | 
| 90 | 
            +
             * @returns {boolean|null}
         | 
| 91 | 
            +
             */
         | 
| 92 | 
            +
            function getCategoryCheckboxState(category) {
         | 
| 93 | 
            +
              let categoryColumns = columnsInCategory(category).filter(c => !c.required);
         | 
| 94 | 
            +
              const visibleColumns = categoryColumns.filter(c => !props.hiddenColumnNames.includes(c.name));
         | 
| 95 | 
            +
              if (visibleColumns.length === 0) {
         | 
| 96 | 
            +
                return false;
         | 
| 97 | 
            +
              } else if (visibleColumns.length === categoryColumns.length) {
         | 
| 98 | 
            +
                return true;
         | 
| 99 | 
            +
              }
         | 
| 100 | 
            +
              return null;
         | 
| 101 | 
            +
            }
         | 
| 102 | 
            +
            /**
         | 
| 103 | 
            +
             * Toggle all columns in a category
         | 
| 104 | 
            +
             * @param columns
         | 
| 105 | 
            +
             * @param showColumns
         | 
| 106 | 
            +
             */
         | 
| 107 | 
            +
            function toggleColumns(columns, showColumns) {
         | 
| 108 | 
            +
              // Ignore required columns
         | 
| 109 | 
            +
              columns = columns.filter(c => !c.required);
         | 
| 110 | 
            +
             | 
| 111 | 
            +
              let hiddenColumnNames = [...props.hiddenColumnNames];
         | 
| 112 | 
            +
              if (showColumns) {
         | 
| 113 | 
            +
                hiddenColumnNames = hiddenColumnNames.filter(c => !columns.map(c => c.name).includes(c));
         | 
| 114 | 
            +
              } else {
         | 
| 115 | 
            +
                hiddenColumnNames = [...new Set([...hiddenColumnNames, ...columns.map(c => c.name)])];
         | 
| 116 | 
            +
              }
         | 
| 117 | 
            +
              emit('update:hidden-column-names', hiddenColumnNames);
         | 
| 118 | 
            +
            }
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            /**
         | 
| 121 | 
            +
             * Toggle a single column
         | 
| 122 | 
            +
             * @param columnName
         | 
| 123 | 
            +
             * @param showColumn
         | 
| 124 | 
            +
             */
         | 
| 125 | 
            +
            function toggleColumn(columnName, showColumn) {
         | 
| 126 | 
            +
              // Do not allow toggling required columns
         | 
| 127 | 
            +
              if (props.columns.find(c => c.name === columnName).required) return;
         | 
| 128 | 
            +
             | 
| 129 | 
            +
              // Determine weather to add (hide) or remove (show) the column
         | 
| 130 | 
            +
              showColumn = showColumn ?? props.hiddenColumnNames.includes(columnName);
         | 
| 131 | 
            +
             | 
| 132 | 
            +
              let hiddenColumnNames = [...props.hiddenColumnNames];
         | 
| 133 | 
            +
             | 
| 134 | 
            +
              // Add or remove the column from the hiddenColumnNames array
         | 
| 135 | 
            +
              if (showColumn) {
         | 
| 136 | 
            +
                hiddenColumnNames = remove(hiddenColumnNames, columnName);
         | 
| 137 | 
            +
              } else {
         | 
| 138 | 
            +
                hiddenColumnNames.push(columnName);
         | 
| 139 | 
            +
                hiddenColumnNames = [...new Set(hiddenColumnNames)];
         | 
| 140 | 
            +
              }
         | 
| 141 | 
            +
             | 
| 142 | 
            +
              emit('update:hidden-column-names', hiddenColumnNames);
         | 
| 143 | 
            +
            }
         | 
| 144 | 
            +
            </script>
         | 
| 145 | 
            +
            <style
         | 
| 146 | 
            +
                lang="scss"
         | 
| 147 | 
            +
                scoped
         | 
| 148 | 
            +
            >
         | 
| 149 | 
            +
            .category-toggle {
         | 
| 150 | 
            +
              @apply text-xs font-bold rounded-lg border border-solid border-neutral-plus-5 px-2 py-1 mx-1 cursor-pointer flex items-center;
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              &.has-visible-columns {
         | 
| 153 | 
            +
                @apply text-white bg-blue-base;
         | 
| 154 | 
            +
              }
         | 
| 155 | 
            +
            }
         | 
| 156 | 
            +
            </style>
         | 
| @@ -0,0 +1,4 @@ | |
| 1 | 
            +
            export { default as ColumnListItem } from "./ColumnListItem.vue";
         | 
| 2 | 
            +
            export { default as ColumnSettingsDialog } from "./ColumnSettingsDialog.vue";
         | 
| 3 | 
            +
            export { default as TitleColumnFormat } from "./TItleColumnFormat.vue";
         | 
| 4 | 
            +
            export { default as VisibleColumnsToggleButtons } from "./VisibleColumnsToggleButtons.vue";
         | 
| @@ -20,11 +20,12 @@ | |
| 20 20 | 
             
            </template>
         | 
| 21 21 |  | 
| 22 22 | 
             
            <script setup>
         | 
| 23 | 
            +
            import { useDebounceFn } from '@vueuse/core';
         | 
| 23 24 | 
             
            import { computed, nextTick, ref, watch } from 'vue';
         | 
| 24 25 | 
             
            import { fNumber } from '../../../../helpers';
         | 
| 25 26 | 
             
            import FieldLabel from './FieldLabel';
         | 
| 26 27 |  | 
| 27 | 
            -
            const emit = defineEmits(['update:model-value']);
         | 
| 28 | 
            +
            const emit = defineEmits(['update:model-value', 'update']);
         | 
| 28 29 | 
             
            const props = defineProps({
         | 
| 29 30 | 
             
              modelValue: {
         | 
| 30 31 | 
             
                type: [String, Number],
         | 
| @@ -46,6 +47,10 @@ const props = defineProps({ | |
| 46 47 | 
             
                type: String,
         | 
| 47 48 | 
             
                default: ''
         | 
| 48 49 | 
             
              },
         | 
| 50 | 
            +
              delay: {
         | 
| 51 | 
            +
                type: Number,
         | 
| 52 | 
            +
                default: 1000
         | 
| 53 | 
            +
              },
         | 
| 49 54 | 
             
              hidePrependLabel: Boolean,
         | 
| 50 55 | 
             
              currency: Boolean,
         | 
| 51 56 | 
             
              showName: Boolean
         | 
| @@ -73,6 +78,9 @@ function format(number) { | |
| 73 78 | 
             
              }
         | 
| 74 79 | 
             
              return fNumber(number, options);
         | 
| 75 80 | 
             
            }
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            const onUpdateDebounced = useDebounceFn((val) => emit('update', val), props.delay);
         | 
| 83 | 
            +
             | 
| 76 84 | 
             
            function onInput(value) {
         | 
| 77 85 | 
             
              let number = '';
         | 
| 78 86 |  | 
| @@ -89,6 +97,11 @@ function onInput(value) { | |
| 89 97 | 
             
                number = Number(value);
         | 
| 90 98 | 
             
                numberVal.value = format(number);
         | 
| 91 99 | 
             
              }
         | 
| 92 | 
            -
             | 
| 100 | 
            +
             | 
| 101 | 
            +
              number = number === '' ? undefined : number;
         | 
| 102 | 
            +
              emit('update:model-value', number);
         | 
| 103 | 
            +
             | 
| 104 | 
            +
              // Delay the change event, so we only see the value after the user has finished
         | 
| 105 | 
            +
              onUpdateDebounced(number);
         | 
| 93 106 | 
             
            }
         | 
| 94 107 | 
             
            </script>
         | 
| @@ -41,13 +41,13 @@ | |
| 41 41 | 
             
                            :key="'selected-' + chipOption.label"
         | 
| 42 42 | 
             
                            class="!mr-1"
         | 
| 43 43 | 
             
                        >{{ chipOption.label }}
         | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 44 | 
            +
                        </q-chip>
         | 
| 45 | 
            +
                        <q-chip
         | 
| 46 | 
            +
                            v-if="selectedOptions.length > chipOptions.length"
         | 
| 47 | 
            +
                            class="!mr-1"
         | 
| 48 | 
            +
                        >
         | 
| 49 | 
            +
                          +{{ selectedOptions.length - chipOptions.length }}
         | 
| 50 | 
            +
                        </q-chip>
         | 
| 51 51 | 
             
                      </template>
         | 
| 52 52 | 
             
                      <template v-else>
         | 
| 53 53 | 
             
                        {{ placeholder }}
         | 
| @@ -59,7 +59,7 @@ | |
| 59 59 | 
             
                    >{{ selectedLabel }}
         | 
| 60 60 | 
             
                    </div>
         | 
| 61 61 | 
             
                  </template>
         | 
| 62 | 
            -
             | 
| 62 | 
            +
                </q-select>
         | 
| 63 63 | 
             
              </div>
         | 
| 64 64 | 
             
            </template>
         | 
| 65 65 | 
             
            <script setup>
         | 
| @@ -257,7 +257,11 @@ function onUpdate(value) { | |
| 257 257 | 
             
              if (Array.isArray(value)) {
         | 
| 258 258 | 
             
                value = value.map((v) => v === '__null__' ? null : v);
         | 
| 259 259 | 
             
              }
         | 
| 260 | 
            -
             | 
| 260 | 
            +
             | 
| 261 | 
            +
              value = value === '__null__' ? null : value;
         | 
| 262 | 
            +
             | 
| 263 | 
            +
              emit('change', value);
         | 
| 264 | 
            +
              emit('update:model-value', value);
         | 
| 261 265 | 
             
            }
         | 
| 262 266 |  | 
| 263 267 | 
             
            /** XXX: This tells us when we should apply the filter. QSelect likes to trigger a new filter everytime you open the dropdown
         | 
| @@ -292,6 +296,7 @@ async function onFilter(val, update) { | |
| 292 296 | 
             
             */
         | 
| 293 297 | 
             
            function onClear() {
         | 
| 294 298 | 
             
              emit('update:model-value', undefined);
         | 
| 299 | 
            +
              emit('change', undefined);
         | 
| 295 300 | 
             
            }
         | 
| 296 301 |  | 
| 297 302 | 
             
            /**
         | 
| @@ -147,6 +147,11 @@ export function useListControls(name: string, { | |
| 147 147 | 
             
                function setItemInPagedList(updatedItem) {
         | 
| 148 148 | 
             
                    const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
         | 
| 149 149 | 
             
                    pagedItems.value = { ...pagedItems.value, data };
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    // Update the active item as well if it is set
         | 
| 152 | 
            +
                    if (activeItem.value?.id === updatedItem.id) {
         | 
| 153 | 
            +
                        activeItem.value = { ...activeItem.value, ...updatedItem };
         | 
| 154 | 
            +
                    }
         | 
| 150 155 | 
             
                }
         | 
| 151 156 |  | 
| 152 157 | 
             
                /**
         | 
| @@ -1,16 +1,19 @@ | |
| 1 1 | 
             
            <template>
         | 
| 2 2 | 
             
              <QTabPanels
         | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 3 | 
            +
                  :model-value="activePanel"
         | 
| 4 | 
            +
                  class="overflow-y-auto bg-neutral-plus-7 h-full transition-all"
         | 
| 5 5 | 
             
              >
         | 
| 6 6 | 
             
                <QTabPanel v-for="panel in panels" :key="panel.name" :name="panel.name">
         | 
| 7 | 
            -
                  <RenderVnode | 
| 7 | 
            +
                  <RenderVnode
         | 
| 8 | 
            +
                      v-if="panel.vnode"
         | 
| 9 | 
            +
                      :vnode="panel.vnode"
         | 
| 10 | 
            +
                  />
         | 
| 8 11 | 
             
                </QTabPanel>
         | 
| 9 12 | 
             
              </QTabPanels>
         | 
| 10 13 | 
             
            </template>
         | 
| 11 14 |  | 
| 12 15 | 
             
            <script setup>
         | 
| 13 | 
            -
            import { RenderVnode } from  | 
| 16 | 
            +
            import { RenderVnode } from '../Utility';
         | 
| 14 17 |  | 
| 15 18 | 
             
            defineProps({
         | 
| 16 19 | 
             
              activePanel: {
         | 
    
        package/src/helpers/actions.ts
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            import { shallowRef, VNode } from "vue";
         | 
| 1 | 
            +
            import { ref, shallowRef, VNode } from "vue";
         | 
| 2 2 | 
             
            import { FlashMessages } from "./index";
         | 
| 3 3 |  | 
| 4 4 | 
             
            interface ActionOptions {
         | 
| @@ -8,11 +8,15 @@ interface ActionOptions { | |
| 8 8 | 
             
                batch?: boolean;
         | 
| 9 9 | 
             
                category?: string;
         | 
| 10 10 | 
             
                class?: string;
         | 
| 11 | 
            +
                trigger?: (target: object[] | object, input: any) => Promise<any>;
         | 
| 12 | 
            +
                activeTarget?: any;
         | 
| 11 13 | 
             
                vnode?: (target: object[] | object) => VNode;
         | 
| 12 14 | 
             
                enabled?: (target: object) => boolean;
         | 
| 13 15 | 
             
                batchEnabled?: (targets: object[]) => boolean;
         | 
| 16 | 
            +
                optimistic?: (action: ActionOptions, target: object, input: any) => void;
         | 
| 14 17 | 
             
                onAction?: (action: string | null, target: object, input: any) => Promise<any>;
         | 
| 15 18 | 
             
                onBatchAction?: (action: string | null, targets: object[], input: any) => Promise<any>;
         | 
| 19 | 
            +
                onStart?: (action: ActionOptions | null, targets: object, input: any) => boolean;
         | 
| 16 20 | 
             
                onSuccess?: (action: string | null, targets: object, input: any) => any;
         | 
| 17 21 | 
             
                onError?: (action: string | null, targets: object, input: any) => any;
         | 
| 18 22 | 
             
                onFinish?: (action: string | null, targets: object, input: any) => any;
         | 
| @@ -28,111 +32,113 @@ export const activeActionVnode = shallowRef(null); | |
| 28 32 | 
             
             * @param {ActionOptions} globalOptions
         | 
| 29 33 | 
             
             */
         | 
| 30 34 | 
             
            export function useActions(actions: ActionOptions[], globalOptions: ActionOptions = null) {
         | 
| 31 | 
            -
                const  | 
| 35 | 
            +
                const mappedActions = actions.map(action => {
         | 
| 36 | 
            +
                    const mappedAction = { ...globalOptions, ...action };
         | 
| 37 | 
            +
                    if (!mappedAction.trigger) {
         | 
| 38 | 
            +
                        mappedAction.trigger = (target, input) => performAction(mappedAction, target, input);
         | 
| 39 | 
            +
                        mappedAction.activeTarget = ref(null);
         | 
| 40 | 
            +
                    }
         | 
| 41 | 
            +
                    return mappedAction;
         | 
| 42 | 
            +
                });
         | 
| 32 43 |  | 
| 33 44 | 
             
                /**
         | 
| 34 | 
            -
                 *  | 
| 45 | 
            +
                 * Check if the provided target is currently being saved by any of the actions
         | 
| 46 | 
            +
                 */
         | 
| 47 | 
            +
                function isSavingTarget(target: any): boolean {
         | 
| 48 | 
            +
                    if (!target) return false;
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    for (const action of mappedActions) {
         | 
| 51 | 
            +
                        const activeTargets = (Array.isArray(action.activeTarget.value) ? action.activeTarget.value : [action.activeTarget.value]).filter(t => t);
         | 
| 52 | 
            +
                        if (activeTargets.length === 0) continue;
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                        for (const activeTarget of activeTargets) {
         | 
| 55 | 
            +
                            if (activeTarget === target || (activeTarget.id && activeTarget.id === target.id)) {
         | 
| 56 | 
            +
                                return true;
         | 
| 57 | 
            +
                            }
         | 
| 58 | 
            +
                        }
         | 
| 59 | 
            +
                    }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    return false;
         | 
| 62 | 
            +
                }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                /**
         | 
| 65 | 
            +
                 * Perform an action on a set of targets
         | 
| 35 66 | 
             
                 *
         | 
| 36 | 
            -
                 * @param name
         | 
| 37 | 
            -
                 * @param { | 
| 38 | 
            -
                 * @ | 
| 67 | 
            +
                 * @param {string} name - can either be a string or an action object
         | 
| 68 | 
            +
                 * @param {object[]|object} target - an array of targets or a single target object
         | 
| 69 | 
            +
                 * @param {any} input - The input data to pass to the action handler
         | 
| 39 70 | 
             
                 */
         | 
| 40 | 
            -
                function  | 
| 41 | 
            -
                    const action = typeof name === "string" ?  | 
| 71 | 
            +
                async function performAction(name: string | object, target: object[] | object, input: any = null) {
         | 
| 72 | 
            +
                    const action: ActionOptions = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
         | 
| 42 73 | 
             
                    if (!action) {
         | 
| 43 74 | 
             
                        throw new Error(`Unknown action: ${name}`);
         | 
| 44 75 | 
             
                    }
         | 
| 45 76 |  | 
| 46 | 
            -
                     | 
| 47 | 
            -
             | 
| 77 | 
            +
                    const vnode = action.vnode && action.vnode(target);
         | 
| 78 | 
            +
                    let result: any;
         | 
| 48 79 |  | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
                     | 
| 52 | 
            -
                     | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
                     * Filter the list of actions based on the provided filters in key-value pairs
         | 
| 56 | 
            -
                     * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
         | 
| 57 | 
            -
                     *
         | 
| 58 | 
            -
                     * @param filters
         | 
| 59 | 
            -
                     * @returns {ActionOptions[]}
         | 
| 60 | 
            -
                     */
         | 
| 61 | 
            -
                    filterActions(filters: object) {
         | 
| 62 | 
            -
                        let filteredActions = [...actions];
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                        for (const filter of Object.keys(filters)) {
         | 
| 65 | 
            -
                            const filterValue = filters[filter];
         | 
| 66 | 
            -
                            filteredActions = filteredActions.filter(a => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
         | 
| 67 | 
            -
                        }
         | 
| 68 | 
            -
                        return filteredActions;
         | 
| 69 | 
            -
                    },
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                    /**
         | 
| 72 | 
            -
                     * TODO: HOW TO INTEGRATE optimistic updates and single item updates?
         | 
| 73 | 
            -
                     */
         | 
| 74 | 
            -
                    async applyAction(item, input, itemData = {}) {
         | 
| 75 | 
            -
                        setItemInPagedList({ ...item, ...input, ...itemData });
         | 
| 76 | 
            -
                        const result = await applyActionRoute(item, input);
         | 
| 77 | 
            -
                        if (result.success) {
         | 
| 78 | 
            -
                            // Only render the most recent campaign changes
         | 
| 79 | 
            -
                            if (resultNumber !== actionResultCount) return;
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                            // Update the updated item in the previously loaded list if it exists
         | 
| 82 | 
            -
                            setItemInPagedList(result.item);
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                            // Update the active item if it is the same as the updated item
         | 
| 85 | 
            -
                            if (activeItem.value?.id === result.item.id) {
         | 
| 86 | 
            -
                                activeItem.value = { ...activeItem.value, ...result.item };
         | 
| 87 | 
            -
                            }
         | 
| 88 | 
            -
                        }
         | 
| 89 | 
            -
                        return result;
         | 
| 90 | 
            -
                    },
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                    /**
         | 
| 93 | 
            -
                     * Perform an action on a set of targets
         | 
| 94 | 
            -
                     *
         | 
| 95 | 
            -
                     * @param {string} name - can either be a string or an action object
         | 
| 96 | 
            -
                     * @param {object[]|object} target - an array of targets or a single target object
         | 
| 97 | 
            -
                     * @param {any} input
         | 
| 98 | 
            -
                     */
         | 
| 99 | 
            -
                    async performAction(name: string | object, target: object[] | object, input: any = null) {
         | 
| 100 | 
            -
                        const action = resolveAction(name);
         | 
| 101 | 
            -
                        const vnode = action.vnode && action.vnode(target);
         | 
| 102 | 
            -
                        let result = null;
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                        isSavingTarget.value = target;
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                        // If additional input is required, first render the vnode and wait for the confirm or cancel action
         | 
| 107 | 
            -
                        if (vnode) {
         | 
| 108 | 
            -
                            // If the action requires an input, we set the activeActionVnode to the input component.
         | 
| 109 | 
            -
                            // This will tell the ActionVnode to render the input component, and confirm or cancel the
         | 
| 110 | 
            -
                            // action The confirm function has the input from the component passed and will resolve the promise
         | 
| 111 | 
            -
                            // with the result of the action
         | 
| 112 | 
            -
                            result = await new Promise((resolve, reject) => {
         | 
| 113 | 
            -
                                activeActionVnode.value = {
         | 
| 114 | 
            -
                                    vnode,
         | 
| 115 | 
            -
                                    confirm: async input => {
         | 
| 116 | 
            -
                                        const result = await onConfirmAction(action, target, input);
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                                        // Only resolve when we have a non-error response, so we can show the error message w/o
         | 
| 119 | 
            -
                                        // hiding the dialog / vnode
         | 
| 120 | 
            -
                                        if (result === undefined || result === true || result?.success) {
         | 
| 121 | 
            -
                                            resolve(result);
         | 
| 122 | 
            -
                                        }
         | 
| 123 | 
            -
                                    },
         | 
| 124 | 
            -
                                    cancel: resolve
         | 
| 125 | 
            -
                                };
         | 
| 126 | 
            -
                            });
         | 
| 127 | 
            -
             | 
| 128 | 
            -
                            activeActionVnode.value = null;
         | 
| 129 | 
            -
                        } else {
         | 
| 130 | 
            -
                            result = await onConfirmAction(action, target, input);
         | 
| 80 | 
            +
                    action.activeTarget.value = target;
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    // Run the onStart handler if it exists and quit the operation if it returns false
         | 
| 83 | 
            +
                    if (action.onStart) {
         | 
| 84 | 
            +
                        if (action.onStart(action, target, input) === false) {
         | 
| 85 | 
            +
                            return;
         | 
| 131 86 | 
             
                        }
         | 
| 87 | 
            +
                    }
         | 
| 132 88 |  | 
| 133 | 
            -
             | 
| 134 | 
            -
             | 
| 89 | 
            +
                    // If additional input is required, first render the vnode and wait for the confirm or cancel action
         | 
| 90 | 
            +
                    if (vnode) {
         | 
| 91 | 
            +
                        // If the action requires an input, we set the activeActionVnode to the input component.
         | 
| 92 | 
            +
                        // This will tell the ActionVnode to render the input component, and confirm or cancel the
         | 
| 93 | 
            +
                        // action The confirm function has the input from the component passed and will resolve the promise
         | 
| 94 | 
            +
                        // with the result of the action
         | 
| 95 | 
            +
                        result = await new Promise((resolve, reject) => {
         | 
| 96 | 
            +
                            activeActionVnode.value = {
         | 
| 97 | 
            +
                                vnode,
         | 
| 98 | 
            +
                                confirm: async (confirmInput: any) => {
         | 
| 99 | 
            +
                                    const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                                    // Only resolve when we have a non-error response, so we can show the error message w/o
         | 
| 102 | 
            +
                                    // hiding the dialog / vnode
         | 
| 103 | 
            +
                                    if (result === undefined || result === true || result?.success) {
         | 
| 104 | 
            +
                                        resolve(result);
         | 
| 105 | 
            +
                                    }
         | 
| 106 | 
            +
                                },
         | 
| 107 | 
            +
                                cancel: resolve
         | 
| 108 | 
            +
                            };
         | 
| 109 | 
            +
                        });
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                        activeActionVnode.value = null;
         | 
| 112 | 
            +
                    } else {
         | 
| 113 | 
            +
                        result = await onConfirmAction(action, target, input);
         | 
| 135 114 | 
             
                    }
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    action.activeTarget.value = null;
         | 
| 117 | 
            +
                    return result;
         | 
| 118 | 
            +
                }
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                /**
         | 
| 121 | 
            +
                 * Filter the list of actions based on the provided filters in key-value pairs
         | 
| 122 | 
            +
                 * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
         | 
| 123 | 
            +
                 *
         | 
| 124 | 
            +
                 * @param filters
         | 
| 125 | 
            +
                 * @returns {ActionOptions[]}
         | 
| 126 | 
            +
                 */
         | 
| 127 | 
            +
                function filterActions(filters: object): ActionOptions[] {
         | 
| 128 | 
            +
                    let filteredActions = [...mappedActions];
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                    for (const filter of Object.keys(filters)) {
         | 
| 131 | 
            +
                        const filterValue = filters[filter];
         | 
| 132 | 
            +
                        filteredActions = filteredActions.filter(a => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
         | 
| 133 | 
            +
                    }
         | 
| 134 | 
            +
                    return filteredActions;
         | 
| 135 | 
            +
                }
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                return {
         | 
| 138 | 
            +
                    actions: mappedActions,
         | 
| 139 | 
            +
                    isSavingTarget,
         | 
| 140 | 
            +
                    filterActions,
         | 
| 141 | 
            +
                    performAction
         | 
| 136 142 | 
             
                };
         | 
| 137 143 | 
             
            }
         | 
| 138 144 |  | 
| @@ -146,6 +152,12 @@ async function onConfirmAction(action: ActionOptions, target: object[] | object, | |
| 146 152 | 
             
                    if (Array.isArray(target)) {
         | 
| 147 153 | 
             
                        result = await action.onBatchAction(action.name, target, input);
         | 
| 148 154 | 
             
                    } else {
         | 
| 155 | 
            +
                        // If the action has an optimistic callback, we call it before the actual action to immediately
         | 
| 156 | 
            +
                        // update the UI
         | 
| 157 | 
            +
                        if (action.optimistic) {
         | 
| 158 | 
            +
                            action.optimistic(action, target, input);
         | 
| 159 | 
            +
                        }
         | 
| 160 | 
            +
             | 
| 149 161 | 
             
                        result = await action.onAction(action.name, target, input);
         | 
| 150 162 | 
             
                    }
         | 
| 151 163 | 
             
                } catch (e) {
         | 
    
        package/src/svg/index.ts
    CHANGED