@svgrid/enterprise 3.1.25 → 3.1.27
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/dist/cdn/svgrid-enterprise.svelte-external.js +18048 -17567
- package/dist/designer/assets/{GridFooter-DKMfZpL4.js → GridFooter-EyLg77jG.js} +1 -1
- package/dist/designer/assets/{GridMenus-DWDghydL.js → GridMenus-BAgfpDd-.js} +3 -3
- package/dist/designer/assets/{SvDateRangeInput-DbfmXqJj.js → SvDateRangeInput-y8PkioQk.js} +1 -1
- package/dist/designer/assets/SvGrid.helpers-DHkH3mHW.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-BHxH4b5h.js +4 -0
- package/dist/designer/assets/{SvGridChart-DYtGHM3F.js → SvGridChart-DzCeEdlb.js} +3 -3
- package/dist/designer/assets/{SvGridChartBuilder-BjdQrnz5.js → SvGridChartBuilder-CsXVVKvA.js} +1 -1
- package/dist/designer/assets/SvGridChartPanel-Dj3IxTS2.js +4 -0
- package/dist/designer/assets/{SvGridChartView-Bfs0bvDb.js → SvGridChartView-Dwm4hxbW.js} +1 -1
- package/dist/designer/assets/{chart-export-B6bkQ3X8.js → chart-export-Dqq_eHRq.js} +4 -4
- package/dist/designer/assets/{chart-export-pdf-BbA28-gt.js → chart-export-pdf-Gw22jkxa.js} +1 -1
- package/dist/designer/assets/column-types-Bl5R-97G.js +1 -0
- package/dist/designer/assets/csv-injection-C4a0uulO.js +1 -0
- package/dist/designer/assets/export-format-NYxEY-0Y.js +6 -0
- package/dist/designer/assets/index-BU3ieI05.css +1 -0
- package/dist/designer/assets/{index-UhFdGGXv.js → index-Cim1_diz.js} +340 -330
- package/dist/designer/assets/src-D23PPsK1.js +1 -0
- package/dist/designer/assets/src-sFRH_up7.js +60 -0
- package/dist/designer/index.html +13 -11
- package/dist/export-conditional.d.ts +2 -0
- package/dist/export-conditional.js +2 -0
- package/dist/export-pdf.d.ts +142 -7
- package/dist/export-pdf.js +368 -44
- package/dist/export.d.ts +22 -4
- package/dist/export.js +150 -53
- package/dist/import.js +14 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node/studio.js +20 -12
- package/dist/pdf-worker.d.ts +92 -0
- package/dist/pdf-worker.js +147 -0
- package/dist/sheet/ods-document.js +3 -1
- package/dist/sheet/open-file.js +4 -2
- package/dist/sheet/xlsx-document.d.ts +2 -2
- package/dist/sheet/xlsx-document.js +18 -2
- package/dist/studio/emit-schema.js +18 -10
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/wc/{GridMenus-h7RsvcmI.js → GridMenus-BIQXB6a7.js} +1 -1
- package/dist/wc/{SvDateRangeInput-CJZohgBe.js → SvDateRangeInput-DHYuAJeY.js} +1 -0
- package/dist/wc/SvGridCellEditor-CZwhQx0a.js +584 -0
- package/dist/wc/{SvGridChart-DWRqlhCV.js → SvGridChart-DcfmVvzB.js} +3 -3
- package/dist/wc/{SvGridChartBuilder-BXW3yUR0.js → SvGridChartBuilder-1Z69mxbM.js} +2 -2
- package/dist/wc/{SvGridChartPanel-28n4tomx.js → SvGridChartPanel-BaVsa-T-.js} +5 -5
- package/dist/wc/{SvGridChartView-CbG_WrU-.js → SvGridChartView-LEC8ZNeU.js} +1 -1
- package/dist/wc/{chart-export-CeSADGfi.js → chart-export-C1KSVdMU.js} +29 -28
- package/dist/wc/{chart-export-pdf-BGko7MER.js → chart-export-pdf-CmEd365D.js} +1 -1
- package/dist/wc/csv-injection-Ce26_TOb.js +8 -0
- package/dist/wc/sv-sheet-element.js +6275 -6215
- package/dist/xlsx-limits.d.ts +51 -0
- package/dist/xlsx-limits.js +89 -0
- package/package.json +7 -3
- package/dist/designer/assets/SvGrid.helpers-BF2QRcNt.js +0 -1
- package/dist/designer/assets/SvGridCellEditor-od4VbndV.js +0 -4
- package/dist/designer/assets/SvGridChartPanel-Cm9_K7BV.js +0 -4
- package/dist/designer/assets/export-format-BCuqcexA.js +0 -6
- package/dist/designer/assets/index-CHx3F4Ij.css +0 -1
- package/dist/designer/assets/src-CITpcm6b.js +0 -1
- package/dist/designer/assets/src-fddEt37f.js +0 -58
- package/dist/wc/SvGridCellEditor-C7ios4i_.js +0 -582
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/// <reference path="./pdfmake-shims.d.ts" />
|
|
2
|
+
/**
|
|
3
|
+
* Render a PDF export in a worker, so a large report does not freeze the
|
|
4
|
+
* page. pdfmake lays out and writes the whole document in one synchronous
|
|
5
|
+
* run, about a millisecond per row of eight columns on a desktop machine:
|
|
6
|
+
* a 20,000-row export held the main thread for over twenty seconds.
|
|
7
|
+
*
|
|
8
|
+
* The app owns the worker file, so any bundler builds it:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* // pdf.worker.ts
|
|
12
|
+
* import { servePdfWorker } from '@svgrid/enterprise/pdf-worker'
|
|
13
|
+
* servePdfWorker()
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* await api.exportData({
|
|
18
|
+
* format: 'pdf',
|
|
19
|
+
* pdf: { worker: () => new Worker(new URL('./pdf.worker.ts', import.meta.url), { type: 'module' }) },
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The main thread builds the document as plain data (`buildPdfDocData`) and
|
|
24
|
+
* posts it here with the font, if any; the worker loads pdfmake, attaches
|
|
25
|
+
* the layout and footer functions (`hydratePdfDoc`), renders, and posts the
|
|
26
|
+
* bytes back with progress along the way.
|
|
27
|
+
*/
|
|
28
|
+
import { hydratePdfDoc, registerPdfFont, registerPdfFonts } from './export-pdf.js';
|
|
29
|
+
/** Tags this channel's messages, so a worker shared with other traffic ignores the rest. */
|
|
30
|
+
export const PDF_WORKER_CHANNEL = 'svgrid-pdf';
|
|
31
|
+
/**
|
|
32
|
+
* Render a document from `buildPdfDocData` with the pdfmake given. The
|
|
33
|
+
* worker's half of the export, exported so it can run anywhere pdfmake does.
|
|
34
|
+
*/
|
|
35
|
+
export function renderPdfDoc(doc, pdfMake, opts = {}) {
|
|
36
|
+
if (opts.font)
|
|
37
|
+
registerPdfFont(pdfMake, opts.font);
|
|
38
|
+
const def = hydratePdfDoc(doc);
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const done = (bytes) => resolve(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes));
|
|
41
|
+
try {
|
|
42
|
+
// 0.2 calls back; 0.3 returns a promise and ignores the callback.
|
|
43
|
+
const returned = pdfMake.createPdf(def).getBuffer(done, { progressCallback: opts.onProgress });
|
|
44
|
+
if (returned && typeof returned.then === 'function') {
|
|
45
|
+
;
|
|
46
|
+
returned.then(done, reject);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
reject(err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function loadPdfMake() {
|
|
55
|
+
// @ts-ignore - "pdfmake" is an optional peerDependency
|
|
56
|
+
const mod = (await import('pdfmake/build/pdfmake'));
|
|
57
|
+
// @ts-ignore - "pdfmake" is an optional peerDependency
|
|
58
|
+
const vfs = await import('pdfmake/build/vfs_fonts');
|
|
59
|
+
const pdfMake = (mod.default ?? mod);
|
|
60
|
+
if (!registerPdfFonts(pdfMake, vfs)) {
|
|
61
|
+
throw new Error('"pdfmake/build/vfs_fonts" did not contain a font map; the installed pdfmake is not one this export knows.');
|
|
62
|
+
}
|
|
63
|
+
// createPdf reads `pdfMake.tableLayouts` off the global, and a bundled
|
|
64
|
+
// import never sets that global, so without this every render failed with
|
|
65
|
+
// "Cannot read properties of undefined (reading 'tableLayouts')". The page
|
|
66
|
+
// side does the same in ensureGlobals.
|
|
67
|
+
;
|
|
68
|
+
globalThis.pdfMake = pdfMake;
|
|
69
|
+
return pdfMake;
|
|
70
|
+
}
|
|
71
|
+
/** Answer PDF export requests from the main thread. Call it once, in the worker. */
|
|
72
|
+
export function servePdfWorker(options = {}) {
|
|
73
|
+
const scope = options.scope ?? globalThis;
|
|
74
|
+
const load = options.loadPdfMake ?? loadPdfMake;
|
|
75
|
+
let pdfMake = null;
|
|
76
|
+
const reply = (message, transfer) => scope.postMessage(message, transfer);
|
|
77
|
+
const listener = async (event) => {
|
|
78
|
+
const msg = event.data;
|
|
79
|
+
if (!msg || msg.channel !== PDF_WORKER_CHANNEL || typeof msg.id !== 'number' || !msg.doc)
|
|
80
|
+
return;
|
|
81
|
+
const { id, doc, font } = msg;
|
|
82
|
+
try {
|
|
83
|
+
pdfMake ?? (pdfMake = load());
|
|
84
|
+
// pdfmake reports progress per item drawn: a 5,000-row table sent
|
|
85
|
+
// 79,129 messages. One per percent is enough for a progress bar.
|
|
86
|
+
let sent = -1;
|
|
87
|
+
const bytes = await renderPdfDoc(doc, await pdfMake, {
|
|
88
|
+
font,
|
|
89
|
+
onProgress: (progress) => {
|
|
90
|
+
if (progress < 1 && progress - sent < 0.01)
|
|
91
|
+
return;
|
|
92
|
+
sent = progress;
|
|
93
|
+
reply({ channel: PDF_WORKER_CHANNEL, id, progress });
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// A copy of exactly these bytes, handed over rather than cloned.
|
|
97
|
+
const pdf = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
98
|
+
reply({ channel: PDF_WORKER_CHANNEL, id, pdf }, [pdf]);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
pdfMake = null;
|
|
102
|
+
reply({ channel: PDF_WORKER_CHANNEL, id, error: err instanceof Error ? err.message : String(err) });
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
scope.addEventListener('message', listener);
|
|
106
|
+
return { dispose: () => scope.removeEventListener('message', listener) };
|
|
107
|
+
}
|
|
108
|
+
let nextJob = 1;
|
|
109
|
+
/**
|
|
110
|
+
* The main thread's half: post the document to a fresh worker, report its
|
|
111
|
+
* progress, and resolve with the PDF. An abort terminates the worker.
|
|
112
|
+
*/
|
|
113
|
+
export function renderPdfInWorker(makeWorker, doc, opts = {}) {
|
|
114
|
+
const worker = makeWorker();
|
|
115
|
+
const id = nextJob++;
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const finish = () => {
|
|
118
|
+
opts.signal?.removeEventListener('abort', onAbort);
|
|
119
|
+
worker.terminate();
|
|
120
|
+
};
|
|
121
|
+
const onAbort = () => {
|
|
122
|
+
finish();
|
|
123
|
+
reject(new DOMException('Export aborted', 'AbortError'));
|
|
124
|
+
};
|
|
125
|
+
if (opts.signal?.aborted)
|
|
126
|
+
return onAbort();
|
|
127
|
+
opts.signal?.addEventListener('abort', onAbort, { once: true });
|
|
128
|
+
worker.addEventListener('message', (event) => {
|
|
129
|
+
const msg = event.data;
|
|
130
|
+
if (!msg || msg.channel !== PDF_WORKER_CHANNEL || msg.id !== id)
|
|
131
|
+
return;
|
|
132
|
+
if ('progress' in msg)
|
|
133
|
+
return opts.onProgress?.(msg.progress);
|
|
134
|
+
finish();
|
|
135
|
+
if ('pdf' in msg)
|
|
136
|
+
resolve(new Blob([msg.pdf], { type: 'application/pdf' }));
|
|
137
|
+
else
|
|
138
|
+
reject(new Error(`@svgrid/enterprise: the PDF worker failed: ${msg.error}`));
|
|
139
|
+
});
|
|
140
|
+
worker.addEventListener('error', (event) => {
|
|
141
|
+
finish();
|
|
142
|
+
reject(new Error(`@svgrid/enterprise: the PDF worker failed to start or crashed: ${event.message || 'no message'}. Check that it calls servePdfWorker().`));
|
|
143
|
+
});
|
|
144
|
+
const request = { channel: PDF_WORKER_CHANNEL, id, doc, ...(opts.font ? { font: opts.font } : {}) };
|
|
145
|
+
worker.postMessage(request);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -17,6 +17,7 @@ import { colToLetters, lettersToCol } from './address.js';
|
|
|
17
17
|
import { tokenize } from './tokenize.js';
|
|
18
18
|
import { formatKeyAt } from './format-store.js';
|
|
19
19
|
import { linkAt } from './links.js';
|
|
20
|
+
import { createUnzipBudget, readZipEntry } from '../xlsx-limits.js';
|
|
20
21
|
import { threadAt } from './comments.js';
|
|
21
22
|
import { loadZip } from './xlsx-document.js';
|
|
22
23
|
import { accountingParts, accountingPattern } from './number-format.js';
|
|
@@ -747,6 +748,7 @@ function durationSeconds(text) {
|
|
|
747
748
|
export async function documentFromOds(file, JSZip) {
|
|
748
749
|
const Zip = await loadZip(JSZip);
|
|
749
750
|
const zip = await Zip.loadAsync(file);
|
|
751
|
+
const budget = createUnzipBudget();
|
|
750
752
|
const parts = {};
|
|
751
753
|
const pending = [];
|
|
752
754
|
zip.forEach((path, entry) => {
|
|
@@ -755,7 +757,7 @@ export async function documentFromOds(file, JSZip) {
|
|
|
755
757
|
if (!/\.(xml)$/i.test(path))
|
|
756
758
|
return;
|
|
757
759
|
const clean = path.replace(/^\//, '');
|
|
758
|
-
pending.push(entry
|
|
760
|
+
pending.push(readZipEntry(entry, 'string', budget).then((text) => { parts[clean] = text; }));
|
|
759
761
|
});
|
|
760
762
|
await Promise.all(pending);
|
|
761
763
|
return sheetStateFromOds(parts);
|
package/dist/sheet/open-file.js
CHANGED
|
@@ -14,6 +14,7 @@ import { documentFromXlsxParts, loadZip } from './xlsx-document.js';
|
|
|
14
14
|
import { sheetStateFromOds } from './ods-document.js';
|
|
15
15
|
import { sheetStateFromCsv } from './csv.js';
|
|
16
16
|
import { isXlsFile, sheetStateFromXls } from './xls-document.js';
|
|
17
|
+
import { createUnzipBudget, readZipEntry } from '../xlsx-limits.js';
|
|
17
18
|
/** A picture travels as the data URL the document holds, as in the xlsx reader. */
|
|
18
19
|
const MEDIA_PREFIX = 'xl/media/';
|
|
19
20
|
const mediaTypeOf = (path) => {
|
|
@@ -31,6 +32,7 @@ export async function readSpreadsheetFile(file, JSZip) {
|
|
|
31
32
|
}
|
|
32
33
|
const Zip = await loadZip(JSZip);
|
|
33
34
|
const zip = await Zip.loadAsync(file);
|
|
35
|
+
const budget = createUnzipBudget();
|
|
34
36
|
const parts = {};
|
|
35
37
|
const pending = [];
|
|
36
38
|
zip.forEach((path, entry) => {
|
|
@@ -38,12 +40,12 @@ export async function readSpreadsheetFile(file, JSZip) {
|
|
|
38
40
|
return;
|
|
39
41
|
const clean = path.replace(/^\//, '');
|
|
40
42
|
if (clean.startsWith(MEDIA_PREFIX)) {
|
|
41
|
-
pending.push(entry
|
|
43
|
+
pending.push(readZipEntry(entry, 'base64', budget).then((base64) => { parts[clean] = `data:${mediaTypeOf(clean)};base64,${base64}`; }));
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
if (!/\.(xml|rels|vml)$/i.test(clean))
|
|
45
47
|
return;
|
|
46
|
-
pending.push(entry
|
|
48
|
+
pending.push(readZipEntry(entry, 'string', budget).then((text) => { parts[clean] = text; }));
|
|
47
49
|
});
|
|
48
50
|
await Promise.all(pending);
|
|
49
51
|
if (parts['content.xml'] && !parts['xl/workbook.xml'])
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* unzip them with jszip, an optional peer loaded on demand.
|
|
24
24
|
*/
|
|
25
25
|
import type { SheetDocument, SheetState } from './document.js';
|
|
26
|
+
import { type ZipEntryLike } from '../xlsx-limits.js';
|
|
26
27
|
export declare const isoToSerial: (iso: string) => number | null;
|
|
27
28
|
export declare const serialToIso: (serial: number) => string;
|
|
28
29
|
/** Excel's built-in number formats, the ones a file names by id alone. */
|
|
@@ -94,9 +95,8 @@ export type ZipCtor = {
|
|
|
94
95
|
file(path: string): {
|
|
95
96
|
async(type: 'string' | 'base64'): Promise<string>;
|
|
96
97
|
} | null;
|
|
97
|
-
forEach(fn: (path: string, entry: {
|
|
98
|
+
forEach(fn: (path: string, entry: ZipEntryLike & {
|
|
98
99
|
dir: boolean;
|
|
99
|
-
async(type: 'string' | 'base64'): Promise<string>;
|
|
100
100
|
}) => void): void;
|
|
101
101
|
}>;
|
|
102
102
|
};
|
|
@@ -13,6 +13,7 @@ import { drawingPartsFor, objectsFromDrawing, rectOfRef, REL_DRAWING } from './x
|
|
|
13
13
|
import { objectId } from './objects.js';
|
|
14
14
|
import { sparklineLines, sparklineId } from './sparklines.js';
|
|
15
15
|
import { autoFilterXlsx, filteredRows, filtersFromXlsx } from './filter-files.js';
|
|
16
|
+
import { createUnzipBudget, readZipEntry, XLSX_MAX_COLS, XLSX_MAX_FILL, XLSX_MAX_ROWS, xlsxFillError } from '../xlsx-limits.js';
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
17
18
|
// Shared pieces
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
@@ -1282,10 +1283,24 @@ export function documentFromXlsxParts(parts) {
|
|
|
1282
1283
|
const outlineCols = emptyOutline();
|
|
1283
1284
|
if (xml) {
|
|
1284
1285
|
const root = parseXml(xml, path).documentElement;
|
|
1286
|
+
// Rows and lines are filled up to the cell, so a reference from the
|
|
1287
|
+
// file decides how much is allocated: a cell outside the sheet is
|
|
1288
|
+
// skipped, as other malformed parts are, and the blanks are budgeted
|
|
1289
|
+
// (xlsx-limits.ts).
|
|
1290
|
+
let filled = 0;
|
|
1285
1291
|
const put = (r, c, text) => {
|
|
1292
|
+
if (r >= XLSX_MAX_ROWS || c >= XLSX_MAX_COLS)
|
|
1293
|
+
return;
|
|
1294
|
+
if (r > cells.length)
|
|
1295
|
+
filled += r - cells.length;
|
|
1286
1296
|
while (cells.length <= r)
|
|
1287
1297
|
cells.push([]);
|
|
1288
1298
|
const line = cells[r];
|
|
1299
|
+
if (c >= line.length) {
|
|
1300
|
+
filled += c - line.length;
|
|
1301
|
+
if (filled > XLSX_MAX_FILL)
|
|
1302
|
+
throw xlsxFillError();
|
|
1303
|
+
}
|
|
1289
1304
|
while (line.length <= c)
|
|
1290
1305
|
line.push('');
|
|
1291
1306
|
line[c] = text;
|
|
@@ -1828,6 +1843,7 @@ export async function documentToXlsx(doc, JSZip) {
|
|
|
1828
1843
|
export async function documentFromXlsx(file, JSZip) {
|
|
1829
1844
|
const Zip = await loadZip(JSZip);
|
|
1830
1845
|
const zip = await Zip.loadAsync(file);
|
|
1846
|
+
const budget = createUnzipBudget();
|
|
1831
1847
|
const parts = {};
|
|
1832
1848
|
const pending = [];
|
|
1833
1849
|
zip.forEach((path, entry) => {
|
|
@@ -1837,12 +1853,12 @@ export async function documentFromXlsx(file, JSZip) {
|
|
|
1837
1853
|
// A picture comes back as the data URL an image object holds, so the
|
|
1838
1854
|
// rest of the reader never has to think about bytes.
|
|
1839
1855
|
if (clean.startsWith(MEDIA_PREFIX)) {
|
|
1840
|
-
pending.push(entry
|
|
1856
|
+
pending.push(readZipEntry(entry, 'base64', budget).then((base64) => { parts[clean] = `data:${mediaTypeOf(clean)};base64,${base64}`; }));
|
|
1841
1857
|
return;
|
|
1842
1858
|
}
|
|
1843
1859
|
if (!/\.(xml|rels|vml)$/i.test(path))
|
|
1844
1860
|
return;
|
|
1845
|
-
pending.push(entry
|
|
1861
|
+
pending.push(readZipEntry(entry, 'string', budget).then((text) => { parts[clean] = text; }));
|
|
1846
1862
|
});
|
|
1847
1863
|
await Promise.all(pending);
|
|
1848
1864
|
return documentFromXlsxParts(parts);
|
|
@@ -834,8 +834,8 @@ export function layoutFile(nav, opts = {}) {
|
|
|
834
834
|
? `<div class="sv-app__toolbar">
|
|
835
835
|
<div class="sv-app__search">
|
|
836
836
|
<svg class="sv-app__search-ic" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg>
|
|
837
|
-
<input class="sv-app__search-in" type="search" placeholder="Search {brand}…" bind:value={q}
|
|
838
|
-
<
|
|
837
|
+
<input class="sv-app__search-in" type="search" placeholder="Search {brand}…" bind:value={q} aria-label="Search screens" />
|
|
838
|
+
<button type="button" class="sv-app__kbd" title="Command palette (⌘K / Ctrl+K)" aria-label="Open the command palette" onclick={() => (paletteOpen = true)}>⌘K</button>
|
|
839
839
|
{#if results.length}
|
|
840
840
|
<div class="sv-app__results">
|
|
841
841
|
{#each results as r (r.href)}
|
|
@@ -881,6 +881,7 @@ export function layoutFile(nav, opts = {}) {
|
|
|
881
881
|
</div>
|
|
882
882
|
</div>
|
|
883
883
|
{#if bellOpen || menuOpen}<button type="button" class="sv-app__scrim" tabindex="-1" aria-label="Close menus" onclick={() => { bellOpen = false; menuOpen = false }}></button>{/if}
|
|
884
|
+
<SvCommand bind:open={paletteOpen} {commands} placeholder="Go to a screen or run an action…" emptyText="No matching screen" />
|
|
884
885
|
</div>\n `
|
|
885
886
|
: '';
|
|
886
887
|
// Supabase Auth: gate the whole app behind a client-side sign-in. SvAuthGate shows
|
|
@@ -987,7 +988,7 @@ export function layoutFile(nav, opts = {}) {
|
|
|
987
988
|
import '../app.css'
|
|
988
989
|
import '../custom.css'
|
|
989
990
|
import { page } from '$app/state'
|
|
990
|
-
import { setLicenseKey } from '@svgrid/enterprise'${opts.access ? `\n import { currentRole, canScreen } from '../lib/access'` : ''}${opts.i18n ? `\n import { t, currentLocale, locales } from '../lib/i18n'` : ''}${opts.auth ? `\n import type { LayoutData } from './$types'` : ''}${opts.supabaseAuth ? `\n import { SvAuthGate, type SupabaseAuthClientLike } from '@svgrid/enterprise'\n import { supabaseClient } from '../lib/connections'` : ''}
|
|
991
|
+
import { setLicenseKey } from '@svgrid/enterprise'${toolbarOn ? `\n import { goto } from '$app/navigation'\n import { SvCommand, type CommandItem } from '@svgrid/grid'` : ''}${opts.access ? `\n import { currentRole, canScreen } from '../lib/access'` : ''}${opts.i18n ? `\n import { t, currentLocale, locales } from '../lib/i18n'` : ''}${opts.auth ? `\n import type { LayoutData } from './$types'` : ''}${opts.supabaseAuth ? `\n import { SvAuthGate, type SupabaseAuthClientLike } from '@svgrid/enterprise'\n import { supabaseClient } from '../lib/connections'` : ''}
|
|
991
992
|
|
|
992
993
|
// VITE_SVPRO_KEY in .env is your SvGrid license key; it removes the unlicensed watermark.
|
|
993
994
|
if (import.meta.env.VITE_SVPRO_KEY) setLicenseKey(import.meta.env.VITE_SVPRO_KEY)
|
|
@@ -1013,24 +1014,30 @@ export function layoutFile(nav, opts = {}) {
|
|
|
1013
1014
|
try { const s = localStorage.getItem('svapp:theme'); if (s === 'light' || s === 'dark') t = s } catch (_) { /* storage blocked */ }
|
|
1014
1015
|
applyTheme(t)
|
|
1015
1016
|
})` : ''}${sideState}${toolbarOn ? `
|
|
1016
|
-
// App-chrome quick-search: filter the screens by label as you type.
|
|
1017
|
+
// App-chrome quick-search: filter the screens by label as you type. Only the
|
|
1018
|
+
// screens the sidebar would show${opts.access ? ' to this role' : ''}.
|
|
1017
1019
|
const initials = ${JSON.stringify(initials)}
|
|
1018
1020
|
const acctEmail = ${JSON.stringify(`admin@${brandSlug}.com`)}
|
|
1021
|
+
const visibleNav = ${opts.access ? '$derived(nav.filter((n) => !n.id || canScreen($currentRole, n.id)))' : 'nav'}
|
|
1019
1022
|
let q = $state('')
|
|
1020
|
-
const results = $derived(q.trim() ?
|
|
1023
|
+
const results = $derived(q.trim() ? visibleNav.filter((n) => n.label.toLowerCase().includes(q.trim().toLowerCase())).slice(0, 6) : [])
|
|
1024
|
+
// Cmd/Ctrl+K: a palette over the same screens. SvCommand binds the key itself.
|
|
1025
|
+
let paletteOpen = $state(false)
|
|
1026
|
+
const commands: CommandItem[] = $derived([
|
|
1027
|
+
...visibleNav.map((n) => ({ id: 'go:' + n.href, label: ${opts.i18n ? "n.id ? $t('nav.' + n.id, n.label) : n.label" : 'n.label'}, group: 'Go to', onRun: () => goto(n.href) })),${hasSwitch ? `
|
|
1028
|
+
{ id: 'theme', label: theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode', group: 'Actions', onRun: toggleTheme },` : ''}
|
|
1029
|
+
])
|
|
1021
1030
|
let bellOpen = $state(false)
|
|
1022
1031
|
let menuOpen = $state(false)
|
|
1023
|
-
let searchEl: HTMLInputElement | null = $state(null)
|
|
1024
1032
|
const notifications = [
|
|
1025
1033
|
{ title: 'Welcome to ' + brand, time: 'just now', color: 'var(--sg-accent, #6366f1)' },
|
|
1026
1034
|
{ title: 'Your workspace is ready', time: '2m ago', color: '#10b981' },
|
|
1027
1035
|
{ title: 'Sample data loaded', time: '5m ago', color: '#f59e0b' },
|
|
1028
1036
|
]
|
|
1029
|
-
//
|
|
1037
|
+
// Escape closes the menus.
|
|
1030
1038
|
$effect(() => {
|
|
1031
1039
|
function onKey(e: KeyboardEvent) {
|
|
1032
|
-
if (
|
|
1033
|
-
else if (e.key === 'Escape') { bellOpen = false; menuOpen = false }
|
|
1040
|
+
if (e.key === 'Escape') { bellOpen = false; menuOpen = false }
|
|
1034
1041
|
}
|
|
1035
1042
|
window.addEventListener('keydown', onKey)
|
|
1036
1043
|
return () => window.removeEventListener('keydown', onKey)
|
|
@@ -1069,7 +1076,8 @@ ${styles}
|
|
|
1069
1076
|
.sv-app__tool:hover { color: var(--sg-fg, #0f172a); border-color: color-mix(in srgb, var(--sg-fg, #0f172a) 20%, var(--sg-border, #e6e8ec)); }
|
|
1070
1077
|
.sv-app__dot { position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%; background: var(--sg-accent, #6366f1); box-shadow: 0 0 0 2px var(--sg-bg, #fff); }
|
|
1071
1078
|
.sv-app__avatar { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: 50%; font: inherit; font-size: 12px; font-weight: 700; color: #fff; background: var(--sg-accent, #6366f1); letter-spacing: 0.02em; cursor: pointer; }
|
|
1072
|
-
.sv-app__kbd { position: absolute; right: 8px; font-size: 10.5px; font-weight: 600; color: var(--sg-muted, #94a3b8); background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 5px; padding: 1px 5px;
|
|
1079
|
+
.sv-app__kbd { position: absolute; right: 8px; font-size: 10.5px; font-weight: 600; color: var(--sg-muted, #94a3b8); background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 5px; padding: 1px 5px; font-family: inherit; line-height: inherit; cursor: pointer; }
|
|
1080
|
+
.sv-app__kbd:hover { color: var(--sg-fg, #0f172a); }
|
|
1073
1081
|
.sv-app__pop { position: relative; }
|
|
1074
1082
|
.sv-app__scrim { position: fixed; inset: 0; z-index: 44; border: 0; padding: 0; background: transparent; cursor: default; }
|
|
1075
1083
|
.sv-app__menu { position: absolute; top: calc(100% + 8px); right: 0; z-index: 46; min-width: 232px; padding: 6px; background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 11px; box-shadow: 0 16px 40px -10px rgba(15, 23, 42, 0.3); }
|
package/dist/version.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
12
12
|
*/
|
|
13
|
-
export declare const SVGRID_VERSION = "3.1.
|
|
13
|
+
export declare const SVGRID_VERSION = "3.1.27";
|
|
14
14
|
/**
|
|
15
15
|
* The version of `@svgrid/grid`, which releases on its own line: enterprise is
|
|
16
16
|
* on 3.1.x while the grid is on 3.0.x.
|
|
@@ -23,4 +23,4 @@ export declare const SVGRID_VERSION = "3.1.25";
|
|
|
23
23
|
*
|
|
24
24
|
* `version.test.ts` asserts this matches packages/grid/package.json.
|
|
25
25
|
*/
|
|
26
|
-
export declare const GRID_VERSION = "3.0.
|
|
26
|
+
export declare const GRID_VERSION = "3.0.23";
|
package/dist/version.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
12
12
|
*/
|
|
13
|
-
export const SVGRID_VERSION = '3.1.
|
|
13
|
+
export const SVGRID_VERSION = '3.1.27';
|
|
14
14
|
/**
|
|
15
15
|
* The version of `@svgrid/grid`, which releases on its own line: enterprise is
|
|
16
16
|
* on 3.1.x while the grid is on 3.0.x.
|
|
@@ -23,4 +23,4 @@ export const SVGRID_VERSION = '3.1.25';
|
|
|
23
23
|
*
|
|
24
24
|
* `version.test.ts` asserts this matches packages/grid/package.json.
|
|
25
25
|
*/
|
|
26
|
-
export const GRID_VERSION = '3.0.
|
|
26
|
+
export const GRID_VERSION = '3.0.23';
|
|
@@ -988,7 +988,7 @@ function Mt(r, u) {
|
|
|
988
988
|
let e = n(Y) ?? n(J);
|
|
989
989
|
if (!e) return;
|
|
990
990
|
let t = n(Q).find((t) => t.id === e);
|
|
991
|
-
ue(t?.columnDef) && (n(Kt) || import("./SvDateTimePicker-DmK_QkkU.js").then((e) => y(Kt, e.default, !0)), n(qt) || import("./SvDateRangeInput-
|
|
991
|
+
ue(t?.columnDef) && (n(Kt) || import("./SvDateTimePicker-DmK_QkkU.js").then((e) => y(Kt, e.default, !0)), n(qt) || import("./SvDateRangeInput-DHYuAJeY.js").then((e) => y(qt, e.default, !0)));
|
|
992
992
|
});
|
|
993
993
|
function Jt(e, t) {
|
|
994
994
|
let n = () => {
|