@bpmnkit/plugins 0.0.8

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 (140) hide show
  1. package/README.md +153 -0
  2. package/dist/ai-bridge/css.d.ts +2 -0
  3. package/dist/ai-bridge/css.js +366 -0
  4. package/dist/ai-bridge/index.d.ts +44 -0
  5. package/dist/ai-bridge/index.js +132 -0
  6. package/dist/ai-bridge/panel.d.ts +27 -0
  7. package/dist/ai-bridge/panel.js +715 -0
  8. package/dist/ascii-view/css.d.ts +4 -0
  9. package/dist/ascii-view/css.js +83 -0
  10. package/dist/ascii-view/index.d.ts +25 -0
  11. package/dist/ascii-view/index.js +92 -0
  12. package/dist/command-palette/css.d.ts +4 -0
  13. package/dist/command-palette/css.js +157 -0
  14. package/dist/command-palette/index.d.ts +53 -0
  15. package/dist/command-palette/index.js +378 -0
  16. package/dist/command-palette-editor/index.d.ts +35 -0
  17. package/dist/command-palette-editor/index.js +60 -0
  18. package/dist/config-panel/css.d.ts +4 -0
  19. package/dist/config-panel/css.js +719 -0
  20. package/dist/config-panel/index.d.ts +36 -0
  21. package/dist/config-panel/index.js +73 -0
  22. package/dist/config-panel/renderer.d.ts +127 -0
  23. package/dist/config-panel/renderer.js +1162 -0
  24. package/dist/config-panel/types.d.ts +118 -0
  25. package/dist/config-panel/types.js +2 -0
  26. package/dist/config-panel-bpmn/index.d.ts +58 -0
  27. package/dist/config-panel-bpmn/index.js +1320 -0
  28. package/dist/config-panel-bpmn/template-engine.d.ts +17 -0
  29. package/dist/config-panel-bpmn/template-engine.js +307 -0
  30. package/dist/config-panel-bpmn/template-to-service-task.d.ts +30 -0
  31. package/dist/config-panel-bpmn/template-to-service-task.js +92 -0
  32. package/dist/config-panel-bpmn/template-types.d.ts +126 -0
  33. package/dist/config-panel-bpmn/template-types.js +6 -0
  34. package/dist/config-panel-bpmn/templates/generated.d.ts +3 -0
  35. package/dist/config-panel-bpmn/templates/generated.js +57865 -0
  36. package/dist/config-panel-bpmn/util.d.ts +46 -0
  37. package/dist/config-panel-bpmn/util.js +195 -0
  38. package/dist/dmn-editor/css.d.ts +3 -0
  39. package/dist/dmn-editor/css.js +667 -0
  40. package/dist/dmn-editor/dmn-editor.d.ts +34 -0
  41. package/dist/dmn-editor/dmn-editor.js +646 -0
  42. package/dist/dmn-editor/drd-canvas.d.ts +85 -0
  43. package/dist/dmn-editor/drd-canvas.js +1087 -0
  44. package/dist/dmn-editor/index.d.ts +3 -0
  45. package/dist/dmn-editor/index.js +2 -0
  46. package/dist/dmn-viewer/css.d.ts +3 -0
  47. package/dist/dmn-viewer/css.js +151 -0
  48. package/dist/dmn-viewer/dmn-viewer.d.ts +30 -0
  49. package/dist/dmn-viewer/dmn-viewer.js +160 -0
  50. package/dist/dmn-viewer/feel.d.ts +3 -0
  51. package/dist/dmn-viewer/feel.js +2 -0
  52. package/dist/dmn-viewer/index.d.ts +14 -0
  53. package/dist/dmn-viewer/index.js +12 -0
  54. package/dist/element-docs/content.d.ts +20 -0
  55. package/dist/element-docs/content.js +1032 -0
  56. package/dist/element-docs/index.d.ts +7 -0
  57. package/dist/element-docs/index.js +643 -0
  58. package/dist/feel-playground/css.d.ts +4 -0
  59. package/dist/feel-playground/css.js +292 -0
  60. package/dist/feel-playground/index.d.ts +4 -0
  61. package/dist/feel-playground/index.js +3 -0
  62. package/dist/feel-playground/playground-plugin.d.ts +14 -0
  63. package/dist/feel-playground/playground-plugin.js +292 -0
  64. package/dist/form-editor/css.d.ts +3 -0
  65. package/dist/form-editor/css.js +703 -0
  66. package/dist/form-editor/form-editor.d.ts +48 -0
  67. package/dist/form-editor/form-editor.js +1039 -0
  68. package/dist/form-editor/index.d.ts +3 -0
  69. package/dist/form-editor/index.js +2 -0
  70. package/dist/form-viewer/css.d.ts +3 -0
  71. package/dist/form-viewer/css.js +303 -0
  72. package/dist/form-viewer/form-viewer.d.ts +29 -0
  73. package/dist/form-viewer/form-viewer.js +376 -0
  74. package/dist/form-viewer/index.d.ts +12 -0
  75. package/dist/form-viewer/index.js +11 -0
  76. package/dist/history/checkpoint.d.ts +10 -0
  77. package/dist/history/checkpoint.js +92 -0
  78. package/dist/history/css.d.ts +2 -0
  79. package/dist/history/css.js +130 -0
  80. package/dist/history/history-panel.d.ts +12 -0
  81. package/dist/history/history-panel.js +142 -0
  82. package/dist/history/index.d.ts +5 -0
  83. package/dist/history/index.js +3 -0
  84. package/dist/main-menu/css.d.ts +4 -0
  85. package/dist/main-menu/css.js +272 -0
  86. package/dist/main-menu/index.d.ts +59 -0
  87. package/dist/main-menu/index.js +285 -0
  88. package/dist/minimap/css.d.ts +10 -0
  89. package/dist/minimap/css.js +54 -0
  90. package/dist/minimap/index.d.ts +38 -0
  91. package/dist/minimap/index.js +69 -0
  92. package/dist/minimap/minimap.d.ts +35 -0
  93. package/dist/minimap/minimap.js +166 -0
  94. package/dist/optimize/index.d.ts +21 -0
  95. package/dist/optimize/index.js +304 -0
  96. package/dist/process-runner/css.d.ts +2 -0
  97. package/dist/process-runner/css.js +467 -0
  98. package/dist/process-runner/index.d.ts +56 -0
  99. package/dist/process-runner/index.js +549 -0
  100. package/dist/storage/auto-save.d.ts +11 -0
  101. package/dist/storage/auto-save.js +44 -0
  102. package/dist/storage/css.d.ts +2 -0
  103. package/dist/storage/css.js +2 -0
  104. package/dist/storage/db.d.ts +99 -0
  105. package/dist/storage/db.js +116 -0
  106. package/dist/storage/dialog.d.ts +15 -0
  107. package/dist/storage/dialog.js +201 -0
  108. package/dist/storage/export.d.ts +3 -0
  109. package/dist/storage/export.js +137 -0
  110. package/dist/storage/index.d.ts +44 -0
  111. package/dist/storage/index.js +201 -0
  112. package/dist/storage/sidebar.d.ts +2 -0
  113. package/dist/storage/sidebar.js +2 -0
  114. package/dist/storage/storage-api.d.ts +76 -0
  115. package/dist/storage/storage-api.js +342 -0
  116. package/dist/storage/types.d.ts +44 -0
  117. package/dist/storage/types.js +2 -0
  118. package/dist/storage-tabs-bridge/index.d.ts +96 -0
  119. package/dist/storage-tabs-bridge/index.js +502 -0
  120. package/dist/tabs/css.d.ts +3 -0
  121. package/dist/tabs/css.js +642 -0
  122. package/dist/tabs/file-resolver.d.ts +71 -0
  123. package/dist/tabs/file-resolver.js +46 -0
  124. package/dist/tabs/index.d.ts +15 -0
  125. package/dist/tabs/index.js +13 -0
  126. package/dist/tabs/tabs-plugin.d.ts +250 -0
  127. package/dist/tabs/tabs-plugin.js +1119 -0
  128. package/dist/token-highlight/css.d.ts +2 -0
  129. package/dist/token-highlight/css.js +81 -0
  130. package/dist/token-highlight/index.d.ts +44 -0
  131. package/dist/token-highlight/index.js +164 -0
  132. package/dist/watermark/css.d.ts +10 -0
  133. package/dist/watermark/css.js +54 -0
  134. package/dist/watermark/index.d.ts +48 -0
  135. package/dist/watermark/index.js +64 -0
  136. package/dist/zoom-controls/css.d.ts +4 -0
  137. package/dist/zoom-controls/css.js +48 -0
  138. package/dist/zoom-controls/index.d.ts +36 -0
  139. package/dist/zoom-controls/index.js +111 -0
  140. package/package.json +121 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @bpmnkit/canvas-plugin-storage-tabs-bridge
