quasar-ui-danx 0.4.28 → 0.4.29

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.28",
3
+ "version": "0.4.29",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -1,30 +1,55 @@
1
1
  <template>
2
- <div
3
- contenteditable
4
- class="inline-block hover:bg-blue-200 focus:bg-blue-200 transition duration-300 outline-none"
5
- @input="onInput"
6
- >
7
- {{ text }}
2
+ <div class="inline-block relative">
3
+ <div
4
+ contenteditable
5
+ class="relative inline-block transition duration-300 outline-none outline-offset-0 border-none focus:outline-4 hover:outline-4 rounded-sm z-10"
6
+ :style="{minWidth, minHeight}"
7
+ :class="contentClass"
8
+ @input="onInput"
9
+ >
10
+ {{ text }}
11
+ </div>
12
+ <div
13
+ v-if="!text && placeholder"
14
+ ref="placeholderDiv"
15
+ class="text-gray-600 absolute-top-left whitespace-nowrap z-1"
16
+ >
17
+ {{ placeholder }}
18
+ </div>
8
19
  </div>
9
20
  </template>
10
21
 
11
- <script setup>
22
+ <script setup lang="ts">
12
23
  import { useDebounceFn } from "@vueuse/core";
13
- import { ref } from "vue";
24
+ import { computed, onMounted, ref } from "vue";
14
25
 
15
26
  const emit = defineEmits(["update:model-value", "change"]);
