picocrank 1.23.1 → 1.25.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.
- package/AGENTS.md +2 -0
- package/docs/filter-query-v1.md +180 -0
- package/logo.svg +7 -7
- package/package.json +7 -5
- package/public/themes/README.md +19 -2
- package/public/themes/index.json +1 -5
- package/styles/dark-theme.css +16 -1
- package/supplemental-themes/README.md +9 -0
- package/supplemental-themes/catppuccin-latte-frappe/theme.css +276 -0
- package/supplemental-themes/dracula-alucard/theme.css +258 -0
- package/supplemental-themes/gruvbox-dark-light/theme.css +264 -0
- package/supplemental-themes/index.json +17 -0
- package/{public/themes → supplemental-themes}/waffles/theme.css +99 -0
- package/vite.config.js +91 -0
- package/vue/components/FormField.vue +69 -1
- package/vue/components/Header.vue +3 -3
- package/vue/components/Login.vue +1 -3
- package/vue/components/Section.vue +45 -14
- package/vue/components/Sidebar.vue +2 -1
- package/vue/components/SidebarNavLink.vue +6 -0
- package/vue/components/Table.vue +1113 -73
- package/vue/components/TableColumnFilterPopover.vue +263 -44
- package/vue/components/TableColumnOptionsPopover.vue +1116 -0
- package/vue/components/ThemeSwitcher.vue +119 -0
- package/vue/components/TopBar.vue +2 -2
- package/vue/composables/tableColumnPresets.js +296 -0
- package/vue/composables/tableFilterQuery.js +202 -0
- package/vue/composables/tableFilters.js +167 -3
- package/vue/composables/useCustomTheme.js +167 -24
- package/vue/data/examplePeople.js +2 -6
- package/vue/examples/App.vue +1 -1
- package/vue/examples/FormExample.vue +40 -6
- package/vue/examples/LoginExample.vue +1 -3
- package/vue/examples/TableExample.vue +329 -11
- package/vue/examples/TableRemoteExample.vue +112 -4
- package/vue/examples/ThemePreview.vue +17 -31
- package/public/themes/catppuccin/theme.css +0 -195
- package/public/themes/dracula/theme.css +0 -181
|
@@ -0,0 +1,1116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<div
|
|
4
|
+
v-if="open"
|
|
5
|
+
ref="popoverRef"
|
|
6
|
+
class="table-column-options-popover"
|
|
7
|
+
role="dialog"
|
|
8
|
+
:aria-labelledby="titleId"
|
|
9
|
+
:style="popoverStyle"
|
|
10
|
+
@click.stop
|
|
11
|
+
>
|
|
12
|
+
<div class="popover-body">
|
|
13
|
+
<h4 :id="titleId" ref="titleRef" class="popover-title">
|
|
14
|
+
Column Options
|
|
15
|
+
</h4>
|
|
16
|
+
|
|
17
|
+
<Tabs
|
|
18
|
+
class="column-options-tabs"
|
|
19
|
+
:tabs="optionTabs"
|
|
20
|
+
default-tab="custom"
|
|
21
|
+
@tab-change="onOptionTabChange"
|
|
22
|
+
>
|
|
23
|
+
<template #tab-custom>
|
|
24
|
+
<p class="subtle column-options-help">
|
|
25
|
+
Drag rows or use the arrows to reorder. Toggle checkboxes to show or hide columns. Set responsive priority (1 hides first) when using column priorities layout.
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<div ref="listScrollRef" class="column-options-scroll" :style="listScrollStyle">
|
|
29
|
+
<ul class="column-options-list">
|
|
30
|
+
<li
|
|
31
|
+
v-for="key in draftOrder"
|
|
32
|
+
:key="key"
|
|
33
|
+
class="column-options-item"
|
|
34
|
+
:class="{
|
|
35
|
+
'is-dragging': draggingKey === key,
|
|
36
|
+
'is-drag-over': dragOverKey === key && draggingKey !== key,
|
|
37
|
+
'is-fixed': !isOrderable(key),
|
|
38
|
+
}"
|
|
39
|
+
:draggable="isOrderable(key)"
|
|
40
|
+
@dragstart="onDragStart(key, $event)"
|
|
41
|
+
@dragover.prevent="onDragOver(key)"
|
|
42
|
+
@drop.prevent="onDrop(key)"
|
|
43
|
+
@dragend="onDragEnd"
|
|
44
|
+
>
|
|
45
|
+
<label class="column-options-label">
|
|
46
|
+
<input
|
|
47
|
+
type="checkbox"
|
|
48
|
+
:checked="isVisible(key)"
|
|
49
|
+
:disabled="!isHideable(key) || (isVisible(key) && draftVisibleKeys.length <= 1)"
|
|
50
|
+
@change="toggleVisible(key)"
|
|
51
|
+
/>
|
|
52
|
+
<span>{{ columnDisplayLabel(key) }}</span>
|
|
53
|
+
</label>
|
|
54
|
+
|
|
55
|
+
<select
|
|
56
|
+
v-if="isPriorityConfigurable(key)"
|
|
57
|
+
class="column-options-priority"
|
|
58
|
+
:value="prioritySelectValue(key)"
|
|
59
|
+
:aria-label="`Priority for ${columnLabel(key)} column`"
|
|
60
|
+
@change="setPriority(key, $event.target.value)"
|
|
61
|
+
>
|
|
62
|
+
<option value="">Never hide</option>
|
|
63
|
+
<option
|
|
64
|
+
v-for="level in priorityLevels"
|
|
65
|
+
:key="level"
|
|
66
|
+
:value="level"
|
|
67
|
+
>
|
|
68
|
+
{{ priorityOptionLabel(level) }}
|
|
69
|
+
</option>
|
|
70
|
+
</select>
|
|
71
|
+
<span v-else class="column-options-priority-spacer" aria-hidden="true" />
|
|
72
|
+
|
|
73
|
+
<div class="column-options-reorder">
|
|
74
|
+
<div class="column-options-move" role="group" :aria-label="`Move ${columnLabel(key)} column`">
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
class="neutral column-options-move-button"
|
|
78
|
+
:disabled="!canMove(key, -1)"
|
|
79
|
+
:aria-label="`Move ${columnLabel(key)} up`"
|
|
80
|
+
@click="moveColumn(key, -1)"
|
|
81
|
+
>
|
|
82
|
+
<HugeiconsIcon
|
|
83
|
+
:icon="ArrowUp01Icon"
|
|
84
|
+
width="0.9em"
|
|
85
|
+
height="0.9em"
|
|
86
|
+
:strokeWidth="2"
|
|
87
|
+
aria-hidden="true"
|
|
88
|
+
/>
|
|
89
|
+
</button>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
class="neutral column-options-move-button"
|
|
93
|
+
:disabled="!canMove(key, 1)"
|
|
94
|
+
:aria-label="`Move ${columnLabel(key)} down`"
|
|
95
|
+
@click="moveColumn(key, 1)"
|
|
96
|
+
>
|
|
97
|
+
<HugeiconsIcon
|
|
98
|
+
:icon="ArrowDown01Icon"
|
|
99
|
+
width="0.9em"
|
|
100
|
+
height="0.9em"
|
|
101
|
+
:strokeWidth="2"
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
/>
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<span
|
|
108
|
+
class="column-options-drag-handle"
|
|
109
|
+
:class="{ disabled: !isOrderable(key) }"
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
>
|
|
112
|
+
<HugeiconsIcon
|
|
113
|
+
:icon="DragDropVerticalIcon"
|
|
114
|
+
width="0.95em"
|
|
115
|
+
height="0.95em"
|
|
116
|
+
:strokeWidth="2"
|
|
117
|
+
/>
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
</li>
|
|
121
|
+
</ul>
|
|
122
|
+
</div>
|
|
123
|
+
</template>
|
|
124
|
+
|
|
125
|
+
<template #tab-load>
|
|
126
|
+
<div class="column-options-load-panel">
|
|
127
|
+
<template v-if="layoutPresetsEnabled">
|
|
128
|
+
<ul v-if="presets.length > 0" class="column-options-preset-list">
|
|
129
|
+
<li
|
|
130
|
+
v-for="preset in presets"
|
|
131
|
+
:key="preset.id"
|
|
132
|
+
class="column-options-preset-item"
|
|
133
|
+
>
|
|
134
|
+
<div class="column-options-preset-meta">
|
|
135
|
+
<strong>{{ preset.name }}</strong>
|
|
136
|
+
<span v-if="preset.id === defaultPresetId" class="tag">Default</span>
|
|
137
|
+
<p class="subtle column-options-preset-date">{{ formatPresetDate(preset.savedAt) }}</p>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="column-options-preset-actions" role="group" :aria-label="`Actions for ${preset.name}`">
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
class="neutral"
|
|
143
|
+
@click="submitLoadPreset(preset.id)"
|
|
144
|
+
>
|
|
145
|
+
Load
|
|
146
|
+
</button>
|
|
147
|
+
<button
|
|
148
|
+
v-if="preset.id !== defaultPresetId"
|
|
149
|
+
type="button"
|
|
150
|
+
class="neutral"
|
|
151
|
+
@click="submitSetDefaultPreset(preset.id)"
|
|
152
|
+
>
|
|
153
|
+
Set default
|
|
154
|
+
</button>
|
|
155
|
+
<button
|
|
156
|
+
v-else
|
|
157
|
+
type="button"
|
|
158
|
+
class="neutral"
|
|
159
|
+
@click="submitSetDefaultPreset(null)"
|
|
160
|
+
>
|
|
161
|
+
Clear default
|
|
162
|
+
</button>
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
class="neutral"
|
|
166
|
+
@click="submitDeletePreset(preset.id, preset.name)"
|
|
167
|
+
>
|
|
168
|
+
Delete
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
</li>
|
|
172
|
+
</ul>
|
|
173
|
+
<p v-else class="subtle column-options-tab-placeholder">No saved layouts yet.</p>
|
|
174
|
+
</template>
|
|
175
|
+
<p v-else class="subtle column-options-tab-placeholder">
|
|
176
|
+
Provide a <code>table-id</code> or remote <code>layout-presets</code> callbacks to enable saved layouts.
|
|
177
|
+
</p>
|
|
178
|
+
|
|
179
|
+
<p v-if="loadError" class="column-options-tab-error">{{ loadError }}</p>
|
|
180
|
+
|
|
181
|
+
<div class="column-options-load-default">
|
|
182
|
+
<button type="button" class="neutral" @click="submitLoadDeveloperDefaults">
|
|
183
|
+
Load default
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</template>
|
|
188
|
+
|
|
189
|
+
<template #tab-save>
|
|
190
|
+
<div v-if="!layoutPresetsEnabled" class="column-options-tab-placeholder">
|
|
191
|
+
<p class="subtle">Provide a <code>table-id</code> or remote <code>layout-presets</code> callbacks to enable saved layouts.</p>
|
|
192
|
+
</div>
|
|
193
|
+
<div v-else class="column-options-save-panel">
|
|
194
|
+
<p class="subtle column-options-save-help">
|
|
195
|
+
Saves the current table view (filters, page size, column order, visibility, and priorities).
|
|
196
|
+
</p>
|
|
197
|
+
<label class="column-options-save-field">
|
|
198
|
+
<span class="field-label">Layout name</span>
|
|
199
|
+
<input
|
|
200
|
+
v-model="savePresetName"
|
|
201
|
+
type="text"
|
|
202
|
+
placeholder="e.g. Managers view"
|
|
203
|
+
@keydown.enter.prevent="submitSavePreset"
|
|
204
|
+
/>
|
|
205
|
+
</label>
|
|
206
|
+
<label class="column-options-save-default">
|
|
207
|
+
<input v-model="saveAsDefault" type="checkbox" />
|
|
208
|
+
<span>Set as default</span>
|
|
209
|
+
</label>
|
|
210
|
+
<p v-if="saveError" class="column-options-tab-error">{{ saveError }}</p>
|
|
211
|
+
<p v-if="saveSuccess" class="column-options-tab-success">{{ saveSuccess }}</p>
|
|
212
|
+
<button type="button" @click="submitSavePreset">Save layout</button>
|
|
213
|
+
</div>
|
|
214
|
+
</template>
|
|
215
|
+
</Tabs>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div ref="actionsRef" role="toolbar" class="popover-actions">
|
|
219
|
+
<button type="button" class="neutral" @click="close">Close</button>
|
|
220
|
+
<button type="button" class="neutral" @click="showAll">Show all</button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</Teleport>
|
|
224
|
+
</template>
|
|
225
|
+
|
|
226
|
+
<script setup>
|
|
227
|
+
import { computed, nextTick, onBeforeUnmount, ref, useId, watch } from 'vue'
|
|
228
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
229
|
+
import {
|
|
230
|
+
ArrowDown01Icon,
|
|
231
|
+
ArrowUp01Icon,
|
|
232
|
+
DragDropVerticalIcon,
|
|
233
|
+
} from '@hugeicons/core-free-icons'
|
|
234
|
+
import {
|
|
235
|
+
isFilterEntryActive,
|
|
236
|
+
normalizeColumnFilterEntries,
|
|
237
|
+
} from '../composables/tableFilters.js'
|
|
238
|
+
import Tabs from './Tabs.vue'
|
|
239
|
+
|
|
240
|
+
const optionTabs = [
|
|
241
|
+
{ id: 'custom', label: 'Custom' },
|
|
242
|
+
{ id: 'load', label: 'Load' },
|
|
243
|
+
{ id: 'save', label: 'Save' },
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
const props = defineProps({
|
|
247
|
+
open: {
|
|
248
|
+
type: Boolean,
|
|
249
|
+
default: false,
|
|
250
|
+
},
|
|
251
|
+
headers: {
|
|
252
|
+
type: Array,
|
|
253
|
+
default: () => [],
|
|
254
|
+
},
|
|
255
|
+
columnKeys: {
|
|
256
|
+
type: Array,
|
|
257
|
+
default: () => [],
|
|
258
|
+
},
|
|
259
|
+
visibleKeys: {
|
|
260
|
+
type: Array,
|
|
261
|
+
default: () => [],
|
|
262
|
+
},
|
|
263
|
+
columnPriorities: {
|
|
264
|
+
type: Object,
|
|
265
|
+
default: () => ({}),
|
|
266
|
+
},
|
|
267
|
+
anchorEl: {
|
|
268
|
+
type: Object,
|
|
269
|
+
default: null,
|
|
270
|
+
},
|
|
271
|
+
filters: {
|
|
272
|
+
type: Object,
|
|
273
|
+
default: () => ({}),
|
|
274
|
+
},
|
|
275
|
+
rows: {
|
|
276
|
+
type: Array,
|
|
277
|
+
default: () => [],
|
|
278
|
+
},
|
|
279
|
+
tableId: {
|
|
280
|
+
type: String,
|
|
281
|
+
default: '',
|
|
282
|
+
},
|
|
283
|
+
layoutPresetsEnabled: {
|
|
284
|
+
type: Boolean,
|
|
285
|
+
default: false,
|
|
286
|
+
},
|
|
287
|
+
presets: {
|
|
288
|
+
type: Array,
|
|
289
|
+
default: () => [],
|
|
290
|
+
},
|
|
291
|
+
defaultPresetId: {
|
|
292
|
+
type: String,
|
|
293
|
+
default: null,
|
|
294
|
+
},
|
|
295
|
+
saveLayoutPreset: {
|
|
296
|
+
type: Function,
|
|
297
|
+
default: null,
|
|
298
|
+
},
|
|
299
|
+
loadLayoutPreset: {
|
|
300
|
+
type: Function,
|
|
301
|
+
default: null,
|
|
302
|
+
},
|
|
303
|
+
deleteLayoutPreset: {
|
|
304
|
+
type: Function,
|
|
305
|
+
default: null,
|
|
306
|
+
},
|
|
307
|
+
setDefaultLayoutPreset: {
|
|
308
|
+
type: Function,
|
|
309
|
+
default: null,
|
|
310
|
+
},
|
|
311
|
+
loadDeveloperDefaults: {
|
|
312
|
+
type: Function,
|
|
313
|
+
default: null,
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
const emit = defineEmits(['update:open', 'apply', 'cancel'])
|
|
318
|
+
|
|
319
|
+
const MIN_VISIBLE_OPTIONS = 8
|
|
320
|
+
const MAX_VISIBLE_OPTIONS = 20
|
|
321
|
+
const VIEWPORT_MARGIN_PX = 8
|
|
322
|
+
|
|
323
|
+
const popoverRef = ref(null)
|
|
324
|
+
const titleRef = ref(null)
|
|
325
|
+
const actionsRef = ref(null)
|
|
326
|
+
const listScrollRef = ref(null)
|
|
327
|
+
const popoverStyle = ref({ top: '0px', left: '0px' })
|
|
328
|
+
const listScrollStyle = ref({})
|
|
329
|
+
const titleId = useId()
|
|
330
|
+
const priorityLevels = [1, 2, 3, 4, 5]
|
|
331
|
+
const draftOrder = ref([])
|
|
332
|
+
const draftVisibleKeys = ref([])
|
|
333
|
+
const draftPriorities = ref({})
|
|
334
|
+
const draggingKey = ref(null)
|
|
335
|
+
const dragOverKey = ref(null)
|
|
336
|
+
const savePresetName = ref('')
|
|
337
|
+
const saveAsDefault = ref(false)
|
|
338
|
+
const saveError = ref('')
|
|
339
|
+
const saveSuccess = ref('')
|
|
340
|
+
const loadError = ref('')
|
|
341
|
+
|
|
342
|
+
const headerByKey = computed(() => new Map(
|
|
343
|
+
props.headers
|
|
344
|
+
.filter((header) => header?.key)
|
|
345
|
+
.map((header) => [header.key, header]),
|
|
346
|
+
))
|
|
347
|
+
|
|
348
|
+
function columnLabel(key) {
|
|
349
|
+
return headerByKey.value.get(key)?.label || key
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function columnFilterCount(key) {
|
|
353
|
+
const header = headerByKey.value.get(key)
|
|
354
|
+
if (!header || header.filterable === false) {
|
|
355
|
+
return 0
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return normalizeColumnFilterEntries(props.filters?.[key], header, props.rows)
|
|
359
|
+
.filter((entry) => isFilterEntryActive(entry))
|
|
360
|
+
.length
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function columnDisplayLabel(key) {
|
|
364
|
+
const label = columnLabel(key)
|
|
365
|
+
const count = columnFilterCount(key)
|
|
366
|
+
if (count > 0) {
|
|
367
|
+
return `${label} (${count})`
|
|
368
|
+
}
|
|
369
|
+
return label
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function isHideable(key) {
|
|
373
|
+
const header = headerByKey.value.get(key)
|
|
374
|
+
return header?.hideable !== false && !header?.hidden
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isOrderable(key) {
|
|
378
|
+
const header = headerByKey.value.get(key)
|
|
379
|
+
return header?.orderable !== false && !header?.hidden
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function isPriorityConfigurable(key) {
|
|
383
|
+
const header = headerByKey.value.get(key)
|
|
384
|
+
return header?.colPriorityConfigurable !== false && !header?.hidden
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function isValidColPriority(priority) {
|
|
388
|
+
return Number.isInteger(priority) && priority >= 1 && priority <= 5
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function defaultColumnPriority(key) {
|
|
392
|
+
const header = headerByKey.value.get(key)
|
|
393
|
+
return isValidColPriority(header?.colPriority) ? header.colPriority : null
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function normalizePriority(value) {
|
|
397
|
+
if (value === '' || value === null || value === undefined) {
|
|
398
|
+
return null
|
|
399
|
+
}
|
|
400
|
+
const numeric = Number(value)
|
|
401
|
+
return isValidColPriority(numeric) ? numeric : null
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function prioritySelectValue(key) {
|
|
405
|
+
const value = draftPriorities.value[key]
|
|
406
|
+
return value == null ? '' : String(value)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function priorityOptionLabel(level) {
|
|
410
|
+
if (level === 1) {
|
|
411
|
+
return '1 - Hide first'
|
|
412
|
+
}
|
|
413
|
+
if (level === 5) {
|
|
414
|
+
return '5 - Hide last'
|
|
415
|
+
}
|
|
416
|
+
return String(level)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function setPriority(key, rawValue) {
|
|
420
|
+
draftPriorities.value = {
|
|
421
|
+
...draftPriorities.value,
|
|
422
|
+
[key]: normalizePriority(rawValue),
|
|
423
|
+
}
|
|
424
|
+
emitLiveChange()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function isVisible(key) {
|
|
428
|
+
if (!isHideable(key)) {
|
|
429
|
+
return true
|
|
430
|
+
}
|
|
431
|
+
return draftVisibleKeys.value.includes(key)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function positionPopover() {
|
|
435
|
+
const anchor = props.anchorEl
|
|
436
|
+
if (!anchor || !popoverRef.value) {
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const rect = anchor.getBoundingClientRect()
|
|
441
|
+
const popoverRect = popoverRef.value.getBoundingClientRect()
|
|
442
|
+
const margin = VIEWPORT_MARGIN_PX
|
|
443
|
+
let top = rect.bottom + margin
|
|
444
|
+
let left = rect.right - popoverRect.width
|
|
445
|
+
|
|
446
|
+
if (left + popoverRect.width > window.innerWidth - margin) {
|
|
447
|
+
left = window.innerWidth - popoverRect.width - margin
|
|
448
|
+
}
|
|
449
|
+
if (left < margin) {
|
|
450
|
+
left = margin
|
|
451
|
+
}
|
|
452
|
+
if (top + popoverRect.height > window.innerHeight - margin) {
|
|
453
|
+
top = rect.top - popoverRect.height - margin
|
|
454
|
+
}
|
|
455
|
+
if (top < margin) {
|
|
456
|
+
top = margin
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
popoverStyle.value = {
|
|
460
|
+
top: `${top}px`,
|
|
461
|
+
left: `${left}px`,
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function updateListScrollLimit() {
|
|
466
|
+
listScrollStyle.value = {}
|
|
467
|
+
|
|
468
|
+
if (!popoverRef.value || !listScrollRef.value) {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const anchor = props.anchorEl
|
|
473
|
+
const anchorRect = anchor?.getBoundingClientRect()
|
|
474
|
+
const popoverEl = popoverRef.value
|
|
475
|
+
const titleHeight = titleRef.value?.offsetHeight ?? 0
|
|
476
|
+
const tabsHeaderHeight = popoverEl.querySelector('.column-options-tabs .tabs-header')?.offsetHeight ?? 0
|
|
477
|
+
const helpHeight = popoverEl.querySelector('.column-options-help')?.offsetHeight ?? 0
|
|
478
|
+
const actionsHeight = actionsRef.value?.offsetHeight ?? 0
|
|
479
|
+
const popoverStyles = window.getComputedStyle(popoverEl)
|
|
480
|
+
const paddingTop = Number.parseFloat(popoverStyles.paddingTop) || 0
|
|
481
|
+
const paddingBottom = Number.parseFloat(popoverStyles.paddingBottom) || 0
|
|
482
|
+
const bodyGap = 12
|
|
483
|
+
|
|
484
|
+
let availablePopoverHeight = window.innerHeight - (VIEWPORT_MARGIN_PX * 2)
|
|
485
|
+
if (anchorRect) {
|
|
486
|
+
const spaceBelow = window.innerHeight - VIEWPORT_MARGIN_PX - anchorRect.bottom
|
|
487
|
+
const spaceAbove = anchorRect.top - VIEWPORT_MARGIN_PX
|
|
488
|
+
availablePopoverHeight = Math.max(spaceBelow, spaceAbove)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const reservedHeight = paddingTop + paddingBottom + titleHeight + tabsHeaderHeight + helpHeight + actionsHeight + bodyGap
|
|
492
|
+
const optionsAreaBudget = Math.max(0, availablePopoverHeight - reservedHeight)
|
|
493
|
+
|
|
494
|
+
const firstItem = listScrollRef.value.querySelector('.column-options-item')
|
|
495
|
+
const rowHeight = firstItem?.offsetHeight || 40
|
|
496
|
+
const optionCount = draftOrder.value.length
|
|
497
|
+
|
|
498
|
+
const maxVisibleByViewport = Math.floor(optionsAreaBudget / rowHeight)
|
|
499
|
+
const visibleSlots = Math.min(
|
|
500
|
+
MAX_VISIBLE_OPTIONS,
|
|
501
|
+
Math.max(MIN_VISIBLE_OPTIONS, maxVisibleByViewport),
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
const rowsToShow = optionCount === 0
|
|
505
|
+
? MIN_VISIBLE_OPTIONS
|
|
506
|
+
: Math.min(
|
|
507
|
+
Math.max(optionCount, MIN_VISIBLE_OPTIONS),
|
|
508
|
+
visibleSlots,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
listScrollStyle.value = {
|
|
512
|
+
maxHeight: `${rowsToShow * rowHeight}px`,
|
|
513
|
+
...(optionCount > 0 && optionCount < MIN_VISIBLE_OPTIONS
|
|
514
|
+
? { minHeight: `${MIN_VISIBLE_OPTIONS * rowHeight}px` }
|
|
515
|
+
: {}),
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function resetPresetForm() {
|
|
520
|
+
savePresetName.value = ''
|
|
521
|
+
saveAsDefault.value = false
|
|
522
|
+
saveError.value = ''
|
|
523
|
+
saveSuccess.value = ''
|
|
524
|
+
loadError.value = ''
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function formatPresetDate(savedAt) {
|
|
528
|
+
if (!savedAt) {
|
|
529
|
+
return ''
|
|
530
|
+
}
|
|
531
|
+
const date = new Date(savedAt)
|
|
532
|
+
if (Number.isNaN(date.getTime())) {
|
|
533
|
+
return savedAt
|
|
534
|
+
}
|
|
535
|
+
return date.toLocaleString()
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function submitSavePreset() {
|
|
539
|
+
saveError.value = ''
|
|
540
|
+
saveSuccess.value = ''
|
|
541
|
+
|
|
542
|
+
if (!props.saveLayoutPreset) {
|
|
543
|
+
saveError.value = 'Save is unavailable.'
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const result = await Promise.resolve(props.saveLayoutPreset({
|
|
548
|
+
name: savePresetName.value,
|
|
549
|
+
setAsDefault: saveAsDefault.value,
|
|
550
|
+
}))
|
|
551
|
+
|
|
552
|
+
if (!result?.ok) {
|
|
553
|
+
saveError.value = result?.error || 'Unable to save layout.'
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
saveSuccess.value = `Saved “${result.preset.name}”.`
|
|
558
|
+
savePresetName.value = ''
|
|
559
|
+
saveAsDefault.value = false
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function submitLoadPreset(presetId) {
|
|
563
|
+
loadError.value = ''
|
|
564
|
+
const result = await Promise.resolve(props.loadLayoutPreset?.(presetId))
|
|
565
|
+
if (!result?.ok) {
|
|
566
|
+
loadError.value = result?.error || 'Unable to load layout.'
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
resetDraft()
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function submitDeletePreset(presetId, presetName) {
|
|
573
|
+
loadError.value = ''
|
|
574
|
+
if (!globalThis.confirm?.(`Delete saved layout “${presetName}”?`)) {
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const result = await Promise.resolve(props.deleteLayoutPreset?.(presetId))
|
|
579
|
+
if (!result?.ok) {
|
|
580
|
+
loadError.value = result?.error || 'Unable to delete layout.'
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function submitSetDefaultPreset(presetId) {
|
|
585
|
+
loadError.value = ''
|
|
586
|
+
const result = await Promise.resolve(props.setDefaultLayoutPreset?.(presetId))
|
|
587
|
+
if (!result?.ok) {
|
|
588
|
+
loadError.value = result?.error || 'Unable to update default layout.'
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function submitLoadDeveloperDefaults() {
|
|
593
|
+
loadError.value = ''
|
|
594
|
+
const result = props.loadDeveloperDefaults?.()
|
|
595
|
+
if (!result?.ok) {
|
|
596
|
+
loadError.value = result?.error || 'Unable to load defaults.'
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
resetDraft()
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function resetDraft() {
|
|
603
|
+
draftOrder.value = [...props.columnKeys]
|
|
604
|
+
draftVisibleKeys.value = [...props.visibleKeys]
|
|
605
|
+
draftPriorities.value = { ...props.columnPriorities }
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function toggleVisible(key) {
|
|
609
|
+
if (!isHideable(key)) {
|
|
610
|
+
return
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (isVisible(key)) {
|
|
614
|
+
if (draftVisibleKeys.value.length <= 1) {
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
draftVisibleKeys.value = draftVisibleKeys.value.filter((item) => item !== key)
|
|
618
|
+
emitLiveChange()
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
draftVisibleKeys.value = [...draftVisibleKeys.value, key]
|
|
623
|
+
emitLiveChange()
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function canMove(key, direction) {
|
|
627
|
+
if (!isOrderable(key)) {
|
|
628
|
+
return false
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
let index = draftOrder.value.indexOf(key)
|
|
632
|
+
while (index >= 0 && index < draftOrder.value.length) {
|
|
633
|
+
index += direction
|
|
634
|
+
if (index < 0 || index >= draftOrder.value.length) {
|
|
635
|
+
return false
|
|
636
|
+
}
|
|
637
|
+
if (isOrderable(draftOrder.value[index])) {
|
|
638
|
+
return true
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return false
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function moveColumn(key, direction) {
|
|
646
|
+
if (!canMove(key, direction)) {
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const order = [...draftOrder.value]
|
|
651
|
+
let index = order.indexOf(key)
|
|
652
|
+
|
|
653
|
+
while (true) {
|
|
654
|
+
const targetIndex = index + direction
|
|
655
|
+
if (targetIndex < 0 || targetIndex >= order.length) {
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
if (isOrderable(order[targetIndex])) {
|
|
659
|
+
const swapKey = order[targetIndex]
|
|
660
|
+
order[index] = swapKey
|
|
661
|
+
order[targetIndex] = key
|
|
662
|
+
draftOrder.value = order
|
|
663
|
+
emitLiveChange()
|
|
664
|
+
return
|
|
665
|
+
}
|
|
666
|
+
index = targetIndex
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function onDragStart(key, event) {
|
|
671
|
+
if (!isOrderable(key)) {
|
|
672
|
+
event.preventDefault()
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
draggingKey.value = key
|
|
676
|
+
dragOverKey.value = key
|
|
677
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
678
|
+
event.dataTransfer.setData('text/plain', key)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function onDragOver(key) {
|
|
682
|
+
if (!draggingKey.value || draggingKey.value === key) {
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
dragOverKey.value = key
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function onDrop(targetKey) {
|
|
689
|
+
const sourceKey = draggingKey.value
|
|
690
|
+
if (!sourceKey || sourceKey === targetKey) {
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const order = draftOrder.value.filter((key) => key !== sourceKey)
|
|
695
|
+
const targetIndex = order.indexOf(targetKey)
|
|
696
|
+
if (targetIndex === -1) {
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
order.splice(targetIndex, 0, sourceKey)
|
|
701
|
+
draftOrder.value = order
|
|
702
|
+
emitLiveChange()
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function onDragEnd() {
|
|
706
|
+
draggingKey.value = null
|
|
707
|
+
dragOverKey.value = null
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function onDocumentPointerDown(event) {
|
|
711
|
+
if (!props.open || !popoverRef.value) {
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
if (popoverRef.value.contains(event.target)) {
|
|
715
|
+
return
|
|
716
|
+
}
|
|
717
|
+
if (props.anchorEl?.contains?.(event.target)) {
|
|
718
|
+
return
|
|
719
|
+
}
|
|
720
|
+
close()
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function onDocumentKeyDown(event) {
|
|
724
|
+
if (!props.open) {
|
|
725
|
+
return
|
|
726
|
+
}
|
|
727
|
+
if (event.key === 'Escape') {
|
|
728
|
+
event.preventDefault()
|
|
729
|
+
close()
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function buildVisibleKeys() {
|
|
734
|
+
const visibleKeySet = new Set(draftVisibleKeys.value)
|
|
735
|
+
return draftOrder.value.filter((key) => !isHideable(key) || visibleKeySet.has(key))
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function buildPriorityOverrides() {
|
|
739
|
+
const overrides = {}
|
|
740
|
+
|
|
741
|
+
for (const key of draftOrder.value) {
|
|
742
|
+
if (!isPriorityConfigurable(key)) {
|
|
743
|
+
continue
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const draftValue = normalizePriority(draftPriorities.value[key])
|
|
747
|
+
const defaultValue = defaultColumnPriority(key)
|
|
748
|
+
if (draftValue !== defaultValue) {
|
|
749
|
+
overrides[key] = draftValue
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return overrides
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function emitLiveChange() {
|
|
757
|
+
const visibleKeys = buildVisibleKeys()
|
|
758
|
+
if (visibleKeys.length === 0) {
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
emit('apply', {
|
|
763
|
+
order: [...draftOrder.value],
|
|
764
|
+
visibleKeys,
|
|
765
|
+
priorities: buildPriorityOverrides(),
|
|
766
|
+
})
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function showAll() {
|
|
770
|
+
draftVisibleKeys.value = draftOrder.value.filter((key) => isHideable(key))
|
|
771
|
+
emitLiveChange()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async function onOptionTabChange() {
|
|
775
|
+
await nextTick()
|
|
776
|
+
updateListScrollLimit()
|
|
777
|
+
await nextTick()
|
|
778
|
+
positionPopover()
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function close() {
|
|
782
|
+
emit('cancel')
|
|
783
|
+
emit('update:open', false)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function initializePopover() {
|
|
787
|
+
resetDraft()
|
|
788
|
+
resetPresetForm()
|
|
789
|
+
await nextTick()
|
|
790
|
+
updateListScrollLimit()
|
|
791
|
+
await nextTick()
|
|
792
|
+
positionPopover()
|
|
793
|
+
popoverRef.value?.querySelector('input, button')?.focus()
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
watch(
|
|
797
|
+
() => props.open,
|
|
798
|
+
async (isOpen, wasOpen) => {
|
|
799
|
+
if (!isOpen) {
|
|
800
|
+
listScrollStyle.value = {}
|
|
801
|
+
draggingKey.value = null
|
|
802
|
+
dragOverKey.value = null
|
|
803
|
+
document.removeEventListener('pointerdown', onDocumentPointerDown)
|
|
804
|
+
document.removeEventListener('keydown', onDocumentKeyDown)
|
|
805
|
+
return
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (!wasOpen) {
|
|
809
|
+
await initializePopover()
|
|
810
|
+
document.addEventListener('pointerdown', onDocumentPointerDown)
|
|
811
|
+
document.addEventListener('keydown', onDocumentKeyDown)
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
{ immediate: true },
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
watch(
|
|
818
|
+
() => [props.columnKeys, props.headers],
|
|
819
|
+
() => {
|
|
820
|
+
if (props.open) {
|
|
821
|
+
resetDraft()
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
onBeforeUnmount(() => {
|
|
827
|
+
document.removeEventListener('pointerdown', onDocumentPointerDown)
|
|
828
|
+
document.removeEventListener('keydown', onDocumentKeyDown)
|
|
829
|
+
})
|
|
830
|
+
</script>
|
|
831
|
+
|
|
832
|
+
<style scoped>
|
|
833
|
+
.table-column-options-popover {
|
|
834
|
+
position: fixed;
|
|
835
|
+
z-index: 20;
|
|
836
|
+
display: flex;
|
|
837
|
+
flex-direction: column;
|
|
838
|
+
width: min(30rem, calc(100vw - 1rem));
|
|
839
|
+
min-width: min(30rem, calc(100vw - 1rem));
|
|
840
|
+
max-height: calc(100vh - 1rem);
|
|
841
|
+
padding: 0.75rem;
|
|
842
|
+
border: 1px solid var(--border-color, #e1e5e9);
|
|
843
|
+
border-radius: 0.4rem;
|
|
844
|
+
background: var(--background-color, #fff);
|
|
845
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
846
|
+
overflow: hidden;
|
|
847
|
+
box-sizing: border-box;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.popover-body {
|
|
851
|
+
flex: 1 1 auto;
|
|
852
|
+
min-height: 0;
|
|
853
|
+
display: flex;
|
|
854
|
+
flex-direction: column;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.popover-title {
|
|
858
|
+
margin: 0 0 0.35rem;
|
|
859
|
+
font-size: 0.95rem;
|
|
860
|
+
flex-shrink: 0;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.column-options-help {
|
|
864
|
+
margin: 0 0 0.75rem;
|
|
865
|
+
font-size: 0.85rem;
|
|
866
|
+
flex-shrink: 0;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.column-options-tabs {
|
|
870
|
+
flex: 1 1 auto;
|
|
871
|
+
min-height: 0;
|
|
872
|
+
min-width: 0;
|
|
873
|
+
display: flex;
|
|
874
|
+
flex-direction: column;
|
|
875
|
+
width: 100%;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.column-options-tabs :deep(.tabs-content) {
|
|
879
|
+
flex: 1 1 auto;
|
|
880
|
+
min-height: 0;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.column-options-tabs :deep(.tab-panel) {
|
|
884
|
+
padding-block: 0.75rem;
|
|
885
|
+
padding-inline: 0;
|
|
886
|
+
min-height: 0;
|
|
887
|
+
display: flex;
|
|
888
|
+
flex-direction: column;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.column-options-tabs :deep(.tab-button) {
|
|
892
|
+
padding: 0.45rem 0.85rem;
|
|
893
|
+
font-size: 0.9rem;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.column-options-tab-placeholder {
|
|
897
|
+
margin: 0;
|
|
898
|
+
font-size: 0.85rem;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.column-options-save-panel {
|
|
902
|
+
display: flex;
|
|
903
|
+
flex-direction: column;
|
|
904
|
+
gap: 0.75rem;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.column-options-save-help {
|
|
908
|
+
margin: 0;
|
|
909
|
+
font-size: 0.85rem;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.column-options-save-field {
|
|
913
|
+
display: flex;
|
|
914
|
+
flex-direction: column;
|
|
915
|
+
gap: 0.25rem;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.column-options-save-default {
|
|
919
|
+
display: flex;
|
|
920
|
+
align-items: center;
|
|
921
|
+
gap: 0.45rem;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.field-label {
|
|
925
|
+
font-size: 0.85rem;
|
|
926
|
+
color: var(--text-muted, #666);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.column-options-tab-error {
|
|
930
|
+
margin: 0;
|
|
931
|
+
font-size: 0.85rem;
|
|
932
|
+
color: var(--bad-color, #c62828);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.column-options-tab-success {
|
|
936
|
+
margin: 0;
|
|
937
|
+
font-size: 0.85rem;
|
|
938
|
+
color: var(--good-color, #2e7d32);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.column-options-load-panel {
|
|
942
|
+
display: flex;
|
|
943
|
+
flex-direction: column;
|
|
944
|
+
gap: 0.75rem;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.column-options-load-default {
|
|
948
|
+
display: flex;
|
|
949
|
+
justify-content: center;
|
|
950
|
+
padding-top: 0.25rem;
|
|
951
|
+
border-top: 1px solid var(--border-color, #e1e5e9);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.column-options-preset-list {
|
|
955
|
+
list-style: none;
|
|
956
|
+
margin: 0;
|
|
957
|
+
padding: 0;
|
|
958
|
+
display: flex;
|
|
959
|
+
flex-direction: column;
|
|
960
|
+
gap: 0.65rem;
|
|
961
|
+
max-height: 14rem;
|
|
962
|
+
overflow-y: auto;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.column-options-preset-item {
|
|
966
|
+
display: grid;
|
|
967
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
968
|
+
gap: 0.5rem;
|
|
969
|
+
align-items: start;
|
|
970
|
+
padding-bottom: 0.65rem;
|
|
971
|
+
border-bottom: 1px solid var(--border-color, #e1e5e9);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.column-options-preset-item:last-child {
|
|
975
|
+
padding-bottom: 0;
|
|
976
|
+
border-bottom: 0;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.column-options-preset-meta strong {
|
|
980
|
+
display: inline;
|
|
981
|
+
margin-right: 0.35rem;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.column-options-preset-date {
|
|
985
|
+
margin: 0.2rem 0 0;
|
|
986
|
+
font-size: 0.8rem;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.column-options-preset-actions {
|
|
990
|
+
display: flex;
|
|
991
|
+
flex-wrap: wrap;
|
|
992
|
+
justify-content: flex-end;
|
|
993
|
+
gap: 0.35rem;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
.column-options-scroll {
|
|
997
|
+
overflow-y: auto;
|
|
998
|
+
min-height: 0;
|
|
999
|
+
overscroll-behavior: contain;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.column-options-list {
|
|
1003
|
+
list-style: none;
|
|
1004
|
+
margin: 0;
|
|
1005
|
+
padding: 0;
|
|
1006
|
+
display: flex;
|
|
1007
|
+
flex-direction: column;
|
|
1008
|
+
gap: 0.35rem;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.column-options-item {
|
|
1012
|
+
display: grid;
|
|
1013
|
+
grid-template-columns: minmax(0, 1fr) 5.5rem auto;
|
|
1014
|
+
align-items: center;
|
|
1015
|
+
gap: 0.5rem;
|
|
1016
|
+
padding: 0.25rem 0.45rem;
|
|
1017
|
+
border: 1px solid var(--border-color, #e1e5e9);
|
|
1018
|
+
border-radius: 0.35rem;
|
|
1019
|
+
background: var(--standout-bg-color, #f8f9fa);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.column-options-item.is-dragging {
|
|
1023
|
+
opacity: 0.55;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.column-options-item.is-drag-over {
|
|
1027
|
+
border-color: var(--text-color, #334155);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
.column-options-item.is-fixed {
|
|
1031
|
+
opacity: 0.85;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.column-options-drag-handle {
|
|
1035
|
+
display: inline-flex;
|
|
1036
|
+
align-items: center;
|
|
1037
|
+
color: var(--text-color, inherit);
|
|
1038
|
+
opacity: 0.65;
|
|
1039
|
+
cursor: grab;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
.column-options-item.is-fixed .column-options-drag-handle.disabled {
|
|
1043
|
+
opacity: 0.35;
|
|
1044
|
+
cursor: default;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
.column-options-label {
|
|
1048
|
+
display: inline-flex;
|
|
1049
|
+
align-items: center;
|
|
1050
|
+
gap: 0.5rem;
|
|
1051
|
+
margin: 0;
|
|
1052
|
+
min-width: 0;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.column-options-label span {
|
|
1056
|
+
overflow: hidden;
|
|
1057
|
+
text-overflow: ellipsis;
|
|
1058
|
+
white-space: nowrap;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.column-options-priority {
|
|
1062
|
+
width: 100%;
|
|
1063
|
+
min-width: 0;
|
|
1064
|
+
padding-top: 0.15rem;
|
|
1065
|
+
padding-bottom: 0.15rem;
|
|
1066
|
+
font-size: 0.9em;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.column-options-priority-spacer {
|
|
1070
|
+
display: block;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.column-options-reorder {
|
|
1074
|
+
display: inline-flex;
|
|
1075
|
+
align-items: center;
|
|
1076
|
+
gap: 0.25rem;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
.column-options-move {
|
|
1080
|
+
display: inline-flex;
|
|
1081
|
+
gap: 0.2rem;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.column-options-move-button {
|
|
1085
|
+
display: inline-flex;
|
|
1086
|
+
align-items: center;
|
|
1087
|
+
justify-content: center;
|
|
1088
|
+
min-width: 1.75rem;
|
|
1089
|
+
padding: 0.2rem 0.35rem;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.popover-actions {
|
|
1093
|
+
display: flex;
|
|
1094
|
+
flex-shrink: 0;
|
|
1095
|
+
flex-wrap: wrap;
|
|
1096
|
+
justify-content: flex-end;
|
|
1097
|
+
gap: 0.5rem;
|
|
1098
|
+
margin-top: 0.75rem;
|
|
1099
|
+
padding-top: 0.75rem;
|
|
1100
|
+
border-top: 1px solid var(--border-color, #e1e5e9);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
html[data-theme="dark"] .table-column-options-popover {
|
|
1104
|
+
background: var(--background-color, #1e1e1e);
|
|
1105
|
+
border-color: var(--border-color, #444);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
html[data-theme="dark"] .column-options-item {
|
|
1109
|
+
background: var(--standout-bg-color, #1f1f1f);
|
|
1110
|
+
border-color: var(--border-color, #444);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
html[data-theme="dark"] .popover-actions {
|
|
1114
|
+
border-top-color: var(--border-color, #444);
|
|
1115
|
+
}
|
|
1116
|
+
</style>
|