3
+ *
4
+ * Wires the storage plugin and tabs plugin together, handling:
5
+ * - Tab ↔ storage file mapping
6
+ * - Auto-save for DMN/Form tabs
7
+ * - MRU (most-recently-used) file tracking per project
8
+ * - File-search commands in the command palette
9
+ * - Ctrl+E file switcher
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import type { CanvasPlugin } from "@bpmnkit/canvas";
14
+ import type { CommandPalettePlugin } from "../command-palette/index.js";
15
+ import type { MainMenuApi } from "../main-menu/index.js";
16
+ import type { StorageApi } from "../storage/index.js";
17
+ import { InMemoryFileResolver } from "../tabs/index.js";
18
+ import type { TabConfig, TabsApi, WelcomeExample, WelcomeSection } from "../tabs/index.js";
19
+ export type { TabConfig, TabsApi, WelcomeExample, WelcomeSection };
20
+ export { InMemoryFileResolver };
21
+ export type { StorageApi };
22
+ export interface StorageTabsBridgeOptions {
23
+ /** Main menu plugin — required for storage project navigation. */
24
+ mainMenu: CanvasPlugin & {
25
+ api: MainMenuApi;
26
+ };
27
+ /**
28
+ * File resolver for DMN/Form reference navigation.
29
+ * Use `InMemoryFileResolver` to enable cross-file "Open Decision" / "Open Form".
30
+ * The bridge automatically calls `registerDmn`/`registerForm` when files are opened.
31
+ */
32
+ resolver?: InMemoryFileResolver;
33
+ /**
34
+ * Factory called each time the welcome screen renders to get example items.
35
+ * Receives the `TabsApi` so examples can open tabs.
36
+ */
37
+ getExamples?: (api: TabsApi) => WelcomeExample[];
38
+ /** Dynamic sections shown below examples on the welcome screen. */
39
+ getWelcomeSections?: () => WelcomeSection[];
40
+ /** Called when the welcome screen becomes visible. Use to hide editor HUD. */
41
+ onWelcomeShow?: () => void;
42
+ /**
43
+ * Called when "New diagram" is clicked on the welcome screen.
44
+ * Defaults to opening a new BPMN tab with the sample diagram.
45
+ */
46
+ onNewDiagram?: () => void;
47
+ /**
48
+ * Called after a tab is activated, after the bridge has updated storage tracking.
49
+ * Use this to load BPMN into the editor, update HUD visibility, etc.
50
+ */
51
+ onTabActivate?: (id: string, config: TabConfig) => void;
52
+ /** Called when a project is left, after the bridge has cleared its maps. */
53
+ onLeaveProject?: () => void;
54
+ /** Called when storage data is ready (IndexedDB caches loaded). */
55
+ onReady?: () => void;
56
+ /** Optional command palette — enables file-search commands when provided. */
57
+ palette?: CommandPalettePlugin;
58
+ /** Title shown in the main menu when no project is open. Defaults to "". */
59
+ initialTitle?: string;
60
+ /**
61
+ * Called when a tab's download button is clicked.
62
+ * Defaults to serializing the content and triggering a browser download.
63
+ */
64
+ onDownloadTab?: (config: TabConfig) => void;
65
+ /** When true, enables built-in file import (file picker + drag-and-drop). */
66
+ enableFileImport?: boolean;
67
+ /**
68
+ * Side dock to manage automatically. When provided, the dock is hidden on the
69
+ * welcome screen and shown whenever a tab is activated — no manual wiring needed.
70
+ */
71
+ sideDock?: {
72
+ setVisible(visible: boolean): void;
73
+ };
74
+ /**
75
+ * Optional element to place in the center of the tabs bar.
76
+ * Passed through to the tabs plugin's `centerSlot` option.
77
+ */
78
+ centerSlot?: HTMLElement;
79
+ /**
80
+ * Called when raw source view mode is toggled on or off.
81
+ * Use this to disable editing UI (toolbar buttons, sidebar) while raw mode is active.
82
+ */
83
+ onRawModeChange?: (active: boolean) => void;
84
+ }
85
+ export interface StorageTabsBridgeResult {
86
+ tabsPlugin: CanvasPlugin & {
87
+ api: TabsApi;
88
+ };
89
+ storagePlugin: CanvasPlugin & {
90
+ api: StorageApi;
91
+ };
92
+ /** Install this plugin to activate storage↔tab sync, palette commands, and Ctrl+E. */
93
+ bridgePlugin: CanvasPlugin;
94
+ }
95
+ export declare function createStorageTabsBridge(options: StorageTabsBridgeOptions): StorageTabsBridgeResult;
96
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,502 @@
1
+ /**
2
+ * @bpmnkit/canvas-plugin-storage-tabs-bridge
3
+ *
4
+ * Wires the storage plugin and tabs plugin together, handling:
5
+ * - Tab ↔ storage file mapping
6
+ * - Auto-save for DMN/Form tabs
7
+ * - MRU (most-recently-used) file tracking per project
8
+ * - File-search commands in the command palette
9
+ * - Ctrl+E file switcher
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { Bpmn, Dmn, Form } from "@bpmnkit/core";
14
+ import { showInputDialog } from "../storage/index.js";
15
+ import { createStoragePlugin } from "../storage/index.js";
16
+ import { InMemoryFileResolver, createTabsPlugin } from "../tabs/index.js";
17
+ export { InMemoryFileResolver };
18
+ // ── Icons ─────────────────────────────────────────────────────────────────────
19
+ const FILE_ICON = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2h6l4 4v9H4z"/><polyline points="10,2 10,6 14,6"/></svg>';
20
+ // ── Factory ───────────────────────────────────────────────────────────────────
21
+ export function createStorageTabsBridge(options) {
22
+ // ── Bridge state ────────────────────────────────────────────────────────────
23
+ /** Maps tab ID → storage file ID for project-mode tabs. */
24
+ const tabIdToStorageFileId = new Map();
25
+ /** Reverse: storage file ID → tab ID. */
26
+ const storageFileIdToTabId = new Map();
27
+ /** Display name cache — kept in sync with file renames. */
28
+ const storageFileToName = new Map();
29
+ /** Type cache ("bpmn" | "dmn" | "form") — used in the file switcher. */
30
+ const storageFileToType = new Map();
31
+ /** MRU tab order for Ctrl+E cycling. */
32
+ let tabMruOrder = [];
33
+ /** Deregister function for current file-search palette commands. */
34
+ let removeFileCommands;
35
+ /** Last known project ID — used to detect project changes. */
36
+ let lastProjectId = null;
37
+ /**
38
+ * Late-bound reference to storagePlugin. Set immediately after createStoragePlugin
39
+ * returns. Safe to use in all callbacks because they are called at runtime.
40
+ */
41
+ let storageRef = null;
42
+ // ── Default download handler ────────────────────────────────────────────────
43
+ function defaultDownloadTab(config) {
44
+ let content;
45
+ let filename;
46
+ if (config.type === "bpmn") {
47
+ content = config.xml;
48
+ filename = config.name ?? "diagram.bpmn";
49
+ }
50
+ else if (config.type === "dmn") {
51
+ content = Dmn.export(config.defs);
52
+ filename = config.name ?? "decision.dmn";
53
+ }
54
+ else if (config.type === "form") {
55
+ content = Form.export(config.form);
56
+ filename = config.name ?? "form.form";
57
+ }
58
+ else {
59
+ return;
60
+ }
61
+ const blob = new Blob([content], { type: "application/octet-stream" });
62
+ const url = URL.createObjectURL(blob);
63
+ const a = document.createElement("a");
64
+ a.href = url;
65
+ a.download = filename;
66
+ a.click();
67
+ URL.revokeObjectURL(url);
68
+ }
69
+ // ── tabsPlugin ──────────────────────────────────────────────────────────────
70
+ const tabsPlugin = createTabsPlugin({
71
+ resolver: options.resolver,
72
+ get examples() {
73
+ return options.getExamples ? options.getExamples(tabsPlugin.api) : undefined;
74
+ },
75
+ getWelcomeSections: options.getWelcomeSections,
76
+ getRecentProjects() {
77
+ return (storageRef?.api.getRecentProjects().map(({ project, workspace }) => ({
78
+ label: project.name,
79
+ description: workspace.name,
80
+ onOpen: () => {
81
+ void storageRef?.api.openProject(project.id).then(() => {
82
+ options.mainMenu.api.setTitle(`${workspace.name} / ${project.name}`);
83
+ });
84
+ },
85
+ })) ?? null);
86
+ },
87
+ onNewDiagram() {
88
+ if (options.onNewDiagram) {
89
+ options.onNewDiagram();
90
+ }
91
+ else {
92
+ tabsPlugin.api.openTab({ type: "bpmn", xml: Bpmn.SAMPLE_XML, name: "New Diagram" });
93
+ }
94
+ },
95
+ onWelcomeShow: () => {
96
+ options.sideDock?.setVisible(false);
97
+ options.onWelcomeShow?.();
98
+ },
99
+ enableFileImport: options.enableFileImport,
100
+ centerSlot: options.centerSlot,
101
+ onRawModeChange: options.onRawModeChange,
102
+ onTabActivate(id, config) {
103
+ // Update storage current-file pointer
104
+ storageRef?.api.setCurrentFileId(tabIdToStorageFileId.get(id) ?? null);
105
+ // MRU tracking (project mode only)
106
+ const projectId = storageRef?.api.getCurrentProjectId();
107
+ if (projectId) {
108
+ tabMruOrder = [id, ...tabMruOrder.filter((tid) => tid !== id)];
109
+ const fileId = tabIdToStorageFileId.get(id);
110
+ if (fileId) {
111
+ void storageRef?.api.pushMruFile(projectId, fileId);
112
+ }
113
+ }
114
+ options.sideDock?.setVisible(true);
115
+ options.onTabActivate?.(id, config);
116
+ },
117
+ onTabChange(tabId) {
118
+ const fileId = tabIdToStorageFileId.get(tabId);
119
+ if (!fileId)
120
+ return;
121
+ const snapshots = tabsPlugin.api.getAllTabContent();
122
+ const snapshot = snapshots.find((s) => s.tabId === tabId);
123
+ if (snapshot) {
124
+ storageRef?.api.scheduleSave(fileId, snapshot.content);
125
+ }
126
+ },
127
+ onDownloadTab: options.onDownloadTab ?? defaultDownloadTab,
128
+ });
129
+ // ── storagePlugin ───────────────────────────────────────────────────────────
130
+ const storagePlugin = createStoragePlugin({
131
+ mainMenu: options.mainMenu,
132
+ getOpenTabs: () => tabsPlugin.api.getAllTabContent(),
133
+ initialTitle: options.initialTitle,
134
+ onLeaveProject() {
135
+ tabsPlugin.api.closeAllTabs();
136
+ tabIdToStorageFileId.clear();
137
+ storageFileIdToTabId.clear();
138
+ storageFileToName.clear();
139
+ storageFileToType.clear();
140
+ tabMruOrder = [];
141
+ options.onLeaveProject?.();
142
+ },
143
+ onReady() {
144
+ tabsPlugin.api.refreshWelcomeScreen();
145
+ options.onReady?.();
146
+ },
147
+ onOpenFile(file, content) {
148
+ let tabId = null;
149
+ if (file.type === "bpmn") {
150
+ tabId = tabsPlugin.api.openTab({ type: "bpmn", xml: content, name: file.name });
151
+ }
152
+ else if (file.type === "dmn") {
153
+ try {
154
+ const defs = Dmn.parse(content);
155
+ options.resolver?.registerDmn(defs);
156
+ tabId = tabsPlugin.api.openTab({ type: "dmn", defs, name: file.name });
157
+ }
158
+ catch {
159
+ console.error("[storage] Failed to parse DMN:", file.name);
160
+ }
161
+ }
162
+ else if (file.type === "form") {
163
+ try {
164
+ const form = Form.parse(content);
165
+ options.resolver?.registerForm(form);
166
+ tabId = tabsPlugin.api.openTab({ type: "form", form, name: file.name });
167
+ }
168
+ catch {
169
+ console.error("[storage] Failed to parse form:", file.name);
170
+ }
171
+ }
172
+ if (tabId !== null) {
173
+ tabIdToStorageFileId.set(tabId, file.id);
174
+ storageFileIdToTabId.set(file.id, tabId);
175
+ storageFileToName.set(file.id, file.name);
176
+ storageFileToType.set(file.id, file.type);
177
+ // onTabActivate fires synchronously inside openTab before the map is set,
178
+ // so correct currentFileId explicitly here.
179
+ storagePlugin.api.setCurrentFileId(file.id);
180
+ }
181
+ },
182
+ onRenameCurrentFile(fileId, name) {
183
+ storageFileToName.set(fileId, name);
184
+ const tabId = storageFileIdToTabId.get(fileId);
185
+ if (tabId)
186
+ tabsPlugin.api.renameTab(tabId, name);
187
+ rebuildFileCommands();
188
+ },
189
+ });
190
+ // Resolve the late-bound reference
191
+ storageRef = storagePlugin;
192
+ // ── File commands ───────────────────────────────────────────────────────────
193
+ function rebuildFileCommands() {
194
+ removeFileCommands?.();
195
+ removeFileCommands = undefined;
196
+ if (!options.palette)
197
+ return;
198
+ const projectId = storagePlugin.api.getCurrentProjectId();
199
+ if (!projectId)
200
+ return;
201
+ const cmds = [];
202
+ for (const [tabId, fileId] of tabIdToStorageFileId) {
203
+ const name = storageFileToName.get(fileId) ?? fileId;
204
+ const tid = tabId;
205
+ cmds.push({
206
+ id: `open-file-${fileId}`,
207
+ title: name,
208
+ description: "Switch to file",
209
+ action: () => tabsPlugin.api.setActiveTab(tid),
210
+ });
211
+ }
212
+ const currentFileId = storagePlugin.api.getCurrentFileId();
213
+ if (currentFileId && storageFileIdToTabId.has(currentFileId)) {
214
+ const fid = currentFileId;
215
+ const currentName = storageFileToName.get(fid) ?? "";
216
+ cmds.push({
217
+ id: "rename-current-file",
218
+ title: "Rename current file\u2026",
219
+ action: async () => {
220
+ const newName = await showInputDialog({
221
+ title: "Rename file",
222
+ defaultValue: currentName,
223
+ });
224
+ if (!newName || newName === currentName)
225
+ return;
226
+ storageFileToName.set(fid, newName);
227
+ const tabId = storageFileIdToTabId.get(fid);
228
+ if (tabId)
229
+ tabsPlugin.api.renameTab(tabId, newName);
230
+ void storagePlugin.api.renameFile(fid, newName);
231
+ },
232
+ });
233
+ }
234
+ removeFileCommands = options.palette.addCommands(cmds);
235
+ }
236
+ // ── File switcher (Ctrl+E / Cmd+E) ─────────────────────────────────────────
237
+ let fileSwitcherEl = null;
238
+ let fileSwitcherKeyHandler = null;
239
+ let fileSwitcherCtrlRelease = null;
240
+ let fileSwitcherCycle = null;
241
+ let bridgeContainer = null;
242
+ function closeFileSwitcher() {
243
+ fileSwitcherEl?.remove();
244
+ fileSwitcherEl = null;
245
+ if (fileSwitcherKeyHandler) {
246
+ document.removeEventListener("keydown", fileSwitcherKeyHandler);
247
+ fileSwitcherKeyHandler = null;
248
+ }
249
+ if (fileSwitcherCtrlRelease) {
250
+ document.removeEventListener("keyup", fileSwitcherCtrlRelease);
251
+ fileSwitcherCtrlRelease = null;
252
+ }
253
+ fileSwitcherCycle = null;
254
+ }
255
+ function openFileSwitcher() {
256
+ if (!storagePlugin.api.getCurrentProjectId())
257
+ return;
258
+ const isLight = bridgeContainer?.dataset.theme !== "dark";
259
+ let focusIdx = 0;
260
+ let isSearchMode = false;
261
+ const overlay = document.createElement("div");
262
+ overlay.className = `bpmnkit-palette-overlay${isLight ? " bpmnkit-palette--light" : ""}`;
263
+ overlay.setAttribute("role", "dialog");
264
+ overlay.setAttribute("aria-modal", "true");
265
+ overlay.setAttribute("aria-label", "Switch to file");
266
+ const panel = document.createElement("div");
267
+ panel.className = "bpmnkit-palette-panel";
268
+ const searchRow = document.createElement("div");
269
+ searchRow.className = "bpmnkit-palette-search";
270
+ const iconEl = document.createElement("span");
271
+ iconEl.className = "bpmnkit-palette-search-icon";
272
+ iconEl.innerHTML = FILE_ICON;
273
+ searchRow.appendChild(iconEl);
274
+ const input = document.createElement("input");
275
+ input.type = "text";
276
+ input.className = "bpmnkit-palette-input";
277
+ input.placeholder = "Switch to file\u2026";
278
+ input.setAttribute("autocomplete", "off");
279
+ input.setAttribute("spellcheck", "false");
280
+ searchRow.appendChild(input);
281
+ const kbdHint = document.createElement("span");
282
+ kbdHint.className = "bpmnkit-palette-kbd";
283
+ kbdHint.innerHTML = "<kbd>E</kbd> cycle &nbsp;<kbd>Tab</kbd> search &nbsp;<kbd>Esc</kbd> close";
284
+ searchRow.appendChild(kbdHint);
285
+ const list = document.createElement("div");
286
+ list.className = "bpmnkit-palette-list";
287
+ list.setAttribute("role", "listbox");
288
+ panel.appendChild(searchRow);
289
+ panel.appendChild(list);
290
+ overlay.appendChild(panel);
291
+ document.body.appendChild(overlay);
292
+ fileSwitcherEl = overlay;
293
+ function getEntries(query) {
294
+ const currentFileId = storagePlugin.api.getCurrentFileId();
295
+ const allTabIds = tabsPlugin.api.getTabIds();
296
+ const mru = tabMruOrder.filter((id) => allTabIds.includes(id));
297
+ for (const id of allTabIds) {
298
+ if (!mru.includes(id))
299
+ mru.push(id);
300
+ }
301
+ const q = query.toLowerCase();
302
+ return mru
303
+ .map((tabId) => {
304
+ const fileId = tabIdToStorageFileId.get(tabId) ?? "";
305
+ return {
306
+ tabId,
307
+ name: storageFileToName.get(fileId) ?? fileId,
308
+ type: storageFileToType.get(fileId) ?? "",
309
+ isCurrent: fileId === currentFileId,
310
+ };
311
+ })
312
+ .filter((e) => !q || e.name.toLowerCase().includes(q));
313
+ }
314
+ function renderList(query) {
315
+ list.innerHTML = "";
316
+ const entries = getEntries(query);
317
+ if (entries.length === 0) {
318
+ const empty = document.createElement("div");
319
+ empty.className = "bpmnkit-palette-empty";
320
+ empty.textContent = "No files found";
321
+ list.appendChild(empty);
322
+ return;
323
+ }
324
+ if (focusIdx >= entries.length)
325
+ focusIdx = 0;
326
+ entries.forEach((entry, i) => {
327
+ const item = document.createElement("div");
328
+ item.className = `bpmnkit-palette-item${i === focusIdx ? " bpmnkit-palette-focused" : ""}`;
329
+ item.setAttribute("role", "option");
330
+ item.setAttribute("aria-selected", String(i === focusIdx));
331
+ const nameEl = document.createElement("span");
332
+ nameEl.className = "bpmnkit-palette-item-title";
333
+ nameEl.textContent = entry.name;
334
+ if (entry.isCurrent)
335
+ nameEl.style.fontWeight = "600";
336
+ item.appendChild(nameEl);
337
+ const descEl = document.createElement("span");
338
+ descEl.className = "bpmnkit-palette-item-desc";
339
+ descEl.textContent = entry.isCurrent
340
+ ? `${entry.type.toUpperCase()} · current`
341
+ : entry.type.toUpperCase();
342
+ item.appendChild(descEl);
343
+ item.addEventListener("pointerenter", () => {
344
+ focusIdx = i;
345
+ updateFocus();
346
+ });
347
+ item.addEventListener("pointerdown", (e) => {
348
+ e.preventDefault();
349
+ tabsPlugin.api.setActiveTab(entry.tabId);
350
+ closeFileSwitcher();
351
+ });
352
+ list.appendChild(item);
353
+ });
354
+ }
355
+ function updateFocus() {
356
+ const items = list.querySelectorAll(".bpmnkit-palette-item");
357
+ items.forEach((item, i) => {
358
+ item.classList.toggle("bpmnkit-palette-focused", i === focusIdx);
359
+ item.setAttribute("aria-selected", String(i === focusIdx));
360
+ });
361
+ list.querySelector(".bpmnkit-palette-focused")?.scrollIntoView({
362
+ block: "nearest",
363
+ });
364
+ }
365
+ function enterSearchMode() {
366
+ if (isSearchMode)
367
+ return;
368
+ isSearchMode = true;
369
+ if (fileSwitcherCtrlRelease) {
370
+ document.removeEventListener("keyup", fileSwitcherCtrlRelease);
371
+ fileSwitcherCtrlRelease = null;
372
+ }
373
+ input.focus();
374
+ }
375
+ // Pre-focus the second entry (most recently previous file); no input focus in cycle mode
376
+ const initial = getEntries("");
377
+ focusIdx = initial.length > 1 ? 1 : 0;
378
+ renderList("");
379
+ input.addEventListener("input", () => {
380
+ focusIdx = 0;
381
+ renderList(input.value);
382
+ });
383
+ overlay.addEventListener("pointerdown", (e) => {
384
+ if (e.target === overlay)
385
+ closeFileSwitcher();
386
+ });
387
+ // Ctrl/Meta release → commit the highlighted file (cycle mode only)
388
+ fileSwitcherCtrlRelease = (e) => {
389
+ if (e.key === "Control" || e.key === "Meta") {
390
+ const entries = getEntries(input.value);
391
+ const entry = entries[focusIdx];
392
+ if (entry)
393
+ tabsPlugin.api.setActiveTab(entry.tabId);
394
+ closeFileSwitcher();
395
+ }
396
+ };
397
+ document.addEventListener("keyup", fileSwitcherCtrlRelease);
398
+ // Exposed to the global Ctrl+E handler for cycling
399
+ fileSwitcherCycle = () => {
400
+ if (isSearchMode)
401
+ return;
402
+ const entries = getEntries("");
403
+ if (entries.length > 0) {
404
+ focusIdx = (focusIdx + 1) % entries.length;
405
+ updateFocus();
406
+ }
407
+ };
408
+ fileSwitcherKeyHandler = (e) => {
409
+ const entries = getEntries(input.value);
410
+ const count = entries.length;
411
+ if (e.key === "ArrowDown") {
412
+ e.preventDefault();
413
+ if (count > 0) {
414
+ focusIdx = (focusIdx + 1) % count;
415
+ updateFocus();
416
+ }
417
+ }
418
+ else if (e.key === "ArrowUp") {
419
+ e.preventDefault();
420
+ if (count > 0) {
421
+ focusIdx = (focusIdx - 1 + count) % count;
422
+ updateFocus();
423
+ }
424
+ }
425
+ else if (e.key === "Tab") {
426
+ e.preventDefault();
427
+ enterSearchMode();
428
+ }
429
+ else if (e.key === "ArrowRight" && document.activeElement !== input) {
430
+ e.preventDefault();
431
+ enterSearchMode();
432
+ }
433
+ else if (e.key === "Enter") {
434
+ e.preventDefault();
435
+ const entry = entries[focusIdx];
436
+ if (entry) {
437
+ tabsPlugin.api.setActiveTab(entry.tabId);
438
+ closeFileSwitcher();
439
+ }
440
+ }
441
+ else if (e.key === "Escape") {
442
+ e.preventDefault();
443
+ closeFileSwitcher();
444
+ }
445
+ };
446
+ document.addEventListener("keydown", fileSwitcherKeyHandler);
447
+ }
448
+ const onCtrlE = (e) => {
449
+ if ((e.ctrlKey || e.metaKey) && e.key === "e" && !e.shiftKey && !e.altKey) {
450
+ e.preventDefault();
451
+ if (fileSwitcherEl && fileSwitcherCycle) {
452
+ fileSwitcherCycle();
453
+ }
454
+ else {
455
+ openFileSwitcher();
456
+ }
457
+ }
458
+ };
459
+ // ── bridgePlugin ────────────────────────────────────────────────────────────
460
+ let offStorageChange;
461
+ const bridgePlugin = {
462
+ name: "storage-tabs-bridge",
463
+ install(canvasApi) {
464
+ bridgeContainer = canvasApi.container;
465
+ offStorageChange = storagePlugin.api.onChange(() => {
466
+ const projectId = storagePlugin.api.getCurrentProjectId();
467
+ const projectChanged = projectId !== lastProjectId;
468
+ lastProjectId = projectId;
469
+ tabsPlugin.api.setProjectMode(!!projectId);
470
+ if (projectChanged && projectId) {
471
+ void storagePlugin.api.getMru(projectId).then((fileIds) => {
472
+ const allTabIds = tabsPlugin.api.getTabIds();
473
+ const ordered = [];
474
+ for (const fileId of fileIds) {
475
+ const tabId = storageFileIdToTabId.get(fileId);
476
+ if (tabId && allTabIds.includes(tabId))
477
+ ordered.push(tabId);
478
+ }
479
+ for (const tabId of allTabIds) {
480
+ if (!ordered.includes(tabId))
481
+ ordered.push(tabId);
482
+ }
483
+ tabMruOrder = ordered;
484
+ });
485
+ }
486
+ else if (!projectId) {
487
+ tabMruOrder = [];
488
+ }
489
+ rebuildFileCommands();
490
+ });
491
+ document.addEventListener("keydown", onCtrlE);
492
+ },
493
+ uninstall() {
494
+ offStorageChange?.();
495
+ closeFileSwitcher();
496
+ document.removeEventListener("keydown", onCtrlE);
497
+ bridgeContainer = null;
498
+ },
499
+ };
500
+ return { tabsPlugin, storagePlugin, bridgePlugin };
501
+ }
502
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ export declare const TABS_CSS: string;
2
+ export declare function injectTabsStyles(): void;
3
+ //# sourceMappingURL=css.d.ts.map