@hybridly/vue 0.10.0-beta.25 → 0.10.0-beta.27

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/dist/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FormDataConvertible, Path, PathValue, RequestData, SearchableObject } from "@hybridly/utils";
2
2
  import * as vue from "vue";
3
3
  import { App, Component, ComputedRef, DeepReadonly, DefineComponent, MaybeRefOrGetter, Plugin, PropType, Ref, SlotsType, h } from "vue";
4
+ import * as _hybridly_core0 from "@hybridly/core";
4
5
  import { Errors, GlobalHybridlyProperties, HttpClient, HybridRequestOptions, Method, NavigationResponse, Plugin as Plugin$1, Progress, RequestMode, RouteName, RouteParameters, RouterContext, RouterContextOptions, UrlResolvable, Validation, registerHook as registerHook$1, route, router } from "@hybridly/core";
5
6
  import { Path as Path$1, SearchableObject as SearchableObject$1 } from "@clickbar/dot-diver";
6
7
 
@@ -33,10 +34,8 @@ declare const Deferred: vue.DefineComponent<vue.ExtractPropTypes<{
33
34
  type Errors$1<T extends SearchableObject$1> = { [K in keyof T]?: T[K] extends Record<string, any> ? Errors$1<T[K]> : string };
34
35
  type FormFieldBehavior$1 = boolean | string[];
35
36
  type DefaultFormOptions = Pick<FormOptions<object>, 'timeout' | 'resetOnSuccess' | 'resetOnError' | 'setDefaultOnSuccess' | 'progress' | 'preserveScroll' | 'preserveState' | 'preserveUrl' | 'headers' | 'errorBag' | 'spoof' | 'transformUrl' | 'updateHistoryState' | 'useFormData'>;
36
- interface FormOptions<T extends SearchableObject$1> extends Omit<HybridRequestOptions, 'data' | 'url' | 'reset'> {
37
- fields: T;
37
+ type FormSubmitOptions$1<T extends SearchableObject$1> = Omit<HybridRequestOptions, 'data' | 'url' | 'reset'> & {
38
38
  url?: UrlResolvable | (() => UrlResolvable);
39
- key?: string | false;
40
39
  /**
41
40
  * Defines the delay after which the `recentlySuccessful` and `recentlyFailed` variables are reset to `false`.
42
41
  */
@@ -60,6 +59,27 @@ interface FormOptions<T extends SearchableObject$1> extends Omit<HybridRequestOp
60
59
  * Callback executed before the form submission for transforming the fields.
61
60
  */
62
61
  transform?: (fields: T) => any;
62
+ };
63
+ type FormAutomaticallySubmitOptions<T extends SearchableObject$1> = true | (FormSubmitOptions$1<T> & {
64
+ /**
65
+ * Debounce delay in milliseconds before submitting automatically.
66
+ * @default 100
67
+ */
68
+ debounce?: number;
69
+ /**
70
+ * Whether to immediately submit the form on any change. If `false`, submits after the `debounce` delay.
71
+ * @default true
72
+ */
73
+ immediate?: boolean;
74
+ });
75
+ interface FormOptions<T extends SearchableObject$1> extends FormSubmitOptions$1<T> {
76
+ fields: T;
77
+ key?: string | false;
78
+ /**
79
+ * Automatically submits the form when fields change.
80
+ * Pass `true` to use defaults or an object to customize submit overrides and debounce delay.
81
+ */
82
+ automaticallySubmit?: FormAutomaticallySubmitOptions<T>;
63
83
  }
64
84
  interface FormReturn<T extends SearchableObject$1, P extends Path$1<T> & string = Path$1<T> & string> {
65
85
  resetFields: (...keys: P[]) => void;
@@ -73,7 +93,7 @@ interface FormReturn<T extends SearchableObject$1, P extends Path$1<T> & string
73
93
  clearError: (key: P) => void;
74
94
  setDefault: (newDefault: Partial<T>) => void;
75
95
  hasDirty: (...keys: P[]) => boolean;
76
- submit: (optionsOverrides?: Omit<FormOptions<T>, 'fields' | 'key'>) => Promise<any>;
96
+ submit: (optionsOverrides?: FormSubmitOptions$1<T>) => Promise<any>;
77
97
  hasErrors: boolean;
78
98
  defaults: DeepReadonly<T>;
79
99
  loaded: DeepReadonly<T>;
@@ -324,6 +344,14 @@ interface BulkSelection<T = any> {
324
344
  /** Excluded records. */
325
345
  except: Set<T>;
326
346
  }
347
+ /**
348
+ * Returns the inclusive range between two records in the given order.
349
+ *
350
+ * This is useful for implementing shift-click bulk selection: keep track of the last selected anchor,
351
+ * then pass the currently visible record identifiers and the clicked target identifier to this helper.
352
+ * If the target is not in the list, an empty range is returned. If the anchor is missing, only the target is returned.
353
+ */
354
+ declare function getBulkSelectionRange<T>(records: readonly T[], anchor: T | undefined, target: T): T[];
327
355
  declare function useBulkSelect<T = any>(): {
328
356
  allSelected: vue.ComputedRef<boolean>;
329
357
  anySelected: vue.ComputedRef<boolean>;
@@ -1049,19 +1077,18 @@ declare function useRefinements<T extends Refinements>(input: MaybeRefOrGetter<T
1049
1077
  declare const registerHook: typeof registerHook$1;
1050
1078
  //#endregion
1051
1079
  //#region src/composables/table.d.ts
1052
- declare global {
1053
- interface Table<T extends Record<string, any> = any, PaginatorKind extends 'cursor' | 'length-aware' | 'simple' = 'length-aware'> {
1054
- id: string;
1055
- keyName: string;
1056
- scope?: string;
1057
- columns: Column<T>[];
1058
- inlineActions: InlineAction[];
1059
- bulkActions: BulkAction[];
1060
- records: Array<T>;
1061
- paginator: Omit<PaginatorKind extends 'cursor' ? CursorPaginator<T> : (PaginatorKind extends 'simple' ? SimplePaginator<T> : Paginator<T>), 'data'>;
1062
- refinements: Refinements;
1063
- endpoint: string;
1064
- }
1080
+ interface Table<T extends Record<string, any> = any, PaginatorKind extends 'cursor' | 'length-aware' | 'simple' = 'length-aware'> {
1081
+ id: string;
1082
+ keyName: keyof T | null;
1083
+ scope?: string;
1084
+ columns: Column<T>[];
1085
+ inlineActions: InlineAction[];
1086
+ bulkActions: BulkAction[];
1087
+ records: Array<T>;
1088
+ cells: TableCellRow<T>[];
1089
+ paginator: Omit<PaginatorKind extends 'cursor' ? CursorPaginator<T> : (PaginatorKind extends 'simple' ? SimplePaginator<T> : Paginator<T>), 'data'>;
1090
+ refinements: Refinements;
1091
+ endpoint: string;
1065
1092
  }
1066
1093
  interface Column<T extends object = never> {
1067
1094
  /** The name of this column. */
@@ -1073,6 +1100,14 @@ interface Column<T extends object = never> {
1073
1100
  /** Metadata of this column. */
1074
1101
  metadata: Record<string, any>;
1075
1102
  }
1103
+ interface TableCell<T extends object = never> {
1104
+ value: any;
1105
+ extra: Record<string, any>;
1106
+ }
1107
+ interface TableCellRow<T extends object = never> {
1108
+ key: RecordIdentifier | null;
1109
+ columns: Partial<Record<keyof T | string, TableCell<T>>>;
1110
+ }
1076
1111
  interface Action {
1077
1112
  /** The name of this action. */
1078
1113
  name: string;
@@ -1093,15 +1128,8 @@ interface BulkActionOptions extends Omit<HybridRequestOptions, 'url'> {
1093
1128
  /** Force deselecting all records after action. */
1094
1129
  deselect?: boolean;
1095
1130
  }
1096
- interface InlineActionOptions<T> extends Omit<HybridRequestOptions, 'url'> {
1097
- record: T;
1098
- }
1099
1131
  interface InlineAction extends Action {}
1100
1132
  type RecordIdentifier = string | number;
1101
- type AsRecordTypeWithExtra<T extends Record<string, any>> = { [K in keyof T]: {
1102
- extra: Record<string, any>;
1103
- value: T[K];
1104
- } };
1105
1133
  interface TableDefaultOptions extends AvailableHybridRequestOptions {
1106
1134
  /**
1107
1135
  * Whether to include existing query parameters in the request.
@@ -1113,98 +1141,126 @@ interface TableDefaultOptions extends AvailableHybridRequestOptions {
1113
1141
  */
1114
1142
  data?: Record<string, FormDataConvertible> | FormDataConvertible;
1115
1143
  }
1116
- /**
1117
- * Provides utilities for working with tables.
1118
- */
1119
- declare function useTable<T extends Table<any, any>, RecordType extends Record<string, any> = (T extends Table<infer R, any> ? R : any), PaginatorKind extends 'cursor' | 'length-aware' | 'simple' = (T extends Table<any, infer P> ? P : 'length-aware'), RecordTypeWithExtra extends Record<string, any> = AsRecordTypeWithExtra<RecordType>>(input: MaybeRefOrGetter<T>, defaultOptions?: TableDefaultOptions): {
1120
- bindFilter: <T_1 = string | number>(name: string, options?: BindFilterOptions<T_1>) => vue.Ref<string>;
1121
- filters: BoundFilterRefinement[];
1122
- sorts: BoundSortRefinement[];
1123
- filtersKey: string;
1124
- sortsKey: string;
1125
- getFilter: (name: string) => BoundFilterRefinement | undefined;
1126
- getSort: (name: string) => BoundSortRefinement | undefined;
1127
- reset: (options?: AvailableHybridRequestOptions) => Promise<index_d_exports.NavigationResponse>;
1128
- toggleSort: (name: string, options?: ToggleSortOptions) => Promise<index_d_exports.NavigationResponse | undefined>;
1129
- isSorting: (name?: string, direction?: SortDirection) => boolean;
1130
- isFiltering: (name?: string) => boolean;
1131
- currentSorts: () => Array<SortRefinement>;
1132
- currentFilters: () => Array<FilterRefinement>;
1133
- clearFilter: (filter: string, options?: AvailableHybridRequestOptions) => Promise<index_d_exports.NavigationResponse>;
1134
- clearSorts: (options?: AvailableHybridRequestOptions) => Promise<index_d_exports.NavigationResponse>;
1135
- clearFilters: (options?: AvailableHybridRequestOptions) => Promise<index_d_exports.NavigationResponse>;
1136
- applyFilter: (name: string, value: any, options?: AvailableHybridRequestOptionsForFilters) => Promise<index_d_exports.NavigationResponse | undefined>;
1144
+ type UseTableNavigationResponse = Promise<_hybridly_core0.NavigationResponse | undefined>;
1145
+ type ExtractRefValue<T> = T extends {
1146
+ value: infer Value;
1147
+ } ? Value : T;
1148
+ interface UseTableInlineActionItem<RecordType extends Record<string, any>> extends InlineAction {
1149
+ /** Executes the action. */
1150
+ execute: (record: RecordIdentifier | RecordType) => UseTableNavigationResponse;
1151
+ }
1152
+ interface UseTableBulkActionItem extends BulkAction {
1153
+ /** Executes the action. */
1154
+ execute: (options?: BulkActionOptions) => UseTableNavigationResponse;
1155
+ }
1156
+ interface UseTableColumn<RecordType extends Record<string, any>> extends Column<RecordType> {
1157
+ /** Toggles sorting for this column. */
1158
+ toggleSort: (options?: ToggleSortOptions) => UseTableNavigationResponse;
1159
+ /** Checks whether the column is being sorted. */
1160
+ isSorting: (direction?: SortDirection) => boolean;
1161
+ /** Applies the filter for this column. */
1162
+ applyFilter: (value: any, options?: AvailableHybridRequestOptions) => UseTableNavigationResponse;
1163
+ /** Clears the filter for this column. */
1164
+ clearFilter: (options?: AvailableHybridRequestOptions) => UseTableNavigationResponse;
1165
+ /** Checks whether the column is sortable. */
1166
+ isSortable: boolean;
1167
+ /** Checks whether the column is filterable. */
1168
+ isFilterable: boolean;
1169
+ }
1170
+ interface UseTableRecordItem<RecordType extends Record<string, any>> {
1171
+ /** The actual record. */
1172
+ record: RecordType;
1173
+ /** Local key of the record. Use this as a rendering key. */
1174
+ key: RecordIdentifier;
1175
+ /** Server-side key of the record, if any. */
1176
+ recordKey: RecordIdentifier | undefined;
1177
+ /** Whether this record has a server-side key. */
1178
+ hasKey: boolean;
1179
+ /** Executes the given inline action. */
1180
+ execute: (action: string | InlineAction) => UseTableNavigationResponse;
1181
+ /** Gets the available inline actions. */
1182
+ actions: Array<InlineAction & {
1183
+ execute: () => UseTableNavigationResponse;
1184
+ }>;
1185
+ /** Selects this record. */
1186
+ select: () => void;
1187
+ /** Deselects this record. */
1188
+ deselect: () => void;
1189
+ /** Toggles the selection for this record. */
1190
+ toggle: (force?: boolean) => void;
1191
+ /** Checks whether this record is selected. */
1192
+ selected: boolean;
1193
+ /** Gets the value of the record for the specified column. */
1194
+ value: (column: string | Column<RecordType>) => any;
1195
+ /** Gets the extra object of the record for the specified column. */
1196
+ extra: (column: string | Column<RecordType>, path: string) => any;
1197
+ }
1198
+ interface UseTableReturn<T extends Table<any, any>, RecordType extends Record<string, any> = (T extends Table<infer R, any> ? R : any), PaginatorKind extends 'cursor' | 'length-aware' | 'simple' = (T extends Table<any, infer P> ? P : 'length-aware')> extends Omit<UseRefinements, 'filters' | 'sorts' | 'filtersKey' | 'sortsKey'> {
1199
+ /** Selects all records. */
1137
1200
  selectAll: () => void;
1201
+ /** Deselects all records. */
1138
1202
  deselectAll: () => void;
1203
+ /** Selects records on the current page. */
1139
1204
  selectPage: () => void;
1205
+ /** Deselects records on the current page. */
1140
1206
  deselectPage: () => void;
1207
+ /** Whether all records on the current page are selected. */
1141
1208
  isPageSelected: boolean;
1142
- isSelected: (record: RecordTypeWithExtra | RecordType) => boolean;
1209
+ /** Checks if the given record is selected. */
1210
+ isSelected: (record: RecordType) => boolean;
1211
+ /** Whether all records are selected. */
1143
1212
  allSelected: boolean;
1213
+ /** Whether any records are selected. */
1144
1214
  anySelected: boolean;
1215
+ /** The current record selection. */
1145
1216
  selection: BulkSelection<RecordIdentifier>;
1217
+ /** Binds a checkbox to its selection state. */
1146
1218
  bindCheckbox: (key: RecordIdentifier) => {
1147
1219
  onChange: (event: Event) => void;
1148
1220
  checked: boolean;
1149
1221
  value: RecordIdentifier;
1150
1222
  };
1151
- toggle: (record: RecordTypeWithExtra | RecordType, force?: boolean) => void;
1223
+ /** Toggles selection for the given record. */
1224
+ toggle: (record: RecordType, force?: boolean) => void;
1225
+ /** Toggles selection for all records. */
1152
1226
  toggleAll: (force?: boolean) => void;
1153
- select: (record: RecordTypeWithExtra | RecordType) => void;
1154
- deselect: (record: RecordTypeWithExtra | RecordType) => void;
1155
- inlineActions: {
1156
- /** The name of this action. */name: string; /** The label of this action. */
1157
- label: string; /** The type of this action. */
1158
- type: string; /** Custom metadata for this action. */
1159
- metadata: any; /** A user-defined URL to which to post the action. */
1160
- url?: string; /** Executes the action. */
1161
- execute: (record: RecordTypeWithExtra | RecordIdentifier | RecordType) => Promise<index_d_exports.NavigationResponse | undefined>;
1162
- }[];
1163
- bulkActions: {
1164
- /** Should deselect all records after action. */deselect: boolean; /** The name of this action. */
1165
- name: string; /** The label of this action. */
1166
- label: string; /** The type of this action. */
1167
- type: string; /** Custom metadata for this action. */
1168
- metadata: any; /** A user-defined URL to which to post the action. */
1169
- url?: string; /** Executes the action. */
1170
- execute: (options?: BulkActionOptions) => Promise<index_d_exports.NavigationResponse | undefined>;
1171
- }[];
1172
- executeInlineAction: (action: InlineAction | string, options: InlineActionOptions<RecordTypeWithExtra | RecordIdentifier | RecordType>) => Promise<index_d_exports.NavigationResponse | undefined>;
1173
- executeBulkAction: (action: BulkAction | string, options?: BulkActionOptions) => Promise<index_d_exports.NavigationResponse | undefined>;
1174
- columns: {
1175
- /** Toggles sorting for this column. */toggleSort: (options?: ToggleSortOptions) => Promise<index_d_exports.NavigationResponse | undefined>; /** Checks whether the column is being sorted. */
1176
- isSorting: (direction?: SortDirection) => boolean; /** Applies the filer for this column. */
1177
- applyFilter: (value: any, options?: AvailableHybridRequestOptions) => Promise<index_d_exports.NavigationResponse | undefined>; /** Clears the filter for this column. */
1178
- clearFilter: (options?: AvailableHybridRequestOptions) => Promise<index_d_exports.NavigationResponse>; /** Checks whether the column is sortable. */
1179
- isSortable: boolean; /** Checks whether the column is filterable. */
1180
- isFilterable: boolean; /** The name of this column. */
1181
- name: keyof RecordTypeWithExtra; /** The label of this column. */
1182
- label: string; /** The type of this column. */
1183
- type: string; /** Metadata of this column. */
1184
- metadata: Record<string, any>;
1185
- }[];
1227
+ /** Selects selection for the given record. */
1228
+ select: (record: RecordType) => void;
1229
+ /** Deselects selection for the given record. */
1230
+ deselect: (record: RecordType) => void;
1231
+ /** List of inline actions for this table. */
1232
+ inlineActions: Array<UseTableInlineActionItem<RecordType>>;
1233
+ /** List of bulk actions for this table. */
1234
+ bulkActions: Array<UseTableBulkActionItem>;
1235
+ /** Executes the given inline action for the given record. */
1236
+ executeInlineAction: (action: InlineAction | string, options: {
1237
+ record: RecordIdentifier | RecordType;
1238
+ } & Omit<HybridRequestOptions, 'url'>) => UseTableNavigationResponse;
1239
+ /** Executes the given bulk action. */
1240
+ executeBulkAction: (action: BulkAction | string, options?: Omit<HybridRequestOptions, 'url'> & {
1241
+ deselect?: boolean;
1242
+ }) => UseTableNavigationResponse;
1243
+ /** List of columns for this table. */
1244
+ columns: Array<UseTableColumn<RecordType>>;
1245
+ /** List of records for this table. */
1186
1246
  data: RecordType[];
1187
- records: {
1188
- /** The actual record. */record: RecordType; /** The key of the record. Use this instead of `id`. */
1189
- key: RecordIdentifier; /** Executes the given inline action. */
1190
- execute: (action: string | InlineAction) => Promise<index_d_exports.NavigationResponse | undefined>; /** Gets the available inline actions. */
1191
- actions: {
1192
- /** Executes the action. */execute: () => Promise<index_d_exports.NavigationResponse | undefined>; /** The name of this action. */
1193
- name: string; /** The label of this action. */
1194
- label: string; /** The type of this action. */
1195
- type: string; /** Custom metadata for this action. */
1196
- metadata: any; /** A user-defined URL to which to post the action. */
1197
- url?: string;
1198
- }[]; /** Selects this record. */
1199
- select: () => void; /** Deselects this record. */
1200
- deselect: () => void; /** Toggles the selection for this record. */
1201
- toggle: (force?: boolean) => void; /** Checks whether this record is selected. */
1202
- selected: boolean; /** Gets the value of the record for the specified column. */
1203
- value: (column: string | Column<RecordTypeWithExtra>) => any; /** Gets the extra object of the record for the specified column. */
1204
- extra: (column: string | Column<RecordTypeWithExtra>, path: string) => any;
1205
- }[];
1206
- paginator: PaginatorResult<unknown, Omit<PaginatorKind extends "cursor" ? CursorPaginator<RecordTypeWithExtra> : PaginatorKind extends "simple" ? SimplePaginator<RecordTypeWithExtra> : Paginator<RecordTypeWithExtra>, "data">>;
1207
- };
1247
+ /** List of records for this table. */
1248
+ records: Array<UseTableRecordItem<RecordType>>;
1249
+ /** Paginated meta and links. */
1250
+ paginator: PaginatorResult<RecordType, Table<RecordType, PaginatorKind>['paginator']>;
1251
+ /** Available filters. */
1252
+ filters: ExtractRefValue<UseRefinements['filters']>;
1253
+ /** Available sorts. */
1254
+ sorts: ExtractRefValue<UseRefinements['sorts']>;
1255
+ /** The key for the filters. */
1256
+ filtersKey: ExtractRefValue<UseRefinements['filtersKey']>;
1257
+ /** The key for the sorts. */
1258
+ sortsKey: ExtractRefValue<UseRefinements['sortsKey']>;
1259
+ }
1260
+ /**
1261
+ * Provides utilities for working with tables.
1262
+ */
1263
+ declare function useTable<T extends Table<any, any>, RecordType extends Record<string, any> = (T extends Table<infer R, any> ? R : any), PaginatorKind extends 'cursor' | 'length-aware' | 'simple' = (T extends Table<any, infer P> ? P : 'length-aware')>(input: MaybeRefOrGetter<T>, defaultOptions?: TableDefaultOptions): UseTableReturn<T, RecordType, PaginatorKind>;
1208
1264
  //#endregion
1209
1265
  //#region src/composables/validation.d.ts
1210
1266
  /** Accesses all validation errors grouped by bag name. */
@@ -1302,4 +1358,4 @@ interface SetupArguments {
1302
1358
  payload: Record<string, any>;
1303
1359
  }
1304
1360
  //#endregion
1305
- export { Action, AvailableHybridRequestOptions, AvailableHybridRequestOptionsForFilters, BaseFilterRefinement, BindFilterOptions, BooleanFilterRefinement, BoundBooleanFilterRefinement, BoundCallbackFilterRefinement, BoundDateFilterRefinement, BoundFilterRefinement, BoundSelectFilterRefinement, BoundSortRefinement, BoundTernaryFilterRefinement, BoundTextFilterRefinement, BoundTrashedFilterRefinement, BulkAction, BulkSelection, CallbackFilterRefinement, Column, DateFilterRefinement, DefaultFormOptions, Deferred, FilterOperator, FilterRefinement, Form, type FormProps, FormReturn, type FormSlotProps, type FormSubmitOptions, type InitializeOptions, InlineAction, InternalProperties, MaybeWithData, NumericFilterRefinement, PaginatorResult, RecordIdentifier, Refinements, RouterLink, SelectFilterRefinement, SortDirection, SortRefinement, TableDefaultOptions, TernaryFilterRefinement, TextFilterRefinement, TimeSuggestion, TimeframeSuggestion, ToggleSortOptions, TrashedFilterRefinement, UseRefinements, WhenVisible, createPaginator, initializeHybridly, isBooleanFilter, isCallbackFilter, isDateFilter, isSelectFilter, isTernaryFilter, isTextFilter, isTrashedFilter, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable, useValidation, useValidationBag };
1361
+ export { Action, AvailableHybridRequestOptions, AvailableHybridRequestOptionsForFilters, BaseFilterRefinement, BindFilterOptions, BooleanFilterRefinement, BoundBooleanFilterRefinement, BoundCallbackFilterRefinement, BoundDateFilterRefinement, BoundFilterRefinement, BoundSelectFilterRefinement, BoundSortRefinement, BoundTernaryFilterRefinement, BoundTextFilterRefinement, BoundTrashedFilterRefinement, BulkAction, BulkSelection, CallbackFilterRefinement, Column, DateFilterRefinement, DefaultFormOptions, Deferred, FilterOperator, FilterRefinement, Form, type FormProps, FormReturn, type FormSlotProps, type FormSubmitOptions, type InitializeOptions, InlineAction, InternalProperties, MaybeWithData, NumericFilterRefinement, PaginatorResult, RecordIdentifier, Refinements, RouterLink, SelectFilterRefinement, SortDirection, SortRefinement, Table, TableCell, TableCellRow, TableDefaultOptions, TernaryFilterRefinement, TextFilterRefinement, TimeSuggestion, TimeframeSuggestion, ToggleSortOptions, TrashedFilterRefinement, UseRefinements, UseTableBulkActionItem, UseTableColumn, UseTableInlineActionItem, UseTableRecordItem, UseTableReturn, WhenVisible, createPaginator, getBulkSelectionRange, initializeHybridly, isBooleanFilter, isCallbackFilter, isDateFilter, isSelectFilter, isTernaryFilter, isTextFilter, isTrashedFilter, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable, useValidation, useValidationBag };
package/dist/index.mjs CHANGED
@@ -3,11 +3,11 @@ import { debug, merge, random, showViewComponentErrorModal, wrap } from "@hybrid
3
3
  import { computed, createApp, defineComponent, getCurrentInstance, h, isRef, nextTick, onMounted, onUnmounted, reactive, readonly, ref, shallowRef, toRaw, toValue, triggerRef, unref, watch } from "vue";
4
4
  import { createRouter, definePlugin, makeUrl, parseQueryString, registerHook as registerHook$1, route, route as route$1, router, router as router$1, stringifyQueryString } from "@hybridly/core";
5
5
  import { get, set, unset } from "es-toolkit/compat";
6
+ import { debounce } from "es-toolkit/function";
6
7
  import { clone, cloneDeep } from "es-toolkit/object";
7
8
  import { isEqual } from "es-toolkit/predicate";
8
9
  import { setupDevtoolsPlugin } from "@vue/devtools-api";
9
10
  import nprogress from "nprogress";
10
- import { debounce } from "es-toolkit/function";
11
11
  import { getByPath } from "@clickbar/dot-diver";
12
12
  //#region src/stores/state.ts
13
13
  const state = {
@@ -216,7 +216,7 @@ function useForm(options) {
216
216
  * Submits the form.
217
217
  */
218
218
  function submit(optionsOverrides) {
219
- const { fields: _f, key: _k, ...optionsWithoutFields } = options;
219
+ const { fields: _f, key: _k, automaticallySubmit: _as, ...optionsWithoutFields } = options;
220
220
  const resolvedOptions = optionsOverrides ? merge(optionsWithoutFields, optionsOverrides, { mergePlainObjects: true }) : optionsWithoutFields;
221
221
  const { timeout, resetOnSuccess, resetOnError, setDefaultOnSuccess, transform, ...requestOptions } = merge(formStore.getDefaultConfig(), resolvedOptions, { mergePlainObjects: true });
222
222
  const url = typeof requestOptions.url === "function" ? requestOptions.url() : requestOptions.url;
@@ -274,6 +274,23 @@ function useForm(options) {
274
274
  }
275
275
  });
276
276
  }
277
+ if (options.automaticallySubmit) {
278
+ const submitOptions = options.automaticallySubmit === true ? void 0 : (() => {
279
+ const { debounce: _debounce, ...submitOptions } = options.automaticallySubmit;
280
+ return submitOptions;
281
+ })();
282
+ const automaticallySubmitOptions = options.automaticallySubmit === true ? {
283
+ debounce: 100,
284
+ immediate: true
285
+ } : options.automaticallySubmit;
286
+ watch(() => fields, debounce(() => {
287
+ if (!isDirty.value) return;
288
+ submit(submitOptions);
289
+ }, automaticallySubmitOptions.debounce ?? 100), {
290
+ deep: true,
291
+ immediate: automaticallySubmitOptions.immediate ?? true
292
+ });
293
+ }
277
294
  /**
278
295
  * Clears all errors.
279
296
  */
@@ -1373,6 +1390,21 @@ function useBackForward(options) {
1373
1390
  }
1374
1391
  //#endregion
1375
1392
  //#region src/composables/bulk-select.ts
1393
+ /**
1394
+ * Returns the inclusive range between two records in the given order.
1395
+ *
1396
+ * This is useful for implementing shift-click bulk selection: keep track of the last selected anchor,
1397
+ * then pass the currently visible record identifiers and the clicked target identifier to this helper.
1398
+ * If the target is not in the list, an empty range is returned. If the anchor is missing, only the target is returned.
1399
+ */
1400
+ function getBulkSelectionRange(records, anchor, target) {
1401
+ const targetIndex = records.indexOf(target);
1402
+ if (targetIndex === -1) return [];
1403
+ if (anchor === void 0) return [target];
1404
+ const anchorIndex = records.indexOf(anchor);
1405
+ if (anchorIndex === -1) return [target];
1406
+ return records.slice(Math.min(anchorIndex, targetIndex), Math.max(anchorIndex, targetIndex) + 1);
1407
+ }
1376
1408
  function useBulkSelect() {
1377
1409
  const selection = ref({
1378
1410
  all: false,
@@ -1921,11 +1953,24 @@ function useTable(input, defaultOptions = {}) {
1921
1953
  */
1922
1954
  function getRecordKey(record) {
1923
1955
  if (typeof record !== "object") return record;
1924
- if (Reflect.has(record, "__hybridId")) return Reflect.get(record, "__hybridId");
1925
- if (!table.value.keyName) throw new Error("Record key cannot be fetched because the table has no defined key.");
1956
+ if (!table.value.keyName) return;
1926
1957
  const value = Reflect.get(record, table.value.keyName);
1927
- if (typeof value === "object" && Reflect.has(value, "value")) return value.value;
1928
- return value;
1958
+ if (typeof value === "string" || typeof value === "number") return value;
1959
+ }
1960
+ function getPageRecordKeys() {
1961
+ return table.value.records.map((record) => getRecordKey(record)).filter((key) => key !== void 0);
1962
+ }
1963
+ function warnMissingRecordKey(action) {
1964
+ console.warn(`Cannot ${action} because this table record has no key.`);
1965
+ }
1966
+ function getColumnName(column) {
1967
+ return typeof column === "string" ? column : column.name;
1968
+ }
1969
+ function getCell(index, column) {
1970
+ return table.value.cells[index]?.columns[getColumnName(column)] ?? {
1971
+ value: void 0,
1972
+ extra: {}
1973
+ };
1929
1974
  }
1930
1975
  function resolveInlineAction(action) {
1931
1976
  if (typeof action !== "string") return action;
@@ -1944,10 +1989,15 @@ function useTable(input, defaultOptions = {}) {
1944
1989
  */
1945
1990
  async function executeInlineAction(action, options) {
1946
1991
  const resolvedAction = resolveInlineAction(action);
1992
+ const recordKey = getRecordKey(options.record);
1947
1993
  if (!resolvedAction) {
1948
1994
  console.warn(`Action [${action}] is not defined`);
1949
1995
  return;
1950
1996
  }
1997
+ if (recordKey === void 0) {
1998
+ warnMissingRecordKey("execute an inline action");
1999
+ return;
2000
+ }
1951
2001
  return await router$1.navigate({
1952
2002
  method: "post",
1953
2003
  url: getActionUrl(resolvedAction, table.value),
@@ -1957,7 +2007,7 @@ function useTable(input, defaultOptions = {}) {
1957
2007
  type: "action:inline",
1958
2008
  action: resolvedAction.name,
1959
2009
  tableId: table.value.id,
1960
- recordId: getRecordKey(options.record)
2010
+ recordId: recordKey
1961
2011
  }
1962
2012
  });
1963
2013
  }
@@ -1996,20 +2046,35 @@ function useTable(input, defaultOptions = {}) {
1996
2046
  });
1997
2047
  }
1998
2048
  return reactive({
1999
- selectAll: bulk.selectAll,
2049
+ selectAll: () => table.value.keyName ? bulk.selectAll() : warnMissingRecordKey("select all records"),
2000
2050
  deselectAll: bulk.deselectAll,
2001
- selectPage: () => bulk.select(...table.value.records.map((record) => getRecordKey(record))),
2002
- deselectPage: () => bulk.deselect(...table.value.records.map((record) => getRecordKey(record))),
2003
- isPageSelected: computed(() => table.value.records.length > 0 && table.value.records.every((record) => bulk.selected(getRecordKey(record)))),
2004
- isSelected: (record) => bulk.selected(getRecordKey(record)),
2051
+ selectPage: () => bulk.select(...getPageRecordKeys()),
2052
+ deselectPage: () => bulk.deselect(...getPageRecordKeys()),
2053
+ isPageSelected: computed(() => {
2054
+ const keys = getPageRecordKeys();
2055
+ return keys.length > 0 && keys.every((key) => bulk.selected(key));
2056
+ }),
2057
+ isSelected: (record) => {
2058
+ const key = getRecordKey(record);
2059
+ return key === void 0 ? false : bulk.selected(key);
2060
+ },
2005
2061
  allSelected: bulk.allSelected,
2006
2062
  anySelected: bulk.anySelected,
2007
2063
  selection: bulk.selection,
2008
2064
  bindCheckbox: (key) => bulk.bindCheckbox(key),
2009
- toggle: (record, force) => bulk.toggle(getRecordKey(record), force),
2010
- toggleAll: (force) => bulk.toggleAll(force),
2011
- select: (record) => bulk.select(getRecordKey(record)),
2012
- deselect: (record) => bulk.deselect(getRecordKey(record)),
2065
+ toggle: (record, force) => {
2066
+ const key = getRecordKey(record);
2067
+ return key === void 0 ? warnMissingRecordKey("toggle record selection") : bulk.toggle(key, force);
2068
+ },
2069
+ toggleAll: (force) => table.value.keyName ? bulk.toggleAll(force) : warnMissingRecordKey("toggle all records"),
2070
+ select: (record) => {
2071
+ const key = getRecordKey(record);
2072
+ return key === void 0 ? warnMissingRecordKey("select this record") : bulk.select(key);
2073
+ },
2074
+ deselect: (record) => {
2075
+ const key = getRecordKey(record);
2076
+ return key === void 0 ? warnMissingRecordKey("deselect this record") : bulk.deselect(key);
2077
+ },
2013
2078
  inlineActions: computed(() => table.value.inlineActions.map((action) => ({
2014
2079
  execute: (record) => executeInlineAction(action, { record }),
2015
2080
  ...action
@@ -2029,29 +2094,27 @@ function useTable(input, defaultOptions = {}) {
2029
2094
  isSortable: !!refinements.sorts.value.find((sort) => sort.name === column.name),
2030
2095
  isFilterable: !!refinements.filters.value.find((filters) => filters.name === column.name)
2031
2096
  }))),
2032
- data: computed(() => {
2033
- return table.value.records.map((record) => {
2034
- const entries = Object.entries(record).map(([key, value]) => [key, value.value]).filter(([key]) => key !== "__hybridId");
2035
- if (entries.length === 0) return;
2036
- return Object.fromEntries(entries);
2037
- }).filter(Boolean);
2038
- }),
2039
- records: computed(() => table.value.records.map((record) => {
2040
- const entries = Object.entries(record).map(([key, value]) => [key, value.value]).filter(([key]) => key !== "__hybridId");
2097
+ data: computed(() => table.value.records),
2098
+ records: computed(() => table.value.records.map((record, index) => {
2099
+ const recordKey = getRecordKey(record);
2100
+ const localKey = recordKey ?? `row-${index}`;
2101
+ const selected = recordKey === void 0 ? false : bulk.selected(recordKey);
2041
2102
  return {
2042
- record: entries.length > 0 ? Object.fromEntries(entries) : {},
2043
- key: getRecordKey(record),
2044
- execute: (action) => executeInlineAction(action, { record: getRecordKey(record) }),
2103
+ record,
2104
+ key: localKey,
2105
+ recordKey,
2106
+ hasKey: recordKey !== void 0,
2107
+ execute: (action) => executeInlineAction(action, { record }),
2045
2108
  actions: table.value.inlineActions.map((action) => ({
2046
2109
  ...action,
2047
- execute: () => executeInlineAction(action.name, { record: getRecordKey(record) })
2110
+ execute: () => executeInlineAction(action.name, { record })
2048
2111
  })),
2049
- select: () => bulk.select(getRecordKey(record)),
2050
- deselect: () => bulk.deselect(getRecordKey(record)),
2051
- toggle: (force) => bulk.toggle(getRecordKey(record), force),
2052
- selected: bulk.selected(getRecordKey(record)),
2053
- value: (column) => record[typeof column === "string" ? column : column.name].value,
2054
- extra: (column, path) => getByPath(record[typeof column === "string" ? column : column.name].extra, path)
2112
+ select: () => recordKey === void 0 ? warnMissingRecordKey("select this record") : bulk.select(recordKey),
2113
+ deselect: () => recordKey === void 0 ? warnMissingRecordKey("deselect this record") : bulk.deselect(recordKey),
2114
+ toggle: (force) => recordKey === void 0 ? warnMissingRecordKey("toggle record selection") : bulk.toggle(recordKey, force),
2115
+ selected,
2116
+ value: (column) => getCell(index, column).value,
2117
+ extra: (column, path) => getByPath(getCell(index, column).extra, path)
2055
2118
  };
2056
2119
  })),
2057
2120
  paginator: computed(() => createPaginator(table.value.paginator, defaultOptions)),
@@ -2069,4 +2132,4 @@ function useValidationBag(bag = "default") {
2069
2132
  return readonly(toReactive(computed(() => state.context.value?.validation?.[bag] ?? {})));
2070
2133
  }
2071
2134
  //#endregion
2072
- export { Deferred, Form, RouterLink, WhenVisible, createPaginator, initializeHybridly, isBooleanFilter, isCallbackFilter, isDateFilter, isSelectFilter, isTernaryFilter, isTextFilter, isTrashedFilter, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable, useValidation, useValidationBag };
2135
+ export { Deferred, Form, RouterLink, WhenVisible, createPaginator, getBulkSelectionRange, initializeHybridly, isBooleanFilter, isCallbackFilter, isDateFilter, isSelectFilter, isTernaryFilter, isTextFilter, isTrashedFilter, registerHook, route, router, setProperty, useBackForward, useBulkSelect, useDialog, useForm, useHistoryState, useProperties, useProperty, useQueryParameter, useQueryParameters, useRefinements, useRoute, useTable, useValidation, useValidationBag };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hybridly/vue",
3
3
  "type": "module",
4
- "version": "0.10.0-beta.25",
4
+ "version": "0.10.0-beta.27",
5
5
  "description": "Vue adapter for Hybridly",
6
6
  "author": "Enzo Innocenzi <enzo@innocenzi.dev>",
7
7
  "license": "MIT",
@@ -47,8 +47,8 @@
47
47
  "dependencies": {
48
48
  "@clickbar/dot-diver": "^1.0.7",
49
49
  "es-toolkit": "^1.45.1",
50
- "@hybridly/core": "0.10.0-beta.25",
51
- "@hybridly/utils": "0.10.0-beta.25",
50
+ "@hybridly/core": "0.10.0-beta.27",
51
+ "@hybridly/utils": "0.10.0-beta.27",
52
52
  "@vue/devtools-api": "^8.1.0",
53
53
  "defu": "^6.1.4",
54
54
  "nprogress": "^0.2.0"