@stonecrop/atable 0.31.0 → 0.32.0

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.
Files changed (79) hide show
  1. package/dist/assets/index.css +804 -1
  2. package/dist/atable.js +2809 -1944
  3. package/dist/atable.js.map +1 -1
  4. package/dist/src/components/ACell.vue.d.ts +17 -0
  5. package/dist/src/components/ACell.vue.d.ts.map +1 -0
  6. package/dist/src/components/AGanttCell.vue.d.ts +34 -0
  7. package/dist/src/components/AGanttCell.vue.d.ts.map +1 -0
  8. package/dist/src/components/AGanttConnection.vue.d.ts +13 -0
  9. package/dist/src/components/AGanttConnection.vue.d.ts.map +1 -0
  10. package/dist/src/components/ARow.vue.d.ts +2693 -0
  11. package/dist/src/components/ARow.vue.d.ts.map +1 -0
  12. package/dist/src/components/ARowActions.vue.d.ts +16 -0
  13. package/dist/src/components/ARowActions.vue.d.ts.map +1 -0
  14. package/dist/src/components/ATable.vue.d.ts +13358 -0
  15. package/dist/src/components/ATable.vue.d.ts.map +1 -0
  16. package/dist/src/components/ATableColumnFilter.vue.d.ts +11 -0
  17. package/dist/src/components/ATableColumnFilter.vue.d.ts.map +1 -0
  18. package/dist/src/components/ATableHeader.vue.d.ts +20 -0
  19. package/dist/src/components/ATableHeader.vue.d.ts.map +1 -0
  20. package/dist/src/components/ATableLoading.vue.d.ts +14 -0
  21. package/dist/src/components/ATableLoading.vue.d.ts.map +1 -0
  22. package/dist/src/components/ATableLoadingBar.vue.d.ts +14 -0
  23. package/dist/src/components/ATableLoadingBar.vue.d.ts.map +1 -0
  24. package/dist/src/components/ATableModal.vue.d.ts +18 -0
  25. package/dist/src/components/ATableModal.vue.d.ts.map +1 -0
  26. package/dist/src/components/ATablePaginationFooter.vue.d.ts +17 -0
  27. package/dist/src/components/ATablePaginationFooter.vue.d.ts.map +1 -0
  28. package/dist/src/icons/index.d.ts +14 -7
  29. package/dist/src/icons/index.d.ts.map +1 -1
  30. package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
  31. package/dist/src/types/index.d.ts.map +1 -1
  32. package/package.json +35 -29
  33. package/dist/atable.d.ts +0 -3560
  34. package/dist/atable.tsbuildinfo +0 -1
  35. package/dist/icons/stonecrop-ui-icon-add.svg +0 -5
  36. package/dist/icons/stonecrop-ui-icon-delete.svg +0 -5
  37. package/dist/icons/stonecrop-ui-icon-duplicate.svg +0 -5
  38. package/dist/icons/stonecrop-ui-icon-insert-above.svg +0 -15
  39. package/dist/icons/stonecrop-ui-icon-insert-below.svg +0 -15
  40. package/dist/icons/stonecrop-ui-icon-move.svg +0 -4
  41. package/dist/icons/stonecrop-ui-icon-open.svg +0 -5
  42. package/dist/src/composables/table-pagination.js +0 -108
  43. package/dist/src/icons/index.js +0 -32
  44. package/dist/src/index.js +0 -33
  45. package/dist/src/linkSearchableText.js +0 -44
  46. package/dist/src/resolveFilterType.js +0 -27
  47. package/dist/src/schemaToColumns.js +0 -59
  48. package/dist/src/stores/table.js +0 -833
  49. package/dist/src/types/index.js +0 -0
  50. package/dist/src/utils.js +0 -39
  51. package/src/components/ACell.vue +0 -410
  52. package/src/components/AGanttCell.vue +0 -638
  53. package/src/components/AGanttConnection.vue +0 -164
  54. package/src/components/ARow.vue +0 -251
  55. package/src/components/ARowActions.vue +0 -407
  56. package/src/components/ATable.vue +0 -538
  57. package/src/components/ATableColumnFilter.vue +0 -216
  58. package/src/components/ATableHeader.vue +0 -141
  59. package/src/components/ATableLoading.vue +0 -86
  60. package/src/components/ATableLoadingBar.vue +0 -81
  61. package/src/components/ATableModal.vue +0 -52
  62. package/src/components/ATablePaginationFooter.vue +0 -100
  63. package/src/composables/table-pagination.ts +0 -172
  64. package/src/icons/index.ts +0 -38
  65. package/src/icons/stonecrop-ui-icon-add.svg +0 -5
  66. package/src/icons/stonecrop-ui-icon-delete.svg +0 -5
  67. package/src/icons/stonecrop-ui-icon-duplicate.svg +0 -5
  68. package/src/icons/stonecrop-ui-icon-insert-above.svg +0 -15
  69. package/src/icons/stonecrop-ui-icon-insert-below.svg +0 -15
  70. package/src/icons/stonecrop-ui-icon-move.svg +0 -4
  71. package/src/icons/stonecrop-ui-icon-open.svg +0 -5
  72. package/src/index.ts +0 -62
  73. package/src/linkSearchableText.ts +0 -42
  74. package/src/resolveFilterType.ts +0 -32
  75. package/src/schemaToColumns.ts +0 -67
  76. package/src/shims-vue.d.ts +0 -10
  77. package/src/stores/table.ts +0 -978
  78. package/src/types/index.ts +0 -794
  79. package/src/utils.ts +0 -40
