@blorkfield/blork-tabs 0.2.6 → 0.3.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 +162 -0
- package/dist/index.cjs +314 -5
- package/dist/index.d.cts +179 -2
- package/dist/index.d.ts +179 -2
- package/dist/index.js +309 -5
- package/dist/styles.css +175 -0
- package/package.json +1 -1
- package/src/AutoHideManager.ts +139 -0
- package/src/DebugPanel.ts +98 -0
- package/src/Panel.ts +33 -1
- package/src/TabManager.ts +166 -1
- package/src/index.ts +12 -0
- package/src/types.ts +76 -0
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A framework-agnostic tab/panel management system with snapping and docking capab
|
|
|
8
8
|
- **Anchor Docking** - Dock panels to predefined screen positions
|
|
9
9
|
- **Drag Modes** - Drag entire groups or detach individual panels
|
|
10
10
|
- **Collapse/Expand** - Panels can be collapsed with automatic repositioning
|
|
11
|
+
- **Auto-Hide** - Panels can hide after inactivity and show on interaction
|
|
11
12
|
- **Event System** - Subscribe to drag, snap, and collapse events
|
|
12
13
|
- **Fully Typed** - Complete TypeScript support
|
|
13
14
|
- **Framework Agnostic** - Works with plain DOM or any framework
|
|
@@ -89,6 +90,12 @@ const manager = new TabManager({
|
|
|
89
90
|
|
|
90
91
|
// CSS class prefix (default: 'blork-tabs')
|
|
91
92
|
classPrefix: 'blork-tabs',
|
|
93
|
+
|
|
94
|
+
// Whether panels start hidden (default: false)
|
|
95
|
+
startHidden: false,
|
|
96
|
+
|
|
97
|
+
// Milliseconds before auto-hiding on inactivity (default: undefined = no auto-hide)
|
|
98
|
+
autoHideDelay: undefined,
|
|
92
99
|
});
|
|
93
100
|
```
|
|
94
101
|
|
|
@@ -98,6 +105,7 @@ const manager = new TabManager({
|
|
|
98
105
|
|
|
99
106
|
#### Panel Management
|
|
100
107
|
- `addPanel(config)` - Create a new panel
|
|
108
|
+
- `addDebugPanel(config)` - Create a debug panel with logging interface
|
|
101
109
|
- `registerPanel(id, element, options)` - Register existing DOM element
|
|
102
110
|
- `removePanel(id)` - Remove a panel
|
|
103
111
|
- `getPanel(id)` - Get panel by ID
|
|
@@ -120,6 +128,11 @@ const manager = new TabManager({
|
|
|
120
128
|
- `removeAnchor(id)` - Remove anchor
|
|
121
129
|
- `getAnchors()` - Get all anchors
|
|
122
130
|
|
|
131
|
+
#### Auto-Hide
|
|
132
|
+
- `show(panelId)` - Show a hidden panel
|
|
133
|
+
- `hide(panelId)` - Hide a panel
|
|
134
|
+
- `isHidden(panelId)` - Check if panel is hidden
|
|
135
|
+
|
|
123
136
|
#### Events
|
|
124
137
|
- `on(event, listener)` - Subscribe to event
|
|
125
138
|
- `off(event, listener)` - Unsubscribe from event
|
|
@@ -137,6 +150,8 @@ const manager = new TabManager({
|
|
|
137
150
|
| `snap:anchor` | Panels snapped to anchor |
|
|
138
151
|
| `panel:detached` | Panel detached from chain |
|
|
139
152
|
| `panel:collapse` | Panel collapsed/expanded |
|
|
153
|
+
| `panel:show` | Panel became visible (auto-hide) |
|
|
154
|
+
| `panel:hide` | Panel became hidden (auto-hide) |
|
|
140
155
|
|
|
141
156
|
## Multi-Section Panel Content
|
|
142
157
|
|
|
@@ -172,6 +187,153 @@ manager.addPanel({
|
|
|
172
187
|
|
|
173
188
|
The panel content area has a max-height (default `20vh`) with `overflow-y: auto`, so long content scrolls properly. Splitting sections across multiple content wrappers defeats this behavior.
|
|
174
189
|
|
|
190
|
+
## Auto-Hide
|
|
191
|
+
|
|
192
|
+
Panels can automatically hide after a period of inactivity and reappear when the user interacts with the page. This is useful for OBS overlays or any interface where you want panels to get out of the way.
|
|
193
|
+
|
|
194
|
+
### Configuration
|
|
195
|
+
|
|
196
|
+
Auto-hide has two independent options:
|
|
197
|
+
|
|
198
|
+
- **`startHidden`** - Whether the panel starts invisible (default: `false`)
|
|
199
|
+
- **`autoHideDelay`** - Milliseconds before hiding after inactivity (default: `undefined` = no auto-hide)
|
|
200
|
+
|
|
201
|
+
These can be set globally on the TabManager or per-panel:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// Global: all panels hide after 3 seconds of inactivity
|
|
205
|
+
const manager = new TabManager({
|
|
206
|
+
autoHideDelay: 3000,
|
|
207
|
+
startHidden: true,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Per-panel overrides
|
|
211
|
+
manager.addPanel({
|
|
212
|
+
id: 'always-visible',
|
|
213
|
+
title: 'Always Visible',
|
|
214
|
+
autoHideDelay: 0, // Disable auto-hide for this panel
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
manager.addPanel({
|
|
218
|
+
id: 'quick-hide',
|
|
219
|
+
title: 'Quick Hide',
|
|
220
|
+
autoHideDelay: 1000, // This panel hides faster
|
|
221
|
+
startHidden: false, // But starts visible
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Behavior Matrix
|
|
226
|
+
|
|
227
|
+
| startHidden | autoHideDelay | Behavior |
|
|
228
|
+
|-------------|---------------|----------|
|
|
229
|
+
| `false` | `undefined` | Normal - always visible |
|
|
230
|
+
| `true` | `undefined` | Starts hidden, shows on activity, never hides again |
|
|
231
|
+
| `false` | `3000` | Starts visible, hides after 3s of inactivity |
|
|
232
|
+
| `true` | `3000` | Starts hidden, shows on activity, hides after 3s of inactivity |
|
|
233
|
+
|
|
234
|
+
### Events
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
manager.on('panel:show', ({ panel, trigger }) => {
|
|
238
|
+
// trigger: 'activity' (user interaction) or 'api' (programmatic)
|
|
239
|
+
console.log(`${panel.id} is now visible`);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
manager.on('panel:hide', ({ panel, trigger }) => {
|
|
243
|
+
// trigger: 'timeout' (auto-hide delay) or 'api' (programmatic)
|
|
244
|
+
console.log(`${panel.id} is now hidden`);
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Programmatic Control
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
manager.hide('my-panel'); // Hide immediately
|
|
252
|
+
manager.show('my-panel'); // Show immediately
|
|
253
|
+
manager.isHidden('my-panel'); // Check visibility
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Debug Panel
|
|
257
|
+
|
|
258
|
+
A plug-and-play debug panel for in-browser event logging—useful for OBS browser sources and other environments without console access.
|
|
259
|
+
|
|
260
|
+
### Creating a Debug Panel
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { TabManager } from '@blorkfield/blork-tabs';
|
|
264
|
+
|
|
265
|
+
const manager = new TabManager();
|
|
266
|
+
|
|
267
|
+
// Create a debug panel
|
|
268
|
+
const debug = manager.addDebugPanel({
|
|
269
|
+
id: 'debug',
|
|
270
|
+
title: 'Event Log', // optional, default: 'Debug'
|
|
271
|
+
width: 300, // optional, default: 300
|
|
272
|
+
maxEntries: 50, // optional, default: 50
|
|
273
|
+
showTimestamps: true, // optional, default: false
|
|
274
|
+
showClearButton: true, // optional, default: true
|
|
275
|
+
startCollapsed: true, // optional, default: true
|
|
276
|
+
initialPosition: { x: 16, y: 16 },
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Logging Events
|
|
281
|
+
|
|
282
|
+
Log events with different severity levels, each with distinct color coding:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Info/Log level - blue (accent color)
|
|
286
|
+
debug.log('event-name', { some: 'data' });
|
|
287
|
+
debug.info('connection', { status: 'connected' });
|
|
288
|
+
|
|
289
|
+
// Warning level - yellow
|
|
290
|
+
debug.warn('cache', { message: 'Cache expired, refreshing...' });
|
|
291
|
+
|
|
292
|
+
// Error level - red
|
|
293
|
+
debug.error('request', { code: 500, message: 'Server error' });
|
|
294
|
+
|
|
295
|
+
// Clear all entries
|
|
296
|
+
debug.clear();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Features
|
|
300
|
+
|
|
301
|
+
- **Pre-styled monospace container** - Optimized for log readability
|
|
302
|
+
- **Color-coded log levels** - Blue for info, yellow for warnings, red for errors
|
|
303
|
+
- **Auto-scroll** - Automatically scrolls to show latest entries
|
|
304
|
+
- **Entry limit** - Configurable max entries with FIFO removal of oldest
|
|
305
|
+
- **Timestamps** - Optional timestamp display for each entry
|
|
306
|
+
- **Hover glow effect** - Border lights up with accent color on hover
|
|
307
|
+
- **Enlarge on hover** - Hover for 5 seconds to expand to 75% of screen with 2x text size
|
|
308
|
+
- **Modal backdrop** - Enlarged view has a backdrop; click × or backdrop to close
|
|
309
|
+
- **Standard panel features** - Drag, snap, collapse, auto-hide all work
|
|
310
|
+
|
|
311
|
+
### Enlarged View Behavior
|
|
312
|
+
|
|
313
|
+
The debug panel has a special "focus mode" for reading logs:
|
|
314
|
+
|
|
315
|
+
1. **Hover for 5 seconds** → Panel enlarges to 75% of screen with doubled text size
|
|
316
|
+
2. **Mouse can move freely** → Panel stays enlarged, won't close on mouse leave
|
|
317
|
+
3. **Click × or backdrop** → Returns to normal size
|
|
318
|
+
|
|
319
|
+
The × button is only visible when enlarged.
|
|
320
|
+
|
|
321
|
+
### Configuration Options
|
|
322
|
+
|
|
323
|
+
| Option | Type | Default | Description |
|
|
324
|
+
|--------|------|---------|-------------|
|
|
325
|
+
| `maxEntries` | `number` | `50` | Maximum log entries before oldest are removed |
|
|
326
|
+
| `showTimestamps` | `boolean` | `false` | Show timestamps on each entry |
|
|
327
|
+
|
|
328
|
+
Plus all standard `PanelConfig` options (`id`, `title`, `width`, `initialPosition`, `startCollapsed`, etc.)
|
|
329
|
+
|
|
330
|
+
### Accessing the Underlying Panel
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// The debug panel interface includes access to the panel state
|
|
334
|
+
debug.panel; // PanelState - for advanced manipulation
|
|
335
|
+
```
|
|
336
|
+
|
|
175
337
|
## CSS Customization
|
|
176
338
|
|
|
177
339
|
Override CSS variables to customize appearance:
|
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AnchorManager: () => AnchorManager,
|
|
24
|
+
AutoHideManager: () => AutoHideManager,
|
|
24
25
|
DragManager: () => DragManager,
|
|
25
26
|
SnapPreview: () => SnapPreview,
|
|
26
27
|
TabManager: () => TabManager,
|
|
27
28
|
areInSameChain: () => areInSameChain,
|
|
29
|
+
createDebugPanelContent: () => createDebugPanelContent,
|
|
30
|
+
createDebugPanelInterface: () => createDebugPanelInterface,
|
|
28
31
|
createPanelElement: () => createPanelElement,
|
|
29
32
|
createPanelState: () => createPanelState,
|
|
30
33
|
createPresetAnchor: () => createPresetAnchor,
|
|
@@ -38,8 +41,10 @@ __export(index_exports, {
|
|
|
38
41
|
getPanelDimensions: () => getPanelDimensions,
|
|
39
42
|
getPanelPosition: () => getPanelPosition,
|
|
40
43
|
getRightmostPanel: () => getRightmostPanel,
|
|
44
|
+
hidePanel: () => hidePanel,
|
|
41
45
|
setPanelPosition: () => setPanelPosition,
|
|
42
46
|
setPanelZIndex: () => setPanelZIndex,
|
|
47
|
+
showPanel: () => showPanel,
|
|
43
48
|
snapPanels: () => snapPanels,
|
|
44
49
|
snapPanelsToTarget: () => snapPanelsToTarget,
|
|
45
50
|
toggleCollapse: () => toggleCollapse,
|
|
@@ -48,6 +53,64 @@ __export(index_exports, {
|
|
|
48
53
|
});
|
|
49
54
|
module.exports = __toCommonJS(index_exports);
|
|
50
55
|
|
|
56
|
+
// src/DebugPanel.ts
|
|
57
|
+
function createDebugPanelContent(_config, classes) {
|
|
58
|
+
const content = document.createElement("div");
|
|
59
|
+
const logContainer = document.createElement("div");
|
|
60
|
+
logContainer.className = classes.debugLog;
|
|
61
|
+
content.appendChild(logContainer);
|
|
62
|
+
return {
|
|
63
|
+
content,
|
|
64
|
+
elements: { logContainer, clearButton: null }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createDebugPanelInterface(panel, elements, config, classes) {
|
|
68
|
+
const maxEntries = config.maxEntries ?? 50;
|
|
69
|
+
const showTimestamps = config.showTimestamps ?? false;
|
|
70
|
+
function addEntry(level, eventName, data) {
|
|
71
|
+
const entry = document.createElement("div");
|
|
72
|
+
entry.className = classes.debugLogEntry;
|
|
73
|
+
if (level === "warn") entry.classList.add(classes.debugLogEntryWarn);
|
|
74
|
+
if (level === "error") entry.classList.add(classes.debugLogEntryError);
|
|
75
|
+
let html = "";
|
|
76
|
+
if (showTimestamps) {
|
|
77
|
+
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
|
|
78
|
+
hour12: false,
|
|
79
|
+
hour: "2-digit",
|
|
80
|
+
minute: "2-digit",
|
|
81
|
+
second: "2-digit"
|
|
82
|
+
});
|
|
83
|
+
html += `<span class="${classes.debugLogTimestamp}">${time}</span>`;
|
|
84
|
+
}
|
|
85
|
+
html += `<span class="${classes.debugLogName}">${escapeHtml(eventName)}</span>`;
|
|
86
|
+
if (data) {
|
|
87
|
+
const dataStr = JSON.stringify(data);
|
|
88
|
+
html += `<span class="${classes.debugLogData}">${escapeHtml(dataStr)}</span>`;
|
|
89
|
+
}
|
|
90
|
+
entry.innerHTML = html;
|
|
91
|
+
elements.logContainer.appendChild(entry);
|
|
92
|
+
elements.logContainer.scrollTop = elements.logContainer.scrollHeight;
|
|
93
|
+
while (elements.logContainer.children.length > maxEntries) {
|
|
94
|
+
elements.logContainer.removeChild(elements.logContainer.children[0]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
panel,
|
|
99
|
+
log: (name, data) => addEntry("log", name, data),
|
|
100
|
+
info: (name, data) => addEntry("info", name, data),
|
|
101
|
+
warn: (name, data) => addEntry("warn", name, data),
|
|
102
|
+
error: (name, data) => addEntry("error", name, data),
|
|
103
|
+
clear: () => {
|
|
104
|
+
elements.logContainer.innerHTML = "";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function escapeHtml(str) {
|
|
109
|
+
const div = document.createElement("div");
|
|
110
|
+
div.textContent = str;
|
|
111
|
+
return div.innerHTML;
|
|
112
|
+
}
|
|
113
|
+
|
|
51
114
|
// src/Panel.ts
|
|
52
115
|
function createPanelElement(config, classes) {
|
|
53
116
|
const element = document.createElement("div");
|
|
@@ -102,7 +165,7 @@ function createPanelElement(config, classes) {
|
|
|
102
165
|
detachGrip
|
|
103
166
|
};
|
|
104
167
|
}
|
|
105
|
-
function createPanelState(config, classes) {
|
|
168
|
+
function createPanelState(config, classes, globalConfig) {
|
|
106
169
|
let element;
|
|
107
170
|
let dragHandle;
|
|
108
171
|
let collapseButton;
|
|
@@ -122,6 +185,11 @@ function createPanelState(config, classes) {
|
|
|
122
185
|
contentWrapper = created.contentWrapper;
|
|
123
186
|
detachGrip = created.detachGrip;
|
|
124
187
|
}
|
|
188
|
+
const resolvedStartHidden = config.startHidden ?? globalConfig?.startHidden ?? false;
|
|
189
|
+
const resolvedAutoHideDelay = config.autoHideDelay !== void 0 ? config.autoHideDelay === 0 ? void 0 : config.autoHideDelay : globalConfig?.autoHideDelay;
|
|
190
|
+
if (resolvedStartHidden) {
|
|
191
|
+
element.classList.add(classes.panelHidden);
|
|
192
|
+
}
|
|
125
193
|
return {
|
|
126
194
|
id: config.id,
|
|
127
195
|
element,
|
|
@@ -132,7 +200,9 @@ function createPanelState(config, classes) {
|
|
|
132
200
|
isCollapsed: config.startCollapsed !== false,
|
|
133
201
|
snappedTo: null,
|
|
134
202
|
snappedFrom: null,
|
|
135
|
-
config
|
|
203
|
+
config,
|
|
204
|
+
isHidden: resolvedStartHidden,
|
|
205
|
+
resolvedAutoHideDelay
|
|
136
206
|
};
|
|
137
207
|
}
|
|
138
208
|
function toggleCollapse(state, classes, collapsed) {
|
|
@@ -148,6 +218,16 @@ function toggleCollapse(state, classes, collapsed) {
|
|
|
148
218
|
}
|
|
149
219
|
return newState;
|
|
150
220
|
}
|
|
221
|
+
function showPanel(state, classes) {
|
|
222
|
+
if (!state.isHidden) return;
|
|
223
|
+
state.isHidden = false;
|
|
224
|
+
state.element.classList.remove(classes.panelHidden);
|
|
225
|
+
}
|
|
226
|
+
function hidePanel(state, classes) {
|
|
227
|
+
if (state.isHidden) return;
|
|
228
|
+
state.isHidden = true;
|
|
229
|
+
state.element.classList.add(classes.panelHidden);
|
|
230
|
+
}
|
|
151
231
|
function setPanelPosition(state, x, y) {
|
|
152
232
|
state.element.style.left = `${x}px`;
|
|
153
233
|
state.element.style.top = `${y}px`;
|
|
@@ -817,6 +897,109 @@ var SnapPreview = class {
|
|
|
817
897
|
}
|
|
818
898
|
};
|
|
819
899
|
|
|
900
|
+
// src/AutoHideManager.ts
|
|
901
|
+
var AutoHideManager = class {
|
|
902
|
+
constructor(panels, classes, callbacks) {
|
|
903
|
+
this.hideTimers = /* @__PURE__ */ new Map();
|
|
904
|
+
this.listenersAttached = false;
|
|
905
|
+
this.panels = panels;
|
|
906
|
+
this.classes = classes;
|
|
907
|
+
this.callbacks = callbacks;
|
|
908
|
+
this.boundActivityHandler = this.handleActivity.bind(this);
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Attach activity listeners to document
|
|
912
|
+
*/
|
|
913
|
+
attachListeners() {
|
|
914
|
+
if (this.listenersAttached) return;
|
|
915
|
+
document.addEventListener("mousemove", this.boundActivityHandler);
|
|
916
|
+
document.addEventListener("mousedown", this.boundActivityHandler);
|
|
917
|
+
document.addEventListener("keydown", this.boundActivityHandler);
|
|
918
|
+
this.listenersAttached = true;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Handle user activity - show hidden panels and reset timers
|
|
922
|
+
*/
|
|
923
|
+
handleActivity() {
|
|
924
|
+
for (const panel of this.panels.values()) {
|
|
925
|
+
if (panel.resolvedAutoHideDelay !== void 0 || panel.isHidden) {
|
|
926
|
+
this.show(panel, "activity");
|
|
927
|
+
if (panel.resolvedAutoHideDelay !== void 0) {
|
|
928
|
+
this.scheduleHide(panel);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Schedule a panel to hide after its delay
|
|
935
|
+
*/
|
|
936
|
+
scheduleHide(panel) {
|
|
937
|
+
this.clearTimer(panel.id);
|
|
938
|
+
if (panel.resolvedAutoHideDelay === void 0) return;
|
|
939
|
+
const timer = setTimeout(() => {
|
|
940
|
+
this.hide(panel, "timeout");
|
|
941
|
+
}, panel.resolvedAutoHideDelay);
|
|
942
|
+
this.hideTimers.set(panel.id, timer);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Clear hide timer for a panel
|
|
946
|
+
*/
|
|
947
|
+
clearTimer(panelId) {
|
|
948
|
+
const timer = this.hideTimers.get(panelId);
|
|
949
|
+
if (timer) {
|
|
950
|
+
clearTimeout(timer);
|
|
951
|
+
this.hideTimers.delete(panelId);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Show a panel
|
|
956
|
+
*/
|
|
957
|
+
show(panel, trigger) {
|
|
958
|
+
if (!panel.isHidden) return;
|
|
959
|
+
showPanel(panel, this.classes);
|
|
960
|
+
this.callbacks.onShow?.(panel, trigger);
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Hide a panel
|
|
964
|
+
*/
|
|
965
|
+
hide(panel, trigger) {
|
|
966
|
+
if (panel.isHidden) return;
|
|
967
|
+
hidePanel(panel, this.classes);
|
|
968
|
+
this.callbacks.onHide?.(panel, trigger);
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Initialize a newly added panel's auto-hide state
|
|
972
|
+
*/
|
|
973
|
+
initializePanel(panel) {
|
|
974
|
+
if (panel.resolvedAutoHideDelay !== void 0 || panel.isHidden) {
|
|
975
|
+
this.attachListeners();
|
|
976
|
+
}
|
|
977
|
+
if (!panel.isHidden && panel.resolvedAutoHideDelay !== void 0) {
|
|
978
|
+
this.scheduleHide(panel);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Clean up when a panel is removed
|
|
983
|
+
*/
|
|
984
|
+
cleanupPanel(panelId) {
|
|
985
|
+
this.clearTimer(panelId);
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Clean up all resources
|
|
989
|
+
*/
|
|
990
|
+
destroy() {
|
|
991
|
+
if (this.listenersAttached) {
|
|
992
|
+
document.removeEventListener("mousemove", this.boundActivityHandler);
|
|
993
|
+
document.removeEventListener("mousedown", this.boundActivityHandler);
|
|
994
|
+
document.removeEventListener("keydown", this.boundActivityHandler);
|
|
995
|
+
}
|
|
996
|
+
for (const timer of this.hideTimers.values()) {
|
|
997
|
+
clearTimeout(timer);
|
|
998
|
+
}
|
|
999
|
+
this.hideTimers.clear();
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
820
1003
|
// src/TabManager.ts
|
|
821
1004
|
var DEFAULT_CONFIG = {
|
|
822
1005
|
snapThreshold: 50,
|
|
@@ -826,7 +1009,9 @@ var DEFAULT_CONFIG = {
|
|
|
826
1009
|
defaultPanelWidth: 300,
|
|
827
1010
|
container: document.body,
|
|
828
1011
|
initializeDefaultAnchors: true,
|
|
829
|
-
classPrefix: "blork-tabs"
|
|
1012
|
+
classPrefix: "blork-tabs",
|
|
1013
|
+
startHidden: false,
|
|
1014
|
+
autoHideDelay: void 0
|
|
830
1015
|
};
|
|
831
1016
|
function generateClasses(prefix) {
|
|
832
1017
|
return {
|
|
@@ -842,13 +1027,27 @@ function generateClasses(prefix) {
|
|
|
842
1027
|
anchorIndicator: `${prefix}-anchor-indicator`,
|
|
843
1028
|
anchorIndicatorVisible: `${prefix}-anchor-indicator-visible`,
|
|
844
1029
|
anchorIndicatorActive: `${prefix}-anchor-indicator-active`,
|
|
845
|
-
dragging: `${prefix}-dragging
|
|
1030
|
+
dragging: `${prefix}-dragging`,
|
|
1031
|
+
panelHidden: `${prefix}-panel-hidden`,
|
|
1032
|
+
debugLog: `${prefix}-debug-log`,
|
|
1033
|
+
debugLogEntry: `${prefix}-debug-log-entry`,
|
|
1034
|
+
debugLogEntryInfo: `${prefix}-debug-log-entry-info`,
|
|
1035
|
+
debugLogEntryWarn: `${prefix}-debug-log-entry-warn`,
|
|
1036
|
+
debugLogEntryError: `${prefix}-debug-log-entry-error`,
|
|
1037
|
+
debugLogName: `${prefix}-debug-log-name`,
|
|
1038
|
+
debugLogData: `${prefix}-debug-log-data`,
|
|
1039
|
+
debugLogTimestamp: `${prefix}-debug-log-timestamp`,
|
|
1040
|
+
debugClearButton: `${prefix}-debug-clear-btn`,
|
|
1041
|
+
debugPanel: `${prefix}-debug-panel`,
|
|
1042
|
+
debugPanelEnlarged: `${prefix}-debug-panel-enlarged`,
|
|
1043
|
+
debugBackdrop: `${prefix}-debug-backdrop`
|
|
846
1044
|
};
|
|
847
1045
|
}
|
|
848
1046
|
var TabManager = class {
|
|
849
1047
|
constructor(userConfig = {}) {
|
|
850
1048
|
this.panels = /* @__PURE__ */ new Map();
|
|
851
1049
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
1050
|
+
this.debugPanelElements = /* @__PURE__ */ new Map();
|
|
852
1051
|
this.config = {
|
|
853
1052
|
...DEFAULT_CONFIG,
|
|
854
1053
|
...userConfig,
|
|
@@ -871,6 +1070,14 @@ var TabManager = class {
|
|
|
871
1070
|
findAnchorTarget: (panels) => this.anchorManager.findNearestAnchor(panels)
|
|
872
1071
|
}
|
|
873
1072
|
);
|
|
1073
|
+
this.autoHideManager = new AutoHideManager(
|
|
1074
|
+
this.panels,
|
|
1075
|
+
this.classes,
|
|
1076
|
+
{
|
|
1077
|
+
onShow: (panel, trigger) => this.emit("panel:show", { panel, trigger }),
|
|
1078
|
+
onHide: (panel, trigger) => this.emit("panel:hide", { panel, trigger })
|
|
1079
|
+
}
|
|
1080
|
+
);
|
|
874
1081
|
if (this.config.initializeDefaultAnchors) {
|
|
875
1082
|
this.anchorManager.addDefaultAnchors();
|
|
876
1083
|
}
|
|
@@ -880,7 +1087,10 @@ var TabManager = class {
|
|
|
880
1087
|
* Add a new panel
|
|
881
1088
|
*/
|
|
882
1089
|
addPanel(panelConfig) {
|
|
883
|
-
const state = createPanelState(panelConfig, this.classes
|
|
1090
|
+
const state = createPanelState(panelConfig, this.classes, {
|
|
1091
|
+
startHidden: this.config.startHidden,
|
|
1092
|
+
autoHideDelay: this.config.autoHideDelay
|
|
1093
|
+
});
|
|
884
1094
|
if (!panelConfig.element && !this.config.container.contains(state.element)) {
|
|
885
1095
|
this.config.container.appendChild(state.element);
|
|
886
1096
|
}
|
|
@@ -889,6 +1099,7 @@ var TabManager = class {
|
|
|
889
1099
|
if (panelConfig.initialPosition) {
|
|
890
1100
|
setPanelPosition(state, panelConfig.initialPosition.x, panelConfig.initialPosition.y);
|
|
891
1101
|
}
|
|
1102
|
+
this.autoHideManager.initializePanel(state);
|
|
892
1103
|
this.emit("panel:added", { panel: state });
|
|
893
1104
|
return state;
|
|
894
1105
|
}
|
|
@@ -912,6 +1123,8 @@ var TabManager = class {
|
|
|
912
1123
|
removePanel(id) {
|
|
913
1124
|
const panel = this.panels.get(id);
|
|
914
1125
|
if (!panel) return false;
|
|
1126
|
+
this.autoHideManager.cleanupPanel(id);
|
|
1127
|
+
this.debugPanelElements.delete(id);
|
|
915
1128
|
detachFromGroup(panel, this.panels);
|
|
916
1129
|
if (!panel.config.element) {
|
|
917
1130
|
panel.element.remove();
|
|
@@ -932,6 +1145,70 @@ var TabManager = class {
|
|
|
932
1145
|
getAllPanels() {
|
|
933
1146
|
return Array.from(this.panels.values());
|
|
934
1147
|
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Add a debug panel with built-in logging functionality
|
|
1150
|
+
*/
|
|
1151
|
+
addDebugPanel(config) {
|
|
1152
|
+
const { content, elements } = createDebugPanelContent(config, this.classes);
|
|
1153
|
+
const panelConfig = {
|
|
1154
|
+
...config,
|
|
1155
|
+
title: config.title ?? "Debug",
|
|
1156
|
+
content,
|
|
1157
|
+
startCollapsed: config.startCollapsed ?? true
|
|
1158
|
+
};
|
|
1159
|
+
const state = this.addPanel(panelConfig);
|
|
1160
|
+
state.element.classList.add(this.classes.debugPanel);
|
|
1161
|
+
const closeBtn = document.createElement("button");
|
|
1162
|
+
closeBtn.className = this.classes.debugClearButton;
|
|
1163
|
+
closeBtn.textContent = "\xD7";
|
|
1164
|
+
closeBtn.title = "Close enlarged view";
|
|
1165
|
+
if (state.collapseButton) {
|
|
1166
|
+
state.collapseButton.parentElement?.insertBefore(closeBtn, state.collapseButton);
|
|
1167
|
+
}
|
|
1168
|
+
elements.clearButton = closeBtn;
|
|
1169
|
+
this.debugPanelElements.set(state.id, elements);
|
|
1170
|
+
const debugPanel = createDebugPanelInterface(state, elements, config, this.classes);
|
|
1171
|
+
let hoverTimeout = null;
|
|
1172
|
+
let isEnlarged = false;
|
|
1173
|
+
let backdrop = null;
|
|
1174
|
+
const enlargedClass = this.classes.debugPanelEnlarged;
|
|
1175
|
+
const backdropClass = this.classes.debugBackdrop;
|
|
1176
|
+
const closeEnlarged = () => {
|
|
1177
|
+
if (!isEnlarged) return;
|
|
1178
|
+
isEnlarged = false;
|
|
1179
|
+
state.element.classList.remove(enlargedClass);
|
|
1180
|
+
if (backdrop) {
|
|
1181
|
+
backdrop.remove();
|
|
1182
|
+
backdrop = null;
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
const openEnlarged = () => {
|
|
1186
|
+
if (isEnlarged) return;
|
|
1187
|
+
isEnlarged = true;
|
|
1188
|
+
backdrop = document.createElement("div");
|
|
1189
|
+
backdrop.className = backdropClass;
|
|
1190
|
+
this.config.container.appendChild(backdrop);
|
|
1191
|
+
backdrop.addEventListener("click", closeEnlarged);
|
|
1192
|
+
state.element.classList.add(enlargedClass);
|
|
1193
|
+
};
|
|
1194
|
+
state.element.addEventListener("mouseenter", () => {
|
|
1195
|
+
if (isEnlarged) return;
|
|
1196
|
+
hoverTimeout = setTimeout(() => {
|
|
1197
|
+
openEnlarged();
|
|
1198
|
+
}, 5e3);
|
|
1199
|
+
});
|
|
1200
|
+
state.element.addEventListener("mouseleave", () => {
|
|
1201
|
+
if (hoverTimeout) {
|
|
1202
|
+
clearTimeout(hoverTimeout);
|
|
1203
|
+
hoverTimeout = null;
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
closeBtn.addEventListener("click", (e) => {
|
|
1207
|
+
e.stopPropagation();
|
|
1208
|
+
closeEnlarged();
|
|
1209
|
+
});
|
|
1210
|
+
return debugPanel;
|
|
1211
|
+
}
|
|
935
1212
|
/**
|
|
936
1213
|
* Set up event handlers for a panel
|
|
937
1214
|
*/
|
|
@@ -1017,6 +1294,31 @@ var TabManager = class {
|
|
|
1017
1294
|
getAnchors() {
|
|
1018
1295
|
return this.anchorManager.getAnchors();
|
|
1019
1296
|
}
|
|
1297
|
+
// ==================== Auto-Hide ====================
|
|
1298
|
+
/**
|
|
1299
|
+
* Show a hidden panel
|
|
1300
|
+
*/
|
|
1301
|
+
show(panelId) {
|
|
1302
|
+
const panel = this.panels.get(panelId);
|
|
1303
|
+
if (!panel) return false;
|
|
1304
|
+
this.autoHideManager.show(panel, "api");
|
|
1305
|
+
return true;
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Hide a panel
|
|
1309
|
+
*/
|
|
1310
|
+
hide(panelId) {
|
|
1311
|
+
const panel = this.panels.get(panelId);
|
|
1312
|
+
if (!panel) return false;
|
|
1313
|
+
this.autoHideManager.hide(panel, "api");
|
|
1314
|
+
return true;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Check if a panel is hidden
|
|
1318
|
+
*/
|
|
1319
|
+
isHidden(panelId) {
|
|
1320
|
+
return this.panels.get(panelId)?.isHidden ?? false;
|
|
1321
|
+
}
|
|
1020
1322
|
// ==================== Drag Callbacks ====================
|
|
1021
1323
|
handleDragStart(state) {
|
|
1022
1324
|
this.anchorManager.showIndicators(null);
|
|
@@ -1155,6 +1457,7 @@ var TabManager = class {
|
|
|
1155
1457
|
this.dragManager.destroy();
|
|
1156
1458
|
this.anchorManager.destroy();
|
|
1157
1459
|
this.snapPreview.destroy();
|
|
1460
|
+
this.autoHideManager.destroy();
|
|
1158
1461
|
for (const panel of this.panels.values()) {
|
|
1159
1462
|
if (!panel.config.element) {
|
|
1160
1463
|
panel.element.remove();
|
|
@@ -1162,15 +1465,19 @@ var TabManager = class {
|
|
|
1162
1465
|
}
|
|
1163
1466
|
this.panels.clear();
|
|
1164
1467
|
this.eventListeners.clear();
|
|
1468
|
+
this.debugPanelElements.clear();
|
|
1165
1469
|
}
|
|
1166
1470
|
};
|
|
1167
1471
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1168
1472
|
0 && (module.exports = {
|
|
1169
1473
|
AnchorManager,
|
|
1474
|
+
AutoHideManager,
|
|
1170
1475
|
DragManager,
|
|
1171
1476
|
SnapPreview,
|
|
1172
1477
|
TabManager,
|
|
1173
1478
|
areInSameChain,
|
|
1479
|
+
createDebugPanelContent,
|
|
1480
|
+
createDebugPanelInterface,
|
|
1174
1481
|
createPanelElement,
|
|
1175
1482
|
createPanelState,
|
|
1176
1483
|
createPresetAnchor,
|
|
@@ -1184,8 +1491,10 @@ var TabManager = class {
|
|
|
1184
1491
|
getPanelDimensions,
|
|
1185
1492
|
getPanelPosition,
|
|
1186
1493
|
getRightmostPanel,
|
|
1494
|
+
hidePanel,
|
|
1187
1495
|
setPanelPosition,
|
|
1188
1496
|
setPanelZIndex,
|
|
1497
|
+
showPanel,
|
|
1189
1498
|
snapPanels,
|
|
1190
1499
|
snapPanelsToTarget,
|
|
1191
1500
|
toggleCollapse,
|