@coreui/data-grid 0.1.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 (101) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +37 -0
  3. package/dist/css/data-grid.css +444 -0
  4. package/dist/css/data-grid.css.map +1 -0
  5. package/dist/css/data-grid.min.css +5 -0
  6. package/dist/css/data-grid.min.css.map +1 -0
  7. package/dist/js/csv.cjs +68 -0
  8. package/dist/js/csv.cjs.map +1 -0
  9. package/dist/js/csv.mjs +63 -0
  10. package/dist/js/csv.mjs.map +1 -0
  11. package/dist/js/data-grid.esm.js +9618 -0
  12. package/dist/js/data-grid.esm.js.map +1 -0
  13. package/dist/js/data-grid.esm.min.js +8 -0
  14. package/dist/js/data-grid.esm.min.js.map +1 -0
  15. package/dist/js/data-grid.js +9635 -0
  16. package/dist/js/data-grid.js.map +1 -0
  17. package/dist/js/data-grid.min.js +8 -0
  18. package/dist/js/data-grid.min.js.map +1 -0
  19. package/dist/js/data-grid.mjs +9618 -0
  20. package/dist/js/data-grid.mjs.map +1 -0
  21. package/dist/js/filters.cjs +143 -0
  22. package/dist/js/filters.cjs.map +1 -0
  23. package/dist/js/filters.mjs +137 -0
  24. package/dist/js/filters.mjs.map +1 -0
  25. package/dist/js/labels.cjs +76 -0
  26. package/dist/js/labels.cjs.map +1 -0
  27. package/dist/js/labels.mjs +71 -0
  28. package/dist/js/labels.mjs.map +1 -0
  29. package/dist/types/base-component.d.ts +19 -0
  30. package/dist/types/data-grid.d.ts +128 -0
  31. package/dist/types/dom/event-handler.d.ts +11 -0
  32. package/dist/types/dom/manipulator.d.ts +9 -0
  33. package/dist/types/engine/active-cell.d.ts +32 -0
  34. package/dist/types/engine/data-source.d.ts +19 -0
  35. package/dist/types/engine/scheduler.d.ts +11 -0
  36. package/dist/types/engine/table.d.ts +9 -0
  37. package/dist/types/engine/virtualizer.d.ts +21 -0
  38. package/dist/types/filters.d.mts +1 -0
  39. package/dist/types/filters.d.ts +13 -0
  40. package/dist/types/icons.d.ts +21 -0
  41. package/dist/types/index.d.mts +1 -0
  42. package/dist/types/index.d.ts +12 -0
  43. package/dist/types/labels.d.mts +1 -0
  44. package/dist/types/labels.d.ts +9 -0
  45. package/dist/types/types.d.ts +262 -0
  46. package/dist/types/ui/body.d.ts +34 -0
  47. package/dist/types/ui/column-chooser.d.ts +27 -0
  48. package/dist/types/ui/editor.d.ts +8 -0
  49. package/dist/types/ui/filter-menu.d.ts +21 -0
  50. package/dist/types/ui/header.d.ts +50 -0
  51. package/dist/types/ui/loading.d.ts +12 -0
  52. package/dist/types/ui/menu.d.ts +17 -0
  53. package/dist/types/ui/pagination.d.ts +17 -0
  54. package/dist/types/ui/pinning.d.ts +17 -0
  55. package/dist/types/ui/toolbar.d.ts +34 -0
  56. package/dist/types/ui/tooltip.d.ts +10 -0
  57. package/dist/types/util/config.d.ts +11 -0
  58. package/dist/types/util/csv.d.mts +1 -0
  59. package/dist/types/util/csv.d.ts +19 -0
  60. package/dist/types/util/debounce.d.ts +7 -0
  61. package/dist/types/util/index.d.ts +10 -0
  62. package/dist/types/util/sanitizer.d.ts +11 -0
  63. package/package.json +147 -0
  64. package/scss/data-grid.scss +559 -0
  65. package/src/base-component.d.ts +19 -0
  66. package/src/base-component.js +90 -0
  67. package/src/data-grid.ts +2208 -0
  68. package/src/dom/data.js +59 -0
  69. package/src/dom/event-handler.d.ts +11 -0
  70. package/src/dom/event-handler.js +331 -0
  71. package/src/dom/manipulator.d.ts +9 -0
  72. package/src/dom/manipulator.js +75 -0
  73. package/src/engine/active-cell.ts +195 -0
  74. package/src/engine/data-source.ts +63 -0
  75. package/src/engine/scheduler.ts +42 -0
  76. package/src/engine/table.ts +122 -0
  77. package/src/engine/virtualizer.ts +57 -0
  78. package/src/filters.ts +176 -0
  79. package/src/icons.ts +60 -0
  80. package/src/index.ts +56 -0
  81. package/src/labels.ts +70 -0
  82. package/src/types.ts +334 -0
  83. package/src/ui/body.ts +348 -0
  84. package/src/ui/column-chooser.ts +183 -0
  85. package/src/ui/editor.ts +98 -0
  86. package/src/ui/filter-menu.ts +398 -0
  87. package/src/ui/header.ts +441 -0
  88. package/src/ui/loading.ts +39 -0
  89. package/src/ui/menu.ts +218 -0
  90. package/src/ui/pagination.ts +150 -0
  91. package/src/ui/pinning.ts +53 -0
  92. package/src/ui/toolbar.ts +144 -0
  93. package/src/ui/tooltip.ts +113 -0
  94. package/src/util/config.d.ts +11 -0
  95. package/src/util/config.js +69 -0
  96. package/src/util/csv.ts +85 -0
  97. package/src/util/debounce.ts +18 -0
  98. package/src/util/index.d.ts +10 -0
  99. package/src/util/index.js +310 -0
  100. package/src/util/sanitizer.d.ts +11 -0
  101. package/src/util/sanitizer.js +162 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid ui/pagination.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ import { interpolate } from '../labels'