16
- const props = defineProps({
17
- modelValue: {
18
- type: String,
19
- required: true
20
- },
21
- debounceDelay: {
22
- type: Number,
23
- default: 1000
24
- }
27
+ const props = withDefaults(defineProps<{
28
+ modelValue: string;
29
+ color?: string;
30
+ debounceDelay?: number;
31
+ placeholder?: string;
32
+ }>(), {
33
+ // NOTE: You must safe-list required colors in tailwind.config.js
34
+ // Add text-blue-900, hover:bg-blue-200, hover:outline-blue-200, focus:outline-blue-200 and focus:bg-blue-200 for the following config
35
+ color: "blue-200",
36
+ textColor: "blue-900",
37
+ debounceDelay: 1000,
38
+ placeholder: "Enter Text..."
25
39
  });
26
40
 
27
41
  const text = ref(props.modelValue);
42
+ const placeholderDiv = ref(null);
43
+ const minWidth = ref(0);
44
+ const minHeight = ref(0);
45
+
46
+ onMounted(() => {
47
+ // Set the min-width to the width of the placeholder
48
+ if (placeholderDiv.value) {
49
+ minWidth.value = placeholderDiv.value.offsetWidth + "px";
50
+ minHeight.value = placeholderDiv.value.offsetHeight + "px";
51
+ }
52
+ });
28
53
 
29
54
  const debouncedChange = useDebounceFn(() => {
30
55
  emit("update:model-value", text.value);
@@ -36,4 +61,9 @@ function onInput(e) {
36
61
  debouncedChange();
37
62
  }
38
63
 
64
+ const contentClass = computed(() => [
65
+ `hover:bg-${props.color} focus:bg-${props.color}`,
66
+ `hover:text-${props.textColor} focus:text-${props.textColor}`,
67
+ `hover:outline-${props.color} focus:outline-${props.color}`
68
+ ]);
39
69
  </script>
@@ -23,6 +23,7 @@
23
23
  :disable="disabled"
24
24
  :label-slot="!noLabel"
25
25
  :input-class="inputClass"
26
+ :rows="rows"
26
27
  :class="{'dx-input-prepend-label': prependLabel}"
27
28
  stack-label
28
29
  :type="type"
@@ -71,6 +72,7 @@ withDefaults(defineProps<TextFieldProps>(), {
71
72
  labelClass: "",
72
73
  inputClass: "",
73
74
  maxLength: null,
75
+ rows: 3,
74
76
  debounce: 0,
75
77
  placeholder: null
76
78
  });
@@ -93,25 +93,11 @@
93
93
  </div>
94
94
  </template>
95
95
  <slot />
96
- <div
97
- v-if="savedAt"
98
- :class="savingClass"
99
- class="dx-saving-indicator flex items-center flex-nowrap"
100
- >
101
- <slot
102
- v-if="saving"
103
- name="saving"
104
- >
105
- Saving...
106
- <QSpinnerPie class="ml-2" />
107
- </slot>
108
- <slot
109
- v-else
110
- name="saved"
111
- >
112
- Saved at {{ fDateTime(savedAt, { format: "M/d/yy h:mm:ssa" }) }}
113
- </slot>
114
- </div>
96
+ <SaveStateIndicator
97
+ :saving="saving"
98
+ :saved-at="savedAt"
99
+ :saving-class="savingClass"
100
+ />
115
101
  <ConfirmDialog
116
102
  v-if="variationToEdit !== false"
117
103
  title="Change variation name"
@@ -139,7 +125,7 @@
139
125
  <script setup lang="ts">
140
126
  import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@heroicons/vue/solid";
141
127
  import { computed, onBeforeUnmount, onMounted, ref } from "vue";
142
- import { fDateTime, FlashMessages, incrementName, replace } from "../../../helpers";
128
+ import { FlashMessages, incrementName, replace } from "../../../helpers";
143
129
  import { TrashIcon as RemoveIcon } from "../../../svg";
144
130
  import { AnyObject, FormFieldValue, RenderedFormProps } from "../../../types";
145
131
  import { ConfirmDialog, RenderVnode } from "../../Utility";
@@ -154,6 +140,7 @@ import {
154
140
  TextField,
155
141
  WysiwygField
156
142
  } from "./Fields";
143
+ import SaveStateIndicator from "./Utilities/SaveStateIndicator";
157
144
 
158
145
  const props = withDefaults(defineProps<RenderedFormProps>(), {
159
146
  values: null,
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  v-if="maxLength"
4
- class="danx-input-chars mt-1"
4
+ class="danx-input-chars"
5
5
  :class="{'danx-input-chars--error': length > maxLength}"
6
6
  >
7
7
  {{ fNumber(length) }} / {{ fNumber(maxLength) }} characters
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <div
3
+ :class="savingClass"
4
+ class="dx-saving-indicator flex items-center flex-nowrap"
5
+ >
6
+ <slot
7
+ v-if="saving"
8
+ name="saving"
9
+ >
10
+ Saving...
11
+ <QSpinnerPie class="ml-2" />
12
+ </slot>
13
+ <slot
14
+ v-else
15
+ name="saved"
16
+ >
17
+ <template v-if="savedAt">
18
+ Saved at {{ fDateTime(savedAt, { format: "M/d/yy h:mm:ssa" }) }}
19
+ </template>
20
+ <template v-else>
21
+ Not saved
22
+ </template>
23
+ </slot>
24
+ </div>
25
+ </template>
26
+ <script setup lang="ts">
27
+ import { fDateTime } from "../../../../helpers";
28
+
29
+ withDefaults(defineProps<{
30
+ saving?: boolean;
31
+ savedAt?: string;
32
+ savingClass?: string;
33
+ }>(), {
34
+ savingClass: "text-sm text-slate-500",
35
+ savedAt: ""
36
+ });
37
+ </script>
@@ -1 +1,2 @@
1
1
  export { default as MaxLengthCounter } from "./MaxLengthCounter.vue";
2
+ export { default as SaveStateIndicator } from "./SaveStateIndicator.vue";
@@ -25,7 +25,7 @@
25
25
  v-if="activeFilter"
26
26
  :name="controller.name"
27
27
  :show-filters="showFilters"
28
- :filters="controller.filters.value"
28
+ :filters="controller.filters?.value"
29
29
  :active-filter="activeFilter"
30
30
  class="dx-action-table-filters"
31
31
  @update:active-filter="controller.setActiveFilter"
@@ -73,7 +73,7 @@
73
73
  </template>
74
74
  <script setup lang="ts">
75
75
  import { computed } from "vue";
76
- import { ActionController, ListController } from "../../../types";
76
+ import { DanxController } from "../../../types";
77
77
  import { PanelsDrawer } from "../../PanelsDrawer";
78
78
  import { PreviousNextControls } from "../../Utility";
79
79
  import ActionTable from "../ActionTable.vue";
@@ -81,7 +81,7 @@ import { CollapsableFiltersSidebar } from "../Filters";
81
81
  import { ActionToolbar } from "../Toolbars";
82
82
 
83
83
  export interface ActionTableLayoutProps {
84
- controller: ListController & ActionController;
84
+ controller: DanxController;
85
85
  exporter?: () => Promise<void>;
86
86
  hideToolbar?: boolean;
87
87
  panelTitleField?: string;
@@ -1,4 +1,4 @@
1
- import { computed, Ref, ref, shallowRef, watch } from "vue";
1
+ import { computed, ref, shallowRef, watch } from "vue";
2
2
  import { RouteParams, Router } from "vue-router";
3
3
  import { danxOptions } from "../../config";
4
4
  import { getItem, latestCallOnly, setItem, storeObject, waitForRef } from "../../helpers";
@@ -10,11 +10,12 @@ import {
10
10
  ListControlsFilter,
11
11
  ListControlsOptions,
12
12
  ListControlsPagination,
13
- PagedItems
13
+ PagedItems,
14
+ Ref
14
15
  } from "../../types";
15
16
  import { getFilterFromUrl } from "./listHelpers";
16
17
 
17
- export function useListControls(name: string, options: ListControlsOptions): ListController {
18
+ export function useControls(name: string, options: ListControlsOptions): ListController {
18
19
  let isInitialized = false;
19
20
  const PAGE_SETTINGS_KEY = `dx-${name}-pager`;
20
21
  const pagedItems = shallowRef<PagedItems | null>(null);
@@ -72,12 +73,13 @@ export function useListControls(name: string, options: ListControlsOptions): Lis
72
73
 
73
74
  async function loadList() {
74
75
  if (!isInitialized) return;
75
- isLoadingList.value = true;
76
+ // isLoadingList.value = true;
76
77
  try {
77
78
  setPagedItems(await options.routes.list(pager.value));
78
- isLoadingList.value = false;
79
79
  } catch (e) {
80
80
  // Fail silently
81
+ } finally {
82
+ isLoadingList.value = false;
81
83
  }
82
84
  }
83
85
 
@@ -91,9 +93,10 @@ export function useListControls(name: string, options: ListControlsOptions): Lis
91
93
  }
92
94
  try {
93
95
  summary.value = await options.routes.summary(summaryFilter);
94
- isLoadingSummary.value = false;
95
96
  } catch (e) {
96
97
  // Fail silently
98
+ } finally {
99
+ isLoadingSummary.value = false;
97
100
  }
98
101
  }
99
102
 
@@ -116,9 +119,10 @@ export function useListControls(name: string, options: ListControlsOptions): Lis
116
119
  isLoadingFilters.value = true;
117
120
  try {
118
121
  fieldOptions.value = await options.routes.fieldOptions(activeFilter.value) || {};
119
- isLoadingFilters.value = false;
120
122
  } catch (e) {
121
123
  // Fail silently
124
+ } finally {
125
+ isLoadingFilters.value = false;
122
126
  }
123
127
  }
124
128
 
@@ -1,8 +1,8 @@
1
1
  export * from "./Columns";
2
+ export * from "./controls";
2
3
  export * from "./Filters";
3
4
  export * from "./Form";
4
5
  export * from "./Layouts";
5
- export * from "./listControls";
6
6
  export * from "./listHelpers";
7
7
  export * from "./tableColumns";
8
8
  export * from "./Toolbars";
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <div
3
- class="cursor-move"
3
+ :class="{'cursor-move': !showHandle}"
4
4
  draggable="true"
5
- @dragstart="dragAndDrop.dragStart"
5
+ @dragstart.stop="dragAndDrop.dragStart"
6
6
  @dragend="dragAndDrop.dragEnd"
7
7
  >
8
- <div class="flex items-center">
8
+ <div :class="contentClass">
9
9
  <div
10
10
  v-if="showHandle"
11
+ class="cursor-move"
11
12
  :class="handleClass"
12
13
  >
13
14
  <SvgImg
@@ -33,17 +34,20 @@ const props = withDefaults(defineProps<{
33
34
  dropZone: string | (() => string);
34
35
  direction?: "vertical" | "horizontal";
35
36
  showHandle?: boolean;
37
+ changeDropZone?: boolean;
38
+ contentClass?: string | object;
36
39
  handleClass?: string | object;
37
40
  listItems?: any[];
38
41
  }>(), {
39
42
  direction: "vertical",
40
43
  handleClass: "",
44
+ contentClass: "flex flex-nowrap items-center",
41
45
  listItems: () => []
42
46
  });
43
47
 
44
48
  const dragAndDrop = new ListDragAndDrop()
45
49
  .setDropZone(props.dropZone)
46
- .setOptions({ showPlaceholder: true, direction: props.direction })
50
+ .setOptions({ showPlaceholder: true, allowDropZoneChange: props.changeDropZone, direction: props.direction })
47
51
  .onStart(() => dragging.value = true)
48
52
  .onEnd(() => dragging.value = false)
49
53
  .onDropZoneChange((target, dropZone, newPosition, oldPosition, data) => {
@@ -6,6 +6,7 @@ export class DragAndDrop {
6
6
  direction?: string,
7
7
  hideDragImage?: boolean,
8
8
  showPlaceholder?: boolean,
9
+ allowDropZoneChange?: boolean,
9
10
  } = { direction: "vertical", hideDragImage: false };
10
11
 
11
12
  // State
@@ -138,7 +139,7 @@ export class DragAndDrop {
138
139
  */
139
140
  dragEnd(e) {
140
141
  this.currentDropZone = null;
141
- this.abortController.abort();
142
+ this.abortController?.abort();
142
143
  this.draggableData = null;
143
144
  this.onEndCb && this.onEndCb(e);
144
145
  }
@@ -19,6 +19,7 @@ export class ListDragAndDrop extends DragAndDrop {
19
19
  constructor(options = {}) {
20
20
  super({
21
21
  showPlaceholder: true,
22
+ allowDropZoneChange: true,
22
23
  ...options
23
24
  });
24
25
  }
@@ -156,7 +157,7 @@ export class ListDragAndDrop extends DragAndDrop {
156
157
  */
157
158
  getChildren() {
158
159
  return [...(this.currentDropZone?.children || [])].filter(
159
- (c) => c.className.match(/drag-placeholder/) === null
160
+ (c) => c.className.match(/dx-drag-placeholder/) === null
160
161
  );
161
162
  }
162
163
 
@@ -167,6 +168,13 @@ export class ListDragAndDrop extends DragAndDrop {
167
168
  return target.closest(`[data-drop-zone]`);
168
169
  }
169
170
 
171
+ /**
172
+ * Check if the current drop zone is the same as the initial drop zone
173
+ */
174
+ isSameDropZone() {
175
+ return this.currentDropZone === this.initialDropZone;
176
+ }
177
+
170
178
  /**
171
179
  * Find the element at the current cursor position in the given drop zone
172
180
  * @param point
@@ -236,19 +244,24 @@ export class ListDragAndDrop extends DragAndDrop {
236
244
  * Render a placeholder element at the given position (in between the elements)
237
245
  */
238
246
  renderPlaceholder() {
247
+ // If we're not allowed to change drop zones and we're not in the same drop zone, don't render the placeholder
248
+ if (!this.options.allowDropZoneChange && !this.isSameDropZone()) {
249
+ return;
250
+ }
251
+
239
252
  if (!this.placeholder) {
240
253
  this.placeholder = document.createElement("div");
241
- this.placeholder.classList.add("drag-placeholder");
254
+ this.placeholder.classList.add("dx-drag-placeholder");
242
255
  }
243
256
 
244
257
  // Make sure the placeholder is oriented correctly
245
258
  if (this.isVertical()) {
246
- this.placeholder.classList.add("direction-vertical");
247
- this.placeholder.classList.remove("direction-horizontal");
259
+ this.placeholder.classList.add("dx-direction-vertical");
260
+ this.placeholder.classList.remove("dx-direction-horizontal");
248
261
  this.placeholder.style.height = undefined;
249
262
  } else {
250
- this.placeholder.classList.add("direction-horizontal");
251
- this.placeholder.classList.remove("direction-vertical");
263
+ this.placeholder.classList.add("dx-direction-horizontal");
264
+ this.placeholder.classList.remove("dx-direction-vertical");
252
265
  this.placeholder.style.height =
253
266
  this.currentDropZone.getBoundingClientRect().height + "px";
254
267
  }
@@ -243,11 +243,11 @@ export class FileUpload {
243
243
  /**
244
244
  * Mark the presigned upload as completed and return the file resource from the platform server
245
245
  */
246
- async completePresignedUpload(fileUpload: XHRFileUpload) {
246
+ async completePresignedUpload(xhrFile: XHRFileUpload) {
247
247
  // Show 95% as the last 5% will be to complete the presigned upload
248
- this.fireProgressCallback(fileUpload, .95);
248
+ this.fireProgressCallback(xhrFile, .95);
249
249
 
250
- if (!fileUpload.file.resource_id) {
250
+ if (!xhrFile.file.resource_id) {
251
251
  throw new Error("File resource ID is required to complete presigned upload");
252
252
  }
253
253
 
@@ -256,7 +256,7 @@ export class FileUpload {
256
256
  }
257
257
 
258
258
  // Let the platform know the presigned upload is complete
259
- return await this.options.completePresignedUpload(fileUpload.file.resource_id);
259
+ return await this.options.completePresignedUpload(xhrFile.file.resource_id, xhrFile);
260
260
  }
261
261
 
262
262
  /**
@@ -1,53 +1,66 @@
1
1
  import ExifReader from "exifreader";
2
+ import { UploadedFile } from "src/types";
2
3
  import { useCompatibility } from "./compatibility";
3
4
  import { FlashMessages } from "./FlashMessages";
4
5
 
5
- export async function resolveFileLocation(file, waitMessage = null) {
6
- if (file.location) {
7
- return file.location;
8
- }
6
+ export async function resolveFileLocation(file: UploadedFile, waitMessage: string | null = null) {
7
+ if (file.location) {
8
+ return file.location;
9
+ }
9
10
 
10
- try {
11
- const tags = await ExifReader.load(file.blobUrl || file.url, {
12
- expanded: true
13
- });
14
- if (tags.gps) {
15
- return {
16
- latitude: tags.gps.Latitude,
17
- longitude: tags.gps.Longitude
18
- };
19
- }
11
+ try {
12
+ const tags = await ExifReader.load(file.blobUrl || file.url || "", {
13
+ expanded: true
14
+ });
15
+ if (tags.gps) {
16
+ return {
17
+ latitude: tags.gps.Latitude,
18
+ longitude: tags.gps.Longitude
19
+ };
20
+ }
21
+ } catch (error) {
22
+ console.error("Failed to load EXIF data from file:", error);
23
+ }
20
24
 
21
- const { waitForLocation, location } = useCompatibility();
25
+ try {
26
+ const { waitForLocation, location } = useCompatibility();
22
27
 
23
- // Show a waiting for location message if we have not returned within 1 second
24
- if (waitMessage) {
25
- setTimeout(() => {
26
- if (!location.value && waitMessage) {
27
- FlashMessages.warning(waitMessage);
28
- }
29
- }, 1000);
30
- }
28
+ // Show a waiting for location message if we have not returned within 1 second
29
+ if (waitMessage) {
30
+ setTimeout(() => {
31
+ if (!location.value && waitMessage) {
32
+ FlashMessages.warning(waitMessage);
33
+ }
34
+ }, 1000);
35
+ }
31
36
 
32
- // Wait for the browser to return the location (https only as http will not return a location)
33
- if (window.location.protocol === "https:") {
34
- await waitForLocation();
35
- }
36
- // Ignore the wait message if we already returned
37
- waitMessage = false;
38
- if (!location.value) {
39
- return null;
40
- }
37
+ // Wait for the browser to return the location (https only as http will not return a location)
38
+ if (window.location.protocol === "https:") {
39
+ await waitForLocation();
40
+ } else if (window.location.href.match("localhost")) {
41
+ // XXX: Special case for local development so we can test without https
42
+ return {
43
+ latitude: 37.7749,
44
+ longitude: -122.4194,
45
+ accuracy: 1,
46
+ altitude: 0,
47
+ altitudeAccuracy: 0
48
+ };
49
+ }
41
50
 
42
- return {
43
- latitude: location.value.latitude,
44
- longitude: location.value.longitude,
45
- accuracy: location.value.accuracy,
46
- altitude: location.value.altitude,
47
- altitudeAccuracy: location.value.altitudeAccuracy
48
- };
49
- } catch (error) {
50
- console.error(error);
51
- return null;
52
- }
51
+ if (!location.value) {
52
+ return null;
53
+ }
54
+
55
+ return {
56
+ latitude: location.value.latitude,
57
+ longitude: location.value.longitude,
58
+ accuracy: location.value.accuracy,
59
+ altitude: location.value.altitude,
60
+ altitudeAccuracy: location.value.altitudeAccuracy
61
+ };
62
+ } catch (error) {
63
+ console.error(error);
64
+ return null;
65
+ }
53
66
  }
@@ -1,25 +1,27 @@
1
- import { ListController, ListControlsRoutes } from "src/types/controls";
1
+ import { FilterGroup, ListController, ListControlsRoutes } from "src/types/controls";
2
+ import { FormField } from "src/types/forms";
3
+ import { TableColumn } from "src/types/tables";
2
4
  import { VNode } from "vue";
3
- import { AnyObject, TypedObject } from "./shared";
4
-
5
- export interface ActionPanel {
6
- name: string | number;
7
- label: string;
8
- category?: string;
9
- class?: string | object;
10
- enabled?: boolean | ((target: ActionTargetItem) => boolean);
11
- tabVnode?: (target: ActionTargetItem | null | undefined, activePanel: string | number) => VNode | any;
12
- vnode: (target: ActionTargetItem | null | undefined) => VNode | any;
13
- }
5
+ import { AnyObject, ComputedRef, TypedObject } from "./shared";
14
6
 
15
7
  export interface ActionTargetItem extends TypedObject {
16
8
  isSaving?: boolean;
17
9
  updated_at?: string;
18
10
  }
19
11
 
20
- export type ActionTarget = ActionTargetItem[] | ActionTargetItem | null;
12
+ export type ActionTarget<T = ActionTargetItem> = T[] | T | null;
21
13
 
22
- export interface ActionOptions {
14
+ export interface ActionPanel<T = ActionTargetItem> {
15
+ name: string | number;
16
+ label: string;
17
+ category?: string;
18
+ class?: string | object;
19
+ enabled?: boolean | ((target: T) => boolean);
20
+ tabVnode?: (target: T | null | undefined, activePanel: string | number) => VNode | any;
21
+ vnode: (target: T | null | undefined) => VNode | any;
22
+ }
23
+
24
+ export interface ActionOptions<T = ActionTargetItem> {
23
25
  name: string;
24
26
  alias?: string;
25
27
  label?: string;
@@ -30,17 +32,17 @@ export interface ActionOptions {
30
32
  category?: string;
31
33
  class?: string;
32
34
  debounce?: number;
33
- optimistic?: boolean | ((action: ActionOptions, target: ActionTargetItem | null, input: any) => void);
34
- vnode?: ((target: ActionTarget) => VNode) | any;
35
- enabled?: (target: object) => boolean;
36
- batchEnabled?: (targets: object[]) => boolean;
37
- onAction?: (action: string | ResourceAction | ActionOptions, target: ActionTargetItem | null, input?: AnyObject | any) => Promise<AnyObject | any> | void;
38
- onBatchAction?: (action: string | ResourceAction | ActionOptions, targets: ActionTargetItem[], input: any) => Promise<AnyObject | any> | void;
39
- onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
40
- onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
41
- onBatchSuccess?: (result: any, targets: ActionTargetItem[], input: any) => any;
42
- onError?: (result: any, targets: ActionTarget, input: any) => any;
43
- onFinish?: (result: any, targets: ActionTarget, input: any) => any;
35
+ optimistic?: boolean | ((action: ActionOptions<T>, target: T | null, input: any) => void);
36
+ vnode?: (target: ActionTarget<T>, data: any) => VNode | any;
37
+ enabled?: (target: ActionTarget<T>) => boolean;
38
+ batchEnabled?: (targets: T[]) => boolean;
39
+ onAction?: (action: string | ResourceAction<T> | ActionOptions<T>, target: T | null, input?: AnyObject | any) => Promise<AnyObject | any> | void;
40
+ onBatchAction?: (action: string | ResourceAction<T> | ActionOptions<T>, targets: T[], input: any) => Promise<AnyObject | any> | void;
41
+ onStart?: (action: ActionOptions<T> | null, targets: ActionTarget<T>, input: any) => boolean;
42
+ onSuccess?: (result: any, targets: ActionTarget<T>, input: any) => any;
43
+ onBatchSuccess?: (result: any, targets: T[], input: any) => any;
44
+ onError?: (result: any, targets: ActionTarget<T>, input: any) => any;
45
+ onFinish?: (result: any, targets: ActionTarget<T>, input: any) => any;
44
46
  }
45
47
 
46
48
  export interface ActionGlobalOptions extends Partial<ActionOptions> {
@@ -48,8 +50,24 @@ export interface ActionGlobalOptions extends Partial<ActionOptions> {
48
50
  controls?: ListController;
49
51
  }
50
52
 
51
- export interface ResourceAction extends ActionOptions {
53
+ export interface ResourceAction<T = ActionTargetItem> extends ActionOptions<T> {
52
54
  isApplying: boolean;
53
- trigger: (target?: ActionTarget, input?: any) => Promise<any>;
55
+ trigger: (target?: ActionTarget<T>, input?: any) => Promise<any>;
54
56
  __type: string;
55
57
  }
58
+
59
+ export interface ActionController<T = ActionTargetItem> {
60
+ // Actions
61
+ action?: (actionName: string, target?: T | null, input?: any) => Promise<any | void>;
62
+ getAction?: (actionName: string, actionOptions?: Partial<ActionOptions>) => ResourceAction;
63
+ getActions?: (names?: string[]) => ResourceAction[];
64
+ extendAction?: (actionName: string, extendedId: string | number, actionOptions: Partial<ActionOptions>) => ResourceAction;
65
+ modifyAction?: (actionName: string, actionOptions: Partial<ActionOptions>) => ResourceAction;
66
+ batchActions?: ResourceAction[];
67
+ menuActions?: ResourceAction[];
68
+ columns?: TableColumn[];
69
+ filters?: ComputedRef<FilterGroup[]>;
70
+ fields?: FormField[];
71
+ panels?: ActionPanel[];
72
+ routes?: ListControlsRoutes;
73
+ }