@coreui/data-grid 0.1.0-rc.0 → 0.2.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 (137) hide show
  1. package/README.md +5 -4
  2. package/dist/css/data-grid.css +149 -31
  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/js/clipboard.cjs +56 -0
  7. package/dist/js/clipboard.cjs.map +1 -0
  8. package/dist/js/clipboard.mjs +54 -0
  9. package/dist/js/clipboard.mjs.map +1 -0
  10. package/dist/js/csv.cjs +36 -59
  11. package/dist/js/csv.cjs.map +1 -1
  12. package/dist/js/csv.mjs +35 -55
  13. package/dist/js/csv.mjs.map +1 -1
  14. package/dist/js/data-grid.esm.js +14078 -9332
  15. package/dist/js/data-grid.esm.js.map +1 -1
  16. package/dist/js/data-grid.esm.min.js +6 -6
  17. package/dist/js/data-grid.esm.min.js.map +1 -1
  18. package/dist/js/data-grid.js +14377 -9634
  19. package/dist/js/data-grid.js.map +1 -1
  20. package/dist/js/data-grid.lite.esm.js +10476 -0
  21. package/dist/js/data-grid.lite.esm.js.map +1 -0
  22. package/dist/js/data-grid.lite.esm.min.js +8 -0
  23. package/dist/js/data-grid.lite.esm.min.js.map +1 -0
  24. package/dist/js/data-grid.lite.js +10489 -0
  25. package/dist/js/data-grid.lite.js.map +1 -0
  26. package/dist/js/data-grid.lite.min.js +8 -0
  27. package/dist/js/data-grid.lite.min.js.map +1 -0
  28. package/dist/js/data-grid.lite.mjs +10476 -0
  29. package/dist/js/data-grid.lite.mjs.map +1 -0
  30. package/dist/js/data-grid.min.js +6 -6
  31. package/dist/js/data-grid.min.js.map +1 -1
  32. package/dist/js/data-grid.mjs +14078 -9332
  33. package/dist/js/data-grid.mjs.map +1 -1
  34. package/dist/js/drag.cjs +459 -0
  35. package/dist/js/drag.cjs.map +1 -0
  36. package/dist/js/drag.mjs +456 -0
  37. package/dist/js/drag.mjs.map +1 -0
  38. package/dist/js/export-table-CNAKStzj.mjs +26 -0
  39. package/dist/js/export-table-CNAKStzj.mjs.map +1 -0
  40. package/dist/js/export-table-doIwvE5U.cjs +55 -0
  41. package/dist/js/export-table-doIwvE5U.cjs.map +1 -0
  42. package/dist/js/filters.cjs +89 -132
  43. package/dist/js/filters.cjs.map +1 -1
  44. package/dist/js/filters.mjs +88 -128
  45. package/dist/js/filters.mjs.map +1 -1
  46. package/dist/js/labels.cjs +65 -70
  47. package/dist/js/labels.cjs.map +1 -1
  48. package/dist/js/labels.mjs +64 -66
  49. package/dist/js/labels.mjs.map +1 -1
  50. package/dist/js/print.cjs +66 -0
  51. package/dist/js/print.cjs.map +1 -0
  52. package/dist/js/print.mjs +64 -0
  53. package/dist/js/print.mjs.map +1 -0
  54. package/dist/js/validate.cjs +43 -0
  55. package/dist/js/validate.cjs.map +1 -0
  56. package/dist/js/validate.mjs +42 -0
  57. package/dist/js/validate.mjs.map +1 -0
  58. package/dist/js/xlsx.cjs +240 -0
  59. package/dist/js/xlsx.cjs.map +1 -0
  60. package/dist/js/xlsx.mjs +238 -0
  61. package/dist/js/xlsx.mjs.map +1 -0
  62. package/dist/types/base-component.d.ts +28 -18
  63. package/dist/types/data-grid.d.ts +26 -13
  64. package/dist/types/dom/data.d.ts +15 -0
  65. package/dist/types/dom/event-handler.d.ts +21 -10
  66. package/dist/types/dom/manipulator.d.ts +15 -8
  67. package/dist/types/engine/data-source.d.ts +1 -1
  68. package/dist/types/engine/features-lite.d.ts +25 -0
  69. package/dist/types/engine/features.d.ts +35 -0
  70. package/dist/types/engine/table.d.ts +3 -3
  71. package/dist/types/engine/validate.d.mts +1 -0
  72. package/dist/types/engine/validate.d.ts +20 -0
  73. package/dist/types/engine/virtualizer.d.ts +2 -0
  74. package/dist/types/icons.d.ts +6 -4
  75. package/dist/types/index-lite.d.mts +1 -0
  76. package/dist/types/index-lite.d.ts +19 -0
  77. package/dist/types/index.d.ts +11 -2
  78. package/dist/types/types.d.ts +53 -31
  79. package/dist/types/ui/body.d.ts +6 -3
  80. package/dist/types/ui/header.d.ts +3 -2
  81. package/dist/types/ui/loading.d.ts +1 -1
  82. package/dist/types/ui/pinning.d.ts +1 -1
  83. package/dist/types/ui/toolbar.d.ts +10 -2
  84. package/dist/types/util/clipboard.d.mts +1 -0
  85. package/dist/types/util/clipboard.d.ts +29 -0
  86. package/dist/types/util/config.d.ts +27 -10
  87. package/dist/types/util/csv.d.ts +5 -5
  88. package/dist/types/util/drag.d.mts +1 -0
  89. package/dist/types/util/drag.d.ts +48 -0
  90. package/dist/types/util/export-table.d.ts +47 -0
  91. package/dist/types/util/index.d.ts +66 -10
  92. package/dist/types/util/print.d.mts +1 -0
  93. package/dist/types/util/print.d.ts +32 -0
  94. package/dist/types/util/sanitizer.d.ts +15 -11
  95. package/dist/types/util/xlsx.d.mts +1 -0
  96. package/dist/types/util/xlsx.d.ts +17 -0
  97. package/dist/types/util/zip.d.ts +11 -0
  98. package/package.json +88 -10
  99. package/scss/data-grid.scss +204 -16
  100. package/src/{base-component.js → base-component.ts} +21 -17
  101. package/src/data-grid.ts +507 -200
  102. package/src/dom/{data.js → data.ts} +13 -10
  103. package/src/dom/event-handler.ts +380 -0
  104. package/src/dom/{manipulator.js → manipulator.ts} +11 -11
  105. package/src/engine/data-source.ts +7 -3
  106. package/src/engine/features-lite.ts +47 -0
  107. package/src/engine/features.ts +68 -0
  108. package/src/engine/table.ts +80 -47
  109. package/src/engine/validate.ts +64 -0
  110. package/src/engine/virtualizer.ts +17 -1
  111. package/src/icons.ts +11 -5
  112. package/src/index-lite.ts +85 -0
  113. package/src/index.ts +30 -2
  114. package/src/labels.ts +6 -4
  115. package/src/types.ts +87 -32
  116. package/src/ui/body.ts +73 -17
  117. package/src/ui/header.ts +51 -26
  118. package/src/ui/loading.ts +2 -2
  119. package/src/ui/pinning.ts +16 -16
  120. package/src/ui/toolbar.ts +27 -3
  121. package/src/util/clipboard.ts +112 -0
  122. package/src/util/{config.js → config.ts} +25 -11
  123. package/src/util/csv.ts +11 -30
  124. package/src/util/drag.ts +798 -0
  125. package/src/util/export-table.ts +86 -0
  126. package/src/util/{index.js → index.ts} +55 -35
  127. package/src/util/print.ts +125 -0
  128. package/src/util/{sanitizer.js → sanitizer.ts} +17 -13
  129. package/src/util/xlsx.ts +183 -0
  130. package/src/util/zip.ts +170 -0
  131. package/src/base-component.d.ts +0 -19
  132. package/src/dom/event-handler.d.ts +0 -11
  133. package/src/dom/event-handler.js +0 -331
  134. package/src/dom/manipulator.d.ts +0 -9
  135. package/src/util/config.d.ts +0 -11
  136. package/src/util/index.d.ts +0 -10
  137. package/src/util/sanitizer.d.ts +0 -11
