@revenexx/studio-shared 0.4.0 → 0.4.2

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 (110) hide show
  1. package/dist/module.d.mts +5 -3
  2. package/dist/module.json +2 -2
  3. package/dist/module.mjs +7 -3
  4. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +1 -1
  5. package/dist/runtime/components/Dashboard/DashboardGrid.vue +45 -1
  6. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +1 -1
  7. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +1 -1
  8. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +1 -1
  9. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +1 -1
  10. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +2 -2
  11. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +14 -3
  12. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +2 -2
  13. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +73 -34
  14. package/dist/runtime/components/DataTable/DataTable.vue +352 -52
  15. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +73 -34
  16. package/dist/runtime/components/DataTable/FacetPanel.vue +57 -8
  17. package/dist/runtime/components/DataTable/FilterMenu.vue +5 -5
  18. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +61 -12
  19. package/dist/runtime/components/DataTable/SortMenu.vue +4 -1
  20. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.d.vue.ts +23 -0
  21. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue +29 -0
  22. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue.d.ts +23 -0
  23. package/dist/runtime/components/DataTable/types.d.ts +17 -0
  24. package/dist/runtime/components/Form/FieldGrid.vue +1 -1
  25. package/dist/runtime/components/Form/SaveBar.d.vue.ts +1 -1
  26. package/dist/runtime/components/Form/SaveBar.vue.d.ts +1 -1
  27. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +4 -4
  28. package/dist/runtime/components/Media/MediaFolderTree.vue +1 -2
  29. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +4 -4
  30. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +11 -0
  31. package/dist/runtime/components/Media/MediaPickerDialog.vue +206 -54
  32. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +11 -0
  33. package/dist/runtime/components/Media/MediaUploader.vue +1 -1
  34. package/dist/runtime/components/Shared/AppIcon.d.vue.ts +1 -1
  35. package/dist/runtime/components/Shared/AppIcon.vue.d.ts +1 -1
  36. package/dist/runtime/components/Shared/CardSizeToggle.d.vue.ts +11 -0
  37. package/dist/runtime/components/Shared/CardSizeToggle.vue +19 -0
  38. package/dist/runtime/components/Shared/CardSizeToggle.vue.d.ts +11 -0
  39. package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +1 -1
  40. package/dist/runtime/components/Shared/DetailSheet.vue +9 -5
  41. package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +1 -1
  42. package/dist/runtime/components/Shared/EmptyNote.d.vue.ts +19 -0
  43. package/dist/runtime/components/Shared/EmptyNote.vue +25 -0
  44. package/dist/runtime/components/Shared/EmptyNote.vue.d.ts +19 -0
  45. package/dist/runtime/components/Shared/KpiTile.d.vue.ts +6 -0
  46. package/dist/runtime/components/Shared/KpiTile.vue +10 -9
  47. package/dist/runtime/components/Shared/KpiTile.vue.d.ts +6 -0
  48. package/dist/runtime/components/Shared/PageHeader.vue +15 -4
  49. package/dist/runtime/components/Shared/RecordLayout.vue +1 -2
  50. package/dist/runtime/components/Shared/RecordRailCard.vue +3 -3
  51. package/dist/runtime/components/Shared/SelectionBar.d.vue.ts +22 -0
  52. package/dist/runtime/components/Shared/SelectionBar.vue +40 -0
  53. package/dist/runtime/components/Shared/SelectionBar.vue.d.ts +22 -0
  54. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +36 -2
  55. package/dist/runtime/components/Shared/SidebarNav.vue +74 -25
  56. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +36 -2
  57. package/dist/runtime/components/Shared/SidebarNavAreas.d.vue.ts +7 -0
  58. package/dist/runtime/components/Shared/SidebarNavAreas.vue +291 -0
  59. package/dist/runtime/components/Shared/SidebarNavAreas.vue.d.ts +7 -0
  60. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +1 -9
  61. package/dist/runtime/components/Shared/SidebarNavLink.vue +31 -5
  62. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +1 -9
  63. package/dist/runtime/components/Shared/SortHeader.vue +6 -2
  64. package/dist/runtime/components/Shared/StateCard.vue +1 -1
  65. package/dist/runtime/components/Shared/TabBar.vue +6 -7
  66. package/dist/runtime/components/Shared/Wizard.d.vue.ts +11 -1
  67. package/dist/runtime/components/Shared/Wizard.vue +9 -5
  68. package/dist/runtime/components/Shared/Wizard.vue.d.ts +11 -1
  69. package/dist/runtime/composables/useMediaLibrary.d.ts +12 -0
  70. package/dist/runtime/composables/useSavedViews.d.ts +11 -1
  71. package/dist/runtime/composables/useSavedViews.js +4 -4
  72. package/dist/runtime/composables/useWidgetFigures.d.ts +12 -0
  73. package/dist/runtime/composables/useWidgetFigures.js +24 -0
  74. package/dist/runtime/composables/useWidgetHeader.d.ts +15 -0
  75. package/dist/runtime/composables/useWidgetHeader.js +15 -0
  76. package/dist/runtime/lib/auditEnvelope.d.ts +43 -0
  77. package/dist/runtime/lib/auditEnvelope.js +55 -0
  78. package/dist/runtime/lib/cardSize.d.ts +13 -0
  79. package/dist/runtime/lib/cardSize.js +20 -0
  80. package/dist/runtime/lib/dashboardIcons.js +2 -0
  81. package/dist/runtime/lib/dataTableFilters.d.ts +22 -0
  82. package/dist/runtime/lib/dataTableFilters.js +5 -0
  83. package/dist/runtime/lib/facets.d.ts +13 -0
  84. package/dist/runtime/lib/formLayout.d.ts +74 -4
  85. package/dist/runtime/lib/formLayout.js +53 -6
  86. package/dist/runtime/lib/gridTracks.d.ts +14 -1
  87. package/dist/runtime/lib/gridTracks.js +13 -2
  88. package/dist/runtime/lib/isoLists.d.ts +37 -2
  89. package/dist/runtime/lib/isoLists.js +54 -1
  90. package/dist/runtime/lib/notice.d.ts +14 -0
  91. package/dist/runtime/lib/notice.js +19 -0
  92. package/dist/runtime/lib/pageSize.d.ts +15 -0
  93. package/dist/runtime/lib/pageSize.js +26 -0
  94. package/dist/runtime/lib/sidebarAreas.d.ts +46 -0
  95. package/dist/runtime/lib/sidebarAreas.js +25 -0
  96. package/dist/runtime/lib/sidebarNav.d.ts +39 -2
  97. package/dist/runtime/lib/sidebarNav.js +30 -4
  98. package/dist/runtime/lib/solarIconMap.d.ts +2 -1
  99. package/dist/runtime/lib/solarIconMap.js +50 -4
  100. package/dist/runtime/lib/swipeToClose.d.ts +25 -0
  101. package/dist/runtime/lib/swipeToClose.js +118 -0
  102. package/dist/runtime/lib/tableExport.d.ts +31 -0
  103. package/dist/runtime/lib/tableExport.js +143 -0
  104. package/dist/runtime/lib/talkback.d.ts +5 -0
  105. package/dist/runtime/lib/talkback.js +8 -0
  106. package/dist/runtime/lib/talkbackTokenBatch.d.ts +35 -0
  107. package/dist/runtime/lib/talkbackTokenBatch.js +101 -0
  108. package/dist/runtime/types/nav.d.ts +9 -0
  109. package/dist/types.d.mts +4 -0
  110. package/package.json +4 -3
