@peter.prib/renderengine 0.12.0 → 0.13.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.
- package/README.md +10 -4
- package/dist/renderengine-workspace.umd.js +1920 -1055
- package/dist/renderengine.umd.js +1901 -1052
- package/documentation/REFERENCE.md +66 -5
- package/package.json +1 -1
- package/src/charting/Axis.js +47 -5
- package/src/charting/DiagramEditor.js +239 -34
- package/src/charting/ModelFileFormat.js +47 -0
- package/src/charting/archimate/ArchimateCanvasEditor.js +1108 -1012
- package/src/charting/archimate/deleteFromModel.js +57 -0
- package/src/charting/archimate/elementMenuItems.js +3 -1
- package/src/charting/archimate/heatMaps.js +169 -0
- package/src/charting/archimate/index.js +1 -0
- package/src/charting/charts/BarChart.js +5 -5
- package/src/charting/charts/BarChartBase.js +21 -3
- package/src/charting/charts/GroupedBarChart.js +7 -9
- package/src/charting/charts/TableChart.js +18 -2
- package/src/charting/diagrams/ArchimateModel.js +33 -0
- package/src/charting/diagrams/DeleteFromModelCommand.js +81 -0
- package/src/charting/diagrams/EdgeGeometry.js +16 -0
- package/src/charting/edgeStyle/EdgeStyleSchema.js +6 -2
- package/src/charting/pivot/PivotGridChart.js +1 -1
- package/src/charting/pivot/PivotView.js +11 -2
- package/src/charting/table/TableView.js +10 -1
- package/src/charting/table/TableViewChart.js +1 -1
- package/src/charting/templates/Template.js +10 -0
- package/src/exportImage.js +45 -18
- package/src/index.js +17 -4
- package/src/workspace/parts/ArchimateCanvasPart.js +3 -0
- package/src/workspace/parts/DiagramEditorPart.js +2 -0
- package/src/workspace/parts/ModelHosted.js +15 -4
|
@@ -82,7 +82,7 @@ export class TableViewChart extends TableChart {
|
|
|
82
82
|
columns.forEach((c, i) => {
|
|
83
83
|
const left = x0 + i * colW;
|
|
84
84
|
const hit = this.renderer.rect(left, top, colW, ROW_H, 'transparent', null);
|
|
85
|
-
const text = this._cell(
|
|
85
|
+
const text = this._cell(view.valueText(c.key, row[c.key], nullText), c, left, top, colW, { fill: row[c.key] == null ? '#999' : '#222' });
|
|
86
86
|
this._on(interactive, [hit, text], (e) => this.showMenu(e.clientX, e.clientY, view.cellMenu(row, c.key)), null);
|
|
87
87
|
});
|
|
88
88
|
top += ROW_H;
|
|
@@ -102,6 +102,16 @@ export class Template {
|
|
|
102
102
|
return { x: this.x + ux * t, y: this.y + uy * t };
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Where a line from (px, py), a point inside the shape, heading in
|
|
107
|
+
* direction (dx, dy) leaves its outline (the shape unrotated).
|
|
108
|
+
* @returns {{x: number, y: number}}
|
|
109
|
+
*/
|
|
110
|
+
exitPoint(px, py, dx, dy) {
|
|
111
|
+
const t = rayExitDistance(this.outline(), px, py, dx, dy);
|
|
112
|
+
return { x: px + dx * t, y: py + dy * t };
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
/** Centre of the name text; the z-index line sits just below it. */
|
|
106
116
|
get labelAnchor() { return { x: this.x, y: this.y - 8 }; }
|
|
107
117
|
|
package/src/exportImage.js
CHANGED
|
@@ -31,10 +31,10 @@ export const MAX_IMAGE_PIXELS = 32_000_000;
|
|
|
31
31
|
/**
|
|
32
32
|
* Work out the bitmap size to rasterize at.
|
|
33
33
|
*
|
|
34
|
-
* `scale` is what the caller asked for (2 = retina-sharp
|
|
35
|
-
* the diagram's own dimensions, when the result
|
|
36
|
-
* scale of a huge diagram still shows all of
|
|
37
|
-
* the diagram loses content or legibility.
|
|
34
|
+
* `scale` is what the caller asked for (1 = 100%, 2 = retina-sharp, 0.5 = half
|
|
35
|
+
* size). It is reduced, never the diagram's own dimensions, when the result
|
|
36
|
+
* would be too large: halving the scale of a huge diagram still shows all of
|
|
37
|
+
* it, whereas cropping or shrinking the diagram loses content or legibility.
|
|
38
38
|
*
|
|
39
39
|
* @returns {{width:number, height:number, scale:number, clamped:boolean}}
|
|
40
40
|
*/
|
|
@@ -49,10 +49,6 @@ export function imageSize({ width, height, scale = 2, maxPixels = MAX_IMAGE_PIXE
|
|
|
49
49
|
s = Math.sqrt(maxPixels / (w * h));
|
|
50
50
|
clamped = true;
|
|
51
51
|
}
|
|
52
|
-
// A scale below 1:1 would be a downscale the caller never asked for, but a
|
|
53
|
-
// diagram that is itself over the budget leaves no choice.
|
|
54
|
-
if (s < 1 && w * h <= maxPixels) s = 1;
|
|
55
|
-
|
|
56
52
|
return { width: Math.max(1, Math.round(w * s)), height: Math.max(1, Math.round(h * s)), scale: s, clamped };
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -67,8 +63,10 @@ export function imageSize({ width, height, scale = 2, maxPixels = MAX_IMAGE_PIXE
|
|
|
67
63
|
* @param {number} [options.width] pixels; omitted leaves what is there
|
|
68
64
|
* @param {number} [options.height]
|
|
69
65
|
* @param {string|null} [options.background='#ffffff'] null for transparent
|
|
66
|
+
* @param {string} [options.viewBox] the drawing's own units, when it has none
|
|
67
|
+
* (so a picture sized other than as drawn scales rather than crops)
|
|
70
68
|
*/
|
|
71
|
-
export function prepareSvgMarkup(markup, { width, height, background = '#ffffff' } = {}) {
|
|
69
|
+
export function prepareSvgMarkup(markup, { width, height, background = '#ffffff', viewBox } = {}) {
|
|
72
70
|
let out = String(markup ?? '').trim();
|
|
73
71
|
const open = out.match(/^<svg\b[^>]*>/i);
|
|
74
72
|
if (!open) return out; // not an <svg> — hand it back untouched
|
|
@@ -87,13 +85,23 @@ export function prepareSvgMarkup(markup, { width, height, background = '#ffffff'
|
|
|
87
85
|
};
|
|
88
86
|
if (Number.isFinite(width) && width > 0) setAttr('width', Math.round(width));
|
|
89
87
|
if (Number.isFinite(height) && height > 0) setAttr('height', Math.round(height));
|
|
88
|
+
if (viewBox && !/\sviewBox\s*=/i.test(tag)) setAttr('viewBox', viewBox);
|
|
90
89
|
|
|
91
90
|
out = tag + out.slice(open[0].length);
|
|
92
91
|
|
|
93
92
|
if (background) {
|
|
94
|
-
// First child, so it sits behind everything.
|
|
95
|
-
//
|
|
96
|
-
|
|
93
|
+
// First child, so it sits behind everything. With a viewBox it reaches
|
|
94
|
+
// a whole viewBox past each side (the picture clips it): a percentage
|
|
95
|
+
// counts from (0,0), so a viewBox starting elsewhere (a cropped
|
|
96
|
+
// drawing) would leave a strip bare, and the viewBox alone would leave
|
|
97
|
+
// the bands a picture of another shape letterboxes it with.
|
|
98
|
+
// Without one, percentages, so it covers whatever size it is shown at.
|
|
99
|
+
const box = tag.match(/\sviewBox\s*=\s*["']\s*([-\d.e+]+)[\s,]+([-\d.e+]+)[\s,]+([-\d.e+]+)[\s,]+([-\d.e+]+)\s*["']/i);
|
|
100
|
+
const [x, y, w, h] = box ? box.slice(1).map(Number) : [];
|
|
101
|
+
const rect = box
|
|
102
|
+
? `<rect x="${x - w}" y="${y - h}" width="${3 * w}" height="${3 * h}" fill="${background}"/>`
|
|
103
|
+
: `<rect x="0" y="0" width="100%" height="100%" fill="${background}"/>`;
|
|
104
|
+
out = out.replace(tag, `${tag}${rect}`);
|
|
97
105
|
}
|
|
98
106
|
return out;
|
|
99
107
|
}
|
|
@@ -126,12 +134,12 @@ export function findDrawable(host) {
|
|
|
126
134
|
* Size is taken from the element's laid-out box rather than its attributes,
|
|
127
135
|
* because that is what the user is actually looking at.
|
|
128
136
|
*/
|
|
129
|
-
export function serializeSvg(svgEl, { background = '#ffffff', width, height } = {}) {
|
|
137
|
+
export function serializeSvg(svgEl, { background = '#ffffff', width, height, scale = 1 } = {}) {
|
|
130
138
|
const box = svgEl.getBoundingClientRect?.() ?? {};
|
|
131
139
|
const w = width ?? svgEl.width?.baseVal?.value ?? box.width;
|
|
132
140
|
const h = height ?? svgEl.height?.baseVal?.value ?? box.height;
|
|
133
141
|
const markup = new XMLSerializer().serializeToString(svgEl);
|
|
134
|
-
return prepareSvgMarkup(markup, { width: w, height: h, background });
|
|
142
|
+
return prepareSvgMarkup(markup, { width: w * scale, height: h * scale, background, viewBox: `0 0 ${w} ${h}` });
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
/**
|
|
@@ -150,9 +158,16 @@ export async function toPngBlob(host, { scale = 2, background = '#ffffff', maxPi
|
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
const box = drawable.getBoundingClientRect();
|
|
153
|
-
const size = imageSize({ width: box.width, height: box.height, scale, maxPixels });
|
|
154
161
|
const markup = serializeSvg(drawable, { background, width: box.width, height: box.height });
|
|
162
|
+
return svgMarkupToPngBlob(markup, { width: box.width, height: box.height, scale, background, maxPixels });
|
|
163
|
+
}
|
|
155
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Rasterize standalone SVG markup (prepareSvgMarkup's) drawn `width` × `height`
|
|
167
|
+
* to a PNG Blob at `scale`. DOM only.
|
|
168
|
+
*/
|
|
169
|
+
export async function svgMarkupToPngBlob(markup, { width, height, scale = 1, background = '#ffffff', maxPixels = MAX_IMAGE_PIXELS } = {}) {
|
|
170
|
+
const size = imageSize({ width, height, scale, maxPixels });
|
|
156
171
|
// A data: URL rather than a blob: URL — a blob: URL taints nothing either,
|
|
157
172
|
// but Safari has historically refused to draw SVG from one.
|
|
158
173
|
const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
@@ -200,7 +215,15 @@ export function downloadBlob(blob, filename) {
|
|
|
200
215
|
* @returns {Promise<{via:'clipboard'|'download', filename?:string}>}
|
|
201
216
|
*/
|
|
202
217
|
export async function copyImage(host, { scale = 2, background = '#ffffff', name = 'diagram' } = {}) {
|
|
203
|
-
|
|
218
|
+
return copyPngBlob(await toPngBlob(host, { scale, background }), { name });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Put a PNG Blob on the clipboard, or download it where the browser cannot
|
|
223
|
+
* (as copyImage). DOM only.
|
|
224
|
+
* @returns {Promise<{via:'clipboard'|'download', filename?:string}>}
|
|
225
|
+
*/
|
|
226
|
+
export async function copyPngBlob(blob, { name = 'diagram' } = {}) {
|
|
204
227
|
const canWrite = typeof ClipboardItem !== 'undefined'
|
|
205
228
|
&& typeof navigator !== 'undefined'
|
|
206
229
|
&& navigator.clipboard?.write;
|
|
@@ -220,10 +243,14 @@ export async function copyImage(host, { scale = 2, background = '#ffffff', name
|
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
/** Save a diagram as an SVG file. DOM only. */
|
|
223
|
-
export function downloadSvg(host, { background = '#ffffff', name = 'diagram' } = {}) {
|
|
246
|
+
export function downloadSvg(host, { background = '#ffffff', name = 'diagram', scale = 1 } = {}) {
|
|
224
247
|
const drawable = findDrawable(host);
|
|
225
248
|
if (!drawable || drawable.tagName.toLowerCase() !== 'svg') throw new Error('No <svg> to export');
|
|
226
|
-
|
|
249
|
+
downloadSvgMarkup(serializeSvg(drawable, { background, scale }), { name });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Save standalone SVG markup as a file. DOM only. */
|
|
253
|
+
export function downloadSvgMarkup(markup, { name = 'diagram' } = {}) {
|
|
227
254
|
downloadBlob(new Blob([markup], { type: 'image/svg+xml;charset=utf-8' }), imageFileName(name, 'svg'));
|
|
228
255
|
}
|
|
229
256
|
|
package/src/index.js
CHANGED
|
@@ -188,13 +188,18 @@ import {
|
|
|
188
188
|
parseXML, parseArchiModel, toRenderEngineModel, toArchimateModel, toArchiXML, fromArchimateView, toArchiExport,
|
|
189
189
|
} from './archimateArchiFormat.js';
|
|
190
190
|
import { parseExchangeModel, toExchangeXML, EXCHANGE_NAMESPACE, EXCHANGE_TYPE_PROPERTY, EXCHANGE_VIEWPOINTS } from './archimateExchangeFormat.js';
|
|
191
|
-
import { ModelFileFormat, modelFileMenuItems, importFile } from './charting/ModelFileFormat.js';
|
|
191
|
+
import { ModelFileFormat, modelFileMenuItems, importFile, findPayload, askPasteImport } from './charting/ModelFileFormat.js';
|
|
192
192
|
import { ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS } from './charting/archimate/index.js';
|
|
193
193
|
import { ShowElementCommand } from './charting/diagrams/ShowElementCommand.js';
|
|
194
194
|
import { EmphasisRule, EMPHASIS_OPACITY, EMPHASIS_LEVELS, strongerEmphasis } from './charting/emphasis/EmphasisRule.js';
|
|
195
195
|
import { HighlightRule } from './charting/emphasis/HighlightRule.js';
|
|
196
196
|
import { CatalogDialog } from './charting/CatalogDialog.js';
|
|
197
|
-
import { CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices } from './charting/archimate/index.js';
|
|
197
|
+
import { CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices, askDeleteFromModel, deleteFromModelMessage } from './charting/archimate/index.js';
|
|
198
|
+
import { DeleteFromModelCommand } from './charting/diagrams/DeleteFromModelCommand.js';
|
|
199
|
+
import {
|
|
200
|
+
CHANGE_COLOURS, CHANGE_MAKERS, CHANGE_WORDS, changeAction, relationshipChange, elementChange,
|
|
201
|
+
propertyParams, expressionValues, resultBands, sequentialColours,
|
|
202
|
+
} from './charting/archimate/heatMaps.js';
|
|
198
203
|
import { ViewpointRule, ElementFilterRule } from './charting/archimate/index.js';
|
|
199
204
|
import { MODEL_ELEMENT_MIME, TREE_LAYERS, treeLayerOf } from './charting/modelTree/ModelTreePanel.js';
|
|
200
205
|
import { parseArchiFile, toArchiFile, ARCHI_MODEL_ENTRY } from './archimateArchive.js';
|
|
@@ -207,6 +212,7 @@ import {
|
|
|
207
212
|
// reference sits inside a function body, so loading it under node does nothing.
|
|
208
213
|
import {
|
|
209
214
|
copyImage, toPngBlob, downloadPng, downloadSvg, downloadBlob,
|
|
215
|
+
svgMarkupToPngBlob, copyPngBlob, downloadSvgMarkup,
|
|
210
216
|
serializeSvg, prepareSvgMarkup, imageSize, imageFileName, findDrawable, MAX_IMAGE_PIXELS,
|
|
211
217
|
} from './exportImage.js';
|
|
212
218
|
// Same reasoning as exportImage: every DOM reference sits inside a function
|
|
@@ -249,12 +255,16 @@ export {
|
|
|
249
255
|
importElementsCSV, importRelationshipsCSV, importModelCSV,
|
|
250
256
|
parseArchiModel, toArchimateModel, fromArchimateView, toArchiExport,
|
|
251
257
|
parseExchangeModel, toExchangeXML, EXCHANGE_NAMESPACE, EXCHANGE_TYPE_PROPERTY, EXCHANGE_VIEWPOINTS,
|
|
252
|
-
ModelFileFormat, modelFileMenuItems, importFile, ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS,
|
|
258
|
+
ModelFileFormat, modelFileMenuItems, importFile, findPayload, askPasteImport, ArchiFileFormat, ExchangeFileFormat, ArchimateCsvFileFormat, ARCHIMATE_FILE_FORMATS,
|
|
253
259
|
ShowElementCommand, MODEL_ELEMENT_MIME, TREE_LAYERS, treeLayerOf,
|
|
254
260
|
EmphasisRule, EMPHASIS_OPACITY, EMPHASIS_LEVELS, strongerEmphasis, HighlightRule, ViewpointRule, ElementFilterRule,
|
|
255
261
|
CatalogDialog, CustomViewpoints, CUSTOM_VIEWPOINTS_STORE, specializationCatalogDialog, elementTypeChoices,
|
|
262
|
+
askDeleteFromModel, deleteFromModelMessage, DeleteFromModelCommand,
|
|
263
|
+
CHANGE_COLOURS, CHANGE_MAKERS, CHANGE_WORDS, changeAction, relationshipChange, elementChange,
|
|
264
|
+
propertyParams, expressionValues, resultBands, sequentialColours,
|
|
256
265
|
parseArchiFile, toArchiFile, ARCHI_MODEL_ENTRY, ZipArchive,
|
|
257
266
|
copyImage, toPngBlob, downloadPng, downloadSvg, downloadBlob,
|
|
267
|
+
svgMarkupToPngBlob, copyPngBlob, downloadSvgMarkup,
|
|
258
268
|
serializeSvg, prepareSvgMarkup, imageSize, imageFileName, findDrawable, MAX_IMAGE_PIXELS,
|
|
259
269
|
showPropertiesDialog, showDiagramPropertiesDialog, showGlobalSettingsDialog,
|
|
260
270
|
propertyRowsFor, coerceFieldValue, derivedRefreshPlan, LAYER_CHOICES, NODE_SECTIONS,
|
|
@@ -1460,6 +1470,8 @@ export class RenderEngine {
|
|
|
1460
1470
|
* @param {string} [options.title]
|
|
1461
1471
|
* @param {number} [options.chartHeight] a chart's height (360)
|
|
1462
1472
|
* @param {boolean} [options.fitHeight] size the container to what is drawn (true)
|
|
1473
|
+
* @param {function|Intl.NumberFormat|object} [options.format] how numbers show: for every column, or
|
|
1474
|
+
* per column ({ Amount: Intl.NumberFormat }); copies keep the raw numbers
|
|
1463
1475
|
* @returns {TableView|null} the table's state, for the host to read or change
|
|
1464
1476
|
*/
|
|
1465
1477
|
resultView(rows, options = {}) {
|
|
@@ -1499,7 +1511,8 @@ export class RenderEngine {
|
|
|
1499
1511
|
* the axes. The container's height follows the grid, as resultView's does.
|
|
1500
1512
|
* @param {Array<object>} rows
|
|
1501
1513
|
* @param {object} [options] rowAxis, columnAxis, measures, columns (hints, with the rollup column's),
|
|
1502
|
-
* dimensions, onDrill, menus, title, nullText, fitHeight
|
|
1514
|
+
* dimensions, onDrill, menus, title, nullText, fitHeight, format (how numbers show, per measure or for
|
|
1515
|
+
* all: cells, totals and the traffic lights' cut-offs; the copy keeps the raw numbers)
|
|
1503
1516
|
* @returns {PivotView|null} the grid's state, for the host to read or change
|
|
1504
1517
|
*/
|
|
1505
1518
|
pivotView(rows, options = {}) {
|
|
@@ -253,6 +253,8 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
253
253
|
if (previous) {
|
|
254
254
|
this.editor.showGrid = previous.showGrid;
|
|
255
255
|
this.editor.gridSize = previous.gridSize;
|
|
256
|
+
this.editor.showExportArea = previous.showExportArea;
|
|
257
|
+
this.editor.exportOptions = previous.exportOptions;
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
// A canvas's palette is Archi's canvas palette; another view's, ArchiMate's element types.
|
|
@@ -351,6 +353,7 @@ export class ArchimateCanvasPart extends EditorPart {
|
|
|
351
353
|
this.editor?.selectTemplate(instanceId);
|
|
352
354
|
},
|
|
353
355
|
addToView: (el) => this.editor?.showElement(el.id, this.editor.visibleCentre()),
|
|
356
|
+
deleteFromModel: (el) => this.editor?.askDeleteFromModel([el.id]),
|
|
354
357
|
});
|
|
355
358
|
}
|
|
356
359
|
|
|
@@ -89,6 +89,8 @@ export class DiagramEditorPart extends EditorPart {
|
|
|
89
89
|
if (previous) {
|
|
90
90
|
this.editor.showGrid = previous.showGrid;
|
|
91
91
|
this.editor.gridSize = previous.gridSize;
|
|
92
|
+
this.editor.showExportArea = previous.showExportArea;
|
|
93
|
+
this.editor.exportOptions = previous.exportOptions;
|
|
92
94
|
}
|
|
93
95
|
this.editor.initPaletteForDiagramType(palette, model.templateType);
|
|
94
96
|
this.menuBar?.dispose();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ModelFile } from './ModelFile.js';
|
|
2
2
|
import { EditorPart } from './EditorPart.js';
|
|
3
|
-
import { modelFileMenuItems } from '../../charting/ModelFileFormat.js';
|
|
3
|
+
import { modelFileMenuItems, askPasteImport } from '../../charting/ModelFileFormat.js';
|
|
4
4
|
import { pickFile } from '../../charting/toolbar/pickFile.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -121,9 +121,20 @@ export const ModelHosted = (Base) => class extends Base {
|
|
|
121
121
|
|
|
122
122
|
/** Import ▸: each format that only reads (another tool's export), opened as a new model. */
|
|
123
123
|
importItems() {
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
return [
|
|
125
|
+
...modelFileMenuItems(this.fileFormats().filter(f => f.exports === false), {
|
|
126
|
+
model: () => this.model, only: 'import', onImport: (model) => this.onImported(model), document: () => this.document,
|
|
127
|
+
}),
|
|
128
|
+
...this.pasteItems(),
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Paste …: a model in each of its text formats from pasted text (an assistant's answer, say), as Open… would bring it. */
|
|
133
|
+
pasteItems() {
|
|
134
|
+
return this.fileFormats().filter(f => f.imports && !f.readsBytes).map(format => ({
|
|
135
|
+
label: `From pasted text (${format.label})…`,
|
|
136
|
+
action: () => askPasteImport(format, { onImport: (model) => this.onImported(model), document: this.document ?? globalThis.document }),
|
|
137
|
+
}));
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
/**
|