@@ -0,0 +1,86 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid util/export-table.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ import type { DataGridItem } from '../types'
9
+
10
+ // Typed structurally, not as one edition's table: TFeatures is invariant in v9,
11
+ // so the vanilla grid and each framework binding (whose adapter registers its
12
+ // own reactivity feature) have mutually incompatible Table types even though
13
+ // they expose the same row/column surface an export needs. Everything beyond
14
+ // the core row model is optional - a reduced feature set exports whatever
15
+ // layout/scope its features can express and falls back to core for the rest.
16
+
17
+ export interface ExportColumnMeta {
18
+ label?: string
19
+ formatter?: (value: unknown, item: DataGridItem) => string
20
+ width?: number
21
+ }
22
+
23
+ export interface ExportColumn {
24
+ id: string
25
+ columnDef: { meta?: unknown }
26
+ }
27
+
28
+ export interface ExportRow {
29
+ original: DataGridItem
30
+ getValue: (columnId: string) => unknown
31
+ }
32
+
33
+ export interface ExportTable {
34
+ getAllLeafColumns: () => ExportColumn[]
35
+ getCenterVisibleLeafColumns?: () => ExportColumn[]
36
+ getCoreRowModel: () => { rows: ExportRow[] }
37
+ getEndVisibleLeafColumns?: () => ExportColumn[]
38
+ getFilteredRowModel?: () => { rows: ExportRow[] }
39
+ getSelectedRowModel?: () => { rows: ExportRow[] }
40
+ getSortedRowModel?: () => { rows: ExportRow[] }
41
+ getStartVisibleLeafColumns?: () => ExportColumn[]
42
+ getVisibleLeafColumns?: () => ExportColumn[]
43
+ }
44
+
45
+ export type ExportScope = 'filtered' | 'all' | 'selected'
46
+
47
+ export const rowsFor = (table: ExportTable, scope: ExportScope | undefined): ExportRow[] => {
48
+ if (scope === 'all') {
49
+ return table.getCoreRowModel().rows
50
+ }
51
+
52
+ if (scope === 'selected') {
53
+ return table.getSelectedRowModel?.().rows ?? []
54
+ }
55
+
56
+ return (table.getSortedRowModel ?? table.getFilteredRowModel ?? table.getCoreRowModel)().rows
57
+ }
58
+
59
+ // Exports follow the rendered layout: pinning, column order and visibility -
60
+ // as far as the registered features track them.
61
+ export const columnsFor = (table: ExportTable): ExportColumn[] => {
62
+ if (table.getCenterVisibleLeafColumns) {
63
+ return [
64
+ ...(table.getStartVisibleLeafColumns?.() ?? []),
65
+ ...table.getCenterVisibleLeafColumns(),
66
+ ...(table.getEndVisibleLeafColumns?.() ?? [])
67
+ ]
68
+ }
69
+
70
+ return table.getVisibleLeafColumns?.() ?? table.getAllLeafColumns()
71
+ }
72
+
73
+ export const headerFor = (column: ExportColumn): string =>
74
+ String((column.columnDef.meta as ExportColumnMeta)?.label ?? column.id)
75
+
76
+ // A column formatter is author intent and matches what the grid renders, so
77
+ // exports honor it - which also means a formatted number leaves as text.
78
+ export const formatterFor = (column: ExportColumn): ExportColumnMeta['formatter'] =>
79
+ (column.columnDef.meta as ExportColumnMeta)?.formatter
80
+
81
+ // The author's declared column width in px. Deliberately NOT column.getSize():
82
+ // that returns table-core's 150 px default whenever the grid does not track
83
+ // sizing, which would force an arbitrary width on every exported column. A
84
+ // consequence is that an interactive resize is not carried into the export.
85
+ export const widthFor = (column: ExportColumn): number | undefined =>
86
+ (column.columnDef.meta as ExportColumnMeta)?.width
@@ -1,14 +1,30 @@
1
- // Vendored from https://github.com/coreui/coreui-pro/blob/bc4daf1cd79d527bf665d615c0a31f53ab0c3d4e/js/src/util/index.js — sync manually
1
+ // Vendored from https://github.com/coreui/coreui-pro/blob/e9355580e/js/src/util/index.ts — sync manually
2
2
  /**
3
3
  * --------------------------------------------------------------------------
4
- * CoreUI util/index.js
4
+ * CoreUI util/index.ts
5
5
  * Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
6
6
  *
7
- * This is a modified version of the Bootstrap's util/index.js
7
+ * This is a modified version of the Bootstrap's util/index.ts
8
8
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
9
9
  * --------------------------------------------------------------------------
10
10
  */