@@ -0,0 +1,143 @@
1
+ function csvField(value, sep) {
2
+ if (value === null || value === void 0) return "";
3
+ const s = typeof value === "number" ? String(value).replace(".", sep === ";" ? "," : ".") : String(value);
4
+ return /["\r\n]/.test(s) || s.includes(sep) ? `"${s.replace(/"/g, '""')}"` : s;
5
+ }
6
+ export function toCsv(sheet, sep = ";") {
7
+ const lines = [sheet.headers, ...sheet.rows].map((r) => r.map((v) => csvField(v, sep)).join(sep));
8
+ return `\uFEFF${lines.join("\r\n")}\r
9
+ `;
10
+ }
11
+ const INVALID_XML = /[\u0000-\u0008\u000B\u000C\u000E-\u001F￾￿]/g;
12
+ function xml(s) {
13
+ return s.replace(INVALID_XML, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
14
+ }
15
+ export function columnLetter(index) {
16
+ let n = index + 1;
17
+ let out = "";
18
+ while (n > 0) {
19
+ const m = (n - 1) % 26;
20
+ out = String.fromCharCode(65 + m) + out;
21
+ n = Math.floor((n - 1) / 26);
22
+ }
23
+ return out;
24
+ }
25
+ function cellXml(value, ref, style) {
26
+ if (value === null || value === void 0 || value === "") return "";
27
+ const s = style ? ` s="${style}"` : "";
28
+ if (typeof value === "number" && Number.isFinite(value)) return `<c r="${ref}"${s}><v>${value}</v></c>`;
29
+ return `<c r="${ref}"${s} t="inlineStr"><is><t xml:space="preserve">${xml(String(value))}</t></is></c>`;
30
+ }
31
+ function sheetXml(sheet) {
32
+ const rows = [sheet.headers, ...sheet.rows].map((r, ri) => {
33
+ const cells = r.map((v, ci) => cellXml(v, `${columnLetter(ci)}${ri + 1}`, ri === 0 ? 1 : 0)).join("");
34
+ return `<row r="${ri + 1}">${cells}</row>`;
35
+ }).join("");
36
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetViews><sheetView workbookViewId="0"><pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews><sheetData>${rows}</sheetData></worksheet>`;
37
+ }
38
+ function sheetTitle(name) {
39
+ const clean = (name ?? "Export").replace(/[[\]:*?/\\]/g, " ").trim() || "Export";
40
+ return clean.slice(0, 31);
41
+ }
42
+ function xlsxFiles(sheet) {
43
+ return [
44
+ ["[Content_Types].xml", '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/></Types>'],
45
+ ["_rels/.rels", '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>'],
46
+ ["xl/workbook.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="${xml(sheetTitle(sheet.sheetName))}" sheetId="1" r:id="rId1"/></sheets></workbook>`],
47
+ ["xl/_rels/workbook.xml.rels", '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>'],
48
+ ["xl/styles.xml", '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="2"><font><sz val="11"/><name val="Calibri"/></font><font><b/><sz val="11"/><name val="Calibri"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/></cellXfs></styleSheet>'],
49
+ ["xl/worksheets/sheet1.xml", sheetXml(sheet)]
50
+ ];
51
+ }
52
+ let crcTable = null;
53
+ export function crc32(bytes) {
54
+ if (!crcTable) {
55
+ crcTable = new Uint32Array(256);
56
+ for (let n = 0; n < 256; n++) {
57
+ let c = n;
58
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
59
+ crcTable[n] = c >>> 0;
60
+ }
61
+ }
62
+ let crc = 4294967295;
63
+ for (let i = 0; i < bytes.length; i++) crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
64
+ return (crc ^ 4294967295) >>> 0;
65
+ }
66
+ function zipStored(files) {
67
+ const enc = new TextEncoder();
68
+ const locals = [];
69
+ const centrals = [];
70
+ let offset = 0;
71
+ for (const [name, data] of files) {
72
+ const nameBytes = enc.encode(name);
73
+ const crc = crc32(data);
74
+ const local = new Uint8Array(30 + nameBytes.length);
75
+ const lv = new DataView(local.buffer);
76
+ lv.setUint32(0, 67324752, true);
77
+ lv.setUint16(4, 20, true);
78
+ lv.setUint16(6, 2048, true);
79
+ lv.setUint16(8, 0, true);
80
+ lv.setUint16(10, 0, true);
81
+ lv.setUint16(12, 33, true);
82
+ lv.setUint32(14, crc, true);
83
+ lv.setUint32(18, data.length, true);
84
+ lv.setUint32(22, data.length, true);
85
+ lv.setUint16(26, nameBytes.length, true);
86
+ lv.setUint16(28, 0, true);
87
+ local.set(nameBytes, 30);
88
+ locals.push(local, data);
89
+ const central = new Uint8Array(46 + nameBytes.length);
90
+ const cv = new DataView(central.buffer);
91
+ cv.setUint32(0, 33639248, true);
92
+ cv.setUint16(4, 20, true);
93
+ cv.setUint16(6, 20, true);
94
+ cv.setUint16(8, 2048, true);
95
+ cv.setUint16(10, 0, true);
96
+ cv.setUint16(12, 0, true);
97
+ cv.setUint16(14, 33, true);
98
+ cv.setUint32(16, crc, true);
99
+ cv.setUint32(20, data.length, true);
100
+ cv.setUint32(24, data.length, true);
101
+ cv.setUint16(28, nameBytes.length, true);
102
+ cv.setUint32(42, offset, true);
103
+ central.set(nameBytes, 46);
104
+ centrals.push(central);
105
+ offset += local.length + data.length;
106
+ }
107
+ const cdSize = centrals.reduce((n, c) => n + c.length, 0);
108
+ const end = new Uint8Array(22);
109
+ const ev = new DataView(end.buffer);
110
+ ev.setUint32(0, 101010256, true);
111
+ ev.setUint16(8, files.length, true);
112
+ ev.setUint16(10, files.length, true);
113
+ ev.setUint32(12, cdSize, true);
114
+ ev.setUint32(16, offset, true);
115
+ const parts = [...locals, ...centrals, end];
116
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
117
+ let at = 0;
118
+ for (const p of parts) {
119
+ out.set(p, at);
120
+ at += p.length;
121
+ }
122
+ return out;
123
+ }
124
+ export function toXlsx(sheet) {
125
+ const enc = new TextEncoder();
126
+ return zipStored(xlsxFiles(sheet).map(([name, text]) => [name, enc.encode(text)]));
127
+ }
128
+ export function exportFileName(base, ext, now = /* @__PURE__ */ new Date()) {
129
+ const day = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
130
+ const clean = base.replace(/[\\/:*?"<>|]+/g, " ").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "export";
131
+ return `${clean}-${day}.${ext}`;
132
+ }
133
+ export function downloadExport(sheet, format, fileBase) {
134
+ const blob = format === "csv" ? new Blob([toCsv(sheet)], { type: "text/csv;charset=utf-8" }) : new Blob([toXlsx(sheet)], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
135
+ const url = URL.createObjectURL(blob);
136
+ const a = document.createElement("a");
137
+ a.href = url;
138
+ a.download = exportFileName(fileBase, format);
139
+ document.body.appendChild(a);
140
+ a.click();
141
+ a.remove();
142
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
143
+ }
@@ -29,6 +29,11 @@ export interface PlatformTalkbackConfig {
29
29
  * `@revenexx/talkback-js/testing` satisfies it.
30
30
  */
31
31
  client?: TalkbackOptions['client'];
32
+ /**
33
+ * The fetch the mints go out through, under the subscription-token batching
34
+ * (`lib/talkbackTokenBatch.ts`). Left unset outside tests.
35
+ */
36
+ fetch?: typeof globalThis.fetch;
32
37
  debug?: boolean;
33
38
  }
34
39
  /**
@@ -1,4 +1,5 @@
1
1
  import { shallowRef } from "vue";
2
+ import { createSubscriptionTokenBatchFetch } from "./talkbackTokenBatch.js";
2
3
  export function decodeJwtSub(jwt) {
3
4
  if (!jwt) return null;
4
5
  try {
@@ -33,6 +34,13 @@ export async function createPlatformTalkback(config) {
33
34
  tokenEndpoint: config.tokenEndpoint,
34
35
  subscriptionTokenEndpoint: config.subscriptionTokenEndpoint,
35
36
  accessToken,
37
+ // The subscription tokens a page asks for in the same few milliseconds go out
38
+ // as one batch request where the facade offers it, and one by one where it
39
+ // does not (PE-990) — eleven mints on a cockpit boot become one.
40
+ fetch: createSubscriptionTokenBatchFetch({
41
+ endpoint: config.subscriptionTokenEndpoint,
42
+ ...config.fetch ? { fetch: config.fetch } : {}
43
+ }),
36
44
  ...config.client ? { client: config.client } : {},
37
45
  ...config.debug === void 0 ? {} : { debug: config.debug }
38
46
  });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * One request for the subscription tokens a page asks for at once (PE-990).
3
+ *
4
+ * `@revenexx/talkback-js` mints a subscription token per channel, each its own
5
+ * `POST /v1/subscription-tokens` — eleven of them on a cockpit boot, one per
6
+ * channel the shell and the studios open. The facade answers several at once at
7
+ * `POST /v1/subscription-tokens/batch`. The package has no batch hook, but it does
8
+ * take a `fetch`, so this is a `fetch` that recognises the single mint, holds it
9
+ * for a few milliseconds, and sends whatever arrived in that window as one batch —
10
+ * then hands each caller the single answer it asked for.
11
+ *
12
+ * WHAT IT NEVER CHANGES IS THE ANSWER A CHANNEL GETS. Every fallback ends in the
13
+ * single request the package would have sent itself:
14
+ *
15
+ * - A facade without the batch route (404/405) is remembered for the life of the
16
+ * client, and from then on every mint goes straight through as before. That is
17
+ * what lets this ship ahead of the facade.
18
+ * - Any other refusal of the batch — one foreign channel refuses the whole batch,
19
+ * a 429, a 5xx — or a network failure sends each channel on its own, so the good
20
+ * channels still get their tokens and the bad one gets its own error, exactly as
21
+ * without batching.
22
+ * - A request that is not a plain `{ channel }` mint (a body with `info` or
23
+ * `override`, another endpoint, another method) is never touched.
24
+ */
25
+ export interface SubscriptionTokenBatchOptions {
26
+ /** The single-mint URL; the batch lives at `${endpoint}/batch`. */
27
+ endpoint: string;
28
+ /** The fetch every request finally goes out through. */
29
+ fetch?: typeof globalThis.fetch;
30
+ /** How long a mint waits for company, in ms. A page's boot subscribes over a few ticks. */
31
+ windowMs?: number;
32
+ /** The facade's per-request cap (`TALKBACK_MAX_SUBS_PER_TOKEN`, 32 by default). */
33
+ maxPerBatch?: number;
34
+ }
35
+ export declare function createSubscriptionTokenBatchFetch(opts: SubscriptionTokenBatchOptions): typeof globalThis.fetch;
@@ -0,0 +1,101 @@
1
+ function plainMintChannel(init) {
2
+ if (!init || typeof init.body !== "string") return null;
3
+ try {
4
+ const body = JSON.parse(init.body);
5
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
6
+ const keys = Object.keys(body);
7
+ const channel = body.channel;
8
+ if (keys.length !== 1 || typeof channel !== "string" || !channel) return null;
9
+ return channel;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ function urlOf(input) {
15
+ if (typeof input === "string") return input;
16
+ if (input instanceof URL) return input.href;
17
+ return input.url;
18
+ }
19
+ function headerKey(init) {
20
+ const h = new Headers(init.headers);
21
+ return JSON.stringify([h.get("authorization"), h.get("x-revenexx-tenant"), init.credentials ?? null]);
22
+ }
23
+ function jsonResponse(body, status = 200) {
24
+ return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
25
+ }
26
+ export function createSubscriptionTokenBatchFetch(opts) {
27
+ const baseFetch = opts.fetch ?? ((input, init) => globalThis.fetch(input, init));
28
+ const endpoint = opts.endpoint;
29
+ const batchEndpoint = `${endpoint.replace(/\/+$/, "")}/batch`;
30
+ const windowMs = opts.windowMs ?? 10;
31
+ const maxPerBatch = Math.max(1, opts.maxPerBatch ?? 32);
32
+ let supported;
33
+ const groups = /* @__PURE__ */ new Map();
34
+ function sendSingle(p) {
35
+ baseFetch(p.input, p.init).then(p.resolve, p.reject);
36
+ }
37
+ async function flush(key) {
38
+ const group = groups.get(key);
39
+ if (!group) return;
40
+ groups.delete(key);
41
+ if (group.timer) clearTimeout(group.timer);
42
+ const members = group.members;
43
+ if (members.length === 1 || supported === false) {
44
+ members.forEach(sendSingle);
45
+ return;
46
+ }
47
+ let res;
48
+ try {
49
+ res = await baseFetch(batchEndpoint, {
50
+ ...group.init,
51
+ body: JSON.stringify({ channels: members.map((m) => m.channel) })
52
+ });
53
+ } catch {
54
+ members.forEach(sendSingle);
55
+ return;
56
+ }
57
+ if (res.status === 404 || res.status === 405) {
58
+ supported = false;
59
+ members.forEach(sendSingle);
60
+ return;
61
+ }
62
+ if (!res.ok) {
63
+ members.forEach(sendSingle);
64
+ return;
65
+ }
66
+ let tokens = null;
67
+ try {
68
+ const body = await res.json();
69
+ tokens = Array.isArray(body.tokens) ? body.tokens : null;
70
+ } catch {
71
+ tokens = null;
72
+ }
73
+ if (!tokens) {
74
+ members.forEach(sendSingle);
75
+ return;
76
+ }
77
+ supported = true;
78
+ const byChannel = new Map(tokens.map((t) => [t.channel, t]));
79
+ for (const m of members) {
80
+ const t = byChannel.get(m.channel);
81
+ if (t?.token) m.resolve(jsonResponse({ token: t.token, expires_at: t.expires_at, channel: t.channel }));
82
+ else sendSingle(m);
83
+ }
84
+ }
85
+ return function batchingFetch(input, init) {
86
+ const method = (init?.method ?? "GET").toUpperCase();
87
+ const channel = method === "POST" && urlOf(input) === endpoint ? plainMintChannel(init) : null;
88
+ if (channel === null || supported === false) return baseFetch(input, init);
89
+ const key = headerKey(init);
90
+ return new Promise((resolve, reject) => {
91
+ let group = groups.get(key);
92
+ if (!group) {
93
+ group = { init, members: [], timer: null };
94
+ groups.set(key, group);
95
+ group.timer = setTimeout(() => void flush(key), windowMs);
96
+ }
97
+ group.members.push({ channel, input, init, resolve, reject });
98
+ if (group.members.length >= maxPerBatch) void flush(key);
99
+ });
100
+ };
101
+ }
@@ -28,10 +28,19 @@ export type SidebarNavItem = {
28
28
  children?: SidebarNavItem[];
29
29
  /** Stable `v-for` key. Defaults to `to ?? label`. */
30
30
  key?: string;
31
+ /**
32
+ * A sub-heading drawn above this row, inside its section. A long section
33
+ * reads in bands (Marketing: Planen, Ausspielen, Gewinnen, Anreize); the
34
+ * first row of each band carries the band's name. A heading is a label, not
35
+ * a row: it links nowhere and is skipped by the keyboard.
36
+ */
37
+ heading?: string;
31
38
  };
32
39
  export type SidebarNavSection = {
33
40
  key: string;
34
41
  /** Empty/omitted → no group label (pinned items at the top). */
35
42
  label?: string;
43
+ /** Area mode: the section's icon, drawn in a tile. */
44
+ icon?: Component;
36
45
  items: SidebarNavItem[];
37
46
  };
package/dist/types.d.mts CHANGED
@@ -26,6 +26,10 @@ export { type DetailSheetBinding } from '../dist/runtime/composables/useDetailSh
26
26
 
27
27
  export { type FloatingPanelDock, type FloatingPanelMode, type FloatingPanelOptions } from '../dist/runtime/composables/useFloatingPanel.js'
28
28
 
29
+ export { type WidgetHeaderStatus } from '../dist/runtime/composables/useWidgetHeader.js'
30
+
31
+ export { type WidgetFigure } from '../dist/runtime/composables/useWidgetFigures.js'
32
+
29
33
  export { type IsoOption } from '../dist/runtime/lib/isoLists.js'
30
34
 
31
35
  export { type Translate, type TranslateNamed, type Translator } from '../dist/runtime/lib/i18nBridge.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-shared",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "Shared revenexx Studio shell primitives (PageHeader, SidebarNav, StateCard, DataTable, …) + the platform provider contract (usePlatformAuth/usePlatformTenant). A Nuxt module — add it to `modules`.",
6
6
  "main": "./dist/module.mjs",
@@ -29,7 +29,7 @@
29
29
  "grid-layout-plus": "^1.1.1"
30
30
  },
31
31
  "peerDependencies": {
32
- "@revenexx/studio": "^0.2.3",
32
+ "@revenexx/studio": "^0.2.10",
33
33
  "@revenexx/talkback-js": "^0.3.0",
34
34
  "@solar-icons/nuxt": "^1.2.0",
35
35
  "centrifuge": ">=5.2.0",
@@ -46,8 +46,9 @@
46
46
  }
47
47
  },
48
48
  "devDependencies": {
49
- "@nuxt/module-builder": "^1.0.2",
50
49
  "@nuxt/kit": "^4.0.0",
50
+ "@nuxt/module-builder": "^1.0.2",
51
+ "@nuxt/schema": "^4.6.3",
51
52
  "@revenexx/talkback-js": "^0.3.0",
52
53
  "centrifuge": "^5.4.0",
53
54
  "nuxt": "^4.2.2",