9
+ import type { DataGridLabels, DataGridPaginationInfoState } from '../types'
10
+
11
+ const CLASS_NAME_PAGINATION = 'data-grid-pagination'
12
+ const CLASS_NAME_PAGINATION_INFO = 'data-grid-pagination-info'
13
+
14
+ const PAGE_WINDOW = 5
15
+
16
+ export interface GridPaginationContext {
17
+ info: boolean | ((state: DataGridPaginationInfoState) => string)
18
+ labels: DataGridLabels
19
+ pageSizeOptions: number[]
20
+ }
21
+
22
+ export interface GridPagination {
23
+ element: HTMLDivElement
24
+ update: (state: DataGridPaginationInfoState) => void
25
+ }
26
+
27
+ const createPageItem = (
28
+ label: string,
29
+ page: string,
30
+ options: { active?: boolean; disabled?: boolean; ariaLabel?: string } = {}
31
+ ): HTMLLIElement => {
32
+ const item = document.createElement('li')
33
+ item.classList.add('page-item')
34
+ if (options.active) {
35
+ item.classList.add('active')
36
+ item.setAttribute('aria-current', 'page')
37
+ }
38
+
39
+ if (options.disabled) {
40
+ item.classList.add('disabled')
41
+ }
42
+
43
+ const button = document.createElement('button')
44
+ button.type = 'button'
45
+ button.classList.add('page-link')
46
+ button.textContent = label
47
+ button.disabled = Boolean(options.disabled)
48
+ button.dataset.coreuiPage = page
49
+ if (options.ariaLabel) {
50
+ button.setAttribute('aria-label', options.ariaLabel)
51
+ }
52
+
53
+ item.append(button)
54
+
55
+ return item
56
+ }
57
+
58
+ export const createPagination = (context: GridPaginationContext): GridPagination => {
59
+ const element = document.createElement('div')
60
+ element.classList.add(CLASS_NAME_PAGINATION)
61
+
62
+ const nav = document.createElement('nav')
63
+ nav.setAttribute('aria-label', context.labels.paginationLabel)
64
+ const list = document.createElement('ul')
65
+ list.classList.add('pagination', 'pagination-sm')
66
+ nav.append(list)
67
+
68
+ const info = context.info === false ? null : document.createElement('span')
69
+ if (info) {
70
+ info.classList.add(CLASS_NAME_PAGINATION_INFO)
71
+ }
72
+
73
+ const select = document.createElement('select')
74
+ select.classList.add('form-select', 'form-select-sm')
75
+ select.setAttribute('aria-label', context.labels.pageSizeLabel)
76
+ select.dataset.coreuiPageSize = ''
77
+ for (const size of context.pageSizeOptions) {
78
+ const option = document.createElement('option')
79
+ option.value = String(size)
80
+ option.textContent = String(size)
81
+ select.append(option)
82
+ }
83
+
84
+ element.append(nav, ...(info ? [info] : []), select)
85
+
86
+ return {
87
+ element,
88
+ update(state) {
89
+ const lastPageIndex = Math.max(state.pageCount - 1, 0)
90
+ const windowStart = Math.max(
91
+ Math.min(state.pageIndex - Math.floor(PAGE_WINDOW / 2), state.pageCount - PAGE_WINDOW),
92
+ 0
93
+ )
94
+ // No pages (server mode before the first load, rejected provider) - no
95
+ // page buttons, only the disabled first/prev/next/last chrome.
96
+ const windowEnd = state.pageCount === 0 ? -1 : Math.min(windowStart + PAGE_WINDOW - 1, lastPageIndex)
97
+
98
+ const items = [
99
+ createPageItem('«', 'first', {
100
+ ariaLabel: context.labels.firstPage,
101
+ disabled: state.pageIndex === 0
102
+ }),
103
+ createPageItem('‹', 'prev', {
104
+ ariaLabel: context.labels.previousPage,
105
+ disabled: state.pageIndex === 0
106
+ })
107
+ ]
108
+ for (let page = windowStart; page <= windowEnd; page++) {
109
+ items.push(createPageItem(String(page + 1), String(page), { active: page === state.pageIndex }))
110
+ }
111
+
112
+ items.push(
113
+ createPageItem('›', 'next', {
114
+ ariaLabel: context.labels.nextPage,
115
+ disabled: state.pageIndex >= lastPageIndex
116
+ }),
117
+ createPageItem('»', 'last', {
118
+ ariaLabel: context.labels.lastPage,
119
+ disabled: state.pageIndex >= lastPageIndex
120
+ })
121
+ )
122
+ list.replaceChildren(...items)
123
+
124
+ if (info) {
125
+ if (typeof context.info === 'function') {
126
+ info.textContent = context.info(state)
127
+ } else {
128
+ const first = state.totalRows === 0 ? 0 : (state.pageIndex * state.pageSize) + 1
129
+ const last = Math.min((state.pageIndex + 1) * state.pageSize, state.totalRows)
130
+ info.textContent = interpolate(context.labels.itemsInfo, {
131
+ first,
132
+ last,
133
+ total: state.totalRows
134
+ })
135
+ }
136
+ }
137
+
138
+ // A configured pageSize outside pageSizeOptions would leave the select
139
+ // blank; surface it as an extra option in numeric position.
140
+ if (![...select.options].some(option => option.value === String(state.pageSize))) {
141
+ const option = document.createElement('option')
142
+ option.value = String(state.pageSize)
143
+ option.textContent = String(state.pageSize)
144
+ select.insertBefore(option, [...select.options].find(item => Number(item.value) > state.pageSize) ?? null)
145
+ }
146
+
147
+ select.value = String(state.pageSize)
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid ui/pinning.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ const CLASS_LEFT = 'data-grid-cell-pinned-left'
9
+ const CLASS_RIGHT = 'data-grid-cell-pinned-right'
10
+ const CLASS_LEFT_LAST = 'data-grid-cell-pinned-left-last'
11
+ const CLASS_RIGHT_FIRST = 'data-grid-cell-pinned-right-first'
12
+
13
+ export interface PinnedCellInfo {
14
+ side: 'left' | 'right'
15
+ offset: number
16
+ edge: boolean
17
+ // A left offset that has to clear the (sticky) selection cell; expressed as a
18
+ // CSS calc so the select-cell width stays a single source of truth in CSS.
19
+ afterSelect: boolean
20
+ }
21
+
22
+ export interface GridPinning {
23
+ columns: Record<string, PinnedCellInfo>
24
+ select: PinnedCellInfo | null
25
+ }
26
+
27
+ export const applyPinnedCell = (cell: HTMLTableCellElement, info: PinnedCellInfo | null): void => {
28
+ cell.classList.remove(CLASS_LEFT, CLASS_RIGHT, CLASS_LEFT_LAST, CLASS_RIGHT_FIRST)
29
+ cell.style.left = ''
30
+ cell.style.right = ''
31
+
32
+ if (!info) {
33
+ return
34
+ }
35
+
36
+ if (info.side === 'left') {
37
+ cell.classList.add(CLASS_LEFT)
38
+ if (info.edge) {
39
+ cell.classList.add(CLASS_LEFT_LAST)
40
+ }
41
+
42
+ cell.style.left = info.afterSelect ?
43
+ `calc(var(--cui-data-grid-select-cell-width) + ${info.offset}px)` :
44
+ `${info.offset}px`
45
+ } else {
46
+ cell.classList.add(CLASS_RIGHT)
47
+ if (info.edge) {
48
+ cell.classList.add(CLASS_RIGHT_FIRST)
49
+ }
50
+
51
+ cell.style.right = `${info.offset}px`
52
+ }
53
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid ui/toolbar.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ import type { CsvDownloadOptions } from '../util/csv'
9
+ import type { DataGridLabels, DataGridOptions } from '../types'
10
+
11
+ const CLASS_NAME_TOOLBAR = 'data-grid-toolbar'
12
+ const CLASS_NAME_ACTIONS = 'data-grid-toolbar-actions'
13
+ const CLASS_NAME_SEARCH = 'data-grid-toolbar-search'
14
+
15
+ export interface GridToolbarContext {
16
+ labels: DataGridLabels
17
+ columns: boolean
18
+ export: boolean
19
+ // Undo/redo of edit commits - both buttons start disabled, the grid
20
+ // toggles them as the history stacks fill.
21
+ history: boolean
22
+ search: boolean
23
+ icons: {
24
+ columns: string
25
+ export: string
26
+ redo: string
27
+ undo: string
28
+ }
29
+ }
30
+
31
+ export interface GridToolbar {
32
+ element: HTMLDivElement
33
+ }
34
+
35
+ export interface ResolvedToolbar {
36
+ columns: boolean
37
+ export: boolean
38
+ exportOptions: CsvDownloadOptions
39
+ history: boolean
40
+ search: boolean
41
+ }
42
+
43
+ // Normalizes `toolbar` + `globalFilter` into the individual actions. `toolbar:
44
+ // true` enables each action whose underlying feature is on; a granular object
45
+ // picks individually. `globalFilter: true` stays shorthand for the search input.
46
+ export const resolveToolbar = (options: DataGridOptions): ResolvedToolbar => {
47
+ const { toolbar } = options
48
+ const columnVisibility = Boolean(options.columnVisibility)
49
+
50
+ if (toolbar === true) {
51
+ return {
52
+ columns: columnVisibility,
53
+ export: true,
54
+ exportOptions: {},
55
+ history: options.history,
56
+ search: true
57
+ }
58
+ }
59
+
60
+ if (typeof toolbar === 'object') {
61
+ const exportOption = toolbar.export ?? false
62
+ return {
63
+ columns: (toolbar.columns ?? false) && columnVisibility,
64
+ export: Boolean(exportOption),
65
+ exportOptions: typeof exportOption === 'object' ? exportOption : {},
66
+ history: (toolbar.history ?? false) && options.history,
67
+ search: (toolbar.search ?? false) || Boolean(options.globalFilter)
68
+ }
69
+ }
70
+
71
+ return {
72
+ columns: false,
73
+ export: false,
74
+ exportOptions: {},
75
+ history: false,
76
+ search: Boolean(options.globalFilter)
77
+ }
78
+ }
79
+
80
+ export const isGlobalFilterEnabled = (options: DataGridOptions): boolean =>
81
+ resolveToolbar(options).search
82
+
83
+ const createActionButton = (icon: string, label: string, dataAttribute: string): HTMLButtonElement => {
84
+ const button = document.createElement('button')
85
+ button.type = 'button'
86
+ button.classList.add('btn', 'btn-ghost', 'btn-sm')
87
+ button.innerHTML = icon
88
+ button.setAttribute(dataAttribute, '')
89
+ button.setAttribute('data-coreui-tooltip', label)
90
+ const text = document.createElement('span')
91
+ text.classList.add('visually-hidden')
92
+ text.textContent = label
93
+ button.append(text)
94
+ return button
95
+ }
96
+
97
+ export const createToolbar = (context: GridToolbarContext): GridToolbar => {
98
+ const element = document.createElement('div')
99
+ element.classList.add(CLASS_NAME_TOOLBAR)
100
+
101
+ if (context.columns || context.export || context.history) {
102
+ const actions = document.createElement('div')
103
+ actions.classList.add(CLASS_NAME_ACTIONS)
104
+
105
+ if (context.columns) {
106
+ actions.append(
107
+ createActionButton(context.icons.columns, context.labels.toolbarColumns, 'data-coreui-toolbar-columns')
108
+ )
109
+ }
110
+
111
+ if (context.export) {
112
+ actions.append(
113
+ createActionButton(context.icons.export, context.labels.toolbarExport, 'data-coreui-toolbar-export')
114
+ )
115
+ }
116
+
117
+ if (context.history) {
118
+ const undoButton = createActionButton(context.icons.undo, context.labels.toolbarUndo, 'data-coreui-toolbar-undo')
119
+ const redoButton = createActionButton(context.icons.redo, context.labels.toolbarRedo, 'data-coreui-toolbar-redo')
120
+ undoButton.disabled = true
121
+ redoButton.disabled = true
122
+ actions.append(undoButton, redoButton)
123
+ }
124
+
125
+ element.append(actions)
126
+ }
127
+
128
+ if (context.search) {
129
+ const search = document.createElement('div')
130
+ search.classList.add(CLASS_NAME_SEARCH)
131
+
132
+ const input = document.createElement('input')
133
+ input.type = 'search'
134
+ input.classList.add('form-control', 'form-control-sm')
135
+ input.placeholder = context.labels.globalFilterPlaceholder
136
+ input.setAttribute('aria-label', context.labels.globalFilterLabel)
137
+ input.dataset.coreuiGlobalFilter = ''
138
+
139
+ search.append(input)
140
+ element.append(search)
141
+ }
142
+
143
+ return { element }
144
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid ui/tooltip.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ const CLASS_NAME_TOOLTIP = 'data-grid-tooltip'
9
+ const ATTRIBUTE = 'data-coreui-tooltip'
10
+ const SELECTOR = `[${ATTRIBUTE}]`
11
+ const SHOW_DELAY = 200
12
+ const GAP = 6
13
+ const EDGE = 4
14
+
15
+ export interface GridTooltip {
16
+ dispose: () => void
17
+ }
18
+
19
+ // A single shared, delegated tooltip for grid-owned icon controls. Positioned
20
+ // with position: fixed so it escapes the viewport's overflow, above the anchor
21
+ // with a flip below when there isn't room. Purely visual (aria-hidden) - the
22
+ // anchors already carry their accessible name via aria-label / hidden text.
23
+ export const createTooltip = (root: HTMLElement): GridTooltip => {
24
+ const element = document.createElement('div')
25
+ element.className = CLASS_NAME_TOOLTIP
26
+ element.setAttribute('role', 'tooltip')
27
+ element.setAttribute('aria-hidden', 'true')
28
+ element.hidden = true
29
+ root.append(element)
30
+
31
+ let anchor: HTMLElement | null = null
32
+ let showTimer: ReturnType<typeof setTimeout> | undefined
33
+
34
+ const anchorFrom = (target: EventTarget | null): HTMLElement | null => {
35
+ const element_ = target instanceof Element ? target.closest(SELECTOR) : null
36
+ return element_ instanceof HTMLElement && element_.getAttribute(ATTRIBUTE) ? element_ : null
37
+ }
38
+
39
+ const position = (): void => {
40
+ if (!anchor) {
41
+ return
42
+ }
43
+
44
+ const rect = anchor.getBoundingClientRect()
45
+ const width = element.offsetWidth
46
+ const height = element.offsetHeight
47
+ const above = rect.top - height - GAP
48
+ const top = above >= 0 ? above : rect.bottom + GAP
49
+ const left = Math.max(EDGE, Math.min(rect.left + ((rect.width - width) / 2), window.innerWidth - width - EDGE))
50
+ element.style.top = `${top}px`
51
+ element.style.left = `${left}px`
52
+ element.style.visibility = 'visible'
53
+ }
54
+
55
+ const show = (target: HTMLElement): void => {
56
+ anchor = target
57
+ element.textContent = target.getAttribute(ATTRIBUTE)
58
+ element.style.visibility = 'hidden'
59
+ element.hidden = false
60
+ position()
61
+ }
62
+
63
+ const hide = (): void => {
64
+ clearTimeout(showTimer)
65
+ anchor = null
66
+ element.hidden = true
67
+ }
68
+
69
+ const onPointerOver = (event: PointerEvent): void => {
70
+ const target = anchorFrom(event.target)
71
+ if (!target || target === anchor) {
72
+ return
73
+ }
74
+
75
+ clearTimeout(showTimer)
76
+ showTimer = setTimeout(() => show(target), SHOW_DELAY)
77
+ }
78
+
79
+ const onPointerOut = (event: PointerEvent): void => {
80
+ if (anchor && event.relatedTarget instanceof Node && anchor.contains(event.relatedTarget)) {
81
+ return
82
+ }
83
+
84
+ hide()
85
+ }
86
+
87
+ const onFocusIn = (event: FocusEvent): void => {
88
+ const target = anchorFrom(event.target)
89
+ if (target) {
90
+ show(target)
91
+ }
92
+ }
93
+
94
+ root.addEventListener('pointerover', onPointerOver as EventListener)
95
+ root.addEventListener('pointerout', onPointerOut as EventListener)
96
+ root.addEventListener('pointerdown', hide)
97
+ root.addEventListener('focusin', onFocusIn as EventListener)
98
+ root.addEventListener('focusout', hide)
99
+ window.addEventListener('scroll', hide, true)
100
+
101
+ return {
102
+ dispose() {
103
+ clearTimeout(showTimer)
104
+ root.removeEventListener('pointerover', onPointerOver as EventListener)
105
+ root.removeEventListener('pointerout', onPointerOut as EventListener)
106
+ root.removeEventListener('pointerdown', hide)
107
+ root.removeEventListener('focusin', onFocusIn as EventListener)
108
+ root.removeEventListener('focusout', hide)
109
+ window.removeEventListener('scroll', hide, true)
110
+ element.remove()
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,11 @@
1
+ // Hand-written types for the vendored config.js (see the provenance header there)
2
+ export default class Config {
3
+ static get Default(): unknown
4
+ static get DefaultType(): unknown
5
+ static get NAME(): string
6
+
7
+ _getConfig(config?: unknown): any
8
+ _configAfterMerge(config: any): any
9
+ _mergeConfigObj(config?: unknown, element?: Element): any
10
+ _typeCheckConfig(config: Record<string, unknown>, configTypes?: Record<string, string>): void
11
+ }
@@ -0,0 +1,69 @@
1
+ // Vendored from https://github.com/coreui/coreui-pro/blob/bc4daf1cd79d527bf665d615c0a31f53ab0c3d4e/js/src/util/config.js — sync manually
2
+ /**
3
+ * --------------------------------------------------------------------------
4
+ * CoreUI util/config.js
5
+ * Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE)
6
+ *
7
+ * This is a modified version of the Bootstrap's util/config.js
8
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
9
+ * --------------------------------------------------------------------------
10
+ */
11
+
12
+ import Manipulator from '../dom/manipulator.js'
13
+ import { isElement, toType } from './index.js'
14
+
15
+ /**
16
+ * Class definition
17
+ */
18
+
19
+ class Config {
20
+ // Getters
21
+ static get Default() {
22
+ return {}
23
+ }
24
+
25
+ static get DefaultType() {
26
+ return {}
27
+ }
28
+
29
+ static get NAME() {
30
+ throw new Error('You have to implement the static method "NAME", for each component!')
31
+ }
32
+
33
+ _getConfig(config) {
34
+ config = this._mergeConfigObj(config)
35
+ config = this._configAfterMerge(config)
36
+ this._typeCheckConfig(config)
37
+ return config
38
+ }
39
+
40
+ _configAfterMerge(config) {
41
+ return config
42
+ }
43
+
44
+ _mergeConfigObj(config, element) {
45
+ const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse
46
+
47
+ return {
48
+ ...this.constructor.Default,
49
+ ...(typeof jsonConfig === 'object' ? jsonConfig : {}),
50
+ ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),
51
+ ...(typeof config === 'object' ? config : {})
52
+ }
53
+ }
54
+
55
+ _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {
56
+ for (const [property, expectedTypes] of Object.entries(configTypes)) {
57
+ const value = config[property]
58
+ const valueType = isElement(value) ? 'element' : toType(value)
59
+
60
+ if (!new RegExp(expectedTypes).test(valueType)) {
61
+ throw new TypeError(
62
+ `${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`
63
+ )
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ export default Config
@@ -0,0 +1,85 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid util/csv.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ import type { Table } from '@tanstack/table-core'
9
+ import type { DataGridItem } from '../types'
10
+
11
+ export interface CsvOptions {
12
+ scope?: 'filtered' | 'all' | 'selected'
13
+ delimiter?: string
14
+ bom?: boolean
15
+ sanitize?: boolean
16
+ }
17
+
18
+ export interface CsvDownloadOptions extends CsvOptions {
19
+ filename?: string
20
+ }
21
+
22
+ interface CsvColumnMeta {
23
+ label?: string
24
+ formatter?: (value: unknown, item: DataGridItem) => string
25
+ }
26
+
27
+ const escapeField = (value: string, delimiter: string): string =>
28
+ /["\r\n]/.test(value) || value.includes(delimiter) ? `"${value.replace(/"/g, '""')}"` : value
29
+
30
+ // CSV formula injection: a leading =, +, -, @, tab or CR makes spreadsheets treat
31
+ // the cell as a formula. Opt-in guard prefixes such fields with an apostrophe.
32
+ const sanitizeField = (value: string): string =>
33
+ /^[=+\-@\t\r]/.test(value) ? `'${value}` : value
34
+
35
+ const rowsFor = (table: Table<DataGridItem>, scope: CsvOptions['scope']) => {
36
+ if (scope === 'all') {
37
+ return table.getCoreRowModel().rows
38
+ }
39
+
40
+ if (scope === 'selected') {
41
+ return table.getSelectedRowModel().rows
42
+ }
43
+
44
+ return table.getSortedRowModel().rows
45
+ }
46
+
47
+ export const exportCsv = (table: Table<DataGridItem>, options: CsvOptions = {}): string => {
48
+ const delimiter = options.delimiter ?? ','
49
+ // Export follows the rendered layout: pinning, column order and visibility.
50
+ const columns = [
51
+ ...table.getLeftVisibleLeafColumns(),
52
+ ...table.getCenterVisibleLeafColumns(),
53
+ ...table.getRightVisibleLeafColumns()
54
+ ]
55
+
56
+ const process = (field: string) => escapeField(options.sanitize ? sanitizeField(field) : field, delimiter)
57
+ const line = (fields: string[]) => fields.map(process).join(delimiter)
58
+
59
+ const header = line(columns.map(column => String((column.columnDef.meta as CsvColumnMeta)?.label ?? column.id)))
60
+ const body = rowsFor(table, options.scope).map(row =>
61
+ line(columns.map(column => {
62
+ const value = row.getValue(column.id)
63
+ const meta = column.columnDef.meta as CsvColumnMeta
64
+ // Coerce: a formatter may return a number/null in plain-JS usage.
65
+ return meta?.formatter ? String(meta.formatter(value, row.original) ?? '') : String(value ?? '')
66
+ }))
67
+ )
68
+
69
+ const csv = [header, ...body].join('\r\n')
70
+ return options.bom ? `\uFEFF${csv}` : csv
71
+ }
72
+
73
+ export const downloadCsv = (table: Table<DataGridItem>, options: CsvDownloadOptions = {}): void => {
74
+ const blob = new Blob([exportCsv(table, options)], { type: 'text/csv;charset=utf-8;' })
75
+ const url = URL.createObjectURL(blob)
76
+ const anchor = document.createElement('a')
77
+ anchor.href = url
78
+ anchor.download = options.filename ?? 'export.csv'
79
+ anchor.style.display = 'none'
80
+ // Attach before clicking (older Firefox) and defer revoke (Safari race).
81
+ document.body.append(anchor)
82
+ anchor.click()
83
+ anchor.remove()
84
+ setTimeout(() => URL.revokeObjectURL(url))
85
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * --------------------------------------------------------------------------
3
+ * CoreUI PRO data-grid util/debounce.ts
4
+ * License (https://coreui.io/pro/license/)
5
+ * --------------------------------------------------------------------------
6
+ */
7
+
8
+ export const debounce = <Args extends unknown[]>(
9
+ callback: (...args: Args) => void,
10
+ delay: number
11
+ ): ((...args: Args) => void) => {
12
+ let timeout: ReturnType<typeof setTimeout> | undefined
13
+
14
+ return (...args: Args) => {
15
+ clearTimeout(timeout)
16
+ timeout = setTimeout(() => callback(...args), delay)
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ // Hand-written types for the vendored util/index.js (see the provenance header there)
2
+ export function defineJQueryPlugin(plugin: { NAME: string; jQueryInterface: (this: any, ...args: any[]) => unknown }): void
3
+ export function executeAfterTransition(callback: () => void, transitionElement: Element, waitForTransition?: boolean): void
4
+ export function getElement(object: unknown): HTMLElement | null
5
+ export function getjQuery(): unknown
6
+ export function getUID(prefix: string): string
7
+ export function isElement(object: unknown): boolean
8
+ export function isRTL(): boolean
9
+ export function isVisible(element: Element): boolean
10
+ export function toType(object: unknown): string