@svgrid/grid 1.0.2 → 1.1.1
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/LICENSE +1 -1
- package/README.md +137 -39
- package/dist/GridFooter.svelte +164 -0
- package/dist/GridFooter.svelte.d.ts +29 -0
- package/dist/GridMenus.svelte +648 -0
- package/dist/GridMenus.svelte.d.ts +31 -0
- package/dist/SvGrid.controller.svelte.d.ts +472 -0
- package/dist/SvGrid.controller.svelte.js +1917 -0
- package/dist/SvGrid.css +2012 -0
- package/dist/SvGrid.helpers.d.ts +35 -0
- package/dist/SvGrid.helpers.js +160 -0
- package/dist/SvGrid.helpers.test.d.ts +1 -0
- package/dist/SvGrid.helpers.test.js +298 -0
- package/dist/SvGrid.svelte +2346 -8742
- package/dist/SvGrid.svelte.d.ts +4 -357
- package/dist/SvGrid.types.d.ts +523 -0
- package/dist/SvGrid.types.js +1 -0
- package/dist/SvGridChart.svelte +1060 -23
- package/dist/SvGridChart.svelte.d.ts +17 -0
- package/dist/aligned-grids.d.ts +6 -0
- package/dist/aligned-grids.js +84 -0
- package/dist/aligned-grids.test.d.ts +1 -0
- package/dist/aligned-grids.test.js +75 -0
- package/dist/build-api.coverage.test.d.ts +20 -0
- package/dist/build-api.coverage.test.js +505 -0
- package/dist/build-api.d.ts +5 -0
- package/dist/build-api.js +557 -0
- package/dist/cell-render.d.ts +15 -0
- package/dist/cell-render.js +246 -0
- package/dist/cell-render.test.d.ts +1 -0
- package/dist/cell-render.test.js +338 -0
- package/dist/cell-values.d.ts +28 -0
- package/dist/cell-values.js +89 -0
- package/dist/chart-export.test.d.ts +1 -0
- package/dist/chart-export.test.js +302 -0
- package/dist/chart.coverage.test.d.ts +1 -0
- package/dist/chart.coverage.test.js +748 -0
- package/dist/chart.d.ts +370 -3
- package/dist/chart.js +1135 -42
- package/dist/clipboard.d.ts +15 -0
- package/dist/clipboard.js +420 -0
- package/dist/clipboard.test.d.ts +1 -0
- package/dist/clipboard.test.js +700 -0
- package/dist/collaboration.coverage.test.d.ts +1 -0
- package/dist/collaboration.coverage.test.js +200 -0
- package/dist/column-groups.d.ts +19 -0
- package/dist/column-groups.js +62 -0
- package/dist/column-groups.test.d.ts +1 -0
- package/dist/column-groups.test.js +56 -0
- package/dist/column-types.d.ts +10 -0
- package/dist/column-types.js +63 -0
- package/dist/column-types.test.d.ts +1 -0
- package/dist/column-types.test.js +62 -0
- package/dist/columns.d.ts +30 -0
- package/dist/columns.js +277 -0
- package/dist/columns.test.d.ts +1 -0
- package/dist/columns.test.js +625 -0
- package/dist/core.d.ts +86 -1
- package/dist/css.d.ts +3 -0
- package/dist/editing.d.ts +31 -0
- package/dist/editing.js +529 -0
- package/dist/editing.test.d.ts +1 -0
- package/dist/editing.test.js +732 -0
- package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
- package/dist/editors/cell-editors.coverage.test.js +139 -0
- package/dist/editors/cell-editors.d.ts +1 -1
- package/dist/facet-buckets.d.ts +13 -0
- package/dist/facet-buckets.js +54 -0
- package/dist/facet-buckets.test.d.ts +1 -0
- package/dist/facet-buckets.test.js +296 -0
- package/dist/features.d.ts +5 -0
- package/dist/features.js +30 -0
- package/dist/filter-operators.d.ts +16 -0
- package/dist/filter-operators.js +69 -0
- package/dist/filter-operators.test.d.ts +1 -0
- package/dist/filter-operators.test.js +135 -0
- package/dist/hyperformula-adapter.d.ts +82 -0
- package/dist/hyperformula-adapter.js +73 -0
- package/dist/hyperformula-adapter.test.d.ts +1 -0
- package/dist/hyperformula-adapter.test.js +205 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +8 -2
- package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
- package/dist/keyboard-handlers.coverage.test.js +495 -0
- package/dist/keyboard-handlers.d.ts +7 -0
- package/dist/keyboard-handlers.js +202 -0
- package/dist/menus.d.ts +40 -0
- package/dist/menus.js +390 -0
- package/dist/menus.test.d.ts +1 -0
- package/dist/menus.test.js +560 -0
- package/dist/named-views.coverage.test.d.ts +1 -0
- package/dist/named-views.coverage.test.js +180 -0
- package/dist/named-views.d.ts +27 -0
- package/dist/named-views.js +39 -0
- package/dist/row-drag.d.ts +49 -0
- package/dist/row-drag.js +221 -0
- package/dist/row-drag.test.d.ts +1 -0
- package/dist/row-drag.test.js +142 -0
- package/dist/row-resize.d.ts +43 -0
- package/dist/row-resize.js +158 -0
- package/dist/row-resize.test.d.ts +1 -0
- package/dist/row-resize.test.js +329 -0
- package/dist/scroll-sync.d.ts +9 -0
- package/dist/scroll-sync.js +89 -0
- package/dist/scroll-sync.test.d.ts +1 -0
- package/dist/scroll-sync.test.js +290 -0
- package/dist/selection.d.ts +32 -0
- package/dist/selection.js +427 -0
- package/dist/selection.multi-range.test.d.ts +1 -0
- package/dist/selection.multi-range.test.js +55 -0
- package/dist/selection.test.d.ts +1 -0
- package/dist/selection.test.js +647 -0
- package/dist/server-data-source.coverage.test.d.ts +1 -0
- package/dist/server-data-source.coverage.test.js +154 -0
- package/dist/spreadsheet.d.ts +110 -0
- package/dist/spreadsheet.js +242 -0
- package/dist/spreadsheet.test.d.ts +1 -0
- package/dist/spreadsheet.test.js +446 -0
- package/dist/summaries.d.ts +12 -0
- package/dist/summaries.js +65 -0
- package/dist/sv-grid-scrollbar.js +13 -1
- package/dist/svgrid-wrapper.types.d.ts +31 -1
- package/dist/svgrid.behavior.test.js +20 -0
- package/dist/svgrid.comments-autocomplete.test.d.ts +1 -0
- package/dist/svgrid.comments-autocomplete.test.js +96 -0
- package/dist/svgrid.context-menu.test.d.ts +1 -0
- package/dist/svgrid.context-menu.test.js +102 -0
- package/dist/svgrid.interaction.test.js +31 -0
- package/dist/svgrid.new-features.wrapper.test.js +64 -6
- package/dist/svgrid.wrapper.test.js +27 -1
- package/dist/test-setup.js +9 -3
- package/dist/virtualization/column-virtualizer.d.ts +2 -0
- package/dist/virtualization/scroll-scaling.d.ts +45 -0
- package/dist/virtualization/scroll-scaling.js +99 -0
- package/dist/virtualization/scroll-scaling.test.d.ts +1 -0
- package/dist/virtualization/scroll-scaling.test.js +127 -0
- package/dist/virtualization/svelte-virtualizer.svelte.d.ts +2 -0
- package/dist/virtualization/svelte-virtualizer.svelte.js +2 -0
- package/dist/virtualization/virtualizer.d.ts +7 -0
- package/dist/virtualization/virtualizer.js +30 -0
- package/package.json +2 -1
- package/src/GridFooter.svelte +164 -0
- package/src/GridMenus.svelte +648 -0
- package/src/SvGrid.controller.svelte.ts +2352 -0
- package/src/SvGrid.css +2012 -0
- package/src/SvGrid.helpers.test.ts +415 -0
- package/src/SvGrid.helpers.ts +185 -0
- package/src/SvGrid.svelte +2346 -8742
- package/src/SvGrid.types.ts +537 -0
- package/src/SvGridChart.svelte +1060 -23
- package/src/aligned-grids.test.ts +80 -0
- package/src/aligned-grids.ts +87 -0
- package/src/build-api.coverage.test.ts +532 -0
- package/src/build-api.ts +683 -0
- package/src/cell-render.test.ts +451 -0
- package/src/cell-render.ts +426 -0
- package/src/cell-values.ts +114 -0
- package/src/chart-export.test.ts +370 -0
- package/src/chart.coverage.test.ts +814 -0
- package/src/chart.ts +1352 -47
- package/src/clipboard.test.ts +780 -0
- package/src/clipboard.ts +552 -0
- package/src/collaboration.coverage.test.ts +220 -0
- package/src/column-groups.test.ts +59 -0
- package/src/column-groups.ts +80 -0
- package/src/column-types.test.ts +68 -0
- package/src/column-types.ts +82 -0
- package/src/columns.test.ts +702 -0
- package/src/columns.ts +419 -0
- package/src/core.ts +86 -0
- package/src/css.d.ts +3 -0
- package/src/editing.test.ts +837 -0
- package/src/editing.ts +669 -0
- package/src/editors/cell-editors.coverage.test.ts +156 -0
- package/src/editors/cell-editors.ts +1 -0
- package/src/facet-buckets.test.ts +353 -0
- package/src/facet-buckets.ts +67 -0
- package/src/features.ts +128 -0
- package/src/filter-operators.test.ts +174 -0
- package/src/filter-operators.ts +87 -0
- package/src/hyperformula-adapter.test.ts +256 -0
- package/src/hyperformula-adapter.ts +124 -0
- package/src/index.ts +49 -0
- package/src/keyboard-handlers.coverage.test.ts +560 -0
- package/src/keyboard-handlers.ts +353 -0
- package/src/keyboard.ts +97 -97
- package/src/menus.test.ts +620 -0
- package/src/menus.ts +555 -0
- package/src/named-views.coverage.test.ts +210 -0
- package/src/named-views.ts +48 -0
- package/src/row-drag.test.ts +168 -0
- package/src/row-drag.ts +255 -0
- package/src/row-resize.test.ts +369 -0
- package/src/row-resize.ts +171 -0
- package/src/scroll-sync.test.ts +330 -0
- package/src/scroll-sync.ts +218 -0
- package/src/selection.multi-range.test.ts +61 -0
- package/src/selection.test.ts +722 -0
- package/src/selection.ts +579 -0
- package/src/server-data-source.coverage.test.ts +180 -0
- package/src/spreadsheet.test.ts +489 -0
- package/src/spreadsheet.ts +304 -0
- package/src/summaries.ts +204 -0
- package/src/sv-grid-scrollbar.ts +13 -1
- package/src/svgrid-wrapper.types.ts +31 -1
- package/src/svgrid.behavior.test.ts +22 -0
- package/src/svgrid.comments-autocomplete.test.ts +112 -0
- package/src/svgrid.context-menu.test.ts +126 -0
- package/src/svgrid.interaction.test.ts +30 -0
- package/src/svgrid.new-features.wrapper.test.ts +67 -6
- package/src/svgrid.wrapper.test.ts +27 -1
- package/src/test-setup.ts +9 -6
- package/src/virtualization/scroll-scaling.test.ts +148 -0
- package/src/virtualization/scroll-scaling.ts +121 -0
- package/src/virtualization/svelte-virtualizer.svelte.ts +2 -0
- package/src/virtualization/virtualizer.ts +26 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
<script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
|
|
2
|
+
import {
|
|
3
|
+
applyExcelFilter,
|
|
4
|
+
normalizeForFilter,
|
|
5
|
+
createColumnVirtualizer,
|
|
6
|
+
createCoreRowModel,
|
|
7
|
+
createExpandedRowModel,
|
|
8
|
+
createFilteredRowModel,
|
|
9
|
+
createGroupedRowModel,
|
|
10
|
+
createPaginatedRowModel,
|
|
11
|
+
createSvelteVirtualizer,
|
|
12
|
+
createSortedRowModel,
|
|
13
|
+
createSvGrid,
|
|
14
|
+
filterFns,
|
|
15
|
+
getGridCellA11yProps,
|
|
16
|
+
getGridCellDomId,
|
|
17
|
+
getGridHeaderA11yProps,
|
|
18
|
+
getGridRootA11yProps,
|
|
19
|
+
getGridRowA11yProps,
|
|
20
|
+
parseEditorValue,
|
|
21
|
+
normalizeEditorOptions,
|
|
22
|
+
sortFns,
|
|
23
|
+
tableFeatures,
|
|
24
|
+
rowSortingFeature,
|
|
25
|
+
columnFilteringFeature,
|
|
26
|
+
columnGroupingFeature,
|
|
27
|
+
type CellContext,
|
|
28
|
+
type EditorContext,
|
|
29
|
+
type CellEditorOption,
|
|
30
|
+
type CellEditorType,
|
|
31
|
+
type CellFormatter,
|
|
32
|
+
type CellFormatConfig,
|
|
33
|
+
type Column,
|
|
34
|
+
type ColumnDef,
|
|
35
|
+
type Row,
|
|
36
|
+
type RowData,
|
|
37
|
+
type SvGridApi,
|
|
38
|
+
type TableFeatures,
|
|
39
|
+
} from "./index";
|
|
40
|
+
import "./sv-grid-scrollbar";
|
|
41
|
+
import type { Snippet } from "svelte";
|
|
42
|
+
import { getKeyboardIntent, getNextActiveCell } from "./keyboard";
|
|
43
|
+
import {
|
|
44
|
+
formatNumericWithConfig,
|
|
45
|
+
getDateFormatter,
|
|
46
|
+
resolveDatePattern,
|
|
47
|
+
} from "./cell-formatting";
|
|
48
|
+
import {
|
|
49
|
+
RenderSnippetConfig,
|
|
50
|
+
RenderComponentConfig,
|
|
51
|
+
} from "./render-component";
|
|
52
|
+
import { buildFillPattern } from "./fill-patterns";
|
|
53
|
+
import { buildSparkline, toSparklineValues } from "./sparkline";
|
|
54
|
+
import {
|
|
55
|
+
resolveCellFormat,
|
|
56
|
+
computeColumnStat,
|
|
57
|
+
formatsNeedingStats,
|
|
58
|
+
type ColumnStat,
|
|
59
|
+
type ResolvedCellFormat,
|
|
60
|
+
} from "./conditional-formatting";
|
|
61
|
+
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
62
|
+
import type {
|
|
63
|
+
Props,
|
|
64
|
+
SelectionPoint,
|
|
65
|
+
SelectionRange,
|
|
66
|
+
CellEditState,
|
|
67
|
+
FilterOperator,
|
|
68
|
+
FilterOption,
|
|
69
|
+
MenuPosition,
|
|
70
|
+
} from "./SvGrid.types";
|
|
71
|
+
import {
|
|
72
|
+
cfTextStyle,
|
|
73
|
+
fmtStat,
|
|
74
|
+
getCellKey,
|
|
75
|
+
resolveClassList,
|
|
76
|
+
toDateInputValue,
|
|
77
|
+
toDateTimeLocalInputValue,
|
|
78
|
+
getEditableInputValue,
|
|
79
|
+
getEditorInputType,
|
|
80
|
+
toValueArray,
|
|
81
|
+
getOptionLabel,
|
|
82
|
+
getOptionColor,
|
|
83
|
+
colorfulChipStyle,
|
|
84
|
+
getEditorClass,
|
|
85
|
+
asDate,
|
|
86
|
+
clampMenuX,
|
|
87
|
+
cssEscape,
|
|
88
|
+
rawToNumber,
|
|
89
|
+
formatFacetNumber,
|
|
90
|
+
formatFacetDate,
|
|
91
|
+
} from "./SvGrid.helpers";
|
|
92
|
+
|
|
93
|
+
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
94
|
+
|
|
95
|
+
let { ctrl, icon }: { ctrl: SvGridController<TFeatures, TData>; icon: Snippet<[string]> } = $props();
|
|
96
|
+
|
|
97
|
+
// View facade: re-bind the controller's reactive members so the markup
|
|
98
|
+
// (moved verbatim from SvGrid.svelte) stays identical.
|
|
99
|
+
const groupingControlsEnabled = $derived(ctrl.groupingControlsEnabled);
|
|
100
|
+
const filterMenuValues = $derived(ctrl.filterMenuValues);
|
|
101
|
+
const tooltip = $derived(ctrl.tooltip);
|
|
102
|
+
const showTooltipFor = $derived(ctrl.showTooltipFor);
|
|
103
|
+
const columnMenuFor = $derived(ctrl.columnMenuFor);
|
|
104
|
+
const columnMenuPos = $derived(ctrl.columnMenuPos);
|
|
105
|
+
const filterMenuFor = $derived(ctrl.filterMenuFor);
|
|
106
|
+
const filterMenuPos = $derived(ctrl.filterMenuPos);
|
|
107
|
+
const operatorMenuFor = $derived(ctrl.operatorMenuFor);
|
|
108
|
+
const operatorMenuPos = $derived(ctrl.operatorMenuPos);
|
|
109
|
+
const chooseColumnsPos = $derived(ctrl.chooseColumnsPos);
|
|
110
|
+
const showColumnFiltersEffective = $derived(ctrl.showColumnFiltersEffective);
|
|
111
|
+
const columnMenuTabsEnabled = $derived(ctrl.props.columnMenuTabs === true);
|
|
112
|
+
const grid = $derived(ctrl.grid);
|
|
113
|
+
const allColumns = $derived(ctrl.allColumns);
|
|
114
|
+
const isColumnPinned = $derived(ctrl.isColumnPinned);
|
|
115
|
+
const pinColumnLeft = $derived(ctrl.pinColumnLeft);
|
|
116
|
+
const pinColumnRight = $derived(ctrl.pinColumnRight);
|
|
117
|
+
const unpinColumn = $derived(ctrl.unpinColumn);
|
|
118
|
+
const sortDirectionByColumn = $derived(ctrl.sortDirectionByColumn);
|
|
119
|
+
const groupingColumns = $derived(ctrl.groupingColumns);
|
|
120
|
+
const columnVirtualizationEnabled = $derived(ctrl.columnVirtualizationEnabled);
|
|
121
|
+
const updateFilterOperator = $derived(ctrl.updateFilterOperator);
|
|
122
|
+
const updateFilterMenuValue = $derived(ctrl.updateFilterMenuValue);
|
|
123
|
+
const updateFilterMenuValueTo = $derived(ctrl.updateFilterMenuValueTo);
|
|
124
|
+
const operatorsForColumn = $derived(ctrl.operatorsForColumn);
|
|
125
|
+
const defaultOperatorFor = $derived(ctrl.defaultOperatorFor);
|
|
126
|
+
const operatorLabelFor = $derived(ctrl.operatorLabelFor);
|
|
127
|
+
|
|
128
|
+
// Patch a column's menu filter (used for the optional 2nd condition + join
|
|
129
|
+
// of multi-condition filtering). Merges into the existing entry, creating a
|
|
130
|
+
// base entry from the column's default operator when none exists yet.
|
|
131
|
+
function patchFilter(columnId: string, patch: Record<string, unknown>) {
|
|
132
|
+
const col = allColumns.find((c) => c.id === columnId);
|
|
133
|
+
const cur = ctrl.filterMenuValues[columnId] ?? { operator: defaultOperatorFor(col), value: "" };
|
|
134
|
+
ctrl.filterMenuValues = { ...ctrl.filterMenuValues, [columnId]: { ...cur, ...patch } };
|
|
135
|
+
}
|
|
136
|
+
const isColumnFiltered = $derived(ctrl.isColumnFiltered);
|
|
137
|
+
const closeMenus = $derived(ctrl.closeMenus);
|
|
138
|
+
const autosizeColumn = $derived(ctrl.autosizeColumn);
|
|
139
|
+
const autosizeAllColumns = $derived(ctrl.autosizeAllColumns);
|
|
140
|
+
const resetColumns = $derived(ctrl.resetColumns);
|
|
141
|
+
const openChooseColumns = $derived(ctrl.openChooseColumns);
|
|
142
|
+
const sortColumnFromMenu = $derived(ctrl.sortColumnFromMenu);
|
|
143
|
+
const clearColumnSort = $derived(ctrl.clearColumnSort);
|
|
144
|
+
const groupByColumnFromMenu = $derived(ctrl.groupByColumnFromMenu);
|
|
145
|
+
const clearGroupingFromMenu = $derived(ctrl.clearGroupingFromMenu);
|
|
146
|
+
const columnMenuFacetValues = $derived(ctrl.columnMenuFacetValues);
|
|
147
|
+
const columnMenuVisibleFacets = $derived(ctrl.columnMenuVisibleFacets);
|
|
148
|
+
const isFacetChecked = $derived(ctrl.isFacetChecked);
|
|
149
|
+
const toggleFacetValue = $derived(ctrl.toggleFacetValue);
|
|
150
|
+
const isAllFacetsChecked = $derived(ctrl.isAllFacetsChecked);
|
|
151
|
+
const toggleAllFacets = $derived(ctrl.toggleAllFacets);
|
|
152
|
+
const clearColumnFilter = $derived(ctrl.clearColumnFilter);
|
|
153
|
+
const contextMenuFor = $derived(ctrl.contextMenuFor);
|
|
154
|
+
const contextMenuPos = $derived(ctrl.contextMenuPos);
|
|
155
|
+
const contextMenuItems = $derived(ctrl.contextMenuItems);
|
|
156
|
+
const closeContextMenu = $derived(ctrl.closeContextMenu);
|
|
157
|
+
const commentEditFor = $derived(ctrl.commentEditFor);
|
|
158
|
+
const saveComment = $derived(ctrl.saveComment);
|
|
159
|
+
const removeComment = $derived(ctrl.removeComment);
|
|
160
|
+
const closeCommentEditor = $derived(ctrl.closeCommentEditor);
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
{#if tooltip}
|
|
164
|
+
<!-- Custom hover tooltip. Floats above the grid; positioned by
|
|
165
|
+
showTooltipFor(). Notes and column tooltips both surface here. -->
|
|
166
|
+
<div
|
|
167
|
+
class="sv-grid-tooltip"
|
|
168
|
+
class:sv-grid-tooltip-above={!tooltip.below}
|
|
169
|
+
style={`left: ${tooltip.x}px; top: ${tooltip.y}px; ${tooltip.below ? '' : 'transform: translateY(-100%);'}`}
|
|
170
|
+
role="tooltip"
|
|
171
|
+
>{tooltip.text}</div>
|
|
172
|
+
{/if}
|
|
173
|
+
|
|
174
|
+
{#if columnMenuFor || filterMenuFor || operatorMenuFor || chooseColumnsPos}
|
|
175
|
+
<div
|
|
176
|
+
class="sv-grid-menu-backdrop"
|
|
177
|
+
role="presentation"
|
|
178
|
+
onclick={closeMenus}
|
|
179
|
+
></div>
|
|
180
|
+
{/if}
|
|
181
|
+
|
|
182
|
+
<!-- Shared filter UI: rendered both in the funnel popover and in the column
|
|
183
|
+
menu's Filter tab, so the two never diverge. -->
|
|
184
|
+
{#snippet filterPanelBody(colId: string)}
|
|
185
|
+
{@const menuColumn = allColumns.find((c) => c.id === colId)}
|
|
186
|
+
{@const menuOperatorOptions = operatorsForColumn(menuColumn)}
|
|
187
|
+
{@const menuActiveOperator = filterMenuValues[colId]?.operator ?? defaultOperatorFor(menuColumn)}
|
|
188
|
+
{@const menuInputType = getEditorInputType((menuColumn?.columnDef.editorType ?? "text") as CellEditorType)}
|
|
189
|
+
<div class="sv-grid-menu-filter">
|
|
190
|
+
<div class="sv-grid-menu-filter-head">
|
|
191
|
+
<span class="sv-grid-header-icon">{@render icon("filter")}</span> Filter
|
|
192
|
+
condition
|
|
193
|
+
</div>
|
|
194
|
+
<select
|
|
195
|
+
class="sv-grid-menu-operator-select"
|
|
196
|
+
aria-label="Filter condition"
|
|
197
|
+
value={menuActiveOperator}
|
|
198
|
+
onchange={(event) =>
|
|
199
|
+
updateFilterOperator(colId, (event.currentTarget as HTMLSelectElement).value as FilterOperator)}
|
|
200
|
+
>
|
|
201
|
+
{#each menuOperatorOptions as option (option.value)}
|
|
202
|
+
<option value={option.value}>{operatorLabelFor(option, menuColumn)}</option>
|
|
203
|
+
{/each}
|
|
204
|
+
</select>
|
|
205
|
+
{#if menuActiveOperator !== "isBlank"}
|
|
206
|
+
<input
|
|
207
|
+
class="sv-grid-menu-condition-value"
|
|
208
|
+
type={menuInputType}
|
|
209
|
+
value={filterMenuValues[colId]?.value ?? ""}
|
|
210
|
+
placeholder={menuActiveOperator === "between" ? "From" : "Filter value..."}
|
|
211
|
+
oninput={(event) => updateFilterMenuValue(colId, (event.currentTarget as HTMLInputElement).value)}
|
|
212
|
+
/>
|
|
213
|
+
{#if menuActiveOperator === "between"}
|
|
214
|
+
<input
|
|
215
|
+
class="sv-grid-menu-condition-value"
|
|
216
|
+
type={menuInputType}
|
|
217
|
+
value={filterMenuValues[colId]?.valueTo ?? ""}
|
|
218
|
+
placeholder="To"
|
|
219
|
+
oninput={(event) => updateFilterMenuValueTo(colId, (event.currentTarget as HTMLInputElement).value)}
|
|
220
|
+
/>
|
|
221
|
+
{/if}
|
|
222
|
+
{@const mf = filterMenuValues[colId]}
|
|
223
|
+
{#if mf?.operator2}
|
|
224
|
+
{@const op2 = mf.operator2 ?? defaultOperatorFor(menuColumn)}
|
|
225
|
+
<div class="sv-grid-menu-join" role="radiogroup" aria-label="Combine conditions">
|
|
226
|
+
<button type="button" class:is-on={(mf.join ?? "AND") === "AND"}
|
|
227
|
+
onclick={() => patchFilter(colId, { join: "AND" })}>AND</button>
|
|
228
|
+
<button type="button" class:is-on={mf.join === "OR"}
|
|
229
|
+
onclick={() => patchFilter(colId, { join: "OR" })}>OR</button>
|
|
230
|
+
<button type="button" class="sv-grid-menu-join-x" title="Remove second condition"
|
|
231
|
+
aria-label="Remove second condition"
|
|
232
|
+
onclick={() => patchFilter(colId, { operator2: undefined, value2: undefined, valueTo2: undefined, join: undefined })}>×</button>
|
|
233
|
+
</div>
|
|
234
|
+
<select
|
|
235
|
+
class="sv-grid-menu-operator-select"
|
|
236
|
+
aria-label="Second filter condition"
|
|
237
|
+
value={op2}
|
|
238
|
+
onchange={(event) =>
|
|
239
|
+
patchFilter(colId, { operator2: (event.currentTarget as HTMLSelectElement).value as FilterOperator })}
|
|
240
|
+
>
|
|
241
|
+
{#each menuOperatorOptions as option (option.value)}
|
|
242
|
+
<option value={option.value}>{operatorLabelFor(option, menuColumn)}</option>
|
|
243
|
+
{/each}
|
|
244
|
+
</select>
|
|
245
|
+
{#if op2 !== "isBlank"}
|
|
246
|
+
<input
|
|
247
|
+
class="sv-grid-menu-condition-value"
|
|
248
|
+
type={menuInputType}
|
|
249
|
+
value={mf.value2 ?? ""}
|
|
250
|
+
placeholder={op2 === "between" ? "From" : "Filter value..."}
|
|
251
|
+
oninput={(event) => patchFilter(colId, { value2: (event.currentTarget as HTMLInputElement).value })}
|
|
252
|
+
/>
|
|
253
|
+
{#if op2 === "between"}
|
|
254
|
+
<input
|
|
255
|
+
class="sv-grid-menu-condition-value"
|
|
256
|
+
type={menuInputType}
|
|
257
|
+
value={mf.valueTo2 ?? ""}
|
|
258
|
+
placeholder="To"
|
|
259
|
+
oninput={(event) => patchFilter(colId, { valueTo2: (event.currentTarget as HTMLInputElement).value })}
|
|
260
|
+
/>
|
|
261
|
+
{/if}
|
|
262
|
+
{/if}
|
|
263
|
+
{:else}
|
|
264
|
+
<button type="button" class="sv-grid-menu-add-cond"
|
|
265
|
+
onclick={() => patchFilter(colId, { operator2: defaultOperatorFor(menuColumn), value2: "", join: "AND" })}>
|
|
266
|
+
+ Add condition
|
|
267
|
+
</button>
|
|
268
|
+
{/if}
|
|
269
|
+
{/if}
|
|
270
|
+
<div class="sv-grid-menu-sep"></div>
|
|
271
|
+
<div class="sv-grid-menu-filter-head">Values</div>
|
|
272
|
+
<input
|
|
273
|
+
class="sv-grid-menu-search"
|
|
274
|
+
placeholder="Search values..."
|
|
275
|
+
bind:value={ctrl.columnMenuSearch}
|
|
276
|
+
/>
|
|
277
|
+
<label class="sv-grid-facet sv-grid-facet-all">
|
|
278
|
+
<input type="checkbox" checked={isAllFacetsChecked(colId)} onchange={() => toggleAllFacets(colId)} />
|
|
279
|
+
<span class="sv-grid-facet-label">(Select all)</span>
|
|
280
|
+
</label>
|
|
281
|
+
<div class="sv-grid-facet-list">
|
|
282
|
+
{#each columnMenuVisibleFacets as value (value)}
|
|
283
|
+
<label class="sv-grid-facet">
|
|
284
|
+
<input type="checkbox" checked={isFacetChecked(colId, value)} onchange={() => toggleFacetValue(colId, value)} />
|
|
285
|
+
<span class="sv-grid-facet-label">{value === "" ? "(Blanks)" : value}</span>
|
|
286
|
+
</label>
|
|
287
|
+
{:else}
|
|
288
|
+
<div class="sv-grid-facet-empty">No values</div>
|
|
289
|
+
{/each}
|
|
290
|
+
</div>
|
|
291
|
+
{#if columnMenuFacetValues.length > columnMenuVisibleFacets.length}
|
|
292
|
+
<div class="sv-grid-facet-note">
|
|
293
|
+
Showing {columnMenuVisibleFacets.length} of {columnMenuFacetValues.length}
|
|
294
|
+
</div>
|
|
295
|
+
{/if}
|
|
296
|
+
<div class="sv-grid-menu-actions">
|
|
297
|
+
<button
|
|
298
|
+
type="button"
|
|
299
|
+
class="sv-grid-menu-btn"
|
|
300
|
+
disabled={!isColumnFiltered(colId)}
|
|
301
|
+
onclick={() => clearColumnFilter(colId)}
|
|
302
|
+
>
|
|
303
|
+
Clear filter
|
|
304
|
+
</button>
|
|
305
|
+
<button type="button" class="sv-grid-menu-btn sv-grid-menu-btn-primary" onclick={closeMenus}>
|
|
306
|
+
Done
|
|
307
|
+
</button>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
{/snippet}
|
|
311
|
+
|
|
312
|
+
{#if columnMenuFor}
|
|
313
|
+
{@const menuColumnId = columnMenuFor}
|
|
314
|
+
{@const menuCol = allColumns.find((c) => c.id === menuColumnId)}
|
|
315
|
+
{@const menuCanSort = menuCol?.getCanSort?.() ?? false}
|
|
316
|
+
{@const menuCanFilter = (menuCol?.columnDef.field != null) && (menuCol?.columnDef.filterable !== false)}
|
|
317
|
+
{@const menuTab = ctrl.columnMenuTab}
|
|
318
|
+
<div
|
|
319
|
+
class="sv-grid-menu sv-grid-column-menu"
|
|
320
|
+
role="menu"
|
|
321
|
+
style={`left: ${columnMenuPos.x}px; top: ${columnMenuPos.y}px; max-height: calc(100vh - ${columnMenuPos.y}px - 12px);`}
|
|
322
|
+
>
|
|
323
|
+
{#if columnMenuTabsEnabled}
|
|
324
|
+
<div class="sv-grid-menu-tabs" role="tablist">
|
|
325
|
+
<button type="button" class="sv-grid-menu-tab" class:is-active={menuTab === "general"}
|
|
326
|
+
role="tab" aria-selected={menuTab === "general"}
|
|
327
|
+
onclick={() => (ctrl.columnMenuTab = "general")}>General</button>
|
|
328
|
+
{#if menuCanFilter && showColumnFiltersEffective}
|
|
329
|
+
<button type="button" class="sv-grid-menu-tab" class:is-active={menuTab === "filter"}
|
|
330
|
+
role="tab" aria-selected={menuTab === "filter"}
|
|
331
|
+
onclick={() => (ctrl.columnMenuTab = "filter")}>Filter</button>
|
|
332
|
+
{/if}
|
|
333
|
+
<button type="button" class="sv-grid-menu-tab" class:is-active={menuTab === "columns"}
|
|
334
|
+
role="tab" aria-selected={menuTab === "columns"}
|
|
335
|
+
onclick={() => (ctrl.columnMenuTab = "columns")}>Columns</button>
|
|
336
|
+
</div>
|
|
337
|
+
{/if}
|
|
338
|
+
{#if columnMenuTabsEnabled && menuTab === "filter" && menuCanFilter && showColumnFiltersEffective}
|
|
339
|
+
{@render filterPanelBody(menuColumnId)}
|
|
340
|
+
{:else if columnMenuTabsEnabled && menuTab === "columns"}
|
|
341
|
+
<div class="sv-grid-menu-filter-head">Visible columns</div>
|
|
342
|
+
<div class="sv-grid-facet-list">
|
|
343
|
+
{#each grid.getAllColumns() as column (column.id)}
|
|
344
|
+
<label class="sv-grid-facet">
|
|
345
|
+
<input
|
|
346
|
+
type="checkbox"
|
|
347
|
+
checked={!ctrl.hiddenColumns[column.id]}
|
|
348
|
+
onchange={(event) => {
|
|
349
|
+
const visible = (event.currentTarget as HTMLInputElement).checked;
|
|
350
|
+
if (visible) {
|
|
351
|
+
const next = { ...ctrl.hiddenColumns };
|
|
352
|
+
delete next[column.id];
|
|
353
|
+
ctrl.hiddenColumns = next;
|
|
354
|
+
} else {
|
|
355
|
+
ctrl.hiddenColumns = { ...ctrl.hiddenColumns, [column.id]: true };
|
|
356
|
+
}
|
|
357
|
+
}}
|
|
358
|
+
/>
|
|
359
|
+
<span class="sv-grid-facet-label"
|
|
360
|
+
>{typeof column.columnDef.header === "string" ? column.columnDef.header : column.id}</span
|
|
361
|
+
>
|
|
362
|
+
</label>
|
|
363
|
+
{/each}
|
|
364
|
+
</div>
|
|
365
|
+
{:else}
|
|
366
|
+
{#if menuCanSort}
|
|
367
|
+
<button
|
|
368
|
+
type="button"
|
|
369
|
+
class="sv-grid-menu-item"
|
|
370
|
+
role="menuitem"
|
|
371
|
+
onclick={() => sortColumnFromMenu(menuColumnId, false)}
|
|
372
|
+
>
|
|
373
|
+
<span class="sv-grid-header-icon">{@render icon("sort-asc")}</span> Sort
|
|
374
|
+
ascending
|
|
375
|
+
</button>
|
|
376
|
+
<button
|
|
377
|
+
type="button"
|
|
378
|
+
class="sv-grid-menu-item"
|
|
379
|
+
role="menuitem"
|
|
380
|
+
onclick={() => sortColumnFromMenu(menuColumnId, true)}
|
|
381
|
+
>
|
|
382
|
+
<span class="sv-grid-header-icon">{@render icon("sort-desc")}</span> Sort
|
|
383
|
+
descending
|
|
384
|
+
</button>
|
|
385
|
+
<button
|
|
386
|
+
type="button"
|
|
387
|
+
class="sv-grid-menu-item"
|
|
388
|
+
role="menuitem"
|
|
389
|
+
disabled={!sortDirectionByColumn[menuColumnId]}
|
|
390
|
+
onclick={() => clearColumnSort(menuColumnId)}
|
|
391
|
+
>
|
|
392
|
+
<span class="sv-grid-header-icon">{@render icon("x")}</span> Remove sort
|
|
393
|
+
</button>
|
|
394
|
+
{/if}
|
|
395
|
+
{#if !columnVirtualizationEnabled}
|
|
396
|
+
{@const menuPinSide = isColumnPinned(menuColumnId)}
|
|
397
|
+
<div class="sv-grid-menu-sep"></div>
|
|
398
|
+
<button
|
|
399
|
+
type="button"
|
|
400
|
+
class="sv-grid-menu-item"
|
|
401
|
+
role="menuitem"
|
|
402
|
+
disabled={menuPinSide === "left"}
|
|
403
|
+
onclick={() => pinColumnLeft(menuColumnId)}
|
|
404
|
+
>
|
|
405
|
+
<span class="sv-grid-header-icon"
|
|
406
|
+
>{@render icon("op-startsWith")}</span
|
|
407
|
+
> Pin to left
|
|
408
|
+
</button>
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
class="sv-grid-menu-item"
|
|
412
|
+
role="menuitem"
|
|
413
|
+
disabled={menuPinSide === "right"}
|
|
414
|
+
onclick={() => pinColumnRight(menuColumnId)}
|
|
415
|
+
>
|
|
416
|
+
<span class="sv-grid-header-icon"
|
|
417
|
+
>{@render icon("op-greaterThan")}</span
|
|
418
|
+
> Pin to right
|
|
419
|
+
</button>
|
|
420
|
+
<button
|
|
421
|
+
type="button"
|
|
422
|
+
class="sv-grid-menu-item"
|
|
423
|
+
role="menuitem"
|
|
424
|
+
disabled={!menuPinSide}
|
|
425
|
+
onclick={() => unpinColumn(menuColumnId)}
|
|
426
|
+
>
|
|
427
|
+
<span class="sv-grid-header-icon">{@render icon("x")}</span> Unpin column
|
|
428
|
+
</button>
|
|
429
|
+
{/if}
|
|
430
|
+
<div class="sv-grid-menu-sep"></div>
|
|
431
|
+
<button
|
|
432
|
+
type="button"
|
|
433
|
+
class="sv-grid-menu-item"
|
|
434
|
+
role="menuitem"
|
|
435
|
+
onclick={() => {
|
|
436
|
+
autosizeColumn(menuColumnId);
|
|
437
|
+
closeMenus();
|
|
438
|
+
}}
|
|
439
|
+
>
|
|
440
|
+
<span class="sv-grid-header-icon">{@render icon("autosize")}</span> Autosize
|
|
441
|
+
this column
|
|
442
|
+
</button>
|
|
443
|
+
<button
|
|
444
|
+
type="button"
|
|
445
|
+
class="sv-grid-menu-item"
|
|
446
|
+
role="menuitem"
|
|
447
|
+
onclick={() => {
|
|
448
|
+
autosizeAllColumns();
|
|
449
|
+
closeMenus();
|
|
450
|
+
}}
|
|
451
|
+
>
|
|
452
|
+
<span class="sv-grid-header-icon">{@render icon("autosize")}</span> Autosize
|
|
453
|
+
all columns
|
|
454
|
+
</button>
|
|
455
|
+
{#if groupingControlsEnabled}
|
|
456
|
+
<div class="sv-grid-menu-sep"></div>
|
|
457
|
+
<button
|
|
458
|
+
type="button"
|
|
459
|
+
class="sv-grid-menu-item"
|
|
460
|
+
role="menuitem"
|
|
461
|
+
disabled={groupingColumns.includes(menuColumnId)}
|
|
462
|
+
onclick={() => groupByColumnFromMenu(menuColumnId)}
|
|
463
|
+
>
|
|
464
|
+
<span class="sv-grid-header-icon">{@render icon("group")}</span> Group
|
|
465
|
+
by this column
|
|
466
|
+
</button>
|
|
467
|
+
<button
|
|
468
|
+
type="button"
|
|
469
|
+
class="sv-grid-menu-item"
|
|
470
|
+
role="menuitem"
|
|
471
|
+
disabled={!groupingColumns.includes(menuColumnId)}
|
|
472
|
+
onclick={() => clearGroupingFromMenu(menuColumnId)}
|
|
473
|
+
>
|
|
474
|
+
<span class="sv-grid-header-icon">{@render icon("x")}</span> Remove grouping
|
|
475
|
+
</button>
|
|
476
|
+
{/if}
|
|
477
|
+
<div class="sv-grid-menu-sep"></div>
|
|
478
|
+
{#if !columnMenuTabsEnabled}
|
|
479
|
+
<!-- Flat menu: reach the column chooser via a submenu (tabbed mode has
|
|
480
|
+
a Columns tab instead). -->
|
|
481
|
+
<button
|
|
482
|
+
type="button"
|
|
483
|
+
class="sv-grid-menu-item"
|
|
484
|
+
class:is-open={chooseColumnsPos !== null}
|
|
485
|
+
role="menuitem"
|
|
486
|
+
aria-haspopup="menu"
|
|
487
|
+
aria-expanded={chooseColumnsPos !== null}
|
|
488
|
+
onclick={(event) => openChooseColumns(event)}
|
|
489
|
+
>
|
|
490
|
+
<span class="sv-grid-header-icon">{@render icon("columns")}</span>
|
|
491
|
+
Choose columns
|
|
492
|
+
<span class="sv-grid-header-icon sv-grid-menu-item-chevron">{@render icon("chevron-down")}</span>
|
|
493
|
+
</button>
|
|
494
|
+
{/if}
|
|
495
|
+
<button
|
|
496
|
+
type="button"
|
|
497
|
+
class="sv-grid-menu-item"
|
|
498
|
+
role="menuitem"
|
|
499
|
+
onclick={() => {
|
|
500
|
+
resetColumns();
|
|
501
|
+
closeMenus();
|
|
502
|
+
}}
|
|
503
|
+
>
|
|
504
|
+
<span class="sv-grid-header-icon">{@render icon("reset")}</span> Reset columns
|
|
505
|
+
</button>
|
|
506
|
+
{/if}
|
|
507
|
+
</div>
|
|
508
|
+
{/if}
|
|
509
|
+
|
|
510
|
+
{#if filterMenuFor && showColumnFiltersEffective}
|
|
511
|
+
<div
|
|
512
|
+
class="sv-grid-menu sv-grid-filter-menu"
|
|
513
|
+
role="menu"
|
|
514
|
+
style={`left: ${filterMenuPos.x}px; top: ${filterMenuPos.y}px; max-height: calc(100vh - ${filterMenuPos.y}px - 12px);`}
|
|
515
|
+
>
|
|
516
|
+
{@render filterPanelBody(filterMenuFor)}
|
|
517
|
+
</div>
|
|
518
|
+
{/if}
|
|
519
|
+
|
|
520
|
+
{#if chooseColumnsPos}
|
|
521
|
+
{@const everyColumn = grid.getAllColumns()}
|
|
522
|
+
<div
|
|
523
|
+
class="sv-grid-menu sv-grid-choose-columns-menu"
|
|
524
|
+
role="menu"
|
|
525
|
+
style={`left: ${chooseColumnsPos.x}px; top: ${chooseColumnsPos.y}px;`}
|
|
526
|
+
>
|
|
527
|
+
<div class="sv-grid-menu-filter-head">Visible columns</div>
|
|
528
|
+
<div class="sv-grid-facet-list">
|
|
529
|
+
{#each everyColumn as column (column.id)}
|
|
530
|
+
<label class="sv-grid-facet">
|
|
531
|
+
<input
|
|
532
|
+
type="checkbox"
|
|
533
|
+
checked={!ctrl.hiddenColumns[column.id]}
|
|
534
|
+
onchange={(event) => {
|
|
535
|
+
const visible = (event.currentTarget as HTMLInputElement)
|
|
536
|
+
.checked;
|
|
537
|
+
if (visible) {
|
|
538
|
+
const next = { ...ctrl.hiddenColumns };
|
|
539
|
+
delete next[column.id];
|
|
540
|
+
ctrl.hiddenColumns = next;
|
|
541
|
+
} else {
|
|
542
|
+
ctrl.hiddenColumns = { ...ctrl.hiddenColumns, [column.id]: true };
|
|
543
|
+
}
|
|
544
|
+
}}
|
|
545
|
+
/>
|
|
546
|
+
<span class="sv-grid-facet-label"
|
|
547
|
+
>{typeof column.columnDef.header === "string"
|
|
548
|
+
? column.columnDef.header
|
|
549
|
+
: column.id}</span
|
|
550
|
+
>
|
|
551
|
+
</label>
|
|
552
|
+
{/each}
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
{/if}
|
|
556
|
+
|
|
557
|
+
{#if operatorMenuFor}
|
|
558
|
+
{@const opColumnId = operatorMenuFor}
|
|
559
|
+
{@const opColumn = allColumns.find((c) => c.id === opColumnId)}
|
|
560
|
+
{@const opOptions = operatorsForColumn(opColumn)}
|
|
561
|
+
{@const opActive =
|
|
562
|
+
filterMenuValues[opColumnId]?.operator ?? defaultOperatorFor(opColumn)}
|
|
563
|
+
<div
|
|
564
|
+
class="sv-grid-menu sv-grid-operator-menu"
|
|
565
|
+
role="menu"
|
|
566
|
+
style={`left: ${operatorMenuPos.x}px; top: ${operatorMenuPos.y}px;`}
|
|
567
|
+
>
|
|
568
|
+
{#each opOptions as option (option.value)}
|
|
569
|
+
<button
|
|
570
|
+
type="button"
|
|
571
|
+
class="sv-grid-menu-item"
|
|
572
|
+
role="menuitemradio"
|
|
573
|
+
aria-checked={opActive === option.value}
|
|
574
|
+
onclick={() => {
|
|
575
|
+
updateFilterOperator(opColumnId, option.value);
|
|
576
|
+
closeMenus();
|
|
577
|
+
}}
|
|
578
|
+
>
|
|
579
|
+
<span class="sv-grid-header-icon"
|
|
580
|
+
>{@render icon(option.iconName)}</span
|
|
581
|
+
>
|
|
582
|
+
{operatorLabelFor(option, opColumn)}
|
|
583
|
+
</button>
|
|
584
|
+
{/each}
|
|
585
|
+
</div>
|
|
586
|
+
{/if}
|
|
587
|
+
|
|
588
|
+
{#if contextMenuFor}
|
|
589
|
+
<div
|
|
590
|
+
class="sv-grid-menu-backdrop"
|
|
591
|
+
role="presentation"
|
|
592
|
+
oncontextmenu={(e) => { e.preventDefault(); closeContextMenu(); }}
|
|
593
|
+
onclick={closeContextMenu}
|
|
594
|
+
></div>
|
|
595
|
+
<div
|
|
596
|
+
class="sv-grid-menu sv-grid-context-menu"
|
|
597
|
+
role="menu"
|
|
598
|
+
style={`left: ${contextMenuPos.x}px; top: ${contextMenuPos.y}px;`}
|
|
599
|
+
>
|
|
600
|
+
{#each contextMenuItems() as item (item.key)}
|
|
601
|
+
{#if item.separator}
|
|
602
|
+
<div class="sv-grid-menu-sep"></div>
|
|
603
|
+
{:else}
|
|
604
|
+
<button
|
|
605
|
+
type="button"
|
|
606
|
+
class="sv-grid-menu-item"
|
|
607
|
+
role="menuitem"
|
|
608
|
+
disabled={item.disabled}
|
|
609
|
+
onclick={() => { item.run?.(); closeContextMenu(); }}
|
|
610
|
+
>{item.label}</button>
|
|
611
|
+
{/if}
|
|
612
|
+
{/each}
|
|
613
|
+
</div>
|
|
614
|
+
{/if}
|
|
615
|
+
|
|
616
|
+
{#if commentEditFor}
|
|
617
|
+
<div
|
|
618
|
+
class="sv-grid-menu-backdrop"
|
|
619
|
+
role="presentation"
|
|
620
|
+
onclick={closeCommentEditor}
|
|
621
|
+
></div>
|
|
622
|
+
<div
|
|
623
|
+
class="sv-grid-menu sv-grid-comment-editor"
|
|
624
|
+
role="dialog"
|
|
625
|
+
aria-label="Edit comment"
|
|
626
|
+
style={`left: ${commentEditFor.x}px; top: ${commentEditFor.y}px;`}
|
|
627
|
+
>
|
|
628
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
629
|
+
<textarea
|
|
630
|
+
class="sv-grid-comment-textarea"
|
|
631
|
+
rows="3"
|
|
632
|
+
placeholder="Add a comment…"
|
|
633
|
+
autofocus
|
|
634
|
+
bind:value={ctrl.commentDraft}
|
|
635
|
+
onkeydown={(e) => {
|
|
636
|
+
e.stopPropagation();
|
|
637
|
+
if (e.key === "Escape") { e.preventDefault(); closeCommentEditor(); }
|
|
638
|
+
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); saveComment(); }
|
|
639
|
+
}}
|
|
640
|
+
></textarea>
|
|
641
|
+
<div class="sv-grid-comment-actions">
|
|
642
|
+
<button type="button" class="sv-grid-comment-remove" onclick={removeComment}>Remove</button>
|
|
643
|
+
<span class="sv-grid-comment-spacer"></span>
|
|
644
|
+
<button type="button" class="sv-grid-comment-cancel" onclick={closeCommentEditor}>Cancel</button>
|
|
645
|
+
<button type="button" class="sv-grid-comment-save" onclick={saveComment}>Save</button>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
{/if}
|