@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,310 @@
1
+ // Vendored from https://github.com/coreui/coreui-pro/blob/bc4daf1cd79d527bf665d615c0a31f53ab0c3d4e/js/src/util/index.js — sync manually
2
+ /**
3
+ * --------------------------------------------------------------------------
4
+ * CoreUI util/index.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/index.js
8
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
9
+ * --------------------------------------------------------------------------
10
+ */
11
+
12
+ const MAX_UID = 1_000_000
13
+ const MILLISECONDS_MULTIPLIER = 1000
14
+ const TRANSITION_END = 'transitionend'
15
+
16
+ /**
17
+ * Properly escape IDs selectors to handle weird IDs
18
+ * @param {string} selector
19
+ * @returns {string}
20
+ */
21
+ const parseSelector = selector => {
22
+ if (selector && window.CSS && window.CSS.escape) {
23
+ // document.querySelector needs escaping to handle IDs (html5+) containing for instance /
24
+ selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
25
+ }
26
+
27
+ return selector
28
+ }
29
+
30
+ // Shout-out Angus Croll (https://goo.gl/pxwQGp)
31
+ const toType = object => {
32
+ if (object === null || object === undefined) {
33
+ return `${object}`
34
+ }
35
+
36
+ return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase()
37
+ }
38
+
39
+ /**
40
+ * Public Util API
41
+ */
42
+
43
+ const getUID = prefix => {
44
+ do {
45
+ prefix += Math.floor(Math.random() * MAX_UID)
46
+ } while (document.getElementById(prefix))
47
+
48
+ return prefix
49
+ }
50
+
51
+ const getTransitionDurationFromElement = element => {
52
+ if (!element) {
53
+ return 0
54
+ }
55
+
56
+ // Get transition-duration of the element
57
+ let { transitionDuration, transitionDelay } = window.getComputedStyle(element)
58
+
59
+ const floatTransitionDuration = Number.parseFloat(transitionDuration)
60
+ const floatTransitionDelay = Number.parseFloat(transitionDelay)
61
+
62
+ // Return 0 if element or transition duration is not found
63
+ if (!floatTransitionDuration && !floatTransitionDelay) {
64
+ return 0
65
+ }
66
+
67
+ // If multiple durations are defined, take the first
68
+ transitionDuration = transitionDuration.split(',')[0]
69
+ transitionDelay = transitionDelay.split(',')[0]
70
+
71
+ return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER
72
+ }
73
+
74
+ const triggerTransitionEnd = element => {
75
+ element.dispatchEvent(new Event(TRANSITION_END))
76
+ }
77
+
78
+ const isElement = object => {
79
+ if (!object || typeof object !== 'object') {
80
+ return false
81
+ }
82
+
83
+ if (typeof object.jquery !== 'undefined') {
84
+ object = object[0]
85
+ }
86
+
87
+ return typeof object.nodeType !== 'undefined'
88
+ }
89
+
90
+ const getElement = object => {
91
+ // it's a jQuery object or a node element
92
+ if (isElement(object)) {
93
+ return object.jquery ? object[0] : object
94
+ }
95
+
96
+ if (typeof object === 'string' && object.length > 0) {
97
+ return document.querySelector(parseSelector(object))
98
+ }
99
+
100
+ return null
101
+ }
102
+
103
+ const isVisible = element => {
104
+ if (!isElement(element) || element.getClientRects().length === 0) {
105
+ return false
106
+ }
107
+
108
+ const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible'
109
+ // Handle `details` element as its content may falsie appear visible when it is closed
110
+ const closedDetails = element.closest('details:not([open])')
111
+
112
+ if (!closedDetails) {
113
+ return elementIsVisible
114
+ }
115
+
116
+ if (closedDetails !== element) {
117
+ const summary = element.closest('summary')
118
+ if (summary && summary.parentNode !== closedDetails) {
119
+ return false
120
+ }
121
+
122
+ if (summary === null) {
123
+ return false
124
+ }
125
+ }
126
+
127
+ return elementIsVisible
128
+ }
129
+
130
+ const isDisabled = element => {
131
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) {
132
+ return true
133
+ }
134
+
135
+ if (element.classList.contains('disabled')) {
136
+ return true
137
+ }
138
+
139
+ if (typeof element.disabled !== 'undefined') {
140
+ return element.disabled
141
+ }
142
+
143
+ return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'
144
+ }
145
+
146
+ const findShadowRoot = element => {
147
+ if (!document.documentElement.attachShadow) {
148
+ return null
149
+ }
150
+
151
+ // Can find the shadow root otherwise it'll return the document
152
+ if (typeof element.getRootNode === 'function') {
153
+ const root = element.getRootNode()
154
+ return root instanceof ShadowRoot ? root : null
155
+ }
156
+
157
+ if (element instanceof ShadowRoot) {
158
+ return element
159
+ }
160
+
161
+ // when we don't find a shadow root
162
+ if (!element.parentNode) {
163
+ return null
164
+ }
165
+
166
+ return findShadowRoot(element.parentNode)
167
+ }
168
+
169
+ const noop = () => {}
170
+
171
+ /**
172
+ * Trick to restart an element's animation
173
+ *
174
+ * @param {HTMLElement} element
175
+ * @return void
176
+ *
177
+ * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
178
+ */
179
+ const reflow = element => {
180
+ element.offsetHeight // eslint-disable-line no-unused-expressions
181
+ }
182
+
183
+ const getjQuery = () => {
184
+ if (window.jQuery && !document.body.hasAttribute('data-coreui-no-jquery')) {
185
+ return window.jQuery
186
+ }
187
+
188
+ return null
189
+ }
190
+
191
+ const DOMContentLoadedCallbacks = []
192
+
193
+ const onDOMContentLoaded = callback => {
194
+ if (document.readyState === 'loading') {
195
+ // add listener on the first call when the document is in loading state
196
+ if (!DOMContentLoadedCallbacks.length) {
197
+ document.addEventListener('DOMContentLoaded', () => {
198
+ for (const callback of DOMContentLoadedCallbacks) {
199
+ callback()
200
+ }
201
+ })
202
+ }
203
+
204
+ DOMContentLoadedCallbacks.push(callback)
205
+ } else {
206
+ callback()
207
+ }
208
+ }
209
+
210
+ const isRTL = () => document.documentElement.dir === 'rtl'
211
+
212
+ const defineJQueryPlugin = plugin => {
213
+ onDOMContentLoaded(() => {
214
+ const $ = getjQuery()
215
+ /* istanbul ignore if */
216
+ if ($) {
217
+ const name = plugin.NAME
218
+ const JQUERY_NO_CONFLICT = $.fn[name]
219
+ $.fn[name] = plugin.jQueryInterface
220
+ $.fn[name].Constructor = plugin
221
+ $.fn[name].noConflict = () => {
222
+ $.fn[name] = JQUERY_NO_CONFLICT
223
+ return plugin.jQueryInterface
224
+ }
225
+ }
226
+ })
227
+ }
228
+
229
+ const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {
230
+ return typeof possibleCallback === 'function' ? possibleCallback.call(...args) : defaultValue
231
+ }
232
+
233
+ const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
234
+ if (!waitForTransition) {
235
+ execute(callback)
236
+ return
237
+ }
238
+
239
+ const durationPadding = 5
240
+ const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding
241
+
242
+ let called = false
243
+
244
+ const handler = ({ target }) => {
245
+ if (target !== transitionElement) {
246
+ return
247
+ }
248
+
249
+ called = true
250
+ transitionElement.removeEventListener(TRANSITION_END, handler)
251
+ execute(callback)
252
+ }
253
+
254
+ transitionElement.addEventListener(TRANSITION_END, handler)
255
+ setTimeout(() => {
256
+ if (!called) {
257
+ triggerTransitionEnd(transitionElement)
258
+ }
259
+ }, emulatedDuration)
260
+ }
261
+
262
+ /**
263
+ * Return the previous/next element of a list.
264
+ *
265
+ * @param {array} list The list of elements
266
+ * @param activeElement The active element
267
+ * @param shouldGetNext Choose to get next or previous element
268
+ * @param isCycleAllowed
269
+ * @return {Element|elem} The proper element
270
+ */
271
+ const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {
272
+ const listLength = list.length
273
+ let index = list.indexOf(activeElement)
274
+
275
+ // if the element does not exist in the list return an element
276
+ // depending on the direction and if cycle is allowed
277
+ if (index === -1) {
278
+ return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0]
279
+ }
280
+
281
+ index += shouldGetNext ? 1 : -1
282
+
283
+ if (isCycleAllowed) {
284
+ index = (index + listLength) % listLength
285
+ }
286
+
287
+ return list[Math.max(0, Math.min(index, listLength - 1))]
288
+ }
289
+
290
+ export {
291
+ defineJQueryPlugin,
292
+ execute,
293
+ executeAfterTransition,
294
+ findShadowRoot,
295
+ getElement,
296
+ getjQuery,
297
+ getNextActiveElement,
298
+ getTransitionDurationFromElement,
299
+ getUID,
300
+ isDisabled,
301
+ isElement,
302
+ isRTL,
303
+ isVisible,
304
+ noop,
305
+ onDOMContentLoaded,
306
+ parseSelector,
307
+ reflow,
308
+ triggerTransitionEnd,
309
+ toType
310
+ }
@@ -0,0 +1,11 @@
1
+ // Hand-written types for the vendored sanitizer.js (see the provenance header there)
2
+ export type Allowlist = Record<string, Array<string | RegExp>>
3
+
4
+ export declare const DefaultAllowlist: Allowlist
5
+ export declare const SVGAllowlist: Allowlist
6
+ export declare function escapeHtml(unsafeText: string): string
7
+ export declare function sanitizeHtml(
8
+ unsafeHtml: string,
9
+ allowList: Allowlist,
10
+ sanitizeFunction?: ((html: string) => string) | null
11
+ ): string
@@ -0,0 +1,162 @@
1
+ // Vendored from https://github.com/coreui/coreui-pro/blob/bc4daf1cd79d527bf665d615c0a31f53ab0c3d4e/js/src/util/sanitizer.js — sync manually
2
+ /**
3
+ * --------------------------------------------------------------------------
4
+ * CoreUI util/sanitizer.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/sanitizer.js
8
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
9
+ * --------------------------------------------------------------------------
10
+ */
11
+
12
+ // js-docs-start allow-list
13
+ const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i
14
+
15
+ export const DefaultAllowlist = {
16
+ // Global attributes allowed on any supplied element below.
17
+ '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
18
+ a: ['target', 'href', 'title', 'rel'],
19
+ area: [],
20
+ b: [],
21
+ br: [],
22
+ col: [],
23
+ code: [],
24
+ dd: [],
25
+ div: [],
26
+ dl: [],
27
+ dt: [],
28
+ em: [],
29
+ hr: [],
30
+ h1: [],
31
+ h2: [],
32
+ h3: [],
33
+ h4: [],
34
+ h5: [],
35
+ h6: [],
36
+ i: [],
37
+ img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
38
+ li: [],
39
+ ol: [],
40
+ p: [],
41
+ pre: [],
42
+ s: [],
43
+ small: [],
44
+ span: [],
45
+ sub: [],
46
+ sup: [],
47
+ strong: [],
48
+ u: [],
49
+ ul: []
50
+ }
51
+ // js-docs-end allow-list
52
+
53
+ // js-docs-start svg-allow-list
54
+ export const SVGAllowlist = {
55
+ ...DefaultAllowlist,
56
+ svg: ['xmlns', 'version', 'baseprofile', 'width', 'height', 'viewbox', 'preserveaspectratio', 'aria-hidden', 'role', 'focusable', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'],
57
+ g: ['id', 'class', 'transform', 'style'],
58
+ path: ['id', 'class', 'd', 'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity'],
59
+ circle: ['id', 'class', 'cx', 'cy', 'r', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
60
+ rect: ['id', 'class', 'x', 'y', 'width', 'height', 'rx', 'ry', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
61
+ ellipse: ['id', 'class', 'cx', 'cy', 'rx', 'ry', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
62
+ line: ['id', 'class', 'x1', 'y1', 'x2', 'y2', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-opacity'],
63
+ polygon: ['id', 'class', 'points', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
64
+ polyline: ['id', 'class', 'points', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
65
+ text: ['id', 'class', 'x', 'y', 'dx', 'dy', 'text-anchor', 'font-family', 'font-size', 'font-weight', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
66
+ tspan: ['id', 'class', 'x', 'y', 'dx', 'dy', 'text-anchor', 'font-family', 'font-size', 'font-weight', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
67
+ defs: [],
68
+ symbol: ['id', 'class', 'viewbox', 'preserveaspectratio'],
69
+ use: ['id', 'class', 'x', 'y', 'width', 'height', 'href'],
70
+ image: ['id', 'class', 'x', 'y', 'width', 'height', 'href', 'preserveaspectratio', 'xlink:href'],
71
+ pattern: ['id', 'class', 'x', 'y', 'width', 'height', 'patternunits', 'patterncontentunits', 'patterntransform', 'preserveaspectratio'],
72
+ lineargradient: ['id', 'class', 'gradientunits', 'x1', 'y1', 'x2', 'y2', 'spreadmethod', 'gradienttransform'],
73
+ radialgradient: ['id', 'class', 'gradientunits', 'cx', 'cy', 'r', 'fx', 'fy', 'spreadmethod', 'gradienttransform'],
74
+ mask: ['id', 'class', 'x', 'y', 'width', 'height', 'maskunits', 'maskcontentunits', 'masktransform'],
75
+ clippath: ['id', 'class', 'clippathunits'],
76
+ marker: ['id', 'class', 'markerunits', 'markerwidth', 'markerheight', 'orient', 'preserveaspectratio', 'viewbox', 'refx', 'refy'],
77
+ title: [],
78
+ desc: []
79
+ }
80
+ // js-docs-end svg-allow-list
81
+
82
+ const ESCAPE_HTML_MAP = {
83
+ '&': '&amp;',
84
+ '<': '&lt;',
85
+ '>': '&gt;',
86
+ '"': '&quot;',
87
+ "'": '&#x27;'
88
+ }
89
+
90
+ export function escapeHtml(unsafeText) {
91
+ return String(unsafeText).replace(/[&<>"']/g, character => ESCAPE_HTML_MAP[character])
92
+ }
93
+
94
+ const uriAttributes = new Set([
95
+ 'background',
96
+ 'cite',
97
+ 'href',
98
+ 'itemtype',
99
+ 'longdesc',
100
+ 'poster',
101
+ 'src',
102
+ 'xlink:href'
103
+ ])
104
+
105
+ /**
106
+ * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
107
+ * contexts.
108
+ *
109
+ * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
110
+ */
111
+ const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
112
+
113
+ const allowedAttribute = (attribute, allowedAttributeList) => {
114
+ const attributeName = attribute.nodeName.toLowerCase()
115
+
116
+ if (allowedAttributeList.includes(attributeName)) {
117
+ if (uriAttributes.has(attributeName)) {
118
+ return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
119
+ }
120
+
121
+ return true
122
+ }
123
+
124
+ // Check if a regular expression validates the attribute.
125
+ return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp)
126
+ .some(regex => regex.test(attributeName))
127
+ }
128
+
129
+ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
130
+ if (!unsafeHtml.length) {
131
+ return unsafeHtml
132
+ }
133
+
134
+ if (sanitizeFunction && typeof sanitizeFunction === 'function') {
135
+ return sanitizeFunction(unsafeHtml)
136
+ }
137
+
138
+ const domParser = new window.DOMParser()
139
+ const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html')
140
+ const elements = [].concat(...createdDocument.body.querySelectorAll('*'))
141
+
142
+ for (const element of elements) {
143
+ const elementName = element.nodeName.toLowerCase()
144
+
145
+ if (!Object.keys(allowList).includes(elementName)) {
146
+ element.remove()
147
+
148
+ continue
149
+ }
150
+
151
+ const attributeList = [].concat(...element.attributes)
152
+ const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || [])
153
+
154
+ for (const attribute of attributeList) {
155
+ if (!allowedAttribute(attribute, allowedAttributes)) {
156
+ element.removeAttribute(attribute.nodeName)
157
+ }
158
+ }
159
+ }
160
+
161
+ return createdDocument.body.innerHTML
162
+ }