11
11
 
12
+ /**
13
+ * Types
14
+ */
15
+
16
+ // The jQuery bridge is untyped on purpose: jQuery is optional at runtime and we
17
+ // only ever touch `.jquery`, index 0, and the plugin registration surface.
18
+ type JQueryLike = { jquery?: unknown, [index: number]: HTMLElement }
19
+ type JQueryStatic = ((selector: unknown) => any) & Record<string, any> & { fn: Record<string, any> }
20
+ type JQueryPlugin = { NAME: string, jQueryInterface: (...args: any[]) => unknown }
21
+
22
+ declare global {
23
+ interface Window {
24
+ jQuery?: JQueryStatic
25
+ }
26
+ }
27
+
12
28
  const MAX_UID = 1_000_000
13
29
  const MILLISECONDS_MULTIPLIER = 1000
14
30
  const TRANSITION_END = 'transitionend'
@@ -18,29 +34,31 @@ const TRANSITION_END = 'transitionend'
18
34
  * @param {string} selector
19
35
  * @returns {string}
20
36
  */
21
- const parseSelector = selector => {
22
- if (selector && window.CSS && window.CSS.escape) {
37
+ const parseSelector = (selector: string): string => {
38
+ // The `window.CSS` checks guard against ancient browsers, so check them as
39
+ // untyped values instead of letting tsc call them always-defined
40
+ if (selector && (window as any).CSS && (window as any).CSS.escape) {
23
41
  // document.querySelector needs escaping to handle IDs (html5+) containing for instance /
24
- selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
42
+ selector = selector.replace(/#([^\s"#']+)/g, (match, id: string) => `#${CSS.escape(id)}`)
25
43
  }
26
44
 
27
45
  return selector
28
46
  }
29
47
 
30
48
  // Shout-out Angus Croll (https://goo.gl/pxwQGp)
31
- const toType = object => {
49
+ const toType = (object: unknown): string => {
32
50
  if (object === null || object === undefined) {
33
51
  return `${object}`
34
52
  }
35
53
 
36
- return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase()
54
+ return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)![1].toLowerCase()
37
55
  }
38
56
 
39
57
  /**
40
58
  * Public Util API
41
59
  */
42
60
 
43
- const getUID = prefix => {
61
+ const getUID = (prefix: string): string => {
44
62
  do {
45
63
  prefix += Math.floor(Math.random() * MAX_UID)
46
64
  } while (document.getElementById(prefix))
@@ -48,7 +66,7 @@ const getUID = prefix => {
48
66
  return prefix
49
67
  }
50
68
 
51
- const getTransitionDurationFromElement = element => {
69
+ const getTransitionDurationFromElement = (element: Element | null): number => {
52
70
  if (!element) {
53
71
  return 0
54
72
  }
@@ -71,26 +89,26 @@ const getTransitionDurationFromElement = element => {
71
89
  return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
72
90
  }
73
91
 
74
- const triggerTransitionEnd = element => {
92
+ const triggerTransitionEnd = (element: Element): void => {
75
93
  element.dispatchEvent(new Event(TRANSITION_END))
76
94
  }
77
95
 
78
- const isElement = object => {
96
+ const isElement = (object: unknown): object is Element => {
79
97
  if (!object || typeof object !== 'object') {
80
98
  return false
81
99
  }
82
100
 
83
- if (typeof object.jquery !== 'undefined') {
84
- object = object[0]
101
+ if (typeof (object as JQueryLike).jquery !== 'undefined') {
102
+ object = (object as JQueryLike)[0]
85
103
  }
86
104
 
87
- return typeof object.nodeType !== 'undefined'
105
+ return typeof (object as Element).nodeType !== 'undefined'
88
106
  }
89
107
 
90
- const getElement = object => {
108
+ const getElement = (object: unknown): HTMLElement | null => {
91
109
  // it's a jQuery object or a node element
92
110
  if (isElement(object)) {
93
- return object.jquery ? object[0] : object
111
+ return (object as unknown as JQueryLike).jquery ? (object as unknown as JQueryLike)[0] : object as HTMLElement
94
112
  }
95
113
 
96
114
  if (typeof object === 'string' && object.length > 0) {
@@ -100,7 +118,7 @@ const getElement = object => {
100
118
  return null
101
119
  }
102
120
 
103
- const isVisible = element => {
121
+ const isVisible = (element: unknown): boolean => {
104
122
  if (!isElement(element) || element.getClientRects().length === 0) {
105
123
  return false
106
124
  }
@@ -127,7 +145,7 @@ const isVisible = element => {
127
145
  return elementIsVisible
128
146
  }
129
147
 
130
- const isDisabled = element => {
148
+ const isDisabled = (element: Element | null | undefined): boolean => {
131
149
  if (!element || element.nodeType !== Node.ELEMENT_NODE) {
132
150
  return true
133
151
  }
@@ -136,14 +154,14 @@ const isDisabled = element => {
136
154
  return true
137
155
  }
138
156
 
139
- if (typeof element.disabled !== 'undefined') {
140
- return element.disabled
157
+ if (typeof (element as HTMLInputElement).disabled !== 'undefined') {
158
+ return (element as HTMLInputElement).disabled
141
159
  }
142
160
 
143
161
  return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'
144
162
  }
145
163
 
146
- const findShadowRoot = element => {
164
+ const findShadowRoot = (element: Node): ShadowRoot | null => {
147
165
  if (!document.documentElement.attachShadow) {
148
166
  return null
149
167
  }
@@ -166,7 +184,7 @@ const findShadowRoot = element => {
166
184
  return findShadowRoot(element.parentNode)
167
185
  }
168
186
 
169
- const noop = () => {}
187
+ const noop = (): void => {}
170
188
 
171
189
  /**
172
190
  * Trick to restart an element's animation
@@ -176,11 +194,11 @@ const noop = () => {}
176
194
  *
177
195
  * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
178
196
  */
179
- const reflow = element => {
180
- element.offsetHeight // eslint-disable-line no-unused-expressions
197
+ const reflow = (element: HTMLElement): void => {
198
+ element.offsetHeight // eslint-disable-line @typescript-eslint/no-unused-expressions
181
199
  }
182
200
 
183
- const getjQuery = () => {
201
+ const getjQuery = (): JQueryStatic | null => {
184
202
  if (window.jQuery && !document.body.hasAttribute('data-coreui-no-jquery')) {
185
203
  return window.jQuery
186
204
  }
@@ -188,9 +206,9 @@ const getjQuery = () => {
188
206
  return null
189
207
  }
190
208
 
191
- const DOMContentLoadedCallbacks = []
209
+ const DOMContentLoadedCallbacks: Array<() => void> = []
192
210
 
193
- const onDOMContentLoaded = callback => {
211
+ const onDOMContentLoaded = (callback: () => void): void => {
194
212
  if (document.readyState === 'loading') {
195
213
  // add listener on the first call when the document is in loading state
196
214
  if (!DOMContentLoadedCallbacks.length) {
@@ -207,9 +225,9 @@ const onDOMContentLoaded = callback => {
207
225
  }
208
226
  }
209
227
 
210
- const isRTL = () => document.documentElement.dir === 'rtl'
228
+ const isRTL = (): boolean => document.documentElement.dir === 'rtl'
211
229
 
212
- const defineJQueryPlugin = plugin => {
230
+ const defineJQueryPlugin = (plugin: JQueryPlugin): void => {
213
231
  onDOMContentLoaded(() => {
214
232
  const $ = getjQuery()
215
233
  /* istanbul ignore if */
@@ -226,11 +244,11 @@ const defineJQueryPlugin = plugin => {
226
244
  })
227
245
  }
228
246
 
229
- const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {
230
- return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue
247
+ const execute = <T = any>(possibleCallback: T | ((...functionArgs: any[]) => T), args: any[] = [], defaultValue: T | ((...functionArgs: any[]) => T) = possibleCallback): T => {
248
+ return typeof possibleCallback === 'function' ? (possibleCallback as (...functionArgs: any[]) => T).call(...args as [any, ...any[]]) : defaultValue as T
231
249
  }
232
250
 
233
- const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
251
+ const executeAfterTransition = (callback: () => void, transitionElement: Element, waitForTransition = true): void => {
234
252
  if (!waitForTransition) {
235
253
  execute(callback)
236
254
  return
@@ -241,7 +259,7 @@ const executeAfterTransition = (callback, transitionElement, waitForTransition =
241
259
 
242
260
  let called = false
243
261
 
244
- const handler = ({ target }) => {
262
+ const handler = ({ target }: Event): void => {
245
263
  if (target !== transitionElement) {
246
264
  return
247
265
  }
@@ -268,7 +286,9 @@ const executeAfterTransition = (callback, transitionElement, waitForTransition =
268
286
  * @param isCycleAllowed
269
287
  * @return {Element|elem} The proper element
270
288
  */
271
- const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
289
+ // `isCycleAllowed` is optional here where upstream requires it: our callers
290
+ // omit it, and adding the argument to match would change the emitted code.
291
+ const getNextActiveElement = <T>(list: T[], activeElement: T, shouldGetNext: boolean, isCycleAllowed?: boolean): T => {
272
292
  const listLength = list.length
273
293
  let index = list.indexOf(activeElement)
274
294
 
@@ -0,0 +1,125 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid util/print.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ import type { DataGridItem } from '../types'
9
+
10
+ // Printing a virtualized grid straight from the DOM would print the twenty
11
+ // rows that happen to be in the window, and a paginated one would print the
12
+ // current page. Neither is what anyone means by "print this table", so the
13
+ // grid renders a plain, complete table into the document, prints that, and
14
+ // throws it away. The print stylesheet hides everything else on the page.
15
+
16
+ const CLASS_NAME_PRINT_ROOT = 'data-grid-print-root'
17
+
18
+ export interface PrintColumn {
19
+ id: string
20
+ label: string
21
+ formatter?: (value: unknown, item: DataGridItem) => string
22
+ }
23
+
24
+ export interface PrintRow {
25
+ getValue: (columnId: string) => unknown
26
+ original: DataGridItem
27
+ }
28
+
29
+ export interface PrintTable {
30
+ // The rows BEFORE pagination: the whole result of the user's sorting and
31
+ // filtering, which is the table they think they are printing.
32
+ getSortedRowModel?: () => { rows: PrintRow[] }
33
+ getFilteredRowModel?: () => { rows: PrintRow[] }
34
+ getCoreRowModel: () => { rows: PrintRow[] }
35
+ }
36
+
37
+ export interface PrintOptions {
38
+ // Printed above the table. Omitted entirely when not set.
39
+ title?: string
40
+ }
41
+
42
+ // The widest row model the registered features expose — feature sets without
43
+ // sorting or filtering fall back to the core one.
44
+ const printRows = (table: PrintTable): PrintRow[] => {
45
+ if (typeof table.getSortedRowModel === 'function') {
46
+ return table.getSortedRowModel().rows
47
+ }
48
+
49
+ if (typeof table.getFilteredRowModel === 'function') {
50
+ return table.getFilteredRowModel().rows
51
+ }
52
+
53
+ return table.getCoreRowModel().rows
54
+ }
55
+
56
+ export const createPrintTable = (
57
+ table: PrintTable,
58
+ columns: PrintColumn[],
59
+ options: PrintOptions = {}
60
+ ): HTMLElement => {
61
+ const root = document.createElement('div')
62
+ root.className = CLASS_NAME_PRINT_ROOT
63
+
64
+ if (options.title) {
65
+ const heading = document.createElement('h1')
66
+ heading.textContent = options.title
67
+ root.append(heading)
68
+ }
69
+
70
+ const tableElement = document.createElement('table')
71
+ tableElement.classList.add('table')
72
+
73
+ const head = document.createElement('thead')
74
+ const headRow = document.createElement('tr')
75
+ for (const column of columns) {
76
+ const cell = document.createElement('th')
77
+ cell.scope = 'col'
78
+ cell.textContent = column.label
79
+ headRow.append(cell)
80
+ }
81
+
82
+ head.append(headRow)
83
+
84
+ const body = document.createElement('tbody')
85
+ for (const row of printRows(table)) {
86
+ const rowElement = document.createElement('tr')
87
+ for (const column of columns) {
88
+ const cell = document.createElement('td')
89
+ const value = row.getValue(column.id)
90
+ // textContent, never innerHTML: a custom cell renderer's markup is not
91
+ // reachable here, and the printed page must not become an injection
92
+ // surface for the data.
93
+ cell.textContent = column.formatter ? column.formatter(value, row.original) : String(value ?? '')
94
+ rowElement.append(cell)
95
+ }
96
+
97
+ body.append(rowElement)
98
+ }
99
+
100
+ tableElement.append(head, body)
101
+ root.append(tableElement)
102
+
103
+ return root
104
+ }
105
+
106
+ export const printGrid = (table: PrintTable, columns: PrintColumn[], options: PrintOptions = {}): void => {
107
+ const root = createPrintTable(table, columns, options)
108
+ document.body.append(root)
109
+
110
+ // afterprint fires on every browser we support; the finally is the safety
111
+ // net for a print dialog that never opens (a headless or blocked context),
112
+ // where leaving the clone in the document would be worse than a flicker.
113
+ const cleanup = () => {
114
+ root.remove()
115
+ window.removeEventListener('afterprint', cleanup)
116
+ }
117
+
118
+ window.addEventListener('afterprint', cleanup)
119
+
120
+ try {
121
+ window.print()
122
+ } finally {
123
+ cleanup()
124
+ }
125
+ }
@@ -1,18 +1,20 @@
1
- // Vendored from https://github.com/coreui/coreui-pro/blob/bc4daf1cd79d527bf665d615c0a31f53ab0c3d4e/js/src/util/sanitizer.js — sync manually
1
+ // Vendored from https://github.com/coreui/coreui-pro/blob/e9355580e/js/src/util/sanitizer.ts — sync manually
2
2
  /**
3
3
  * --------------------------------------------------------------------------
4
- * CoreUI util/sanitizer.js
4
+ * CoreUI util/sanitizer.ts
5
5
  * Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
6
6
  *
7
- * This is a modified version of the Bootstrap's util/sanitizer.js
7
+ * This is a modified version of the Bootstrap's util/sanitizer.ts
8
8
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
9
9
  * --------------------------------------------------------------------------
10
10
  */
11
11
 
12
+ type SanitizerAllowList = Record<string, Array<string | RegExp>>
13
+
12
14
  // js-docs-start allow-list
13
15
  const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
14
16
 
15
- export const DefaultAllowlist = {
17
+ export const DefaultAllowlist: SanitizerAllowList = {
16
18
  // Global attributes allowed on any supplied element below.
17
19
  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
18
20
  a: ['target', 'href', 'title', 'rel'],
@@ -51,7 +53,7 @@ export const DefaultAllowlist = {
51
53
  // js-docs-end allow-list
52
54
 
53
55
  // js-docs-start svg-allow-list
54
- export const SVGAllowlist = {
56
+ export const SVGAllowlist: SanitizerAllowList = {
55
57
  ...DefaultAllowlist,
56
58
  svg: ['xmlns', 'version', 'baseprofile', 'width', 'height', 'viewbox', 'preserveaspectratio', 'aria-hidden', 'role', 'focusable', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'],
57
59
  g: ['id', 'class', 'transform', 'style'],
@@ -79,7 +81,7 @@ export const SVGAllowlist = {
79
81
  }
80
82
  // js-docs-end svg-allow-list
81
83
 
82
- const ESCAPE_HTML_MAP = {
84
+ const ESCAPE_HTML_MAP: Record<string, string> = {
83
85
  '&': '&amp;',
84
86
  '<': '&lt;',
85
87
  '>': '&gt;',
@@ -87,7 +89,7 @@ const ESCAPE_HTML_MAP = {
87
89
  "'": '&#x27;'
88
90
  }
89
91
 
90
- export function escapeHtml(unsafeText) {
92
+ export function escapeHtml(unsafeText: string): string {
91
93
  return String(unsafeText).replace(/[&<>"']/g, character => ESCAPE_HTML_MAP[character])
92
94
  }
93
95
 
@@ -110,12 +112,12 @@ const uriAttributes = new Set([
110
112
  */
111
113
  const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
112
114
 
113
- const allowedAttribute = (attribute, allowedAttributeList) => {
115
+ const allowedAttribute = (attribute: Attr, allowedAttributeList: Array<string | RegExp>): boolean => {
114
116
  const attributeName = attribute.nodeName.toLowerCase()
115
117
 
116
118
  if (allowedAttributeList.includes(attributeName)) {
117
119
  if (uriAttributes.has(attributeName)) {
118
- return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
120
+ return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue!))
119
121
  }
120
122
 
121
123
  return true
@@ -126,7 +128,7 @@ const allowedAttribute = (attribute, allowedAttributeList) => {
126
128
  .some(regex => regex.test(attributeName))
127
129
  }
128
130
 
129
- export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
131
+ export function sanitizeHtml(unsafeHtml: string, allowList: SanitizerAllowList, sanitizeFunction?: ((unsafeHtml: string) => string) | null): string {
130
132
  if (!unsafeHtml.length) {
131
133
  return unsafeHtml
132
134
  }
@@ -137,7 +139,7 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
137
139
 
138
140
  const domParser = new window.DOMParser()
139
141
  const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
140
- const elements = [].concat(...createdDocument.body.querySelectorAll('*'))
142
+ const elements = ([] as Element[]).concat(...createdDocument.body.querySelectorAll('*'))
141
143
 
142
144
  for (const element of elements) {
143
145
  const elementName = element.nodeName.toLowerCase()
@@ -148,8 +150,8 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
148
150
  continue
149
151
  }
150
152
 
151
- const attributeList = [].concat(...element.attributes)
152
- const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])
153
+ const attributeList = ([] as Attr[]).concat(...element.attributes)
154
+ const allowedAttributes = ([] as Array<string | RegExp>).concat(allowList['*'] || [], allowList[elementName] || [])
153
155
 
154
156
  for (const attribute of attributeList) {
155
157
  if (!allowedAttribute(attribute, allowedAttributes)) {
@@ -160,3 +162,5 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
160
162
 
161
163
  return createdDocument.body.innerHTML
162
164
  }
165
+
166
+ export type { SanitizerAllowList }