quasar-ui-danx 0.0.47 → 0.0.48

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.0.47",
3
+ "version": "0.0.48",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  :row-props="rowProps"
53
53
  :settings="columnSettings[rowProps.col.name]"
54
54
  >
55
- <slot />
55
+ <slot :column-name="rowProps.col.name" :row="rowProps.row" :value="rowProps.value" />
56
56
  </ActionTableColumn>
57
57
  </template>
58
58
  <template #bottom>
@@ -66,7 +66,7 @@ import { ref } from 'vue';
66
66
  import { getItem, setItem } from '../../helpers';
67
67
  import { DragHandleIcon as RowResizeIcon } from '../../svg';
68
68
  import { HandleDraggable } from '../DragAndDrop';
69
- import { ActionVnode } from '../index';
69
+ import { ActionVnode, mapSortBy } from '../index';
70
70
  import { ActionTableColumn, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
71
71
 
72
72
  defineEmits(['update:quasar-pagination', 'update:selected-rows']);
@@ -9,27 +9,18 @@
9
9
  class="flex-grow"
10
10
  @click="column.onClick(row)"
11
11
  >
12
- <RenderVnode
13
- v-if="column.vnode"
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
- v-if="column.vnode"
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.actions" class="flex flex-shrink-0 pl-2">
19
+ <div v-if="column.actionMenu" class="flex flex-shrink-0 pl-2">
30
20
  <ActionMenu
31
- :actions="column.actions"
21
+ :actions="column.actionMenu"
32
22
  :target="row"
23
+ :loading="isSaving"
33
24
  />
34
25
  </div>
35
26
  </div>
@@ -54,6 +45,7 @@ const props = defineProps({
54
45
  const row = computed(() => props.rowProps.row);
55
46
  const column = computed(() => props.rowProps.col);
56
47
  const value = computed(() => props.rowProps.value);
48
+ const isSaving = computed(() => column.value.isSaving && column.value.isSaving(row.value));
57
49
 
58
50
  const columnStyle = computed(() => {
59
51
  const width = props.settings?.width || column.value.width;
@@ -61,6 +53,7 @@ const columnStyle = computed(() => {
61
53
  });
62
54
 
63
55
  const columnClass = computed(() => ({
56
+ 'is-saving': isSaving.value,
64
57
  'justify-end': column.value.align === 'right',
65
58
  'justify-center': column.value.align === 'center',
66
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
- emit('update:model-value', number === '' ? undefined : number);
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
- </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>
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
- </q-select>
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
- emit('update:model-value', value === '__null__' ? null : value);
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
  /**
@@ -1,3 +1,4 @@
1
+ export * from "./Columns";
1
2
  export * from "./Filters";
2
3
  export * from "./Form";
3
4
  export * from "./Layouts";
@@ -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,27 +1,6 @@
1
1
  import { ref, shallowRef, VNode } from "vue";
2
2
  import { FlashMessages } from "./index";
3
3
 
4
- /**
5
- * TODO: HOW TO INTEGRATE optimistic updates and single item updates?
6
- */
7
- async function applyAction(item, input, itemData = {}) {
8
- setItemInPagedList({ ...item, ...input, ...itemData });
9
- const result = await applyActionRoute(item, input);
10
- if (result.success) {
11
- // Only render the most recent campaign changes
12
- if (resultNumber !== actionResultCount) return;
13
-
14
- // Update the updated item in the previously loaded list if it exists
15
- setItemInPagedList(result.item);
16
-
17
- // Update the active item if it is the same as the updated item
18
- if (activeItem.value?.id === result.item.id) {
19
- activeItem.value = { ...activeItem.value, ...result.item };
20
- }
21
- }
22
- return result;
23
- }
24
-
25
4
  interface ActionOptions {
26
5
  name?: string;
27
6
  label?: string;
@@ -34,8 +13,10 @@ interface ActionOptions {
34
13
  vnode?: (target: object[] | object) => VNode;
35
14
  enabled?: (target: object) => boolean;
36
15
  batchEnabled?: (targets: object[]) => boolean;
16
+ optimistic?: (action: ActionOptions, target: object, input: any) => void;
37
17
  onAction?: (action: string | null, target: object, input: any) => Promise<any>;
38
18
  onBatchAction?: (action: string | null, targets: object[], input: any) => Promise<any>;
19
+ onStart?: (action: ActionOptions | null, targets: object, input: any) => boolean;
39
20
  onSuccess?: (action: string | null, targets: object, input: any) => any;
40
21
  onError?: (action: string | null, targets: object, input: any) => any;
41
22
  onFinish?: (action: string | null, targets: object, input: any) => any;
@@ -52,11 +33,12 @@ export const activeActionVnode = shallowRef(null);
52
33
  */
53
34
  export function useActions(actions: ActionOptions[], globalOptions: ActionOptions = null) {
54
35
  const mappedActions = actions.map(action => {
55
- if (!action.trigger) {
56
- action.trigger = (target, input) => performAction(action, target, input);
57
- action.activeTarget = ref(null);
36
+ const mappedAction = { ...globalOptions, ...action };
37
+ if (!mappedAction.trigger) {
38
+ mappedAction.trigger = (target, input) => performAction(mappedAction, target, input);
39
+ mappedAction.activeTarget = ref(null);
58
40
  }
59
- return { ...globalOptions, ...action };
41
+ return mappedAction;
60
42
  });
61
43
 
62
44
  /**
@@ -66,7 +48,7 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
66
48
  if (!target) return false;
67
49
 
68
50
  for (const action of mappedActions) {
69
- const activeTargets = Array.isArray(action.activeTarget.value) ? action.activeTarget.value : [action.activeTarget.value];
51
+ const activeTargets = (Array.isArray(action.activeTarget.value) ? action.activeTarget.value : [action.activeTarget.value]).filter(t => t);
70
52
  if (activeTargets.length === 0) continue;
71
53
 
72
54
  for (const activeTarget of activeTargets) {
@@ -97,6 +79,13 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
97
79
 
98
80
  action.activeTarget.value = target;
99
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;
86
+ }
87
+ }
88
+
100
89
  // If additional input is required, first render the vnode and wait for the confirm or cancel action
101
90
  if (vnode) {
102
91
  // If the action requires an input, we set the activeActionVnode to the input component.
@@ -106,8 +95,8 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
106
95
  result = await new Promise((resolve, reject) => {
107
96
  activeActionVnode.value = {
108
97
  vnode,
109
- confirm: async (input: any) => {
110
- const result = await onConfirmAction(action, target, input);
98
+ confirm: async (confirmInput: any) => {
99
+ const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
111
100
 
112
101
  // Only resolve when we have a non-error response, so we can show the error message w/o
113
102
  // hiding the dialog / vnode
@@ -163,6 +152,12 @@ async function onConfirmAction(action: ActionOptions, target: object[] | object,
163
152
  if (Array.isArray(target)) {
164
153
  result = await action.onBatchAction(action.name, target, input);
165
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
+
166
161
  result = await action.onAction(action.name, target, input);
167
162
  }
168
163
  } catch (e) {
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M10 12L6 8H14L10 12Z" fill="currentColor"/>
3
+ </svg>
package/src/svg/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { default as CaretDownIcon } from "./CaretDownIcon.svg";
1
2
  export { default as DragHandleDotsIcon } from "./DragHandleDotsIcon.svg";
2
3
  export { default as DragHandleIcon } from "./DragHandleIcon.svg";
3
4
  export { default as FilterIcon } from "./FilterIcon.svg";