@@ -1,216 +0,0 @@
1
- <template>
2
- <div class="column-filter">
3
- <input
4
- v-if="resolvedFilterType === 'text'"
5
- v-model="filterValue"
6
- type="text"
7
- class="filter-input"
8
- @input="updateFilter(filterValue)" />
9
-
10
- <input
11
- v-else-if="resolvedFilterType === 'number'"
12
- v-model="filterValue"
13
- type="number"
14
- class="filter-input"
15
- @input="updateFilter(filterValue)" />
16
-
17
- <label v-else-if="resolvedFilterType === 'checkbox'" class="checkbox-filter">
18
- <input v-model="filterValue" type="checkbox" class="filter-checkbox" @change="updateFilter(filterValue)" />
19
- <span>{{ column.label }}</span>
20
- </label>
21
-
22
- <select
23
- v-else-if="resolvedFilterType === 'select'"
24
- v-model="filterValue"
25
- class="filter-select"
26
- @change="updateFilter(filterValue)">
27
- <option value="">All</option>
28
- <option v-for="option in getSelectOptions(column)" :key="option.value || option" :value="option.value || option">
29
- {{ option.label || option }}
30
- </option>
31
- </select>
32
-
33
- <input
34
- v-else-if="resolvedFilterType === 'date'"
35
- v-model="filterValue"
36
- type="date"
37
- class="filter-input"
38
- @change="updateFilter(filterValue)" />
39
-
40
- <div v-else-if="resolvedFilterType === 'dateRange'" class="date-range-filter">
41
- <input
42
- v-model="dateFilter.startValue"
43
- type="date"
44
- class="filter-input"
45
- @change="updateDateRangeFilter('start', dateFilter.startValue)" />
46
- <span class="date-separator">-</span>
47
- <input
48
- v-model="dateFilter.endValue"
49
- type="date"
50
- class="filter-input"
51
- @change="updateDateRangeFilter('end', dateFilter.endValue)" />
52
- </div>
53
-
54
- <component
55
- :is="column.filterComponent"
56
- v-else-if="resolvedFilterType === 'component' && column.filterComponent"
57
- :value="filterValue"
58
- :column="column"
59
- :col-index="colIndex"
60
- :store="store"
61
- @update:value="updateFilter($event)" />
62
-
63
- <button v-if="hasActiveFilter" class="clear-btn" title="Clear" @click="clearFilter">×</button>
64
- </div>
65
- </template>
66
-
67
- <script setup lang="ts">
68
- import { ref, reactive, computed } from 'vue'
69
-
70
- import { createTableStore } from '../stores/table'
71
- import { resolveFilterType } from '../resolveFilterType'
72
- import type { TableColumn } from '../types'
73
-
74
- const { column, colIndex, store } = defineProps<{
75
- column: TableColumn
76
- colIndex: number
77
- store: ReturnType<typeof createTableStore>
78
- }>()
79
-
80
- const filterValue = ref<any>('')
81
- const dateFilter = reactive({
82
- startValue: '',
83
- endValue: '',
84
- })
85
-
86
- const resolvedFilterType = computed(() => resolveFilterType(column))
87
-
88
- const getSelectOptions = (col: TableColumn): any[] => {
89
- if (col.filterOptions) return col.filterOptions
90
-
91
- // Auto-generate options from data
92
- const uniqueValues = new Set<any>()
93
- store.rows.forEach(row => {
94
- const value = row[col.name]
95
- if (value !== null && value !== undefined && value !== '') {
96
- uniqueValues.add(value)
97
- }
98
- })
99
-
100
- return Array.from(uniqueValues).map(value => ({
101
- value: value,
102
- label: String(value),
103
- }))
104
- }
105
-
106
- const hasActiveFilter = computed(() => {
107
- return !!(filterValue.value || dateFilter.startValue || dateFilter.endValue)
108
- })
109
-
110
- // Filter actions
111
- const updateFilter = (value: any) => {
112
- if (!value && resolvedFilterType.value !== 'checkbox') {
113
- store.clearFilter(colIndex)
114
- filterValue.value = ''
115
- } else {
116
- filterValue.value = value
117
- store.setFilter(colIndex, { value })
118
- }
119
- }
120
-
121
- const updateDateRangeFilter = (rangeType: 'start' | 'end', value: any) => {
122
- if (rangeType === 'start') {
123
- dateFilter.startValue = value
124
- } else {
125
- dateFilter.endValue = value
126
- }
127
-
128
- if (!dateFilter.startValue && !dateFilter.endValue) {
129
- store.clearFilter(colIndex)
130
- } else {
131
- store.setFilter(colIndex, {
132
- value: null,
133
- startValue: dateFilter.startValue,
134
- endValue: dateFilter.endValue,
135
- })
136
- }
137
- }
138
-
139
- const clearFilter = () => {
140
- filterValue.value = ''
141
- dateFilter.startValue = ''
142
- dateFilter.endValue = ''
143
- store.clearFilter(colIndex)
144
- }
145
- </script>
146
-
147
- <style scoped>
148
- .column-filter {
149
- display: flex;
150
- align-items: center;
151
- gap: 0.25rem;
152
- width: 100%;
153
- }
154
-
155
- .filter-input,
156
- .filter-select {
157
- background-color: var(--sc-form-background) !important;
158
- padding: 0.15rem 0.2rem;
159
- border: 1px solid var(--sc-form-border);
160
- border-radius: 3px;
161
- font-size: 0.875rem;
162
- color: var(--sc-cell-text-color);
163
- width: 100%;
164
- box-sizing: border-box;
165
- }
166
-
167
- .filter-input:focus,
168
- .filter-select:focus {
169
- outline: none;
170
- border-color: var(--sc-input-active-border-color);
171
- }
172
-
173
- .checkbox-filter {
174
- display: flex;
175
- align-items: center;
176
- gap: 0.25rem;
177
- font-size: 0.875rem;
178
- color: var(--sc-cell-text-color);
179
- cursor: pointer;
180
- }
181
-
182
- .filter-checkbox {
183
- margin: 0;
184
- }
185
-
186
- .date-range-filter {
187
- display: flex;
188
- gap: 0.25rem;
189
- align-items: center;
190
- width: 100%;
191
- }
192
-
193
- .date-range-filter .filter-input {
194
- flex: 1;
195
- min-width: 0;
196
- }
197
-
198
- .date-separator {
199
- color: var(--sc-gray-50);
200
- font-weight: 500;
201
- padding: 0 0.25rem;
202
- flex-shrink: 0;
203
- }
204
-
205
- .clear-btn {
206
- background: var(--sc-gray-10);
207
- border: 1px solid var(--sc-form-border);
208
- border-radius: 3px;
209
- color: var(--sc-gray-70);
210
- cursor: pointer;
211
- font-size: 1rem;
212
- padding: 0.15rem 0.4rem;
213
- line-height: 1;
214
- flex-shrink: 0;
215
- }
216
- </style>
@@ -1,141 +0,0 @@
1
- <template>
2
- <thead v-if="columns.length">
3
- <!-- Header row -->
4
- <tr class="atable-header-row" tabindex="-1">
5
- <!-- Row actions header cell (before-index position) -->
6
- <th
7
- v-if="showRowActionsHeader && rowActionsPosition === 'before-index'"
8
- class="row-actions-header"
9
- :class="{ 'sticky-column': rowActionsPosition === 'before-index' }" />
10
- <th
11
- v-if="store.zeroColumn"
12
- id="header-index"
13
- :class="[
14
- store.hasPinnedColumns ? 'sticky-index' : '',
15
- store.isTreeView ? 'tree-index' : '',
16
- store.config.view === 'list-expansion' ? 'list-expansion-index' : '',
17
- ]"
18
- class="list-index" />
19
- <!-- Row actions header cell (after-index position) -->
20
- <th v-if="showRowActionsHeader && rowActionsPosition === 'after-index'" class="row-actions-header" />
21
- <th
22
- v-for="(column, colKey) in columns"
23
- :key="column.name"
24
- v-resize-observer="onResize"
25
- :data-colindex="colKey"
26
- tabindex="-1"
27
- :style="store.getHeaderCellStyle(column)"
28
- :class="`${column.pinned ? 'sticky-column' : ''} ${column.sortable === false ? '' : 'cursor-pointer'}`"
29
- @click="column.sortable !== false ? handleSort(colKey) : undefined">
30
- <slot>{{ column.label || String.fromCharCode(colKey + 97).toUpperCase() }}</slot>
31
- </th>
32
- <!-- Row actions header cell (end position) -->
33
- <th v-if="showRowActionsHeader && rowActionsPosition === 'end'" class="row-actions-header" />
34
- </tr>
35
- <!-- Filters row -->
36
- <tr v-if="filterableColumns.length > 0" class="atable-filters-row">
37
- <!-- Row actions filter cell (before-index position) -->
38
- <th
39
- v-if="showRowActionsHeader && rowActionsPosition === 'before-index'"
40
- class="row-actions-header"
41
- :class="{ 'sticky-column': rowActionsPosition === 'before-index' }" />
42
- <th
43
- v-if="store.zeroColumn"
44
- :class="[
45
- store.hasPinnedColumns ? 'sticky-index' : '',
46
- store.isTreeView ? 'tree-index' : '',
47
- store.config.view === 'list-expansion' ? 'list-expansion-index' : '',
48
- ]"
49
- class="list-index" />
50
- <!-- Row actions filter cell (after-index position) -->
51
- <th v-if="showRowActionsHeader && rowActionsPosition === 'after-index'" class="row-actions-header" />
52
- <th
53
- v-for="(column, colKey) in columns"
54
- :key="`filter-${column.name}`"
55
- :class="`${column.pinned ? 'sticky-column' : ''}`"
56
- :style="store.getHeaderCellStyle(column)">
57
- <ATableColumnFilter v-if="column.filterable" :column="column" :col-index="colKey" :store="store" />
58
- </th>
59
- <!-- Row actions filter cell (end position) -->
60
- <th v-if="showRowActionsHeader && rowActionsPosition === 'end'" class="row-actions-header" />
61
- </tr>
62
- </thead>
63
- </template>
64
-
65
- <script setup lang="ts">
66
- import { computed } from 'vue'
67
- import { vResizeObserver } from '@vueuse/components'
68
- import ATableColumnFilter from './ATableColumnFilter.vue'
69
- import { createTableStore } from '../stores/table'
70
- import type { TableColumn } from '../types'
71
-
72
- const { columns, store } = defineProps<{
73
- columns: TableColumn[]
74
- store: ReturnType<typeof createTableStore>
75
- }>()
76
-
77
- const filterableColumns = computed(() => columns.filter(column => column.filterable))
78
-
79
- // Row actions header support — explicit return type resolves Pinia's inference chain
80
- const showRowActionsHeader = computed(() => store.config.rowActions?.enabled ?? false)
81
- const rowActionsPosition = computed(() => store.config.rowActions?.position ?? 'before-index')
82
-
83
- const handleSort = (colIndex: number) => store.sortByColumn(colIndex)
84
-
85
- const onResize = (entries: ReadonlyArray<ResizeObserverEntry>) => {
86
- for (const entry of entries) {
87
- if (entry.borderBoxSize.length === 0) continue
88
- const observedCell = entry.borderBoxSize[0]
89
- const observedWidth = observedCell.inlineSize
90
- const colIndex = Number((entry.target as HTMLElement).dataset.colindex)
91
- const currentWidth = store.columns[colIndex]?.width
92
-
93
- if (typeof currentWidth === 'number' && currentWidth !== observedWidth) {
94
- store.resizeColumn(colIndex, observedWidth)
95
- }
96
- }
97
- }
98
- </script>
99
-
100
- <style>
101
- .atable-header-row th {
102
- padding-left: 0.5ch !important;
103
- font-weight: 700;
104
- min-width: 3ch;
105
- padding-top: var(--sc-atable-row-padding);
106
- padding-bottom: var(--sc-atable-row-padding);
107
- box-sizing: border-box;
108
- color: var(--sc-header-text-color);
109
- position: relative;
110
- }
111
- #header-index {
112
- padding-left: var(--sc-atable-row-padding);
113
- box-sizing: border-box;
114
- border-top: none;
115
- }
116
- .tree-index {
117
- padding-right: 0;
118
- }
119
- th {
120
- order: 1;
121
- }
122
- .list-expansion-index {
123
- width: 2ch;
124
- margin-left: 5px;
125
- }
126
-
127
- .cursor-pointer {
128
- cursor: pointer;
129
- }
130
-
131
- .atable-filters-row th {
132
- padding: 0.25rem 0.5ch;
133
- vertical-align: top;
134
- }
135
-
136
- .row-actions-header {
137
- width: 2rem;
138
- min-width: 2rem;
139
- padding: 0 0.25rem;
140
- }
141
- </style>
@@ -1,86 +0,0 @@
1
- <template>
2
- <div class="aloading">
3
- <h2 class="aloading-header">
4
- <slot></slot>
5
- </h2>
6
- <div class="aloading-bar"></div>
7
- </div>
8
- </template>
9
-
10
- <style scoped>
11
- .aloading {
12
- width: 100%;
13
- border-top: 1px solid var(--sc-row-border-color);
14
- border-bottom: 1px solid var(--sc-row-border-color);
15
- display: flex;
16
- background-color: white;
17
- border-left: 4px solid var(--sc-row-border-color);
18
- padding-left: 0.5ch !important;
19
- padding-right: 0.5ch;
20
- padding-top: var(--sc-atable-row-padding);
21
- padding-bottom: var(--sc-atable-row-padding);
22
- align-items: center;
23
- box-sizing: border-box;
24
- background: var(--sc-focus-cell-background);
25
- overflow: hidden;
26
- position: relative;
27
- }
28
- .aloading-bar {
29
- width: 100%;
30
- height: 100%;
31
- position: absolute;
32
- left: -100%;
33
- top: 0;
34
- background: linear-gradient(
35
- 90deg,
36
- rgba(var(--sc-table-loading-color), 0) 0%,
37
- rgba(var(--sc-table-loading-color), 1) 50%,
38
- rgba(var(--sc-table-loading-color), 0) 100%
39
- );
40
- animation: gradient infinite 2s;
41
- z-index: 0;
42
- }
43
- .aloading-header {
44
- color: var(--sc-cell-text-color);
45
- font-family: var(--sc-atable-font-family);
46
- -webkit-font-smoothing: antialiased;
47
- -moz-osx-font-smoothing: grayscale;
48
- font-size: var(--sc-table-font-size);
49
- padding: 0;
50
- margin: 0;
51
- font-weight: normal;
52
- z-index: 1;
53
- }
54
- .aloading-header::after {
55
- content: '...';
56
- animation: ellipse 2s;
57
- animation-iteration-count: infinite;
58
- }
59
-
60
- @keyframes gradient {
61
- 0% {
62
- left: -100%;
63
- }
64
- 100% {
65
- left: 100%;
66
- }
67
- }
68
-
69
- @keyframes ellipse {
70
- 0% {
71
- content: '';
72
- }
73
- 20% {
74
- content: '';
75
- }
76
- 40% {
77
- content: '.';
78
- }
79
- 60% {
80
- content: '..';
81
- }
82
- 80% {
83
- content: '...';
84
- }
85
- }
86
- </style>
@@ -1,81 +0,0 @@
1
- <template>
2
- <div class="aloading">
3
- <h2 class="aloading-header">
4
- <slot></slot>
5
- </h2>
6
- <div class="aloading-bar"></div>
7
- </div>
8
- </template>
9
-
10
- <style scoped>
11
- .aloading {
12
- width: 100%;
13
- border-top: 1px solid var(--sc-row-border-color);
14
- border-bottom: 1px solid var(--sc-row-border-color);
15
- display: flex;
16
- background-color: white;
17
- border-left: 4px solid var(--sc-row-border-color);
18
- padding-left: 0.5ch !important;
19
- padding-right: 0.5ch;
20
- padding-top: var(--sc-atable-row-padding);
21
- padding-bottom: var(--sc-atable-row-padding);
22
- align-items: center;
23
- box-sizing: border-box;
24
- background: var(--sc-focus-cell-background);
25
- overflow: hidden;
26
- position: relative;
27
- }
28
- .aloading-bar {
29
- width: 50%;
30
- height: 3px;
31
- position: absolute;
32
- left: -100%;
33
- bottom: 0;
34
- background: var(--sc-row-border-color);
35
- animation: bar-left infinite 2s;
36
- z-index: 0;
37
- }
38
- .aloading-header {
39
- color: var(--sc-cell-text-color);
40
- font-family: var(--sc-atable-font-family);
41
- -webkit-font-smoothing: antialiased;
42
- -moz-osx-font-smoothing: grayscale;
43
- font-size: var(--sc-table-font-size);
44
- padding: 0;
45
- margin: 0;
46
- font-weight: normal;
47
- z-index: 1;
48
- }
49
- .aloading-header::after {
50
- content: '...';
51
- animation: ellipse 2s;
52
- animation-iteration-count: infinite;
53
- }
54
-
55
- @keyframes bar-left {
56
- 0% {
57
- left: -50%;
58
- }
59
- 100% {
60
- left: 100%;
61
- }
62
- }
63
-
64
- @keyframes ellipse {
65
- 0% {
66
- content: '';
67
- }
68
- 20% {
69
- content: '';
70
- }
71
- 40% {
72
- content: '.';
73
- }
74
- 60% {
75
- content: '..';
76
- }
77
- 80% {
78
- content: '...';
79
- }
80
- }
81
- </style>
@@ -1,52 +0,0 @@
1
- <template>
2
- <div ref="amodal" class="amodal" tabindex="-1" :style="amodalStyles" @click.stop @input.stop>
3
- <slot />
4
- </div>
5
- </template>
6
-
7
- <script setup lang="ts">
8
- import { useElementBounding } from '@vueuse/core'
9
- import { useTemplateRef, computed, type StyleValue } from 'vue'
10
-
11
- import { createTableStore } from '../stores/table'
12
-
13
- const { store } = defineProps<{ store: ReturnType<typeof createTableStore> }>()
14
-
15
- const amodalRef = useTemplateRef('amodal')
16
- const { width: modalWidth, height: modalHeight } = useElementBounding(amodalRef)
17
-
18
- const amodalStyles = computed((): StyleValue => {
19
- if (!(store.modal.height && store.modal.width && store.modal.left && store.modal.bottom)) return {}
20
-
21
- const table = store.modal.cell?.closest('table')
22
- if (!table) return {}
23
-
24
- const maxHeight = table.offsetHeight || 0
25
- const maxWidth = table.offsetWidth || 0
26
-
27
- // Get the Y position of the cell clicked by getting the cumulative height of prior rows + the header (if present)
28
- let modalY = store.modal.cell?.offsetTop || 0
29
- const headerHeight = table.querySelector('thead')?.offsetHeight || 0
30
- modalY += headerHeight
31
- // if the modal will overflow the bottom of the table, remove modal and cell heights from the Y position
32
- modalY = modalY + modalHeight.value < maxHeight ? modalY : modalY - (modalHeight.value + store.modal.height)
33
-
34
- // Get the X position of the cell clicked by getting the cumulative width of prior cells within the row
35
- let modalX = store.modal.cell?.offsetLeft || 0
36
- // if the modal will overflow the right of the table, remove modal and cell widths from the X position
37
- modalX = modalX + modalWidth.value <= maxWidth ? modalX : modalX - (modalWidth.value - store.modal.width)
38
-
39
- return {
40
- left: `${modalX}px`,
41
- top: `${modalY}px`,
42
- }
43
- })
44
- </script>
45
-
46
- <style>
47
- .amodal {
48
- position: absolute;
49
- background-color: var(--sc-row-color-zebra-dark);
50
- z-index: 200;
51
- }
52
- </style>
@@ -1,100 +0,0 @@
1
- <template>
2
- <tfoot v-if="showFooter" class="atable-pagination">
3
- <tr>
4
- <td :colspan="columnCount">
5
- <p v-if="hasMore" class="truncation-note">This is a partial list — more records exist on the server.</p>
6
- <p v-else-if="showCompleteNote" class="completion-note">All {{ loadedCount }} records loaded.</p>
7
- <div v-if="showControls" class="atable-pagination-controls">
8
- <button type="button" class="atable-pagination-btn" :disabled="!hasPrev || loading" @click="prev">
9
- Previous
10
- </button>
11
- <button type="button" class="atable-pagination-btn" :disabled="!hasNext || loading" @click="onNext">
12
- {{ hasMore && !hasLocalNext ? 'Load more' : 'Next' }}
13
- </button>
14
- </div>
15
- </td>
16
- </tr>
17
- </tfoot>
18
- </template>
19
-
20
- <script setup lang="ts">
21
- import { computed } from 'vue'
22
-
23
- const props = defineProps<{
24
- columnCount: number
25
- showFooter: boolean
26
- hasMore: boolean
27
- hasPrev: boolean
28
- hasNext: boolean
29
- hasLocalNext: boolean
30
- hasEverHadMore: boolean
31
- loadedCount: number
32
- loading: boolean
33
- next: () => Promise<void>
34
- prev: () => void
35
- }>()
36
-
37
- const showCompleteNote = computed(() => !props.hasMore && props.hasEverHadMore)
38
- const showControls = computed(() => props.hasMore || props.hasPrev)
39
-
40
- const onNext = () => {
41
- void props.next()
42
- }
43
- </script>
44
-
45
- <style scoped>
46
- .atable-pagination td {
47
- padding: 0.75rem 0.5ch;
48
- border-top: 1px solid var(--sc-row-border-color);
49
- }
50
-
51
- .truncation-note,
52
- .completion-note {
53
- margin: 0 0 0.5rem;
54
- font-size: var(--sc-table-font-size);
55
- color: var(--sc-gray-60);
56
- }
57
-
58
- .completion-note {
59
- margin-bottom: 0;
60
- }
61
-
62
- .atable-pagination-controls {
63
- display: flex;
64
- gap: 0.5rem;
65
- }
66
-
67
- .atable-pagination-btn {
68
- appearance: none;
69
- -webkit-appearance: none;
70
- box-sizing: border-box;
71
- padding: 0.5rem 1rem;
72
- background: var(--sc-btn-color);
73
- color: var(--sc-btn-label-color);
74
- border: 1px solid var(--sc-btn-border);
75
- border-radius: var(--sc-border-radius);
76
- cursor: pointer;
77
- font-family: var(--sc-font-family);
78
- font-size: var(--sc-table-font-size);
79
- font-weight: 400;
80
- line-height: 1.2;
81
- transition: background-color 0.15s ease;
82
- }
83
-
84
- .atable-pagination-btn:hover:not(:disabled) {
85
- background: var(--sc-btn-hover);
86
- }
87
-
88
- .atable-pagination-btn:focus-visible {
89
- outline: 2px solid var(--sc-input-active-border-color);
90
- outline-offset: 1px;
91
- }
92
-
93
- .atable-pagination-btn:disabled {
94
- cursor: not-allowed;
95
- background: var(--sc-gray-5);
96
- color: var(--sc-gray-50);
97
- border-color: var(--sc-gray-20);
98
- opacity: 1;
99
- }
100
- </style>