@coreui/data-grid 0.1.0-rc.0 → 0.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +9 -6
  2. package/dist/css/data-grid.css +156 -38
  3. package/dist/css/data-grid.css.map +1 -1
  4. package/dist/css/data-grid.min.css +2 -2
  5. package/dist/css/data-grid.min.css.map +1 -1
  6. package/dist/css/data-grid.standalone.css +1066 -0
  7. package/dist/css/data-grid.standalone.css.map +1 -0
  8. package/dist/css/data-grid.standalone.min.css +5 -0
  9. package/dist/css/data-grid.standalone.min.css.map +1 -0
  10. package/dist/js/clipboard.cjs +56 -0
  11. package/dist/js/clipboard.cjs.map +1 -0
  12. package/dist/js/clipboard.mjs +54 -0
  13. package/dist/js/clipboard.mjs.map +1 -0
  14. package/dist/js/csv.cjs +36 -59
  15. package/dist/js/csv.cjs.map +1 -1
  16. package/dist/js/csv.mjs +35 -55
  17. package/dist/js/csv.mjs.map +1 -1
  18. package/dist/js/data-grid.esm.js +14108 -9331
  19. package/dist/js/data-grid.esm.js.map +1 -1
  20. package/dist/js/data-grid.esm.min.js +6 -6
  21. package/dist/js/data-grid.esm.min.js.map +1 -1
  22. package/dist/js/data-grid.js +14408 -9634
  23. package/dist/js/data-grid.js.map +1 -1
  24. package/dist/js/data-grid.lite.esm.js +10507 -0
  25. package/dist/js/data-grid.lite.esm.js.map +1 -0
  26. package/dist/js/data-grid.lite.esm.min.js +8 -0
  27. package/dist/js/data-grid.lite.esm.min.js.map +1 -0
  28. package/dist/js/data-grid.lite.js +10520 -0
  29. package/dist/js/data-grid.lite.js.map +1 -0
  30. package/dist/js/data-grid.lite.min.js +8 -0
  31. package/dist/js/data-grid.lite.min.js.map +1 -0
  32. package/dist/js/data-grid.lite.mjs +10507 -0
  33. package/dist/js/data-grid.lite.mjs.map +1 -0
  34. package/dist/js/data-grid.min.js +6 -6
  35. package/dist/js/data-grid.min.js.map +1 -1
  36. package/dist/js/data-grid.mjs +14108 -9331
  37. package/dist/js/data-grid.mjs.map +1 -1
  38. package/dist/js/direction.cjs +26 -0
  39. package/dist/js/direction.cjs.map +1 -0
  40. package/dist/js/direction.mjs +24 -0
  41. package/dist/js/direction.mjs.map +1 -0
  42. package/dist/js/drag.cjs +467 -0
  43. package/dist/js/drag.cjs.map +1 -0
  44. package/dist/js/drag.mjs +464 -0
  45. package/dist/js/drag.mjs.map +1 -0
  46. package/dist/js/export-table-BdabEbJG.mjs +26 -0
  47. package/dist/js/export-table-BdabEbJG.mjs.map +1 -0
  48. package/dist/js/export-table-D762wrZ1.cjs +55 -0
  49. package/dist/js/export-table-D762wrZ1.cjs.map +1 -0
  50. package/dist/js/filters.cjs +89 -132
  51. package/dist/js/filters.cjs.map +1 -1
  52. package/dist/js/filters.mjs +88 -128
  53. package/dist/js/filters.mjs.map +1 -1
  54. package/dist/js/labels.cjs +65 -70
  55. package/dist/js/labels.cjs.map +1 -1
  56. package/dist/js/labels.mjs +64 -66
  57. package/dist/js/labels.mjs.map +1 -1
  58. package/dist/js/print.cjs +66 -0
  59. package/dist/js/print.cjs.map +1 -0
  60. package/dist/js/print.mjs +64 -0
  61. package/dist/js/print.mjs.map +1 -0
  62. package/dist/js/validate.cjs +43 -0
  63. package/dist/js/validate.cjs.map +1 -0
  64. package/dist/js/validate.mjs +42 -0
  65. package/dist/js/validate.mjs.map +1 -0
  66. package/dist/js/xlsx.cjs +240 -0
  67. package/dist/js/xlsx.cjs.map +1 -0
  68. package/dist/js/xlsx.mjs +238 -0
  69. package/dist/js/xlsx.mjs.map +1 -0
  70. package/dist/types/base-component.d.ts +28 -18
  71. package/dist/types/data-grid.d.ts +26 -13
  72. package/dist/types/dom/data.d.ts +15 -0
  73. package/dist/types/dom/event-handler.d.ts +21 -10
  74. package/dist/types/dom/manipulator.d.ts +15 -8
  75. package/dist/types/engine/active-cell.d.ts +1 -0
  76. package/dist/types/engine/data-source.d.ts +1 -1
  77. package/dist/types/engine/features-lite.d.ts +25 -0
  78. package/dist/types/engine/features.d.ts +35 -0
  79. package/dist/types/engine/table.d.ts +3 -3
  80. package/dist/types/engine/validate.d.mts +1 -0
  81. package/dist/types/engine/validate.d.ts +20 -0
  82. package/dist/types/engine/virtualizer.d.ts +2 -0
  83. package/dist/types/icons.d.ts +6 -4
  84. package/dist/types/index-lite.d.mts +1 -0
  85. package/dist/types/index-lite.d.ts +19 -0
  86. package/dist/types/index.d.ts +11 -2
  87. package/dist/types/types.d.ts +53 -31
  88. package/dist/types/ui/body.d.ts +6 -3
  89. package/dist/types/ui/header.d.ts +3 -2
  90. package/dist/types/ui/loading.d.ts +1 -1
  91. package/dist/types/ui/pinning.d.ts +1 -1
  92. package/dist/types/ui/toolbar.d.ts +10 -2
  93. package/dist/types/util/clipboard.d.mts +1 -0
  94. package/dist/types/util/clipboard.d.ts +29 -0
  95. package/dist/types/util/config.d.ts +27 -10
  96. package/dist/types/util/csv.d.ts +5 -5
  97. package/dist/types/util/direction.d.mts +1 -0
  98. package/dist/types/util/direction.d.ts +17 -0
  99. package/dist/types/util/drag.d.mts +1 -0
  100. package/dist/types/util/drag.d.ts +48 -0
  101. package/dist/types/util/export-table.d.ts +47 -0
  102. package/dist/types/util/index.d.ts +66 -10
  103. package/dist/types/util/print.d.mts +1 -0
  104. package/dist/types/util/print.d.ts +32 -0
  105. package/dist/types/util/sanitizer.d.ts +15 -11
  106. package/dist/types/util/xlsx.d.mts +1 -0
  107. package/dist/types/util/xlsx.d.ts +17 -0
  108. package/dist/types/util/zip.d.ts +11 -0
  109. package/package.json +101 -10
  110. package/scss/_base.scss +13 -0
  111. package/scss/_config.scss +14 -0
  112. package/scss/_functions.scss +1 -0
  113. package/scss/_mixins.scss +1 -0
  114. package/scss/base/_buttons.scss +113 -0
  115. package/scss/base/_forms.scss +157 -0
  116. package/scss/base/_helpers.scss +24 -0
  117. package/scss/base/_pagination.scss +100 -0
  118. package/scss/base/_reboot.scss +93 -0
  119. package/scss/base/_root.scss +85 -0
  120. package/scss/base/_spinner.scss +45 -0
  121. package/scss/base/_table.scss +78 -0
  122. package/scss/components/_data-grid.scss +807 -0
  123. package/scss/data-grid.scss +7 -555
  124. package/scss/data-grid.standalone.scss +16 -0
  125. package/scss/functions/_defaults.scss +25 -0
  126. package/scss/mixins/_tokens.scss +12 -0
  127. package/src/{base-component.js → base-component.ts} +21 -17
  128. package/src/data-grid.ts +523 -201
  129. package/src/dom/{data.js → data.ts} +13 -10
  130. package/src/dom/event-handler.ts +380 -0
  131. package/src/dom/{manipulator.js → manipulator.ts} +11 -11
  132. package/src/engine/active-cell.ts +10 -3
  133. package/src/engine/data-source.ts +7 -3
  134. package/src/engine/features-lite.ts +47 -0
  135. package/src/engine/features.ts +68 -0
  136. package/src/engine/table.ts +86 -45
  137. package/src/engine/validate.ts +64 -0
  138. package/src/engine/virtualizer.ts +17 -1
  139. package/src/icons.ts +11 -5
  140. package/src/index-lite.ts +85 -0
  141. package/src/index.ts +30 -2
  142. package/src/labels.ts +6 -4
  143. package/src/types.ts +87 -32
  144. package/src/ui/body.ts +73 -17
  145. package/src/ui/header.ts +51 -26
  146. package/src/ui/loading.ts +2 -2
  147. package/src/ui/pinning.ts +16 -16
  148. package/src/ui/toolbar.ts +27 -3
  149. package/src/util/clipboard.ts +112 -0
  150. package/src/util/{config.js → config.ts} +25 -11
  151. package/src/util/csv.ts +11 -30
  152. package/src/util/direction.ts +37 -0
  153. package/src/util/drag.ts +832 -0
  154. package/src/util/export-table.ts +86 -0
  155. package/src/util/{index.js → index.ts} +55 -35
  156. package/src/util/print.ts +125 -0
  157. package/src/util/{sanitizer.js → sanitizer.ts} +17 -13
  158. package/src/util/xlsx.ts +183 -0
  159. package/src/util/zip.ts +170 -0
  160. package/src/base-component.d.ts +0 -19
  161. package/src/dom/event-handler.d.ts +0 -11
  162. package/src/dom/event-handler.js +0 -331
  163. package/src/dom/manipulator.d.ts +0 -9
  164. package/src/util/config.d.ts +0 -11
  165. package/src/util/index.d.ts +0 -10
  166. package/src/util/sanitizer.d.ts +0 -11
