@xy-planning-network/trees 0.11.6-dev-2 → 0.11.6-dev-4

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": "@xy-planning-network/trees",
3
- "version": "0.11.6-dev-2",
3
+ "version": "0.11.6-dev-4",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "repository": "github:xy-planning-network/trees",
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, toRef, watch } from "vue"
3
- import { ActionsDropdown } from "@/lib-components"
3
+ import { ActionsDropdown, TablePaginator } from "@/lib-components"
4
4
  import DateRangePicker from "../forms/DateRangePicker.vue"
5
- import Paginator from "../navigation/Paginator.vue"
6
5
  import BaseAPI from "../../api/base"
7
6
  import type {
8
7
  DynamicTableAPI,
@@ -154,16 +153,10 @@ const selected = defineModel<number[]>("selected", {
154
153
  default: [],
155
154
  })
156
155
 
157
- const selectedData = computed<TableRowData[]>(() => {
158
- return tableData.value.filter((data) => {
159
- return selected.value.includes(data.id)
160
- })
161
- })
162
-
163
156
  const selectedOnPage = computed(() => {
164
157
  return selected.value.filter((id) => {
165
- return selectableIds.value.includes(id)
166
- }).length
158
+ return selectable.value.includes(id)
159
+ })
167
160
  })
168
161
 
169
162
  const bulkActions = computed(() => {
@@ -176,20 +169,21 @@ const bulkActions = computed(() => {
176
169
  ...action,
177
170
  disabled: selected.value.length === 0 || action.disabled,
178
171
  onClick: () =>
179
- action.onClick.apply(undefined, [
180
- selected.value,
181
- selectedData.value,
182
- publicMethods,
183
- ]),
172
+ action.onClick.apply(undefined, [selected.value, publicMethods]),
184
173
  }
185
174
  })
186
175
  })
187
176
 
188
177
  const hasBulkActions = computed(() => bulkActions.value.length > 0)
189
178
 
