@alaarab/ogrid-vue-vuetify 2.1.2 → 2.1.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/dist/esm/index.js CHANGED
@@ -1,15 +1,860 @@
1
- // Re-export all from base package for consumer convenience.
2
- // Note: This prevents tree-shaking of unused utilities.
3
- // Consider explicit named exports in a future major version.
4
- export * from '@alaarab/ogrid-vue';
5
- // Components
6
- export { OGrid } from './OGrid/OGrid';
7
- export { DataGridTable } from './DataGridTable/DataGridTable';
8
- export { ColumnHeaderFilter } from './ColumnHeaderFilter/ColumnHeaderFilter';
9
- export { ColumnChooser } from './ColumnChooser/ColumnChooser';
10
- export { PaginationControls } from './PaginationControls/PaginationControls';
11
- export { ColumnHeaderMenu } from './ColumnHeaderMenu/ColumnHeaderMenu';
12
- // Re-export shared components from base
13
- export { StatusBar, MarchingAntsOverlay } from '@alaarab/ogrid-vue';
14
- export { GridContextMenu } from './DataGridTable/GridContextMenu';
15
- export { InlineCellEditor } from './DataGridTable/InlineCellEditor';
1
+ import { useColumnHeaderFilterState, getColumnHeaderMenuItems, createInlineCellEditor, getContextMenuHandlers, GRID_CONTEXT_MENU_ITEMS, formatShortcut, createDataGridTable, useColumnChooserState, getPaginationViewModel, createOGrid } from '@alaarab/ogrid-vue';
2
+ export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, MAX_PAGE_BUTTONS, MarchingAntsOverlay, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, ROW_NUMBER_COLUMN_WIDTH, SIDEBAR_TRANSITION_MS, StatusBar, UndoRedoStack, Z_INDEX, applyCellDeletion, applyCutClear, applyFillValues, applyPastedValues, applyRangeRowSelection, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, calculateDropTarget, clampSelectionToBounds, computeAggregations, computeArrowNavigation, computeAutoScrollSpeed, computeNextSortState, computeRowSelectionState, computeTabNavigation, computeTotalHeight, computeVisibleRange, createDataGridTable, createInlineCellEditor, createOGrid, currencyParser, dateParser, debounce, deriveFilterOptionsFromData, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellInteractionProps, getCellRenderDescriptor, getCellValue, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getPinStateForColumn, getScrollTopForRow, getStatusBarParts, injectGlobalStyles, isFilterConfig, isInSelectionRange, isRowInRange, measureColumnContentWidth, measureRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, reorderColumnArray, resolveCellDisplayContent, resolveCellStyle, toUserLike, triggerCsvDownload, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnHeaderMenuState, useColumnPinning, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableSetup, useDateFilterState, useDebounce, useDebouncedCallback, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useMultiSelectFilterState, useOGrid, usePeopleFilterState, useRichSelectState, useRowSelection, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll, validateColumns, validateRowIds } from '@alaarab/ogrid-vue';
3
+ import { defineComponent, h, computed } from 'vue';
4
+ import { VTextField, VBtn, VProgressCircular, VCheckbox, VDivider, VAvatar, VIcon, VTooltip, VMenu, VCard, VList, VListItem, VSelect } from 'vuetify/components';
5
+
6
+ // src/index.ts
7
+ var _VBtn = VBtn;
8
+ var _VTextField = VTextField;
9
+ var TextFilterPopover = defineComponent({
10
+ name: "TextFilterPopover",
11
+ props: {
12
+ value: { type: String, required: true },
13
+ onValueChange: { type: Function, required: true },
14
+ onApply: { type: Function, required: true },
15
+ onClear: { type: Function, required: true }
16
+ },
17
+ setup(props) {
18
+ return () => h("div", { style: { width: "260px" } }, [
19
+ h(
20
+ "div",
21
+ { style: { padding: "12px" } },
22
+ h(_VTextField, {
23
+ modelValue: props.value,
24
+ "onUpdate:modelValue": (v) => props.onValueChange(v),
25
+ placeholder: "Enter search term...",
26
+ density: "compact",
27
+ variant: "outlined",
28
+ hideDetails: true,
29
+ autocomplete: "off",
30
+ prependInnerIcon: "mdi-magnify",
31
+ onKeydown: (e) => {
32
+ e.stopPropagation();
33
+ if (e.key === "Enter") {
34
+ e.preventDefault();
35
+ props.onApply();
36
+ }
37
+ }
38
+ })
39
+ ),
40
+ h("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", padding: "0 12px 12px" } }, [
41
+ h(_VBtn, { size: "small", variant: "text", disabled: !props.value, onClick: props.onClear }, () => "Clear"),
42
+ h(_VBtn, { size: "small", variant: "flat", color: "primary", onClick: props.onApply }, () => "Apply")
43
+ ])
44
+ ]);
45
+ }
46
+ });
47
+ var _VBtn2 = VBtn;
48
+ var _VTextField2 = VTextField;
49
+ var _VCheckbox = VCheckbox;
50
+ var _VProgressCircular = VProgressCircular;
51
+ var _VDivider = VDivider;
52
+ var MultiSelectFilterPopover = defineComponent({
53
+ name: "MultiSelectFilterPopover",
54
+ props: {
55
+ searchText: { type: String, required: true },
56
+ onSearchChange: { type: Function, required: true },
57
+ options: { type: Array, required: true },
58
+ filteredOptions: { type: Array, required: true },
59
+ selected: { type: Object, required: true },
60
+ onOptionToggle: { type: Function, required: true },
61
+ onSelectAll: { type: Function, required: true },
62
+ onClearSelection: { type: Function, required: true },
63
+ onApply: { type: Function, required: true },
64
+ isLoading: { type: Boolean, default: false }
65
+ },
66
+ setup(props) {
67
+ return () => h("div", { style: { width: "280px" } }, [
68
+ // Search
69
+ h("div", { style: { padding: "12px 12px 4px" } }, [
70
+ h(_VTextField2, {
71
+ modelValue: props.searchText,
72
+ "onUpdate:modelValue": (v) => props.onSearchChange(v),
73
+ placeholder: "Search...",
74
+ density: "compact",
75
+ variant: "outlined",
76
+ hideDetails: true,
77
+ autocomplete: "off",
78
+ prependInnerIcon: "mdi-magnify",
79
+ onKeydown: (e) => e.stopPropagation()
80
+ }),
81
+ h("span", {
82
+ style: { display: "block", marginTop: "4px", fontSize: "0.75rem", color: "rgba(0,0,0,0.6)" }
83
+ }, `${props.filteredOptions.length} of ${props.options.length} options`)
84
+ ]),
85
+ // Select all / clear
86
+ h("div", { style: { display: "flex", justifyContent: "space-between", padding: "4px 12px" } }, [
87
+ h(
88
+ _VBtn2,
89
+ { size: "small", variant: "text", onClick: props.onSelectAll },
90
+ () => `Select All (${props.filteredOptions.length})`
91
+ ),
92
+ h(_VBtn2, { size: "small", variant: "text", onClick: props.onClearSelection }, () => "Clear")
93
+ ]),
94
+ // Options list
95
+ h(
96
+ "div",
97
+ { style: { maxHeight: "240px", overflowY: "auto", padding: "0 4px" } },
98
+ props.isLoading ? h(
99
+ "div",
100
+ { style: { display: "flex", justifyContent: "center", padding: "16px 0" } },
101
+ h(_VProgressCircular, { size: 24, indeterminate: true })
102
+ ) : props.filteredOptions.length === 0 ? h("div", { style: { padding: "16px 0", textAlign: "center", fontSize: "0.875rem", color: "rgba(0,0,0,0.6)" } }, "No options found") : props.filteredOptions.map(
103
+ (option) => h(
104
+ "div",
105
+ { key: option, style: { display: "flex", alignItems: "center", minHeight: "32px" } },
106
+ h(_VCheckbox, {
107
+ modelValue: props.selected.has(option),
108
+ label: option,
109
+ density: "compact",
110
+ hideDetails: true,
111
+ "onUpdate:modelValue": (checked) => props.onOptionToggle(option, checked)
112
+ })
113
+ )
114
+ )
115
+ ),
116
+ // Footer
117
+ h(_VDivider),
118
+ h("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", padding: "8px 12px" } }, [
119
+ h(_VBtn2, { size: "small", variant: "text", onClick: props.onClearSelection }, () => "Clear"),
120
+ h(_VBtn2, { size: "small", variant: "flat", color: "primary", onClick: props.onApply }, () => "Apply")
121
+ ])
122
+ ]);
123
+ }
124
+ });
125
+ var _VBtn3 = VBtn;
126
+ var _VTextField3 = VTextField;
127
+ var _VProgressCircular2 = VProgressCircular;
128
+ var _VAvatar = VAvatar;
129
+ var _VIcon = VIcon;
130
+ var _VDivider2 = VDivider;
131
+ var PeopleFilterPopover = defineComponent({
132
+ name: "PeopleFilterPopover",
133
+ props: {
134
+ selectedUser: { type: Object, default: void 0 },
135
+ searchText: { type: String, required: true },
136
+ onSearchChange: { type: Function, required: true },
137
+ suggestions: { type: Array, required: true },
138
+ isLoading: { type: Boolean, default: false },
139
+ onUserSelect: { type: Function, required: true },
140
+ onClearUser: { type: Function, required: true }
141
+ },
142
+ setup(props) {
143
+ return () => h("div", { style: { width: "300px" } }, [
144
+ // Selected user display
145
+ ...props.selectedUser ? [
146
+ h("div", { style: { padding: "12px 12px 8px", borderBottom: "1px solid rgba(0,0,0,0.12)" } }, [
147
+ h("span", { style: { fontSize: "0.75rem", color: "rgba(0,0,0,0.6)" } }, "Currently filtered by:"),
148
+ h("div", { style: { display: "flex", alignItems: "center", gap: "8px", marginTop: "4px" } }, [
149
+ h(
150
+ _VAvatar,
151
+ { size: 32, image: props.selectedUser.photo },
152
+ () => props.selectedUser?.displayName?.[0] ?? ""
153
+ ),
154
+ h("div", { style: { flex: "1", minWidth: "0" } }, [
155
+ h("div", { style: { fontSize: "0.875rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, props.selectedUser?.displayName),
156
+ h("div", { style: { fontSize: "0.75rem", color: "rgba(0,0,0,0.6)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, props.selectedUser?.email)
157
+ ]),
158
+ h(_VBtn3, {
159
+ icon: true,
160
+ size: "x-small",
161
+ variant: "text",
162
+ "aria-label": "Remove filter",
163
+ onClick: props.onClearUser
164
+ }, () => h(_VIcon, { size: "16" }, () => "mdi-close"))
165
+ ])
166
+ ])
167
+ ] : [],
168
+ // Search input
169
+ h(
170
+ "div",
171
+ { style: { padding: "12px 12px 4px" } },
172
+ h(_VTextField3, {
173
+ modelValue: props.searchText,
174
+ "onUpdate:modelValue": (v) => props.onSearchChange(v),
175
+ placeholder: "Search for a person...",
176
+ density: "compact",
177
+ variant: "outlined",
178
+ hideDetails: true,
179
+ autocomplete: "off",
180
+ prependInnerIcon: "mdi-magnify",
181
+ onKeydown: (e) => e.stopPropagation()
182
+ })
183
+ ),
184
+ // Suggestions list
185
+ h(
186
+ "div",
187
+ { style: { maxHeight: "240px", overflowY: "auto" } },
188
+ props.isLoading && props.searchText.trim() ? h(
189
+ "div",
190
+ { style: { display: "flex", justifyContent: "center", padding: "16px 0" } },
191
+ h(_VProgressCircular2, { size: 24, indeterminate: true })
192
+ ) : props.suggestions.length === 0 && props.searchText.trim() ? h("div", { style: { padding: "16px 0", textAlign: "center", fontSize: "0.875rem", color: "rgba(0,0,0,0.6)" } }, "No results found") : props.searchText.trim() ? props.suggestions.map(
193
+ (user) => h("div", {
194
+ key: user.id || user.email || user.displayName,
195
+ style: { display: "flex", alignItems: "center", gap: "8px", padding: "8px 12px", cursor: "pointer" },
196
+ onClick: () => props.onUserSelect(user),
197
+ onMouseenter: (e) => {
198
+ e.currentTarget.style.backgroundColor = "rgba(0,0,0,0.04)";
199
+ },
200
+ onMouseleave: (e) => {
201
+ e.currentTarget.style.backgroundColor = "";
202
+ }
203
+ }, [
204
+ h(
205
+ _VAvatar,
206
+ { size: 32, image: user.photo },
207
+ () => user.displayName?.[0] ?? ""
208
+ ),
209
+ h("div", { style: { flex: "1", minWidth: "0" } }, [
210
+ h("div", { style: { fontSize: "0.875rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, user.displayName),
211
+ h("div", { style: { fontSize: "0.75rem", color: "rgba(0,0,0,0.6)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, user.email)
212
+ ])
213
+ ])
214
+ ) : h("div", { style: { padding: "16px 0", textAlign: "center", fontSize: "0.875rem", color: "rgba(0,0,0,0.6)" } }, "Type to search...")
215
+ ),
216
+ // Clear filter button
217
+ ...props.selectedUser ? [
218
+ h(_VDivider2),
219
+ h(
220
+ "div",
221
+ { style: { padding: "8px 12px" } },
222
+ h(_VBtn3, { size: "small", variant: "text", block: true, onClick: props.onClearUser }, () => "Clear Filter")
223
+ )
224
+ ] : []
225
+ ]);
226
+ }
227
+ });
228
+
229
+ // src/ColumnHeaderFilter/ColumnHeaderFilter.ts
230
+ var _VBtn4 = VBtn;
231
+ var _VIcon2 = VIcon;
232
+ var _VMenu = VMenu;
233
+ var _VTooltip = VTooltip;
234
+ var _VCard = VCard;
235
+ var ColumnHeaderFilter = defineComponent({
236
+ name: "ColumnHeaderFilter",
237
+ props: {
238
+ columnKey: { type: String, required: true },
239
+ columnName: { type: String, required: true },
240
+ filterType: { type: String, required: true },
241
+ isSorted: { type: Boolean, default: false },
242
+ isSortedDescending: { type: Boolean, default: false },
243
+ onSort: { type: Function, default: void 0 },
244
+ selectedValues: { type: Array, default: void 0 },
245
+ onFilterChange: { type: Function, default: void 0 },
246
+ options: { type: Array, default: () => [] },
247
+ isLoadingOptions: { type: Boolean, default: false },
248
+ textValue: { type: String, default: "" },
249
+ onTextChange: { type: Function, default: void 0 },
250
+ selectedUser: { type: Object, default: void 0 },
251
+ onUserChange: { type: Function, default: void 0 },
252
+ peopleSearch: { type: Function, default: void 0 },
253
+ dateValue: { type: Object, default: void 0 },
254
+ onDateChange: { type: Function, default: void 0 }
255
+ },
256
+ setup(props) {
257
+ const state = useColumnHeaderFilterState(props);
258
+ const renderPopoverContent = () => {
259
+ if (props.filterType === "multiSelect") {
260
+ return h(MultiSelectFilterPopover, {
261
+ searchText: state.searchText.value,
262
+ onSearchChange: state.setSearchText,
263
+ options: props.options ?? [],
264
+ filteredOptions: state.filteredOptions.value,
265
+ selected: state.tempSelected.value,
266
+ onOptionToggle: state.handlers.handleCheckboxChange,
267
+ onSelectAll: state.handlers.handleSelectAll,
268
+ onClearSelection: state.handlers.handleClearSelection,
269
+ onApply: state.handlers.handleApplyMultiSelect,
270
+ isLoading: props.isLoadingOptions
271
+ });
272
+ }
273
+ if (props.filterType === "text") {
274
+ return h(TextFilterPopover, {
275
+ value: state.tempTextValue.value ?? "",
276
+ onValueChange: state.setTempTextValue,
277
+ onApply: state.handlers.handleTextApply,
278
+ onClear: state.handlers.handleTextClear
279
+ });
280
+ }
281
+ if (props.filterType === "people") {
282
+ return h(PeopleFilterPopover, {
283
+ selectedUser: props.selectedUser,
284
+ searchText: state.peopleSearchText.value,
285
+ onSearchChange: state.setPeopleSearchText,
286
+ suggestions: state.peopleSuggestions.value,
287
+ isLoading: state.isPeopleLoading.value,
288
+ onUserSelect: state.handlers.handleUserSelect,
289
+ onClearUser: state.handlers.handleClearUser
290
+ });
291
+ }
292
+ if (props.filterType === "date") {
293
+ return h("div", { style: { padding: "12px", display: "flex", flexDirection: "column", gap: "8px" } }, [
294
+ h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, [
295
+ h("span", { style: { minWidth: "36px", fontSize: "0.75rem" } }, "From:"),
296
+ h("input", {
297
+ type: "date",
298
+ value: state.tempDateFrom.value ?? "",
299
+ onInput: (e) => state.setTempDateFrom(e.target.value),
300
+ style: { flex: "1", padding: "4px 6px" }
301
+ })
302
+ ]),
303
+ h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, [
304
+ h("span", { style: { minWidth: "36px", fontSize: "0.75rem" } }, "To:"),
305
+ h("input", {
306
+ type: "date",
307
+ value: state.tempDateTo.value ?? "",
308
+ onInput: (e) => state.setTempDateTo(e.target.value),
309
+ style: { flex: "1", padding: "4px 6px" }
310
+ })
311
+ ]),
312
+ h("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "4px" } }, [
313
+ h("button", {
314
+ onClick: state.handlers.handleDateClear,
315
+ disabled: !state.tempDateFrom.value && !state.tempDateTo.value,
316
+ style: { padding: "4px 12px", cursor: "pointer" }
317
+ }, "Clear"),
318
+ h("button", {
319
+ onClick: state.handlers.handleDateApply,
320
+ style: { padding: "4px 12px", cursor: "pointer" }
321
+ }, "Apply")
322
+ ])
323
+ ]);
324
+ }
325
+ return null;
326
+ };
327
+ return () => {
328
+ return h("div", {
329
+ ref: (el) => {
330
+ state.headerRef.value = el;
331
+ },
332
+ style: { display: "flex", alignItems: "center", width: "100%", minWidth: "0" }
333
+ }, [
334
+ // Column name with tooltip
335
+ h(
336
+ "div",
337
+ { style: { flex: "1", minWidth: "0", overflow: "hidden" } },
338
+ h(_VTooltip, { text: props.columnName, location: "top" }, {
339
+ activator: ({ props: tipProps }) => h("span", {
340
+ ...tipProps,
341
+ "data-header-label": "",
342
+ style: {
343
+ fontWeight: "600",
344
+ fontSize: "0.875rem",
345
+ lineHeight: "1.4",
346
+ whiteSpace: "nowrap",
347
+ overflow: "hidden",
348
+ textOverflow: "ellipsis",
349
+ display: "block"
350
+ }
351
+ }, props.columnName)
352
+ })
353
+ ),
354
+ // Sort + filter buttons
355
+ h("div", { style: { display: "flex", alignItems: "center", marginLeft: "4px", flexShrink: "0" } }, [
356
+ // Filter icon + menu
357
+ ...props.filterType !== "none" ? [
358
+ h(_VMenu, {
359
+ modelValue: state.isFilterOpen.value,
360
+ "onUpdate:modelValue": (v) => state.setFilterOpen(v),
361
+ closeOnContentClick: false,
362
+ location: "bottom start"
363
+ }, {
364
+ activator: ({ props: menuProps }) => h("div", { style: { position: "relative" } }, [
365
+ h(_VBtn4, {
366
+ ...menuProps,
367
+ icon: true,
368
+ size: "x-small",
369
+ variant: state.hasActiveFilter.value || state.isFilterOpen.value ? "tonal" : "text",
370
+ color: state.hasActiveFilter.value || state.isFilterOpen.value ? "primary" : "default",
371
+ "aria-label": `Filter ${props.columnName}`,
372
+ title: `Filter ${props.columnName}`,
373
+ style: {
374
+ opacity: state.hasActiveFilter.value || state.isFilterOpen.value ? "1" : "0.7"
375
+ }
376
+ }, () => h(_VIcon2, { size: "16" }, () => "mdi-filter-variant")),
377
+ ...state.hasActiveFilter.value ? [
378
+ h("div", {
379
+ style: {
380
+ position: "absolute",
381
+ top: "2px",
382
+ right: "2px",
383
+ width: "6px",
384
+ height: "6px",
385
+ borderRadius: "50%",
386
+ backgroundColor: "rgb(var(--v-theme-primary))",
387
+ zIndex: "1"
388
+ }
389
+ })
390
+ ] : []
391
+ ]),
392
+ default: () => h(_VCard, {
393
+ elevation: 8,
394
+ ref: (el) => {
395
+ state.popoverRef.value = el;
396
+ },
397
+ onClick: (e) => e.stopPropagation()
398
+ }, () => [
399
+ h("div", {
400
+ style: {
401
+ borderBottom: "1px solid rgba(0,0,0,0.12)",
402
+ padding: "8px 12px",
403
+ fontWeight: "600",
404
+ fontSize: "0.875rem",
405
+ backgroundColor: "rgb(var(--v-theme-surface))"
406
+ }
407
+ }, `Filter: ${props.columnName}`),
408
+ renderPopoverContent()
409
+ ])
410
+ })
411
+ ] : []
412
+ ])
413
+ ]);
414
+ };
415
+ }
416
+ });
417
+ var ColumnHeaderMenu = defineComponent({
418
+ name: "ColumnHeaderMenu",
419
+ props: {
420
+ isOpen: { type: Boolean, required: true },
421
+ anchorElement: { type: Object, default: null },
422
+ onClose: { type: Function, required: true },
423
+ onPinLeft: { type: Function, required: true },
424
+ onPinRight: { type: Function, required: true },
425
+ onUnpin: { type: Function, required: true },
426
+ onSortAsc: { type: Function, required: true },
427
+ onSortDesc: { type: Function, required: true },
428
+ onClearSort: { type: Function, required: true },
429
+ onAutosizeThis: { type: Function, required: true },
430
+ onAutosizeAll: { type: Function, required: true },
431
+ canPinLeft: { type: Boolean, required: true },
432
+ canPinRight: { type: Boolean, required: true },
433
+ canUnpin: { type: Boolean, required: true },
434
+ currentSort: { type: String, default: null },
435
+ isSortable: { type: Boolean, default: true },
436
+ isResizable: { type: Boolean, default: true }
437
+ },
438
+ setup(props) {
439
+ const handleOpenChange = (open) => {
440
+ if (!open) {
441
+ props.onClose();
442
+ }
443
+ };
444
+ const items = computed(
445
+ () => getColumnHeaderMenuItems({
446
+ canPinLeft: props.canPinLeft,
447
+ canPinRight: props.canPinRight,
448
+ canUnpin: props.canUnpin,
449
+ currentSort: props.currentSort,
450
+ isSortable: props.isSortable,
451
+ isResizable: props.isResizable
452
+ })
453
+ );
454
+ const handlers = {
455
+ pinLeft: props.onPinLeft,
456
+ pinRight: props.onPinRight,
457
+ unpin: props.onUnpin,
458
+ sortAsc: props.onSortAsc,
459
+ sortDesc: props.onSortDesc,
460
+ clearSort: props.onClearSort,
461
+ autosizeThis: props.onAutosizeThis,
462
+ autosizeAll: props.onAutosizeAll
463
+ };
464
+ const getHandler = (itemId) => handlers[itemId] || (() => {
465
+ });
466
+ return () => {
467
+ if (!props.anchorElement) return null;
468
+ return h(VMenu, {
469
+ modelValue: props.isOpen,
470
+ "onUpdate:modelValue": handleOpenChange,
471
+ location: "bottom start",
472
+ // Use target prop instead of activator for programmatic positioning
473
+ target: props.anchorElement
474
+ }, {
475
+ default: () => h(VList, { density: "compact", "aria-label": "Column options" }, () => {
476
+ const children = [];
477
+ items.value.forEach((item) => {
478
+ children.push(
479
+ h(VListItem, {
480
+ key: item.id,
481
+ disabled: item.disabled,
482
+ onClick: () => {
483
+ getHandler(item.id)();
484
+ }
485
+ }, () => item.label)
486
+ );
487
+ if (item.divider) {
488
+ children.push(h(VDivider, { key: `divider-${item.id}` }));
489
+ }
490
+ });
491
+ return children;
492
+ })
493
+ });
494
+ };
495
+ }
496
+ });
497
+ var InlineCellEditor = createInlineCellEditor({
498
+ renderCheckbox: ({ checked, onChange, onCancel }) => h(VCheckbox, {
499
+ modelValue: checked,
500
+ hideDetails: true,
501
+ density: "compact",
502
+ "onUpdate:modelValue": (c) => onChange(c),
503
+ onKeydown: (e) => {
504
+ if (e.key === "Escape") {
505
+ e.preventDefault();
506
+ onCancel();
507
+ }
508
+ }
509
+ }),
510
+ renderDatePicker: ({ value, onChange, onCancel }) => h("input", {
511
+ type: "date",
512
+ value,
513
+ style: { width: "100%", height: "100%", border: "none", outline: "none", padding: "0 4px", fontSize: "inherit" },
514
+ onKeydown: (e) => {
515
+ if (e.key === "Enter") {
516
+ e.preventDefault();
517
+ onChange(e.target.value);
518
+ }
519
+ if (e.key === "Escape") {
520
+ e.preventDefault();
521
+ onCancel();
522
+ }
523
+ if (e.key === "Tab") {
524
+ e.preventDefault();
525
+ onChange(e.target.value);
526
+ }
527
+ },
528
+ onBlur: (e) => onChange(e.target.value)
529
+ })
530
+ });
531
+ var GridContextMenu = defineComponent({
532
+ name: "GridContextMenu",
533
+ props: {
534
+ x: { type: Number, required: true },
535
+ y: { type: Number, required: true },
536
+ hasSelection: { type: Boolean, required: true },
537
+ canUndo: { type: Boolean, required: true },
538
+ canRedo: { type: Boolean, required: true },
539
+ onUndo: { type: Function, required: true },
540
+ onRedo: { type: Function, required: true },
541
+ onCopy: { type: Function, required: true },
542
+ onCut: { type: Function, required: true },
543
+ onPaste: { type: Function, required: true },
544
+ onSelectAll: { type: Function, required: true },
545
+ onClose: { type: Function, required: true }
546
+ },
547
+ setup(props) {
548
+ const handlers = getContextMenuHandlers(props);
549
+ const isDisabled = (item) => {
550
+ if (item.disabledWhenNoSelection && !props.hasSelection) return true;
551
+ if (item.id === "undo" && !props.canUndo) return true;
552
+ if (item.id === "redo" && !props.canRedo) return true;
553
+ return false;
554
+ };
555
+ return () => h(VMenu, {
556
+ modelValue: true,
557
+ "onUpdate:modelValue": (v) => {
558
+ if (!v) props.onClose();
559
+ },
560
+ target: [props.x, props.y],
561
+ location: "bottom start"
562
+ }, {
563
+ default: () => h(
564
+ VList,
565
+ { density: "compact", "aria-label": "Grid context menu" },
566
+ () => GRID_CONTEXT_MENU_ITEMS.map((item) => [
567
+ ...item.dividerBefore ? [h(VDivider, { key: `${item.id}-div` })] : [],
568
+ h(
569
+ VListItem,
570
+ {
571
+ key: item.id,
572
+ disabled: isDisabled(item),
573
+ onClick: () => {
574
+ handlers[item.id]();
575
+ }
576
+ },
577
+ () => h("div", { style: { display: "flex", alignItems: "center", width: "100%" } }, [
578
+ h("span", { style: { flex: "1" } }, item.label),
579
+ ...item.shortcut ? [
580
+ h("span", {
581
+ style: { marginLeft: "24px", color: "rgba(0,0,0,0.4)", fontSize: "0.8em" }
582
+ }, formatShortcut(item.shortcut))
583
+ ] : []
584
+ ])
585
+ )
586
+ ]).flat()
587
+ )
588
+ });
589
+ }
590
+ });
591
+ function renderEmptyState({ emptyState }) {
592
+ return h(
593
+ "div",
594
+ { class: "ogrid-empty-state" },
595
+ emptyState.render ? [emptyState.render()] : [
596
+ h("div", { class: "ogrid-empty-state-title" }, "No results found"),
597
+ h(
598
+ "div",
599
+ { class: "ogrid-empty-state-message" },
600
+ emptyState.message != null ? String(emptyState.message) : emptyState.hasActiveFilters ? [
601
+ "No items match your current filters. Try adjusting your search or ",
602
+ h(VBtn, {
603
+ variant: "text",
604
+ size: "small",
605
+ onClick: emptyState.onClearAll
606
+ }, () => "clear all filters"),
607
+ " to see all items."
608
+ ] : "There are no items available at this time."
609
+ )
610
+ ]
611
+ );
612
+ }
613
+
614
+ // src/DataGridTable/DataGridTable.ts
615
+ var DataGridTable = createDataGridTable({
616
+ renderCheckbox: ({ modelValue, indeterminate, ariaLabel, onChange }) => h(VCheckbox, {
617
+ modelValue,
618
+ indeterminate,
619
+ hideDetails: true,
620
+ density: "compact",
621
+ "aria-label": ariaLabel,
622
+ "onUpdate:modelValue": (c) => onChange(!!c)
623
+ }),
624
+ renderSpinner: (message) => h("div", { class: "ogrid-loading-inner" }, [
625
+ h(VProgressCircular, { size: 24, indeterminate: true }),
626
+ h("span", { class: "ogrid-loading-message" }, message)
627
+ ]),
628
+ ColumnHeaderFilter,
629
+ ColumnHeaderMenu,
630
+ InlineCellEditor,
631
+ GridContextMenu,
632
+ renderEmptyState: (emptyState) => renderEmptyState({ emptyState })
633
+ });
634
+ var ColumnChooser = defineComponent({
635
+ name: "ColumnChooser",
636
+ props: {
637
+ columns: { type: Array, required: true },
638
+ visibleColumns: { type: Object, required: true },
639
+ onVisibilityChange: { type: Function, required: true }
640
+ },
641
+ setup(props) {
642
+ const columnsRef = computed(() => props.columns);
643
+ const visibleColumnsRef = computed(() => props.visibleColumns);
644
+ const state = useColumnChooserState({
645
+ columns: columnsRef,
646
+ visibleColumns: visibleColumnsRef,
647
+ onVisibilityChange: props.onVisibilityChange
648
+ });
649
+ return () => {
650
+ return h(VMenu, {
651
+ modelValue: state.open.value,
652
+ "onUpdate:modelValue": (v) => {
653
+ state.setOpen(v);
654
+ },
655
+ closeOnContentClick: false,
656
+ location: "bottom end"
657
+ }, {
658
+ activator: ({ props: activatorProps }) => h(VBtn, {
659
+ ...activatorProps,
660
+ variant: "outlined",
661
+ size: "small",
662
+ prependIcon: "mdi-view-column",
663
+ appendIcon: state.open.value ? "mdi-chevron-up" : "mdi-chevron-down"
664
+ }, () => `Column Visibility (${state.visibleCount.value} of ${state.totalCount.value})`),
665
+ default: () => h("div", { style: { minWidth: "220px" } }, [
666
+ // Header
667
+ h("div", {
668
+ style: {
669
+ padding: "8px 12px",
670
+ borderBottom: "1px solid rgba(0,0,0,0.12)",
671
+ backgroundColor: "rgba(0,0,0,0.04)",
672
+ fontWeight: "600",
673
+ fontSize: "0.875rem"
674
+ }
675
+ }, `Select Columns (${state.visibleCount.value} of ${state.totalCount.value})`),
676
+ // Column list
677
+ h(
678
+ VList,
679
+ { density: "compact", style: { maxHeight: "320px", overflowY: "auto" } },
680
+ () => props.columns.map(
681
+ (column) => h(
682
+ VListItem,
683
+ { key: column.columnId, style: { minHeight: "32px" } },
684
+ () => h("label", {
685
+ style: { display: "flex", alignItems: "center", gap: "8px", cursor: "pointer", width: "100%" }
686
+ }, [
687
+ h("input", {
688
+ type: "checkbox",
689
+ checked: props.visibleColumns.has(column.columnId),
690
+ onChange: (e) => state.handleCheckboxChange(column.columnId)(e.target.checked)
691
+ }),
692
+ h("span", { style: { fontSize: "0.875rem" } }, column.name)
693
+ ])
694
+ )
695
+ )
696
+ ),
697
+ // Footer with actions
698
+ h(VDivider),
699
+ h("div", {
700
+ style: {
701
+ display: "flex",
702
+ justifyContent: "flex-end",
703
+ gap: "8px",
704
+ padding: "8px 12px",
705
+ backgroundColor: "rgba(0,0,0,0.04)"
706
+ }
707
+ }, [
708
+ h(VBtn, { size: "small", variant: "text", onClick: state.handleClearAll }, () => "Clear All"),
709
+ h(VBtn, { size: "small", variant: "flat", color: "primary", onClick: state.handleSelectAll }, () => "Select All")
710
+ ])
711
+ ])
712
+ });
713
+ };
714
+ }
715
+ });
716
+ var PaginationControls = defineComponent({
717
+ name: "PaginationControls",
718
+ props: {
719
+ currentPage: { type: Number, required: true },
720
+ pageSize: { type: Number, required: true },
721
+ totalCount: { type: Number, required: true },
722
+ onPageChange: { type: Function, required: true },
723
+ onPageSizeChange: { type: Function, required: true },
724
+ pageSizeOptions: { type: Array, default: void 0 },
725
+ entityLabelPlural: { type: String, default: "items" }
726
+ },
727
+ setup(props) {
728
+ const vm = computed(
729
+ () => getPaginationViewModel(
730
+ props.currentPage,
731
+ props.pageSize,
732
+ props.totalCount,
733
+ props.pageSizeOptions ? { pageSizeOptions: props.pageSizeOptions } : void 0
734
+ )
735
+ );
736
+ return () => {
737
+ const v = vm.value;
738
+ if (!v) return null;
739
+ const { pageNumbers, showStartEllipsis, showEndEllipsis, totalPages, startItem, endItem } = v;
740
+ const label = props.entityLabelPlural ?? "items";
741
+ return h("div", {
742
+ role: "navigation",
743
+ "aria-label": "Pagination",
744
+ style: {
745
+ display: "flex",
746
+ alignItems: "center",
747
+ justifyContent: "space-between",
748
+ flexWrap: "wrap",
749
+ gap: "16px",
750
+ padding: "0 12px",
751
+ width: "100%",
752
+ minWidth: "0",
753
+ boxSizing: "border-box"
754
+ }
755
+ }, [
756
+ // Summary text
757
+ h("span", {
758
+ style: { fontSize: "0.875rem", color: "var(--ogrid-fg-secondary, rgba(0,0,0,0.6))" }
759
+ }, `Showing ${startItem} to ${endItem} of ${props.totalCount.toLocaleString()} ${label}`),
760
+ // Page buttons
761
+ h("div", { style: { display: "flex", alignItems: "center", gap: "4px" } }, [
762
+ // First page
763
+ h(VBtn, {
764
+ icon: "mdi-page-first",
765
+ size: "small",
766
+ variant: "text",
767
+ disabled: props.currentPage === 1,
768
+ "aria-label": "First page",
769
+ onClick: () => props.onPageChange(1)
770
+ }),
771
+ // Previous
772
+ h(VBtn, {
773
+ icon: "mdi-chevron-left",
774
+ size: "small",
775
+ variant: "text",
776
+ disabled: props.currentPage === 1,
777
+ "aria-label": "Previous page",
778
+ onClick: () => props.onPageChange(props.currentPage - 1)
779
+ }),
780
+ // Start ellipsis
781
+ ...showStartEllipsis ? [
782
+ h(VBtn, {
783
+ size: "small",
784
+ variant: "outlined",
785
+ "aria-label": "Page 1",
786
+ style: { minWidth: "32px" },
787
+ onClick: () => props.onPageChange(1)
788
+ }, () => "1"),
789
+ h("span", { style: { margin: "0 4px", color: "var(--ogrid-fg-secondary, rgba(0,0,0,0.6))" }, "aria-hidden": "true" }, "\u2026")
790
+ ] : [],
791
+ // Page numbers
792
+ ...pageNumbers.map(
793
+ (pageNum) => h(VBtn, {
794
+ key: pageNum,
795
+ size: "small",
796
+ variant: props.currentPage === pageNum ? "flat" : "outlined",
797
+ color: props.currentPage === pageNum ? "primary" : void 0,
798
+ "aria-label": `Page ${pageNum}`,
799
+ "aria-current": props.currentPage === pageNum ? "page" : void 0,
800
+ style: { minWidth: "32px" },
801
+ onClick: () => props.onPageChange(pageNum)
802
+ }, () => String(pageNum))
803
+ ),
804
+ // End ellipsis
805
+ ...showEndEllipsis ? [
806
+ h("span", { style: { margin: "0 4px", color: "var(--ogrid-fg-secondary, rgba(0,0,0,0.6))" }, "aria-hidden": "true" }, "\u2026"),
807
+ h(VBtn, {
808
+ size: "small",
809
+ variant: "outlined",
810
+ "aria-label": `Page ${totalPages}`,
811
+ style: { minWidth: "32px" },
812
+ onClick: () => props.onPageChange(totalPages)
813
+ }, () => String(totalPages))
814
+ ] : [],
815
+ // Next
816
+ h(VBtn, {
817
+ icon: "mdi-chevron-right",
818
+ size: "small",
819
+ variant: "text",
820
+ disabled: props.currentPage >= totalPages,
821
+ "aria-label": "Next page",
822
+ onClick: () => props.onPageChange(props.currentPage + 1)
823
+ }),
824
+ // Last page
825
+ h(VBtn, {
826
+ icon: "mdi-page-last",
827
+ size: "small",
828
+ variant: "text",
829
+ disabled: props.currentPage >= totalPages,
830
+ "aria-label": "Last page",
831
+ onClick: () => props.onPageChange(totalPages)
832
+ })
833
+ ]),
834
+ // Page size selector
835
+ h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, [
836
+ h("span", { style: { fontSize: "0.875rem", color: "var(--ogrid-fg-secondary, rgba(0,0,0,0.6))" } }, "Rows"),
837
+ h(VSelect, {
838
+ modelValue: props.pageSize,
839
+ items: v.pageSizeOptions,
840
+ density: "compact",
841
+ hideDetails: true,
842
+ variant: "outlined",
843
+ "aria-label": "Rows per page",
844
+ style: { minWidth: "70px", maxWidth: "90px" },
845
+ "onUpdate:modelValue": (val) => props.onPageSizeChange(Number(val))
846
+ })
847
+ ])
848
+ ]);
849
+ };
850
+ }
851
+ });
852
+
853
+ // src/OGrid/OGrid.ts
854
+ var OGrid = createOGrid({
855
+ DataGridTable,
856
+ ColumnChooser,
857
+ PaginationControls
858
+ });
859
+
860
+ export { ColumnChooser, ColumnHeaderFilter, ColumnHeaderMenu, DataGridTable, GridContextMenu, InlineCellEditor, OGrid, PaginationControls };