@@ -0,0 +1,807 @@
1
+ @use "../functions/defaults" as *;
2
+ @use "../mixins/tokens" as *;
3
+ @use "../config" as *;
4
+
5
+ // stylelint-disable custom-property-no-missing-var-function
6
+
7
+ // Everything resolves through CoreUI semantic variables, so light/dark theming
8
+ // via [data-coreui-theme] works with no extra rules. The fallbacks keep the grid
9
+ // legible when the CoreUI stylesheet is absent; `data-grid.standalone.css` fills
10
+ // the rest in.
11
+
12
+ // scss-docs-start data-grid-variables
13
+ $data-grid-viewport-max-height: 30rem !default;
14
+ $data-grid-header-bg: var(--#{$prefix}body-bg, #fff) !default;
15
+ $data-grid-header-shadow: inset 0 calc(-1 * var(--#{$prefix}border-width, 1px)) 0 var(--#{$prefix}border-color, #dee2e6) !default;
16
+ $data-grid-cell-selected-bg: rgba(var(--#{$prefix}primary-rgb, 50, 31, 219), .12) !default;
17
+ $data-grid-cell-selected-border-color: var(--#{$prefix}primary, #321fdb) !default;
18
+ $data-grid-cell-selected-border-width: 1px !default;
19
+ $data-grid-row-handle-width: 2rem !default;
20
+ $data-grid-select-cell-width: 2.5rem !default;
21
+ $data-grid-sorter-margin: .25rem !default;
22
+ $data-grid-sorter-opacity: .3 !default;
23
+ $data-grid-spacing: .5rem !default;
24
+ $data-grid-resizer-width: .625rem !default;
25
+ $data-grid-resizer-grip-height: 60% !default;
26
+ $data-grid-resizer-grip-color: var(--#{$prefix}border-color, #dee2e6) !default;
27
+ $data-grid-resizer-grip-color-hover: var(--#{$prefix}secondary-color, #6c757d) !default;
28
+ $data-grid-pinned-border-width: var(--#{$prefix}border-width) !default;
29
+ $data-grid-pinned-border-color: var(--#{$prefix}border-color, #dee2e6) !default;
30
+ $data-grid-drop-indicator: var(--#{$prefix}primary, #321fdb) !default;
31
+ $data-grid-drag-transition: .15s ease !default;
32
+ $data-grid-menu-bg: var(--#{$prefix}body-bg, #fff) !default;
33
+ $data-grid-menu-border-color: var(--#{$prefix}border-color, #dee2e6) !default;
34
+ $data-grid-menu-shadow: var(--#{$prefix}box-shadow, 0 .5rem 1rem rgba(0, 0, 0, .15)) !default;
35
+ $data-grid-menu-min-width: 10rem !default;
36
+ $data-grid-menu-item-hover-bg: var(--#{$prefix}tertiary-bg, #f8f9fa) !default;
37
+ $data-grid-loading-bg: rgba(var(--#{$prefix}body-bg-rgb, 255, 255, 255), .6) !default;
38
+ $data-grid-pinned-bg: var(--#{$prefix}body-bg, #fff) !default;
39
+ $data-grid-focus-ring-width: 2px !default;
40
+ $data-grid-focus-ring-color: var(--#{$prefix}primary, #321fdb) !default;
41
+ // scss-docs-end data-grid-variables
42
+
43
+ // Values that shape the grid but have no runtime knob. Sass-only on purpose:
44
+ // a token here would be a new public custom property, which is a separate call.
45
+ // scss-docs-start data-grid-layout-variables
46
+ $data-grid-bar-min-height: 48px !default;
47
+ $data-grid-border-width: var(--#{$prefix}border-width, 1px) !default;
48
+ $data-grid-border-color: var(--#{$prefix}border-color, #dee2e6) !default;
49
+ $data-grid-border-radius: var(--#{$prefix}border-radius, .375rem) !default;
50
+ $data-grid-muted-color: var(--#{$prefix}secondary-color, #6c757d) !default;
51
+ $data-grid-popup-max-width: 18rem !default;
52
+ $data-grid-gap-sm: .25rem !default;
53
+ $data-grid-tooltip-z-index: 1080 !default;
54
+ // scss-docs-end data-grid-layout-variables
55
+
56
+ $data-grid-tokens: () !default;
57
+
58
+ // scss-docs-start data-grid-css-vars
59
+ // stylelint-disable-next-line scss/dollar-variable-default
60
+ $data-grid-tokens: defaults(
61
+ (
62
+ --#{$prefix}data-grid-viewport-max-height: #{$data-grid-viewport-max-height},
63
+ --#{$prefix}data-grid-header-bg: #{$data-grid-header-bg},
64
+ --#{$prefix}data-grid-header-shadow: #{$data-grid-header-shadow},
65
+ --#{$prefix}data-grid-cell-selected-bg: #{$data-grid-cell-selected-bg},
66
+ --#{$prefix}data-grid-cell-selected-border-color: #{$data-grid-cell-selected-border-color},
67
+ --#{$prefix}data-grid-cell-selected-border-width: #{$data-grid-cell-selected-border-width},
68
+ --#{$prefix}data-grid-row-handle-width: #{$data-grid-row-handle-width},
69
+ --#{$prefix}data-grid-select-cell-width: #{$data-grid-select-cell-width},
70
+ --#{$prefix}data-grid-sorter-margin: #{$data-grid-sorter-margin},
71
+ --#{$prefix}data-grid-sorter-opacity: #{$data-grid-sorter-opacity},
72
+ --#{$prefix}data-grid-spacing: #{$data-grid-spacing},
73
+ --#{$prefix}data-grid-resizer-width: #{$data-grid-resizer-width},
74
+ --#{$prefix}data-grid-resizer-grip-height: #{$data-grid-resizer-grip-height},
75
+ --#{$prefix}data-grid-resizer-grip-color: #{$data-grid-resizer-grip-color},
76
+ --#{$prefix}data-grid-resizer-grip-color-hover: #{$data-grid-resizer-grip-color-hover},
77
+ --#{$prefix}data-grid-pinned-border-width: #{$data-grid-pinned-border-width},
78
+ --#{$prefix}data-grid-pinned-border-color: #{$data-grid-pinned-border-color},
79
+ --#{$prefix}data-grid-drop-indicator: #{$data-grid-drop-indicator},
80
+ --#{$prefix}data-grid-drag-transition: #{$data-grid-drag-transition},
81
+ --#{$prefix}data-grid-menu-bg: #{$data-grid-menu-bg},
82
+ --#{$prefix}data-grid-menu-border-color: #{$data-grid-menu-border-color},
83
+ --#{$prefix}data-grid-menu-shadow: #{$data-grid-menu-shadow},
84
+ --#{$prefix}data-grid-menu-min-width: #{$data-grid-menu-min-width},
85
+ --#{$prefix}data-grid-menu-item-hover-bg: #{$data-grid-menu-item-hover-bg},
86
+ --#{$prefix}data-grid-loading-bg: #{$data-grid-loading-bg},
87
+ --#{$prefix}data-grid-pinned-bg: #{$data-grid-pinned-bg},
88
+ --#{$prefix}data-grid-focus-ring-width: #{$data-grid-focus-ring-width},
89
+ --#{$prefix}data-grid-focus-ring-color: #{$data-grid-focus-ring-color},
90
+ ),
91
+ $data-grid-tokens
92
+ );
93
+ // scss-docs-end data-grid-css-vars
94
+
95
+ .data-grid {
96
+ // The column menu is positioned against the grid root (it must escape the
97
+ // viewport's overflow while staying inside the token scope).
98
+ position: relative;
99
+ // A column so that a root given an explicit height actually constrains the
100
+ // scroller: the viewport takes what the toolbar and pagination leave, and
101
+ // min-height: 0 lets it shrink below its content instead of overflowing the
102
+ // root. With no height set, this changes nothing — the viewport is sized by
103
+ // its content up to the max-height token.
104
+ display: flex;
105
+ flex-direction: column;
106
+
107
+ @include tokens($data-grid-tokens);
108
+
109
+ .data-grid-toolbar {
110
+ display: flex;
111
+ flex-wrap: wrap;
112
+ gap: var(--#{$prefix}data-grid-spacing);
113
+ align-items: center;
114
+ min-height: $data-grid-bar-min-height;
115
+ padding: var(--#{$prefix}data-grid-spacing);
116
+ border-bottom: $data-grid-border-width solid $data-grid-border-color;
117
+ }
118
+
119
+ .data-grid-toolbar-actions {
120
+ display: flex;
121
+ gap: $data-grid-gap-sm;
122
+ align-items: center;
123
+ }
124
+
125
+ .data-grid-toolbar-search {
126
+ margin-inline-start: auto;
127
+ }
128
+
129
+ .data-grid-viewport {
130
+ position: relative;
131
+ flex: 1 1 auto;
132
+ min-height: 0;
133
+ max-height: var(--#{$prefix}data-grid-viewport-max-height);
134
+ overflow-y: auto;
135
+ // Windowed rendering resizes the spacer rows and recycles row elements on
136
+ // every frame; browser scroll anchoring chases those mutations and keeps
137
+ // adjusting scrollTop, which turns wheel scrolling into a feedback loop.
138
+ overflow-anchor: none;
139
+ }
140
+
141
+ // Inline editing: the in-cell editor fills the cell; the popup layer hosts
142
+ // rich editors (date pickers, selects) anchored over the cell.
143
+ .data-grid-cell-editor {
144
+ width: 100%;
145
+ min-width: 4rem;
146
+ }
147
+
148
+ .data-grid-editor-popup {
149
+ position: absolute;
150
+ z-index: 4;
151
+ padding: $data-grid-gap-sm;
152
+ background-color: var(--#{$prefix}data-grid-menu-bg);
153
+ border: $data-grid-border-width solid var(--#{$prefix}data-grid-menu-border-color);
154
+ border-radius: $data-grid-border-radius;
155
+ box-shadow: var(--#{$prefix}data-grid-menu-shadow);
156
+ }
157
+
158
+ // cellNavigation: the roving-tabindex active cell. The ring is inset so the
159
+ // sticky header and pinned columns don't clip it.
160
+ [role="grid"] :is(td, th):focus-visible {
161
+ outline: var(--#{$prefix}data-grid-focus-ring-width) solid var(--#{$prefix}data-grid-focus-ring-color);
162
+ outline-offset: calc(-1 * var(--#{$prefix}data-grid-focus-ring-width));
163
+ }
164
+
165
+ .data-grid-viewport:focus-visible {
166
+ outline: 0;
167
+ }
168
+
169
+ .data-grid-header {
170
+ position: sticky;
171
+ top: 0;
172
+ z-index: 2;
173
+ background-color: var(--#{$prefix}data-grid-header-bg);
174
+ box-shadow: var(--#{$prefix}data-grid-header-shadow);
175
+ }
176
+
177
+ // Header label row: fixed 48px with vertically centered content (height on a
178
+ // table cell acts as a min; min-height is ignored on table-cell).
179
+ .data-grid-header tr:not(.data-grid-filter-row) > th {
180
+ height: $data-grid-bar-min-height;
181
+ vertical-align: middle;
182
+ }
183
+
184
+ .data-grid-filter-row > th {
185
+ padding-top: 0;
186
+ font-weight: normal;
187
+ }
188
+
189
+ .data-grid-header th[data-coreui-draggable] {
190
+ cursor: grab;
191
+ }
192
+
193
+ .data-grid-header-cell {
194
+ display: flex;
195
+ align-items: center;
196
+ gap: $data-grid-gap-sm;
197
+ }
198
+
199
+ .data-grid-header-label {
200
+ overflow: hidden;
201
+ text-overflow: ellipsis;
202
+ white-space: nowrap;
203
+ }
204
+
205
+ // Icon-only ghost buttons: square them by matching horizontal padding to
206
+ // vertical (follows .btn / .btn-sm automatically) and collapsing the text
207
+ // line-height so height follows the icon, not a 1.5em text line.
208
+ .btn-ghost {
209
+ --#{$prefix}btn-padding-x: var(--#{$prefix}btn-padding-y);
210
+ --#{$prefix}btn-line-height: 1;
211
+ }
212
+
213
+ .data-grid-menu-button {
214
+ margin-inline-start: auto;
215
+ }
216
+
217
+ .data-grid-menu {
218
+ position: absolute;
219
+ z-index: 4;
220
+ min-width: var(--#{$prefix}data-grid-menu-min-width);
221
+ padding: $data-grid-gap-sm 0;
222
+ background-color: var(--#{$prefix}data-grid-menu-bg);
223
+ border: $data-grid-border-width solid var(--#{$prefix}data-grid-menu-border-color);
224
+ border-radius: $data-grid-border-radius;
225
+ box-shadow: var(--#{$prefix}data-grid-menu-shadow);
226
+ }
227
+
228
+ .data-grid-menu-item {
229
+ display: flex;
230
+ align-items: center;
231
+ gap: .5rem;
232
+ width: 100%;
233
+ padding: $data-grid-gap-sm .75rem;
234
+ font: inherit;
235
+ color: inherit;
236
+ text-align: start;
237
+ cursor: pointer;
238
+ background: none;
239
+ border: 0;
240
+
241
+ &:hover,
242
+ &:focus-visible {
243
+ background-color: var(--#{$prefix}data-grid-menu-item-hover-bg);
244
+ }
245
+
246
+ // Disabled items stay focusable (aria-disabled, APG menu pattern) so the
247
+ // roving arrow cycle can reach them.
248
+ &[aria-disabled="true"] {
249
+ color: $data-grid-muted-color;
250
+ cursor: default;
251
+ background: none;
252
+ }
253
+ }
254
+
255
+ .data-grid-filter-menu {
256
+ position: absolute;
257
+ z-index: 4;
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: .5rem;
261
+ min-width: var(--#{$prefix}data-grid-menu-min-width);
262
+ max-width: $data-grid-popup-max-width;
263
+ padding: .75rem;
264
+ background-color: var(--#{$prefix}data-grid-menu-bg);
265
+ border: $data-grid-border-width solid var(--#{$prefix}data-grid-menu-border-color);
266
+ border-radius: $data-grid-border-radius;
267
+ box-shadow: var(--#{$prefix}data-grid-menu-shadow);
268
+
269
+ > div {
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: .5rem;
273
+ }
274
+ }
275
+
276
+ .data-grid-filter-menu-condition {
277
+ display: flex;
278
+ flex-direction: column;
279
+ gap: $data-grid-gap-sm;
280
+ }
281
+
282
+ .data-grid-filter-menu-join {
283
+ display: flex;
284
+ gap: 1rem;
285
+
286
+ label {
287
+ display: flex;
288
+ gap: .375rem;
289
+ align-items: center;
290
+ }
291
+ }
292
+
293
+ .data-grid-filter-menu-values {
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: $data-grid-gap-sm;
297
+ max-height: 14rem;
298
+ overflow-y: auto;
299
+ }
300
+
301
+ .data-grid-filter-menu-footer {
302
+ display: flex;
303
+ gap: .5rem;
304
+ justify-content: flex-end;
305
+ }
306
+
307
+ .data-grid-column-chooser {
308
+ position: absolute;
309
+ z-index: 4;
310
+ display: flex;
311
+ flex-direction: column;
312
+ gap: .5rem;
313
+ min-width: var(--#{$prefix}data-grid-menu-min-width);
314
+ max-width: $data-grid-popup-max-width;
315
+ max-height: 20rem;
316
+ padding: .75rem;
317
+ overflow-y: auto;
318
+ background-color: var(--#{$prefix}data-grid-menu-bg);
319
+ border: $data-grid-border-width solid var(--#{$prefix}data-grid-menu-border-color);
320
+ border-radius: $data-grid-border-radius;
321
+ box-shadow: var(--#{$prefix}data-grid-menu-shadow);
322
+ }
323
+
324
+ .data-grid-column-chooser-option {
325
+ display: flex;
326
+ gap: .375rem;
327
+ align-items: center;
328
+ margin-bottom: 0;
329
+ }
330
+
331
+ .data-grid-column-chooser-footer {
332
+ display: flex;
333
+ gap: .5rem;
334
+ justify-content: flex-end;
335
+ padding-top: $data-grid-gap-sm;
336
+ margin-top: $data-grid-gap-sm;
337
+ border-top: $data-grid-border-width solid var(--#{$prefix}data-grid-menu-border-color);
338
+ }
339
+
340
+ .data-grid-filter-button {
341
+ position: relative;
342
+ // Right-align the trailing controls as a group: the filter button takes the
343
+ // auto margin (menu drops its own below) so filter and menu sit together at
344
+ // the end, matching a menu-only header.
345
+ margin-inline-start: auto;
346
+
347
+ ~ .data-grid-menu-button {
348
+ margin-inline-start: 0;
349
+ }
350
+ }
351
+
352
+ // Active-filter indicator dot on the filter button.
353
+ .data-grid-filter-button-filtered::after {
354
+ position: absolute;
355
+ inset-block-start: .125rem;
356
+ inset-inline-end: .125rem;
357
+ width: .375rem;
358
+ height: .375rem;
359
+ content: "";
360
+ background-color: var(--#{$prefix}primary, #321fdb);
361
+ border-radius: 50%;
362
+ }
363
+
364
+ .data-grid-menu-item-icon {
365
+ display: inline-flex;
366
+ opacity: .75;
367
+
368
+ svg {
369
+ width: 1em;
370
+ height: 1em;
371
+ }
372
+ }
373
+
374
+ .data-grid-menu-divider {
375
+ height: $data-grid-border-width;
376
+ margin: $data-grid-gap-sm 0;
377
+ background-color: var(--#{$prefix}data-grid-menu-border-color);
378
+ }
379
+
380
+ .data-grid-dragging {
381
+ opacity: .5;
382
+ }
383
+
384
+ // While a drag is live the whole grid keeps the grabbing cursor and stops
385
+ // painting a text selection under the pointer.
386
+ &.data-grid-drag-active {
387
+ cursor: grabbing;
388
+ user-select: none;
389
+ }
390
+
391
+ // Both overlays are position: fixed (placed from viewport coordinates in JS)
392
+ // so they escape the viewport's overflow, like the tooltip.
393
+ .data-grid-drag-ghost {
394
+ position: fixed;
395
+ top: 0;
396
+ left: 0;
397
+ z-index: $data-grid-tooltip-z-index;
398
+ display: flex;
399
+ align-items: center;
400
+ max-width: 16rem;
401
+ padding: 0 var(--#{$prefix}data-grid-cell-padding-x, .5rem);
402
+ overflow: hidden;
403
+ font-weight: 600;
404
+ text-overflow: ellipsis;
405
+ white-space: nowrap;
406
+ pointer-events: none;
407
+ background-color: var(--#{$prefix}data-grid-pinned-bg);
408
+ border: $data-grid-border-width solid var(--#{$prefix}data-grid-drop-indicator);
409
+ border-radius: $data-grid-border-radius;
410
+ box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
411
+ transform: translate(-9999px, -9999px);
412
+ opacity: .9;
413
+ }
414
+
415
+ // The settling ghost: on release it flies from the pointer to the slot the
416
+ // item landed in (or back home on cancel) and fades out there.
417
+ .data-grid-drag-ghost-settle {
418
+ transition: transform var(--#{$prefix}data-grid-drag-transition), opacity var(--#{$prefix}data-grid-drag-transition);
419
+ }
420
+
421
+ .data-grid-drop-indicator {
422
+ position: fixed;
423
+ z-index: $data-grid-tooltip-z-index;
424
+ pointer-events: none;
425
+ background-color: var(--#{$prefix}data-grid-drop-indicator);
426
+ transition: top var(--#{$prefix}data-grid-drag-transition), left var(--#{$prefix}data-grid-drag-transition);
427
+ }
428
+
429
+ // The live reorder preview: cells slide to the layout the drop will produce.
430
+ // Transform only, so nothing reflows and the real order still changes once.
431
+ .data-grid-drag-preview {
432
+ transition: transform var(--#{$prefix}data-grid-drag-transition);
433
+ }
434
+
435
+ // Someone who asked the OS to stop moving things still gets the preview —
436
+ // it snaps to the final layout instead of gliding there.
437
+ @media (prefers-reduced-motion: reduce) {
438
+ :is(.data-grid-drop-indicator, .data-grid-drag-preview, .data-grid-drag-ghost-settle) {
439
+ transition: none;
440
+ }
441
+ }
442
+
443
+ // The line is 2px across its short axis and spans the grid along the other;
444
+ // JS sets the spanning size, the axis class sets the thickness.
445
+ .data-grid-drop-indicator-x {
446
+ width: .125rem;
447
+ }
448
+
449
+ .data-grid-drop-indicator-y {
450
+ height: .125rem;
451
+ }
452
+
453
+ // Cell selection: a tinted fill on every selected cell, plus an outline
454
+ // drawn only on the sides that sit on the selection's boundary — so a
455
+ // rectangle reads as one region instead of a grid of boxes. The outline is
456
+ // an overlay rather than a border: a border on a table cell would shift the
457
+ // whole row's layout the moment a range is selected.
458
+ .data-grid-cell-selected {
459
+ position: relative;
460
+ background-color: var(--#{$prefix}data-grid-cell-selected-bg);
461
+ }
462
+
463
+ .data-grid-cell-selected::after {
464
+ position: absolute;
465
+ inset: 0;
466
+ pointer-events: none;
467
+ content: "";
468
+ border: 0 solid var(--#{$prefix}data-grid-cell-selected-border-color);
469
+ }
470
+
471
+ .data-grid-cell-selected-top::after {
472
+ border-top-width: var(--#{$prefix}data-grid-cell-selected-border-width);
473
+ }
474
+
475
+ .data-grid-cell-selected-right::after {
476
+ border-right-width: var(--#{$prefix}data-grid-cell-selected-border-width);
477
+ }
478
+
479
+ .data-grid-cell-selected-bottom::after {
480
+ border-bottom-width: var(--#{$prefix}data-grid-cell-selected-border-width);
481
+ }
482
+
483
+ .data-grid-cell-selected-left::after {
484
+ border-left-width: var(--#{$prefix}data-grid-cell-selected-border-width);
485
+ }
486
+
487
+ // The reorder column: as narrow as the grip, and the grip itself is the only
488
+ // thing that starts a row drag (a row is full of interactive content that a
489
+ // whole-row handle would swallow).
490
+ .data-grid-row-handle-cell {
491
+ width: var(--#{$prefix}data-grid-row-handle-width);
492
+ padding-inline: 0;
493
+ text-align: center;
494
+ }
495
+
496
+ .data-grid-row-handle {
497
+ display: inline-flex;
498
+ color: $data-grid-resizer-grip-color-hover;
499
+ cursor: grab;
500
+ touch-action: none;
501
+ }
502
+
503
+ .data-grid-select-cell {
504
+ width: var(--#{$prefix}data-grid-select-cell-width);
505
+ text-align: center;
506
+ }
507
+
508
+ .data-grid-sort-button {
509
+ display: inline-flex;
510
+ align-items: center;
511
+ gap: var(--#{$prefix}data-grid-sorter-margin);
512
+ padding: 0;
513
+ font: inherit;
514
+ color: inherit;
515
+ cursor: pointer;
516
+ user-select: none;
517
+ background: none;
518
+ border: 0;
519
+ }
520
+
521
+ .data-grid-sort-icon {
522
+ display: inline-flex;
523
+ font-size: .875em;
524
+ opacity: 0;
525
+
526
+ svg {
527
+ width: 1em;
528
+ height: 1em;
529
+ }
530
+ }
531
+
532
+ // The active sort direction is always visible; the neutral (unsorted) icon is
533
+ // hidden unless `sorterVisibility` opts it in (steadily via "always", or on
534
+ // header hover/focus via "hover").
535
+ .data-grid-sortable[aria-sort] .data-grid-sort-icon {
536
+ opacity: 1;
537
+ }
538
+
539
+ &[data-coreui-sorter-visibility="always"] .data-grid-sortable:not([aria-sort]) .data-grid-sort-icon {
540
+ opacity: var(--#{$prefix}data-grid-sorter-opacity);
541
+ }
542
+
543
+ &[data-coreui-sorter-visibility="hover"] .data-grid-sortable:not([aria-sort]):hover .data-grid-sort-icon,
544
+ &[data-coreui-sorter-visibility="hover"] .data-grid-sortable:not([aria-sort]):focus-within .data-grid-sort-icon {
545
+ opacity: var(--#{$prefix}data-grid-sorter-opacity);
546
+ }
547
+
548
+ .data-grid-viewport > .table {
549
+ margin-bottom: 0;
550
+ table-layout: fixed;
551
+ }
552
+
553
+ // Body cells render on a single line and truncate with an ellipsis instead of
554
+ // spilling their content across the fixed-layout column boundary onto the next
555
+ // column. Matches the default of AG Grid / MUI X Data Grid and TanStack's
556
+ // virtualized-table guidance, and keeps every row a uniform height for the
557
+ // fixed-height virtualizer (wrapping would desync `estimateSize`). Pinned cells
558
+ // are excluded so their shadow pseudo-element (painted outside the box via a
559
+ // translate) isn't clipped; the select cell holds only a checkbox.
560
+ //
561
+ // Auto row height is the deliberate exception: there the virtualizer measures
562
+ // each row, so cells are free to wrap. Scoping the rule out is what keeps the
563
+ // two modes from fighting over specificity.
564
+ &:not(.data-grid-auto-row-height) .data-grid-viewport > .table > tbody > tr:not(.data-grid-empty) > td:not(.data-grid-select-cell):not(.data-grid-cell-pinned-start):not(.data-grid-cell-pinned-end) {
565
+ overflow: hidden;
566
+ text-overflow: ellipsis;
567
+ white-space: nowrap;
568
+ }
569
+
570
+ // Widths applied to each header cell drive the column layout; letting the
571
+ // table grow to its content (min-width keeps it filling a wide viewport)
572
+ // turns a resize or a pin into horizontal scroll rather than squeezing
573
+ // neighbours. Both features share this layout mode.
574
+ &.data-grid-resizable,
575
+ &.data-grid-pinned {
576
+ .data-grid-viewport {
577
+ overflow-x: auto;
578
+ }
579
+
580
+ .table {
581
+ width: max-content;
582
+ min-width: 100%;
583
+ }
584
+ }
585
+
586
+ // A pinned th is already sticky (a positioned containing block for the
587
+ // resizer), so only non-pinned header cells need explicit relative here.
588
+ &.data-grid-resizable .data-grid-header th:not(.data-grid-cell-pinned-start):not(.data-grid-cell-pinned-end) {
589
+ position: relative;
590
+ }
591
+
592
+ &.data-grid-pinned .table {
593
+ // position: sticky is unreliable on collapsed-border tables; the grid opts
594
+ // into separate borders so pinned cells stay put on horizontal scroll.
595
+ border-collapse: separate;
596
+ border-spacing: 0;
597
+ }
598
+
599
+ .data-grid-cell-pinned-start,
600
+ .data-grid-cell-pinned-end {
601
+ position: sticky;
602
+ z-index: 1;
603
+ background-color: var(--#{$prefix}data-grid-pinned-bg);
604
+ }
605
+
606
+ .data-grid-header .data-grid-cell-pinned-start,
607
+ .data-grid-header .data-grid-cell-pinned-end {
608
+ z-index: 3;
609
+ }
610
+
611
+ // Edge cells cast a shadow over the scrolling content. A pseudo-element is
612
+ // used because the cell's own box-shadow carries the table striping accent.
613
+ .data-grid-cell-pinned-start-last,
614
+ .data-grid-cell-pinned-end-first {
615
+ position: sticky;
616
+
617
+ &::after {
618
+ position: absolute;
619
+ top: 0;
620
+ bottom: 0;
621
+ width: var(--#{$prefix}data-grid-pinned-border-width);
622
+ pointer-events: none;
623
+ content: "";
624
+ }
625
+ }
626
+
627
+ .data-grid-cell-pinned-start-last::after {
628
+ inset-inline-end: 0;
629
+ background: var(--#{$prefix}data-grid-pinned-border-color);
630
+ }
631
+
632
+ .data-grid-cell-pinned-end-first::after {
633
+ inset-inline-start: 0;
634
+ background: var(--#{$prefix}data-grid-pinned-border-color);
635
+ }
636
+
637
+ .data-grid-resizer {
638
+ position: absolute;
639
+ top: 0;
640
+ inset-inline-end: calc(-.5 * var(--#{$prefix}data-grid-resizer-width));
641
+ z-index: 1;
642
+ display: flex;
643
+ align-items: center;
644
+ justify-content: center;
645
+ width: var(--#{$prefix}data-grid-resizer-width);
646
+ height: 100%;
647
+ cursor: col-resize;
648
+ touch-action: none;
649
+ user-select: none;
650
+
651
+ // Always-visible grip so the resizable edge is discoverable, not just a
652
+ // bare hover target.
653
+ &::after {
654
+ width: 2px;
655
+ height: var(--#{$prefix}data-grid-resizer-grip-height);
656
+ content: "";
657
+ background-color: var(--#{$prefix}data-grid-resizer-grip-color);
658
+ transition: background-color .15s ease;
659
+ }
660
+
661
+ &:hover::after,
662
+ &.data-grid-resizer-active::after {
663
+ background-color: var(--#{$prefix}data-grid-resizer-grip-color-hover);
664
+ }
665
+ }
666
+
667
+ .data-grid-spacer > td {
668
+ padding: 0;
669
+ border: 0;
670
+ }
671
+
672
+ // Spacer rows break `nth-of-type` striping, so striping inside the grid is
673
+ // driven by the absolute row index (.data-grid-row-odd) instead.
674
+ .data-grid-viewport > .table-striped > tbody > tr:nth-of-type(odd) > * {
675
+ --#{$prefix}table-color-type: initial;
676
+ --#{$prefix}table-bg-type: initial;
677
+ }
678
+
679
+ .data-grid-viewport > .table-striped > tbody > tr.data-grid-row-odd > * {
680
+ --#{$prefix}table-color-type: var(--#{$prefix}table-striped-color);
681
+ --#{$prefix}table-bg-type: var(--#{$prefix}table-striped-bg);
682
+ }
683
+
684
+ // The last row closes on the viewport edge (or the pagination border), so it
685
+ // doesn't need its own separator line.
686
+ .data-grid-viewport > .table > tbody > tr:last-child > * {
687
+ border-bottom-width: 0;
688
+ }
689
+
690
+ .data-grid-loading {
691
+ position: absolute;
692
+ inset: 0;
693
+ z-index: 3;
694
+ display: flex;
695
+ align-items: center;
696
+ justify-content: center;
697
+ background-color: var(--#{$prefix}data-grid-loading-bg);
698
+ }
699
+
700
+ // Infinite scroll's "next page arriving" strip. Sticky, not an overlay: the
701
+ // rows already loaded stay readable while the grid appends to them.
702
+ .data-grid-loading-more {
703
+ position: sticky;
704
+ inset-inline: 0;
705
+ bottom: 0;
706
+ z-index: 3;
707
+ display: flex;
708
+ justify-content: center;
709
+ padding: var(--#{$prefix}data-grid-spacing);
710
+ background-color: var(--#{$prefix}data-grid-loading-bg);
711
+ }
712
+
713
+ // Shared icon-button tooltip. position: fixed (set in JS) so it escapes the
714
+ // viewport overflow; theme-inverted like CoreUI's own tooltip.
715
+ .data-grid-tooltip {
716
+ position: fixed;
717
+ z-index: $data-grid-tooltip-z-index;
718
+ max-width: 12rem;
719
+ padding: $data-grid-gap-sm .5rem;
720
+ font-size: .875rem;
721
+ line-height: 1.4;
722
+ color: var(--#{$prefix}body-bg, #fff);
723
+ text-align: center;
724
+ pointer-events: none;
725
+ background-color: var(--#{$prefix}emphasis-color, #000);
726
+ border-radius: $data-grid-border-radius;
727
+ }
728
+
729
+ .data-grid-empty > td {
730
+ padding: 2rem 1rem;
731
+ color: $data-grid-muted-color;
732
+ text-align: center;
733
+ }
734
+
735
+ .data-grid-pagination {
736
+ display: flex;
737
+ // Wrap the page controls, count and page-size select onto multiple rows on
738
+ // narrow viewports; without this the fixed row overflows and scrolls the page
739
+ // instead of staying inside the grid.
740
+ flex-wrap: wrap;
741
+ gap: var(--#{$prefix}data-grid-spacing);
742
+ align-items: center;
743
+ min-height: $data-grid-bar-min-height;
744
+ padding: var(--#{$prefix}data-grid-spacing);
745
+ border-top: $data-grid-border-width solid $data-grid-border-color;
746
+
747
+ &.data-grid-pagination-top {
748
+ border-top: 0;
749
+ border-bottom: $data-grid-border-width solid $data-grid-border-color;
750
+ }
751
+
752
+ .pagination {
753
+ margin-bottom: 0;
754
+ }
755
+
756
+ .data-grid-pagination-info {
757
+ margin-inline-start: auto;
758
+ color: $data-grid-muted-color;
759
+ }
760
+
761
+ .form-select {
762
+ width: auto;
763
+ }
764
+ }
765
+ }
766
+
767
+ // Print: the grid renders a plain, complete table into the document (see
768
+ // util/print.ts) and hides everything else, so a virtualized grid prints all
769
+ // its rows instead of the twenty in the window.
770
+ @media print {
771
+ .data-grid-print-root {
772
+ position: absolute;
773
+ inset: 0 0 auto;
774
+ z-index: 2000;
775
+ background-color: #fff;
776
+
777
+ h1 {
778
+ margin-bottom: 1rem;
779
+ font-size: 1.25rem;
780
+ }
781
+
782
+ table {
783
+ width: 100%;
784
+ border-collapse: collapse;
785
+ }
786
+
787
+ :is(th, td) {
788
+ padding: $data-grid-gap-sm .5rem;
789
+ text-align: start;
790
+ border: 1px solid #000;
791
+ }
792
+
793
+ thead {
794
+ display: table-header-group;
795
+ }
796
+
797
+ tr {
798
+ break-inside: avoid;
799
+ }
800
+ }
801
+
802
+ // :has() is inside the browser floor (.browserslistrc), and scoping to the
803
+ // body's own children keeps the clone's ancestors from being hidden.
804
+ body:has(.data-grid-print-root) > :not(.data-grid-print-root) {
805
+ display: none !important;
806
+ }
807
+ }