190
- const selectableIds = computed(() => {
179
+ const selectable = computed(() => {
191
180
  return tableData.value
192
181
  .filter((row) => {
182
+ // NOTE(spk): table data must have an "id" key for bulk actions.
183
+ if (row.id === undefined) {
184
+ return false
185
+ }
186
+
193
187
  if (props.tableBulkActions.isSelectable === undefined) {
194
188
  return true
195
189
  }
@@ -205,14 +199,14 @@ const selectableIds = computed(() => {
205
199
 
206
200
  const bulkSelectChecked = computed(
207
201
  () =>
208
- selected.value.length > 0 &&
209
- selected.value.length === selectableIds.value.length
202
+ selectedOnPage.value.length > 0 &&
203
+ selectedOnPage.value.length === selectable.value.length
210
204
  )
211
205
 
212
206
  const bulkSelectIndeterminate = computed(
213
207
  () =>
214
- selected.value.length > 0 &&
215
- selected.value.length < selectableIds.value.length
208
+ selectedOnPage.value.length > 0 &&
209
+ selectedOnPage.value.length < selectable.value.length
216
210
  )
217
211
 
218
212
  const bulkSelectOnChange = (e: Event) => {
@@ -220,14 +214,14 @@ const bulkSelectOnChange = (e: Event) => {
220
214
 
221
215
  // append all records on current page to existing selection
222
216
  if (selectionsPersisted.value && isChecked) {
223
- selected.value = [...selected.value, ...selectableIds.value.map((id) => id)]
217
+ selected.value = [...selected.value, ...selectable.value.map((id) => id)]
224
218
  return
225
219
  }
226
220
 
227
221
  // remove all records on current page from existing selection
228
222
  if (selectionsPersisted.value && !isChecked) {
229
223
  selected.value = selected.value.filter((id) => {
230
- return !selectableIds.value.includes(id)
224
+ return !selectable.value.includes(id)
231
225
  })
232
226
 
233
227
  return
@@ -235,7 +229,7 @@ const bulkSelectOnChange = (e: Event) => {
235
229
 
236
230
  // set all records on current page to selection
237
231
  if (isChecked) {
238
- selected.value = selectableIds.value.map((id) => id)
232
+ selected.value = selectable.value.map((id) => id)
239
233
  return
240
234
  }
241
235
 
@@ -430,15 +424,18 @@ loadAndRender()
430
424
  </tr>
431
425
 
432
426
  <tr v-if="hasBulkActions && selected.length > 0">
433
- <td colspan="100%" class="px-6 py-3 border-t bg-neutral-50">
434
- <div class="flex items-center space-x-3">
435
- <div v-if="selectionsPersisted" class="text-sm font-semibold">
436
- Selected: {{ selectedOnPage }} ({{ selected.length }} total)
437
- </div>
438
-
439
- <div v-else class="text-sm font-semibold">
440
- {{ selected.length }}
441
- selected
427
+ <td colspan="100%" class="px-6 py-2.5 border-t bg-neutral-50">
428
+ <div class="flex items-center gap-x-3">
429
+ <div class="text-sm shrink-0">
430
+ Selected
431
+ <span class="font-medium">{{ selectedOnPage.length }}</span>
432
+ of
433
+ <span class="font-medium">{{ selectable.length }}</span>
434
+ <span v-if="selectionsPersisted">
435
+ /
436
+ <span class="font-medium">{{ selected.length }}</span>
437
+ total
438
+ </span>
442
439
  </div>
443
440
 
444
441
  <TableActionButtons :actions="bulkActions" />
@@ -463,9 +460,9 @@ loadAndRender()
463
460
  'checked:disabled:bg-xy-blue checked:disabled:border-xy-blue checked:disabled:opacity-50',
464
461
  'border-gray-300 focus:ring-xy-blue-500',
465
462
  ]"
466
- :disabled="!selectableIds.includes(row.rowData.id)"
463
+ :disabled="!selectable.includes(row.rowData?.id)"
467
464
  type="checkbox"
468
- :value="row.rowData.id"
465
+ :value="row.rowData?.id"
469
466
  />
470
467
  </td>
471
468
 
@@ -512,10 +509,11 @@ loadAndRender()
512
509
  </table>
513
510
  </div>
514
511
 
515
- <Paginator
516
- v-if="hasContent"
517
- v-model="pagination"
518
- @update:model-value="loadAndRender()"
519
- />
512
+ <div v-if="hasContent" class="mt-4">
513
+ <TablePaginator
514
+ v-model="pagination"
515
+ @update:model-value="loadAndRender()"
516
+ />
517
+ </div>
520
518
  </div>
521
519
  </template>
@@ -0,0 +1,140 @@
1
+ <script setup lang="ts">
2
+ import { Pagination } from "@/composables/nav"
3
+ import { debounceFn } from "@/entry"
4
+ import { NumberInput, Select } from "@/lib-components"
5
+ import { computed } from "vue"
6
+
7
+ const props = defineProps<{
8
+ pageOptions?: { label: string; value: number }[]
9
+ }>()
10
+
11
+ const pagination = defineModel<Pagination>({ required: true })
12
+
13
+ const page = computed({
14
+ get: () => pagination.value.page,
15
+ set: (v: number) => {
16
+ pagination.value = {
17
+ ...pagination.value,
18
+ page: v,
19
+ }
20
+ },
21
+ })
22
+
23
+ const perPage = computed({
24
+ get: () => pagination.value.perPage,
25
+ set: (v: number) => {
26
+ pagination.value = {
27
+ ...pagination.value,
28
+ perPage: v,
29
+ }
30
+ },
31
+ })
32
+
33
+ const pageSelectOpts = computed(() => {
34
+ if (props.pageOptions) {
35
+ return props.pageOptions
36
+ }
37
+
38
+ return [
39
+ { label: "5 per page", value: 5 },
40
+ { label: "10 per page", value: 10 },
41
+ { label: "20 per page", value: 20 },
42
+ { label: "50 per page", value: 50 },
43
+ ]
44
+ })
45
+
46
+ const debouncePageInput = debounceFn((val: number | null) => {
47
+ if (val === null) {
48
+ return
49
+ }
50
+
51
+ if (val === page.value) {
52
+ return
53
+ }
54
+
55
+ page.value = val
56
+ }, 350)
57
+
58
+ const onDown = debounceFn(() => {
59
+ if (page.value <= 1) {
60
+ return
61
+ }
62
+
63
+ page.value = page.value - 1
64
+ }, 250)
65
+
66
+ const onUp = debounceFn(() => {
67
+ if (page.value >= pagination.value.totalPages) {
68
+ return
69
+ }
70
+
71
+ page.value = page.value + 1
72
+ }, 250)
73
+
74
+ const range = computed(() => {
75
+ const { page, perPage, totalItems } = pagination.value
76
+ return {
77
+ start: totalItems > 0 ? (page - 1) * perPage + 1 : 0,
78
+ end: Math.min(page * perPage, totalItems),
79
+ }
80
+ })
81
+ </script>
82
+
83
+ <template>
84
+ <div
85
+ class="flex flex-col items-center space-y-3.5 sm:flex-row sm:space-y-0 sm:gap-x-3 sm:justify-center"
86
+ >
87
+ <!--Range details-->
88
+ <p class="text-center text-sm text-neutral-700 sm:text-left sm:mr-auto">
89
+ Showing
90
+ <span class="font-medium">{{ range.start }}</span>
91
+ to
92
+ <span class="font-medium">{{ range.end }}</span>
93
+ of
94
+ <span class="font-medium">{{ pagination.totalItems }}</span>
95
+ results
96
+ </p>
97
+
98
+ <!--Pager-->
99
+ <div class="flex gap-3 items-center justify-center shrink-0">
100
+ <button
101
+ class="xy-btn-neutral"
102
+ :disabled="page <= 1"
103
+ type="button"
104
+ @click.prevent="page--"
105
+ >
106
+ &larr; <span class="sr-only">Previous</span>
107
+ </button>
108
+
109
+ <div class="max-w-[50px]">
110
+ <NumberInput
111
+ :model-value="page"
112
+ :min="1"
113
+ :max="pagination.totalPages"
114
+ type="number"
115
+ @update:model-value="debouncePageInput"
116
+ @keydown.down="onDown"
117
+ @keydown.up="onUp"
118
+ />
119
+ </div>
120
+
121
+ <div class="text-sm">
122
+ of <span class="font-medium">{{ pagination.totalPages }}</span>
123
+ </div>
124
+
125
+ <button
126
+ class="xy-btn-neutral"
127
+ :disabled="page >= pagination.totalPages"
128
+ type="button"
129
+ @click.prevent="page++"
130
+ >
131
+ <span class="sr-only">Next</span> &rarr;
132
+ </button>
133
+ </div>
134
+
135
+ <!--Per Page Selector-->
136
+ <div class="max-w-[150px] sm:ml-auto">
137
+ <Select v-model="perPage" :options="pageSelectOpts" />
138
+ </div>
139
+ </div>
140
+ </template>
@@ -49,9 +49,23 @@ export interface TableActionItem<T = TableRowData> extends ActionItem {
49
49
  */
50
50
  show?: boolean | ((rowData: T, rowIndex: number) => boolean);
51
51
  }
52
- export interface TableBulkActionItem<T = TableRowData> extends ActionItem {
52
+ export interface TableBulkActionItem extends ActionItem {
53
+ /**
54
+ * Whether or not the bulk action item is enabled. Disabled actions are
55
+ * visible in the UI, but do not trigger click events.
56
+ */
53
57
  disabled?: boolean;
54
- onClick: (selected: number[], selectedRows: T[], tableAPI: DynamicTableAPI) => void;
58
+ /**
59
+ * The callback method triggered by the action item buttons click event.
60
+ * @param selected the array of selected rows by the primary key `id`
61
+ * @param tableAPI DynamicTableAPI
62
+ * @returns void
63
+ */
64
+ onClick: (selected: number[], tableAPI: DynamicTableAPI) => void;
65
+ /**
66
+ * Whether or not to visible show the action item in the UI. When all action items
67
+ * on a table a hidden with show: false, bulk selections are disabled for the table.
68
+ */
55
69
  show?: boolean;
56
70
  }
57
71
  export interface TableActions<T = TableRowData> {
@@ -64,12 +78,18 @@ export interface TableActions<T = TableRowData> {
64
78
  */
65
79
  type: "dropdown" | "buttons";
66
80
  }
67
- export interface TableBulkActions<T extends TableRowData = TableRowData> {
81
+ export interface TableBulkActions<T = TableRowData> {
68
82
  /**
69
83
  * an array of TableActionItem definitions
70
84
  */
71
- actions: TableBulkActionItem<T>[];
85
+ actions: TableBulkActionItem[];
86
+ /**
87
+ * whether to persist the selections across pagination, searching, sorting, and filtering
88
+ */
72
89
  persistent?: boolean;
90
+ /**
91
+ * a function that determines if the row can be selected for bulk actions
92
+ */
73
93
  isSelectable?: (data: T) => boolean;
74
94
  }
75
95
  export interface TableColumn<T = TableRowData> {
@@ -103,12 +123,6 @@ export interface TableColumn<T = TableRowData> {
103
123
  sort?: string;
104
124
  }
105
125
  export type TableCellAlignment = "left" | "center" | "right";
106
- export type TableRowData<T extends {
107
- [key: string]: any;
108
- id: number;
109
- } = {
110
- [key: string]: any;
111
- id: number;
112
- }> = T;
126
+ export type TableRowData = Record<string, any>;
113
127
  export type TableColumns<T = TableRowData> = TableColumn<T>[];
114
128
  export type TableRowsData = TableRowData[];
File without changes
@@ -3,15 +3,9 @@ import { TableColumns, TableRowsData, TableActions, DynamicTableAPI } from "./ta
3
3
  export declare const useTable: (rowData: TableRowsData | Ref<TableRowsData>, cols: TableColumns | Ref<TableColumns>, acts: TableActions | Ref<TableActions>, exposedAPI?: DynamicTableAPI) => {
4
4
  columns: import("vue").ComputedRef<{
5
5
  alignment: string;
6
- classNames?: string | ((rowData: {
7
- [key: string]: any;
8
- id: number;
9
- }, rowIndex: number) => string) | undefined;
6
+ classNames?: string | ((rowData: import("./table").TableRowData, rowIndex: number) => string) | undefined;
10
7
  title: string;
11
- render: string | number | ((rowData: {
12
- [key: string]: any;
13
- id: number;
14
- }, rowIndex: number) => import("vue").VNodeChild);
8
+ render: string | ((rowData: import("./table").TableRowData, rowIndex: number) => import("vue").VNodeChild);
15
9
  show?: boolean | undefined;
16
10
  sort?: string | undefined;
17
11
  }[]>;
@@ -25,20 +19,14 @@ export declare const useTable: (rowData: TableRowsData | Ref<TableRowsData>, col
25
19
  icon?: import("vue").RenderFunction | import("vue").FunctionalComponent<{}, {}, any, {}> | undefined;
26
20
  label: string;
27
21
  }[];
28
- rowData: {
29
- [key: string]: any;
30
- id: number;
31
- };
22
+ rowData: import("./table").TableRowData;
32
23
  cells: {
33
24
  isComponent: boolean;
34
25
  classNames: string;
35
26
  val: any;
36
27
  alignment: string;
37
28
  title: string;
38
- render: string | number | ((rowData: {
39
- [key: string]: any;
40
- id: number;
41
- }, rowIndex: number) => import("vue").VNodeChild);
29
+ render: string | ((rowData: import("./table").TableRowData, rowIndex: number) => import("vue").VNodeChild);
42
30
  show?: boolean | undefined;
43
31
  sort?: string | undefined;
44
32
  }[];
@@ -19,6 +19,7 @@ import { default as Spinner } from "./overlays/Spinner.vue";
19
19
  import { default as DataTable } from "./lists/DataTable.vue";
20
20
  import { default as Steps } from "./navigation/Steps.vue";
21
21
  import { default as DynamicTable } from "./lists/DynamicTable.vue";
22
+ import { default as TablePaginator } from "./navigation/TablePaginator.vue";
22
23
  import { default as Tabs } from "./navigation/Tabs.vue";
23
24
  import { default as Toggle } from "./forms/Toggle.vue";
24
25
  import { default as XYSpinner } from "./indicators/XYSpinner.vue";
@@ -40,7 +41,7 @@ import { default as Select } from "./forms/Select.vue";
40
41
  import { default as TextArea } from "./forms/TextArea.vue";
41
42
  import { default as YesOrNoRadio } from "./forms/YesOrNoRadio.vue";
42
43
  export { ActionsDropdown, Cards, ContentModal, DateFilter, DetailList, DownloadCell, Flash, InlineAlert, Modal, SidebarLayout, Slideover, StackedLayout, Popover, PopoverContent, PopoverPosition, // Type export
43
- Paginator, Spinner, DataTable, Steps, DynamicTable, Tabs, Toggle, Tooltip, BaseInput, Checkbox, DateRangePicker, DateTime, InputError, InputHelp, InputLabel, FieldsetLegend, MultiCheckboxes, NumberInput, Radio, RadioCards, Select, TextArea, YesOrNoRadio, XYSpinner, ProgressCircles, ProgressCirclesLabeled, };
44
+ Paginator, Spinner, DataTable, Steps, DynamicTable, Tabs, TablePaginator, Toggle, Tooltip, BaseInput, Checkbox, DateRangePicker, DateTime, InputError, InputHelp, InputLabel, FieldsetLegend, MultiCheckboxes, NumberInput, Radio, RadioCards, Select, TextArea, YesOrNoRadio, XYSpinner, ProgressCircles, ProgressCirclesLabeled, };
44
45
  /**
45
46
  * declare global component types for App.use(Trees)
46
47
  */
@@ -64,6 +65,7 @@ export interface TreesComponents {
64
65
  DynamicTable: typeof DynamicTable;
65
66
  Steps: typeof Steps;
66
67
  Tabs: typeof Tabs;
68
+ TablePaginator: typeof TablePaginator;
67
69
  Toggle: typeof Toggle;
68
70
  Tooltip: typeof Tooltip;
69
71
  BaseInput: typeof BaseInput;
@@ -3,9 +3,6 @@ type __VLS_Props = {
3
3
  actions?: TableActionItem[];
4
4
  };
5
5
  declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
6
- actions: TableActionItem<{
7
- [key: string]: any;
8
- id: number;
9
- }>[];
6
+ actions: TableActionItem<import("../../composables").TableRowData>[];
10
7
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
8
  export default _default;
@@ -0,0 +1,16 @@
1
+ import { Pagination } from "../../composables/nav";
2
+ type __VLS_Props = {
3
+ pageOptions?: {
4
+ label: string;
5
+ value: number;
6
+ }[];
7
+ };
8
+ type __VLS_PublicProps = {
9
+ modelValue: Pagination;
10
+ } & __VLS_Props;
11
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
+ "update:modelValue": (value: Pagination) => any;
13
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
14
+ "onUpdate:modelValue"?: ((value: Pagination) => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, HTMLDivElement>;
16
+ export default _default;