@blorkfield/blork-tabs 0.3.0 → 0.3.1
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 +40 -2
- package/dist/index.cjs +149 -61
- package/dist/index.d.cts +75 -4
- package/dist/index.d.ts +75 -4
- package/dist/index.js +147 -61
- package/dist/styles.css +46 -35
- package/package.json +1 -1
- package/src/AutoHideManager.ts +25 -2
- package/src/DebugPanel.ts +185 -15
- package/src/TabManager.ts +35 -68
- package/src/index.ts +3 -1
- package/src/types.ts +30 -0
package/src/DebugPanel.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* In-browser debug log panel for environments without console access
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { DebugPanelConfig, DebugPanel, DebugLogLevel, PanelState, CSSClasses } from './types';
|
|
6
|
+
import type { DebugPanelConfig, DebugPanel, DebugLog, DebugLogConfig, DebugLogLevel, PanelState, CSSClasses } from './types';
|
|
7
7
|
|
|
8
8
|
export interface DebugPanelElements {
|
|
9
9
|
logContainer: HTMLDivElement;
|
|
@@ -30,7 +30,8 @@ export function createDebugPanelContent(
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Create the interface for interacting with a debug panel
|
|
33
|
+
* Create the interface for interacting with a debug panel.
|
|
34
|
+
* Uses the shared createDebugLogInterface internally.
|
|
34
35
|
*/
|
|
35
36
|
export function createDebugPanelInterface(
|
|
36
37
|
panel: PanelState,
|
|
@@ -38,12 +39,46 @@ export function createDebugPanelInterface(
|
|
|
38
39
|
config: DebugPanelConfig,
|
|
39
40
|
classes: CSSClasses
|
|
40
41
|
): DebugPanel {
|
|
42
|
+
const debugLog = createDebugLogInterface(elements.logContainer, config, classes);
|
|
43
|
+
return {
|
|
44
|
+
panel,
|
|
45
|
+
...debugLog,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Escape HTML special characters to prevent XSS
|
|
51
|
+
*/
|
|
52
|
+
function escapeHtml(str: string): string {
|
|
53
|
+
const div = document.createElement('div');
|
|
54
|
+
div.textContent = str;
|
|
55
|
+
return div.innerHTML;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Shared interface config for debug log creation
|
|
60
|
+
*/
|
|
61
|
+
interface DebugLogInterfaceConfig {
|
|
62
|
+
maxEntries?: number;
|
|
63
|
+
showTimestamps?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create the logging interface for a debug log container.
|
|
68
|
+
* This is the shared implementation used by both standalone panels and embedded logs.
|
|
69
|
+
*/
|
|
70
|
+
export function createDebugLogInterface(
|
|
71
|
+
logContainer: HTMLElement,
|
|
72
|
+
config: DebugLogInterfaceConfig,
|
|
73
|
+
classes: CSSClasses
|
|
74
|
+
): DebugLog {
|
|
41
75
|
const maxEntries = config.maxEntries ?? 50;
|
|
42
76
|
const showTimestamps = config.showTimestamps ?? false;
|
|
77
|
+
const entryClass = classes.debugLogEntry;
|
|
43
78
|
|
|
44
79
|
function addEntry(level: DebugLogLevel, eventName: string, data?: Record<string, unknown>): void {
|
|
45
80
|
const entry = document.createElement('div');
|
|
46
|
-
entry.className =
|
|
81
|
+
entry.className = entryClass;
|
|
47
82
|
|
|
48
83
|
// Add level-specific class for color coding
|
|
49
84
|
if (level === 'warn') entry.classList.add(classes.debugLogEntryWarn);
|
|
@@ -69,30 +104,165 @@ export function createDebugPanelInterface(
|
|
|
69
104
|
}
|
|
70
105
|
|
|
71
106
|
entry.innerHTML = html;
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
logContainer.appendChild(entry);
|
|
108
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
74
109
|
|
|
75
|
-
// Remove oldest entries if over limit
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
// Remove oldest entries if over limit (only count actual log entries, not the close button)
|
|
111
|
+
const entries = logContainer.querySelectorAll(`.${entryClass}`);
|
|
112
|
+
if (entries.length > maxEntries) {
|
|
113
|
+
const toRemove = entries.length - maxEntries;
|
|
114
|
+
for (let i = 0; i < toRemove; i++) {
|
|
115
|
+
entries[i].remove();
|
|
116
|
+
}
|
|
78
117
|
}
|
|
79
118
|
}
|
|
80
119
|
|
|
120
|
+
function clearEntries(): void {
|
|
121
|
+
// Only remove log entries, preserve the close button
|
|
122
|
+
const entries = logContainer.querySelectorAll(`.${entryClass}`);
|
|
123
|
+
entries.forEach(entry => entry.remove());
|
|
124
|
+
}
|
|
125
|
+
|
|
81
126
|
return {
|
|
82
|
-
panel,
|
|
83
127
|
log: (name, data) => addEntry('log', name, data),
|
|
84
128
|
info: (name, data) => addEntry('info', name, data),
|
|
85
129
|
warn: (name, data) => addEntry('warn', name, data),
|
|
86
130
|
error: (name, data) => addEntry('error', name, data),
|
|
87
|
-
clear:
|
|
131
|
+
clear: clearEntries,
|
|
88
132
|
};
|
|
89
133
|
}
|
|
90
134
|
|
|
135
|
+
export interface DebugLogSetup {
|
|
136
|
+
debugLog: DebugLog;
|
|
137
|
+
logContainer: HTMLDivElement;
|
|
138
|
+
}
|
|
139
|
+
|
|
91
140
|
/**
|
|
92
|
-
*
|
|
141
|
+
* Create an embeddable debug log in any container element
|
|
93
142
|
*/
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
143
|
+
export function createDebugLog(
|
|
144
|
+
container: HTMLElement,
|
|
145
|
+
config: DebugLogConfig,
|
|
146
|
+
classes: CSSClasses
|
|
147
|
+
): DebugLogSetup {
|
|
148
|
+
// Create log container
|
|
149
|
+
const logContainer = document.createElement('div');
|
|
150
|
+
logContainer.className = classes.debugLog;
|
|
151
|
+
container.appendChild(logContainer);
|
|
152
|
+
|
|
153
|
+
// Use shared interface creation
|
|
154
|
+
const debugLog = createDebugLogInterface(logContainer, config, classes);
|
|
155
|
+
|
|
156
|
+
return { debugLog, logContainer };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface HoverEnlargeConfig {
|
|
160
|
+
/** The log container element (.blork-tabs-debug-log) to enlarge */
|
|
161
|
+
logContainer: HTMLElement;
|
|
162
|
+
/** Hover delay in ms (0 = disable) */
|
|
163
|
+
hoverDelay: number;
|
|
164
|
+
/** Container to append backdrop to */
|
|
165
|
+
backdropContainer: HTMLElement;
|
|
166
|
+
/** CSS classes */
|
|
167
|
+
classes: CSSClasses;
|
|
168
|
+
/** Called when hovering starts */
|
|
169
|
+
onHoverStart?: () => void;
|
|
170
|
+
/** Called when hovering ends (without enlarging) */
|
|
171
|
+
onHoverEnd?: () => void;
|
|
172
|
+
/** Called when enlarged view is closed */
|
|
173
|
+
onClose?: () => void;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Set up hover-to-enlarge behavior for a debug log container.
|
|
178
|
+
* Works the same for both standalone debug panels and embedded logs.
|
|
179
|
+
*/
|
|
180
|
+
export function setupHoverEnlarge(config: HoverEnlargeConfig): void {
|
|
181
|
+
const { logContainer, hoverDelay, backdropContainer, classes, onHoverStart, onHoverEnd, onClose } = config;
|
|
182
|
+
|
|
183
|
+
// Add debug panel class for hover border effect
|
|
184
|
+
logContainer.classList.add(classes.debugPanel);
|
|
185
|
+
|
|
186
|
+
// Create close button inside the log container
|
|
187
|
+
const closeBtn = document.createElement('button');
|
|
188
|
+
closeBtn.className = classes.debugClearButton;
|
|
189
|
+
closeBtn.textContent = '×';
|
|
190
|
+
closeBtn.title = 'Close enlarged view';
|
|
191
|
+
logContainer.appendChild(closeBtn);
|
|
192
|
+
|
|
193
|
+
let hoverTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
194
|
+
let isEnlarged = false;
|
|
195
|
+
let backdrop: HTMLDivElement | null = null;
|
|
196
|
+
let originalParent: HTMLElement | null = null;
|
|
197
|
+
let placeholder: Comment | null = null;
|
|
198
|
+
|
|
199
|
+
const closeEnlarged = () => {
|
|
200
|
+
if (!isEnlarged) return;
|
|
201
|
+
isEnlarged = false;
|
|
202
|
+
logContainer.classList.remove(classes.debugPanelEnlarged);
|
|
203
|
+
|
|
204
|
+
// Move log container back to original parent
|
|
205
|
+
if (originalParent && placeholder) {
|
|
206
|
+
originalParent.insertBefore(logContainer, placeholder);
|
|
207
|
+
placeholder.remove();
|
|
208
|
+
placeholder = null;
|
|
209
|
+
originalParent = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (backdrop) {
|
|
213
|
+
backdrop.remove();
|
|
214
|
+
backdrop = null;
|
|
215
|
+
}
|
|
216
|
+
onClose?.();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const openEnlarged = () => {
|
|
220
|
+
if (isEnlarged) return;
|
|
221
|
+
isEnlarged = true;
|
|
222
|
+
|
|
223
|
+
// Create backdrop
|
|
224
|
+
backdrop = document.createElement('div');
|
|
225
|
+
backdrop.className = classes.debugBackdrop;
|
|
226
|
+
backdropContainer.appendChild(backdrop);
|
|
227
|
+
|
|
228
|
+
// Click backdrop to close
|
|
229
|
+
backdrop.addEventListener('click', closeEnlarged);
|
|
230
|
+
|
|
231
|
+
// Move log container to body to escape parent stacking context
|
|
232
|
+
originalParent = logContainer.parentElement;
|
|
233
|
+
placeholder = document.createComment('debug-log-placeholder');
|
|
234
|
+
originalParent?.insertBefore(placeholder, logContainer);
|
|
235
|
+
backdropContainer.appendChild(logContainer);
|
|
236
|
+
|
|
237
|
+
// Add enlarged class to log container
|
|
238
|
+
logContainer.classList.add(classes.debugPanelEnlarged);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Hover to start enlarge timer
|
|
242
|
+
logContainer.addEventListener('mouseenter', () => {
|
|
243
|
+
onHoverStart?.();
|
|
244
|
+
if (isEnlarged) return;
|
|
245
|
+
if (hoverDelay > 0) {
|
|
246
|
+
hoverTimeout = setTimeout(() => {
|
|
247
|
+
openEnlarged();
|
|
248
|
+
}, hoverDelay);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Cancel timer if mouse leaves
|
|
253
|
+
logContainer.addEventListener('mouseleave', () => {
|
|
254
|
+
if (hoverTimeout) {
|
|
255
|
+
clearTimeout(hoverTimeout);
|
|
256
|
+
hoverTimeout = null;
|
|
257
|
+
}
|
|
258
|
+
if (!isEnlarged) {
|
|
259
|
+
onHoverEnd?.();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Close button closes enlarged view
|
|
264
|
+
closeBtn.addEventListener('click', (e) => {
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
closeEnlarged();
|
|
267
|
+
});
|
|
98
268
|
}
|
package/src/TabManager.ts
CHANGED
|
@@ -20,8 +20,10 @@ import type {
|
|
|
20
20
|
Position,
|
|
21
21
|
DebugPanelConfig,
|
|
22
22
|
DebugPanel,
|
|
23
|
+
DebugLogConfig,
|
|
24
|
+
DebugLog,
|
|
23
25
|
} from './types';
|
|
24
|
-
import { createDebugPanelContent, createDebugPanelInterface, DebugPanelElements } from './DebugPanel';
|
|
26
|
+
import { createDebugPanelContent, createDebugPanelInterface, createDebugLog, setupHoverEnlarge, DebugPanelElements } from './DebugPanel';
|
|
25
27
|
import { createPanelState, toggleCollapse, setPanelPosition } from './Panel';
|
|
26
28
|
import { getConnectedGroup, detachFromGroup, updateSnappedPositions, snapPanels } from './SnapChain';
|
|
27
29
|
import { DragManager } from './DragManager';
|
|
@@ -253,80 +255,45 @@ export class TabManager {
|
|
|
253
255
|
|
|
254
256
|
const state = this.addPanel(panelConfig);
|
|
255
257
|
|
|
256
|
-
// Add debug panel class for hover effects
|
|
257
|
-
state.element.classList.add(this.classes.debugPanel);
|
|
258
|
-
|
|
259
|
-
// Add close button (×) to header - used to close enlarged view
|
|
260
|
-
const closeBtn = document.createElement('button');
|
|
261
|
-
closeBtn.className = this.classes.debugClearButton;
|
|
262
|
-
closeBtn.textContent = '×';
|
|
263
|
-
closeBtn.title = 'Close enlarged view';
|
|
264
|
-
if (state.collapseButton) {
|
|
265
|
-
state.collapseButton.parentElement?.insertBefore(closeBtn, state.collapseButton);
|
|
266
|
-
}
|
|
267
|
-
elements.clearButton = closeBtn;
|
|
268
|
-
|
|
269
258
|
this.debugPanelElements.set(state.id, elements);
|
|
270
259
|
|
|
271
260
|
const debugPanel = createDebugPanelInterface(state, elements, config, this.classes);
|
|
272
261
|
|
|
273
|
-
// Set up hover-to-enlarge behavior (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
backdrop = null;
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const openEnlarged = () => {
|
|
291
|
-
if (isEnlarged) return;
|
|
292
|
-
isEnlarged = true;
|
|
293
|
-
|
|
294
|
-
// Create backdrop
|
|
295
|
-
backdrop = document.createElement('div');
|
|
296
|
-
backdrop.className = backdropClass;
|
|
297
|
-
this.config.container.appendChild(backdrop);
|
|
298
|
-
|
|
299
|
-
// Click backdrop to close
|
|
300
|
-
backdrop.addEventListener('click', closeEnlarged);
|
|
301
|
-
|
|
302
|
-
// Add enlarged class
|
|
303
|
-
state.element.classList.add(enlargedClass);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
// Hover to start enlarge timer (only when not already enlarged)
|
|
307
|
-
state.element.addEventListener('mouseenter', () => {
|
|
308
|
-
if (isEnlarged) return;
|
|
309
|
-
hoverTimeout = setTimeout(() => {
|
|
310
|
-
openEnlarged();
|
|
311
|
-
}, 5000);
|
|
312
|
-
});
|
|
262
|
+
// Set up hover-to-enlarge behavior on the log container (shared with embedded logs)
|
|
263
|
+
const hoverDelay = config.hoverDelay ?? 5000;
|
|
264
|
+
if (hoverDelay > 0) {
|
|
265
|
+
setupHoverEnlarge({
|
|
266
|
+
logContainer: elements.logContainer,
|
|
267
|
+
hoverDelay,
|
|
268
|
+
backdropContainer: this.config.container,
|
|
269
|
+
classes: this.classes,
|
|
270
|
+
onHoverStart: () => this.autoHideManager.pauseTimer(state.id),
|
|
271
|
+
onHoverEnd: () => this.autoHideManager.resumeTimer(state.id),
|
|
272
|
+
onClose: () => this.autoHideManager.resumeTimer(state.id),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
313
275
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (hoverTimeout) {
|
|
317
|
-
clearTimeout(hoverTimeout);
|
|
318
|
-
hoverTimeout = null;
|
|
319
|
-
}
|
|
320
|
-
// Don't close on mouseleave - only × or backdrop click closes
|
|
321
|
-
});
|
|
276
|
+
return debugPanel;
|
|
277
|
+
}
|
|
322
278
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
});
|
|
279
|
+
/**
|
|
280
|
+
* Create an embeddable debug log in any container element
|
|
281
|
+
*/
|
|
282
|
+
createDebugLog(container: HTMLElement, config: DebugLogConfig = {}): DebugLog {
|
|
283
|
+
const { debugLog, logContainer } = createDebugLog(container, config, this.classes);
|
|
284
|
+
|
|
285
|
+
// Set up hover-to-enlarge behavior using shared helper (same as standalone panel)
|
|
286
|
+
const hoverDelay = config.hoverDelay ?? 5000;
|
|
287
|
+
if (hoverDelay > 0) {
|
|
288
|
+
setupHoverEnlarge({
|
|
289
|
+
logContainer,
|
|
290
|
+
hoverDelay,
|
|
291
|
+
backdropContainer: this.config.container,
|
|
292
|
+
classes: this.classes,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
328
295
|
|
|
329
|
-
return
|
|
296
|
+
return debugLog;
|
|
330
297
|
}
|
|
331
298
|
|
|
332
299
|
/**
|
package/src/index.ts
CHANGED
|
@@ -42,7 +42,7 @@ export { AnchorManager, getDefaultAnchorConfigs, createPresetAnchor } from './An
|
|
|
42
42
|
export { DragManager } from './DragManager';
|
|
43
43
|
export { SnapPreview } from './SnapPreview';
|
|
44
44
|
export { AutoHideManager } from './AutoHideManager';
|
|
45
|
-
export { createDebugPanelContent, createDebugPanelInterface } from './DebugPanel';
|
|
45
|
+
export { createDebugPanelContent, createDebugPanelInterface, createDebugLog, setupHoverEnlarge } from './DebugPanel';
|
|
46
46
|
export type { AutoHideCallbacks } from './AutoHideManager';
|
|
47
47
|
export {
|
|
48
48
|
createPanelElement,
|
|
@@ -113,5 +113,7 @@ export type {
|
|
|
113
113
|
|
|
114
114
|
// Debug Panel
|
|
115
115
|
DebugPanel,
|
|
116
|
+
DebugLog,
|
|
117
|
+
DebugLogConfig,
|
|
116
118
|
DebugLogLevel,
|
|
117
119
|
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -370,6 +370,20 @@ export interface DebugPanelConfig extends Omit<PanelConfig, 'content'> {
|
|
|
370
370
|
maxEntries?: number;
|
|
371
371
|
/** Show timestamps on entries (default: false) */
|
|
372
372
|
showTimestamps?: boolean;
|
|
373
|
+
/** Milliseconds to hover before enlarging (default: 5000, 0 = disable) */
|
|
374
|
+
hoverDelay?: number;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Configuration for creating an embeddable debug log
|
|
379
|
+
*/
|
|
380
|
+
export interface DebugLogConfig {
|
|
381
|
+
/** Maximum log entries before oldest are removed (default: 50) */
|
|
382
|
+
maxEntries?: number;
|
|
383
|
+
/** Show timestamps on entries (default: false) */
|
|
384
|
+
showTimestamps?: boolean;
|
|
385
|
+
/** Milliseconds to hover before enlarging (default: 5000, 0 = disable) */
|
|
386
|
+
hoverDelay?: number;
|
|
373
387
|
}
|
|
374
388
|
|
|
375
389
|
/**
|
|
@@ -394,3 +408,19 @@ export interface DebugPanel {
|
|
|
394
408
|
/** The underlying panel state */
|
|
395
409
|
panel: PanelState;
|
|
396
410
|
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Interface for an embeddable debug log (without panel)
|
|
414
|
+
*/
|
|
415
|
+
export interface DebugLog {
|
|
416
|
+
/** Log an event (alias for info) */
|
|
417
|
+
log(eventName: string, data?: Record<string, unknown>): void;
|
|
418
|
+
/** Log an info event (blue) */
|
|
419
|
+
info(eventName: string, data?: Record<string, unknown>): void;
|
|
420
|
+
/** Log a warning event (yellow) */
|
|
421
|
+
warn(eventName: string, data?: Record<string, unknown>): void;
|
|
422
|
+
/** Log an error event (red) */
|
|
423
|
+
error(eventName: string, data?: Record<string, unknown>): void;
|
|
424
|
+
/** Clear all log entries */
|
|
425
|
+
clear(): void;
|
|
426
|
+
}
|