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

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-5",
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(() => {
@@ -178,7 +171,7 @@ const bulkActions = computed(() => {
178
171
  onClick: () =>
179
172
  action.onClick.apply(undefined, [
180
173
  selected.value,
181
- selectedData.value,
174
+ undefined,
182
175
  publicMethods,
183
176
  ]),
184
177
  }
@@ -187,9 +180,14 @@ const bulkActions = computed(() => {
187
180
 
188
181
  const hasBulkActions = computed(() => bulkActions.value.length > 0)
189
182
 
190
- const selectableIds = computed(() => {
183
+ const selectable = computed(() => {
191
184
  return tableData.value
192
185
  .filter((row) => {
186
+ // NOTE(spk): table data must have an "id" key for bulk actions.
187
+ if (row.id === undefined) {
188
+ return false
189
+ }
190
+
193
191
  if (props.tableBulkActions.isSelectable === undefined) {
194
192
  return true
195
193
  }
@@ -205,14 +203,14 @@ const selectableIds = computed(() => {
205
203
 
206
204
  const bulkSelectChecked = computed(
207
205
  () =>
208
- selected.value.length > 0 &&
209
- selected.value.length === selectableIds.value.length
206
+ selectedOnPage.value.length > 0 &&
207
+ selectedOnPage.value.length === selectable.value.length
210
208
  )
211
209
 
212
210
  const bulkSelectIndeterminate = computed(
213
211
  () =>
214
- selected.value.length > 0 &&
215
- selected.value.length < selectableIds.value.length
212
+ selectedOnPage.value.length > 0 &&
213
+ selectedOnPage.value.length < selectable.value.length
216
214
  )
217
215
 
218
216
  const bulkSelectOnChange = (e: Event) => {
@@ -220,14 +218,14 @@ const bulkSelectOnChange = (e: Event) => {
220
218
 
221
219
  // append all records on current page to existing selection
222
220
  if (selectionsPersisted.value && isChecked) {
223
- selected.value = [...selected.value, ...selectableIds.value.map((id) => id)]
221
+ selected.value = [...selected.value, ...selectable.value.map((id) => id)]
224
222
  return
225
223
  }
226
224
 
227
225
  // remove all records on current page from existing selection
228
226
  if (selectionsPersisted.value && !isChecked) {
229
227
  selected.value = selected.value.filter((id) => {
230
- return !selectableIds.value.includes(id)
228
+ return !selectable.value.includes(id)
231
229
  })
232
230
 
233
231
  return
@@ -235,7 +233,7 @@ const bulkSelectOnChange = (e: Event) => {
235
233
 
236
234
  // set all records on current page to selection
237
235
  if (isChecked) {
238
- selected.value = selectableIds.value.map((id) => id)
236
+ selected.value = selectable.value.map((id) => id)
239
237
  return
240
238
  }
241
239
 
@@ -430,15 +428,18 @@ loadAndRender()
430
428
  </tr>
431
429
 
432
430
  <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
431
+ <td colspan="100%" class="px-6 py-2.5 border-t bg-neutral-50">
432
+ <div class="flex items-center gap-x-3">
433
+ <div class="text-sm shrink-0">
434
+ Selected
435
+ <span class="font-medium">{{ selectedOnPage.length }}</span>
436
+ of
437
+ <span class="font-medium">{{ selectable.length }}</span>
438
+ <span v-if="selectionsPersisted">
439
+ /
440
+ <span class="font-medium">{{ selected.length }}</span>
441
+ total
442
+ </span>
442
443
  </div>
443
444
 
444
445
  <TableActionButtons :actions="bulkActions" />
@@ -463,9 +464,9 @@ loadAndRender()
463
464
  'checked:disabled:bg-xy-blue checked:disabled:border-xy-blue checked:disabled:opacity-50',
464
465
  'border-gray-300 focus:ring-xy-blue-500',
465
466
  ]"
466
- :disabled="!selectableIds.includes(row.rowData.id)"
467
+ :disabled="!selectable.includes(row.rowData?.id)"
467
468
  type="checkbox"
468
- :value="row.rowData.id"
469
+ :value="row.rowData?.id"
469
470
  />
470
471
  </td>
471
472
 
@@ -512,10 +513,11 @@ loadAndRender()
512
513
  </table>
513
514
  </div>
514
515
 
515
- <Paginator
516
- v-if="hasContent"
517
- v-model="pagination"
518
- @update:model-value="loadAndRender()"
519
- />
516
+ <div v-if="hasContent" class="mt-4">
517
+ <TablePaginator
518
+ v-model="pagination"
519
+ @update:model-value="loadAndRender()"
520
+ />
521
+ </div>
520
522
  </div>
521
523
  </template>
@@ -0,0 +1,146 @@
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
89
+ class="text-center text-sm text-neutral-700 sm:text-left sm:mr-auto sm:w-1/3"
90
+ >
91
+ Showing
92
+ <span class="font-medium">{{ range.start }}</span>
93
+ to
94
+ <span class="font-medium">{{ range.end }}</span>
95
+ of
96
+ <span class="font-medium">{{ pagination.totalItems }}</span>
97
+ results
98
+ </p>
99
+
100
+ <div
101
+ class="flex flex-col items-center space-y-3.5 sm:flex-row sm:space-y-0 sm:gap-x-3 sm:flex-1"
102
+ >
103
+ <!--Pager-->
104
+ <div class="flex gap-3 items-center justify-center shrink-0 md:w-1/2">
105
+ <button
106
+ class="xy-btn-neutral"
107
+ :disabled="page <= 1"
108
+ type="button"
109
+ @click.prevent="page--"
110
+ >
111
+ &larr; <span class="sr-only">Previous</span>
112
+ </button>
113
+
114
+ <div class="max-w-[50px]">
115
+ <NumberInput
116
+ :model-value="page"
117
+ :min="1"
118
+ :max="pagination.totalPages"
119
+ type="number"
120
+ @update:model-value="debouncePageInput"
121
+ @keydown.down.prevent="onDown"
122
+ @keydown.up.prevent="onUp"
123
+ />
124
+ </div>
125
+
126
+ <div class="text-sm">
127
+ of <span class="font-medium">{{ pagination.totalPages }}</span>
128
+ </div>
129
+
130
+ <button
131
+ class="xy-btn-neutral"
132
+ :disabled="page >= pagination.totalPages"
133
+ type="button"
134
+ @click.prevent="page++"
135
+ >
136
+ <span class="sr-only">Next</span> &rarr;
137
+ </button>
138
+ </div>
139
+
140
+ <!--Per Page Selector-->
141
+ <div class="max-w-[150px] sm:ml-auto">
142
+ <Select v-model="perPage" :options="pageSelectOpts" />
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </template>
@@ -49,9 +49,24 @@ 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 _ NOTE(spk): Holding for T[]
62
+ * @param tableAPI DynamicTableAPI
63
+ * @returns void
64
+ */
65
+ onClick: (selected: number[], _: undefined, tableAPI: DynamicTableAPI) => void;
66
+ /**
67
+ * Whether or not to visible show the action item in the UI. When all action items
68
+ * on a table a hidden with show: false, bulk selections are disabled for the table.
69
+ */
55
70
  show?: boolean;
56
71
  }
57
72
  export interface TableActions<T = TableRowData> {
@@ -64,12 +79,18 @@ export interface TableActions<T = TableRowData> {
64
79
  */
65
80
  type: "dropdown" | "buttons";
66
81
  }
67
- export interface TableBulkActions<T extends TableRowData = TableRowData> {
82
+ export interface TableBulkActions<T = TableRowData> {
68
83
  /**
69
84
  * an array of TableActionItem definitions
70
85
  */
71
- actions: TableBulkActionItem<T>[];
86
+ actions: TableBulkActionItem[];
87
+ /**
88
+ * whether to persist the selections across pagination, searching, sorting, and filtering
89
+ */
72
90
  persistent?: boolean;
91
+ /**
92
+ * a function that determines if the row can be selected for bulk actions
93
+ */
73
94
  isSelectable?: (data: T) => boolean;
74
95
  }
75
96
  export interface TableColumn<T = TableRowData> {
@@ -103,12 +124,6 @@ export interface TableColumn<T = TableRowData> {
103
124
  sort?: string;
104
125
  }
105
126
  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;
127
+ export type TableRowData = Record<string, any>;
113
128
  export type TableColumns<T = TableRowData> = TableColumn<T>[];
114
129
  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;