@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/dist/index.js CHANGED
@@ -10,11 +10,24 @@ function createDebugPanelContent(_config, classes) {
10
10
  };
11
11
  }
12
12
  function createDebugPanelInterface(panel, elements, config, classes) {
13
+ const debugLog = createDebugLogInterface(elements.logContainer, config, classes);
14
+ return {
15
+ panel,
16
+ ...debugLog
17
+ };
18
+ }
19
+ function escapeHtml(str) {
20
+ const div = document.createElement("div");
21
+ div.textContent = str;
22
+ return div.innerHTML;
23
+ }
24
+ function createDebugLogInterface(logContainer, config, classes) {
13
25
  const maxEntries = config.maxEntries ?? 50;
14
26
  const showTimestamps = config.showTimestamps ?? false;
27
+ const entryClass = classes.debugLogEntry;
15
28
  function addEntry(level, eventName, data) {
16
29
  const entry = document.createElement("div");
17
- entry.className = classes.debugLogEntry;
30
+ entry.className = entryClass;
18
31
  if (level === "warn") entry.classList.add(classes.debugLogEntryWarn);
19
32
  if (level === "error") entry.classList.add(classes.debugLogEntryError);
20
33
  let html = "";
@@ -33,27 +46,99 @@ function createDebugPanelInterface(panel, elements, config, classes) {
33
46
  html += `<span class="${classes.debugLogData}">${escapeHtml(dataStr)}</span>`;
34
47
  }
35
48
  entry.innerHTML = html;
36
- elements.logContainer.appendChild(entry);
37
- elements.logContainer.scrollTop = elements.logContainer.scrollHeight;
38
- while (elements.logContainer.children.length > maxEntries) {
39
- elements.logContainer.removeChild(elements.logContainer.children[0]);
49
+ logContainer.appendChild(entry);
50
+ logContainer.scrollTop = logContainer.scrollHeight;
51
+ const entries = logContainer.querySelectorAll(`.${entryClass}`);
52
+ if (entries.length > maxEntries) {
53
+ const toRemove = entries.length - maxEntries;
54
+ for (let i = 0; i < toRemove; i++) {
55
+ entries[i].remove();
56
+ }
40
57
  }
41
58
  }
59
+ function clearEntries() {
60
+ const entries = logContainer.querySelectorAll(`.${entryClass}`);
61
+ entries.forEach((entry) => entry.remove());
62
+ }
42
63
  return {
43
- panel,
44
64
  log: (name, data) => addEntry("log", name, data),
45
65
  info: (name, data) => addEntry("info", name, data),
46
66
  warn: (name, data) => addEntry("warn", name, data),
47
67
  error: (name, data) => addEntry("error", name, data),
48
- clear: () => {
49
- elements.logContainer.innerHTML = "";
50
- }
68
+ clear: clearEntries
51
69
  };
52
70
  }
53
- function escapeHtml(str) {
54
- const div = document.createElement("div");
55
- div.textContent = str;
56
- return div.innerHTML;
71
+ function createDebugLog(container, config, classes) {
72
+ const logContainer = document.createElement("div");
73
+ logContainer.className = classes.debugLog;
74
+ container.appendChild(logContainer);
75
+ const debugLog = createDebugLogInterface(logContainer, config, classes);
76
+ return { debugLog, logContainer };
77
+ }
78
+ function setupHoverEnlarge(config) {
79
+ const { logContainer, hoverDelay, backdropContainer, classes, onHoverStart, onHoverEnd, onClose } = config;
80
+ logContainer.classList.add(classes.debugPanel);
81
+ const closeBtn = document.createElement("button");
82
+ closeBtn.className = classes.debugClearButton;
83
+ closeBtn.textContent = "\xD7";
84
+ closeBtn.title = "Close enlarged view";
85
+ logContainer.appendChild(closeBtn);
86
+ let hoverTimeout = null;
87
+ let isEnlarged = false;
88
+ let backdrop = null;
89
+ let originalParent = null;
90
+ let placeholder = null;
91
+ const closeEnlarged = () => {
92
+ if (!isEnlarged) return;
93
+ isEnlarged = false;
94
+ logContainer.classList.remove(classes.debugPanelEnlarged);
95
+ if (originalParent && placeholder) {
96
+ originalParent.insertBefore(logContainer, placeholder);
97
+ placeholder.remove();
98
+ placeholder = null;
99
+ originalParent = null;
100
+ }
101
+ if (backdrop) {
102
+ backdrop.remove();
103
+ backdrop = null;
104
+ }
105
+ onClose?.();
106
+ };
107
+ const openEnlarged = () => {
108
+ if (isEnlarged) return;
109
+ isEnlarged = true;
110
+ backdrop = document.createElement("div");
111
+ backdrop.className = classes.debugBackdrop;
112
+ backdropContainer.appendChild(backdrop);
113
+ backdrop.addEventListener("click", closeEnlarged);
114
+ originalParent = logContainer.parentElement;
115
+ placeholder = document.createComment("debug-log-placeholder");
116
+ originalParent?.insertBefore(placeholder, logContainer);
117
+ backdropContainer.appendChild(logContainer);
118
+ logContainer.classList.add(classes.debugPanelEnlarged);
119
+ };
120
+ logContainer.addEventListener("mouseenter", () => {
121
+ onHoverStart?.();
122
+ if (isEnlarged) return;
123
+ if (hoverDelay > 0) {
124
+ hoverTimeout = setTimeout(() => {
125
+ openEnlarged();
126
+ }, hoverDelay);
127
+ }
128
+ });
129
+ logContainer.addEventListener("mouseleave", () => {
130
+ if (hoverTimeout) {
131
+ clearTimeout(hoverTimeout);
132
+ hoverTimeout = null;
133
+ }
134
+ if (!isEnlarged) {
135
+ onHoverEnd?.();
136
+ }
137
+ });
138
+ closeBtn.addEventListener("click", (e) => {
139
+ e.stopPropagation();
140
+ closeEnlarged();
141
+ });
57
142
  }
58
143
 
59
144
  // src/Panel.ts
@@ -846,6 +931,7 @@ var SnapPreview = class {
846
931
  var AutoHideManager = class {
847
932
  constructor(panels, classes, callbacks) {
848
933
  this.hideTimers = /* @__PURE__ */ new Map();
934
+ this.pausedPanels = /* @__PURE__ */ new Set();
849
935
  this.listenersAttached = false;
850
936
  this.panels = panels;
851
937
  this.classes = classes;
@@ -867,6 +953,7 @@ var AutoHideManager = class {
867
953
  */
868
954
  handleActivity() {
869
955
  for (const panel of this.panels.values()) {
956
+ if (this.pausedPanels.has(panel.id)) continue;
870
957
  if (panel.resolvedAutoHideDelay !== void 0 || panel.isHidden) {
871
958
  this.show(panel, "activity");
872
959
  if (panel.resolvedAutoHideDelay !== void 0) {
@@ -896,6 +983,23 @@ var AutoHideManager = class {
896
983
  this.hideTimers.delete(panelId);
897
984
  }
898
985
  }
986
+ /**
987
+ * Pause auto-hide timer for a panel (e.g., during debug hover)
988
+ */
989
+ pauseTimer(panelId) {
990
+ this.clearTimer(panelId);
991
+ this.pausedPanels.add(panelId);
992
+ }
993
+ /**
994
+ * Resume auto-hide timer for a panel
995
+ */
996
+ resumeTimer(panelId) {
997
+ this.pausedPanels.delete(panelId);
998
+ const panel = this.panels.get(panelId);
999
+ if (panel && panel.resolvedAutoHideDelay !== void 0) {
1000
+ this.scheduleHide(panel);
1001
+ }
1002
+ }
899
1003
  /**
900
1004
  * Show a panel
901
1005
  */
@@ -1102,58 +1206,38 @@ var TabManager = class {
1102
1206
  startCollapsed: config.startCollapsed ?? true
1103
1207
  };
1104
1208
  const state = this.addPanel(panelConfig);
1105
- state.element.classList.add(this.classes.debugPanel);
1106
- const closeBtn = document.createElement("button");
1107
- closeBtn.className = this.classes.debugClearButton;
1108
- closeBtn.textContent = "\xD7";
1109
- closeBtn.title = "Close enlarged view";
1110
- if (state.collapseButton) {
1111
- state.collapseButton.parentElement?.insertBefore(closeBtn, state.collapseButton);
1112
- }
1113
- elements.clearButton = closeBtn;
1114
1209
  this.debugPanelElements.set(state.id, elements);
1115
1210
  const debugPanel = createDebugPanelInterface(state, elements, config, this.classes);
1116
- let hoverTimeout = null;
1117
- let isEnlarged = false;
1118
- let backdrop = null;
1119
- const enlargedClass = this.classes.debugPanelEnlarged;
1120
- const backdropClass = this.classes.debugBackdrop;
1121
- const closeEnlarged = () => {
1122
- if (!isEnlarged) return;
1123
- isEnlarged = false;
1124
- state.element.classList.remove(enlargedClass);
1125
- if (backdrop) {
1126
- backdrop.remove();
1127
- backdrop = null;
1128
- }
1129
- };
1130
- const openEnlarged = () => {
1131
- if (isEnlarged) return;
1132
- isEnlarged = true;
1133
- backdrop = document.createElement("div");
1134
- backdrop.className = backdropClass;
1135
- this.config.container.appendChild(backdrop);
1136
- backdrop.addEventListener("click", closeEnlarged);
1137
- state.element.classList.add(enlargedClass);
1138
- };
1139
- state.element.addEventListener("mouseenter", () => {
1140
- if (isEnlarged) return;
1141
- hoverTimeout = setTimeout(() => {
1142
- openEnlarged();
1143
- }, 5e3);
1144
- });
1145
- state.element.addEventListener("mouseleave", () => {
1146
- if (hoverTimeout) {
1147
- clearTimeout(hoverTimeout);
1148
- hoverTimeout = null;
1149
- }
1150
- });
1151
- closeBtn.addEventListener("click", (e) => {
1152
- e.stopPropagation();
1153
- closeEnlarged();
1154
- });
1211
+ const hoverDelay = config.hoverDelay ?? 5e3;
1212
+ if (hoverDelay > 0) {
1213
+ setupHoverEnlarge({
1214
+ logContainer: elements.logContainer,
1215
+ hoverDelay,
1216
+ backdropContainer: this.config.container,
1217
+ classes: this.classes,
1218
+ onHoverStart: () => this.autoHideManager.pauseTimer(state.id),
1219
+ onHoverEnd: () => this.autoHideManager.resumeTimer(state.id),
1220
+ onClose: () => this.autoHideManager.resumeTimer(state.id)
1221
+ });
1222
+ }
1155
1223
  return debugPanel;
1156
1224
  }
1225
+ /**
1226
+ * Create an embeddable debug log in any container element
1227
+ */
1228
+ createDebugLog(container, config = {}) {
1229
+ const { debugLog, logContainer } = createDebugLog(container, config, this.classes);
1230
+ const hoverDelay = config.hoverDelay ?? 5e3;
1231
+ if (hoverDelay > 0) {
1232
+ setupHoverEnlarge({
1233
+ logContainer,
1234
+ hoverDelay,
1235
+ backdropContainer: this.config.container,
1236
+ classes: this.classes
1237
+ });
1238
+ }
1239
+ return debugLog;
1240
+ }
1157
1241
  /**
1158
1242
  * Set up event handlers for a panel
1159
1243
  */
@@ -1420,6 +1504,7 @@ export {
1420
1504
  SnapPreview,
1421
1505
  TabManager,
1422
1506
  areInSameChain,
1507
+ createDebugLog,
1423
1508
  createDebugPanelContent,
1424
1509
  createDebugPanelInterface,
1425
1510
  createPanelElement,
@@ -1438,6 +1523,7 @@ export {
1438
1523
  hidePanel,
1439
1524
  setPanelPosition,
1440
1525
  setPanelZIndex,
1526
+ setupHoverEnlarge,
1441
1527
  showPanel,
1442
1528
  snapPanels,
1443
1529
  snapPanelsToTarget,
package/dist/styles.css CHANGED
@@ -270,70 +270,81 @@
270
270
  opacity: 1;
271
271
  }
272
272
 
273
- /* Hide close button when NOT enlarged (only show when zoomed) */
274
- .blork-tabs-debug-panel .blork-tabs-debug-clear-btn {
275
- display: none;
276
- }
277
-
278
- .blork-tabs-debug-panel-enlarged .blork-tabs-debug-clear-btn {
279
- display: flex;
273
+ /* Debug log hover effects - applied to .blork-tabs-debug-log */
274
+ .blork-tabs-debug-log.blork-tabs-debug-panel {
275
+ transition: border-color 0.3s ease;
276
+ position: relative;
280
277
  }
281
278
 
282
- /* Debug panel hover effects */
283
- .blork-tabs-debug-panel {
284
- transition: border-color 0.3s ease, opacity 0.3s ease;
285
- }
286
-
287
- .blork-tabs-debug-panel:hover {
279
+ .blork-tabs-debug-log.blork-tabs-debug-panel:hover {
288
280
  border-color: var(--blork-tabs-accent, #4a90d9);
289
281
  box-shadow: 0 0 12px rgba(74, 144, 217, 0.3);
290
282
  }
291
283
 
292
- /* Enlarged state after hover timeout - 75% of screen, modal-level z-index */
293
- .blork-tabs-debug-panel-enlarged {
284
+ /* No hover effect when already enlarged */
285
+ .blork-tabs-debug-log.blork-tabs-debug-panel.blork-tabs-debug-panel-enlarged:hover {
286
+ border-color: rgba(255, 255, 255, 0.1);
287
+ box-shadow: none;
288
+ }
289
+
290
+ /* Enlarged state for debug log - applies to .blork-tabs-debug-log directly */
291
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged {
294
292
  position: fixed !important;
295
293
  width: 75vw !important;
296
294
  left: 12.5vw !important;
297
295
  top: 12.5vh !important;
296
+ max-height: 75vh !important;
297
+ min-height: 75vh !important;
298
298
  z-index: 100000 !important;
299
- }
300
-
301
- /* Make content area fill the enlarged panel */
302
- .blork-tabs-debug-panel-enlarged .blork-tabs-content {
303
- max-height: 70vh !important;
304
- padding: 16px;
305
- }
306
-
307
- .blork-tabs-debug-panel-enlarged .blork-tabs-debug-log {
308
- max-height: 65vh !important;
309
- min-height: 65vh !important;
310
299
  font-size: 22px !important;
311
300
  line-height: 1.6;
301
+ padding: 16px;
302
+ border-radius: 8px;
303
+ box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6);
304
+ background: rgba(0, 0, 0, 0.95) !important;
312
305
  }
313
306
 
314
- .blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-entry {
307
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-entry {
315
308
  padding: 10px 0;
316
309
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
317
310
  }
318
311
 
319
- .blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-name {
312
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-name {
320
313
  font-size: 22px;
321
314
  }
322
315
 
323
- .blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-data {
316
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-data {
324
317
  font-size: 20px;
325
318
  }
326
319
 
327
- .blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-timestamp {
320
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged .blork-tabs-debug-log-timestamp {
328
321
  font-size: 20px !important;
329
322
  }
330
323
 
331
- .blork-tabs-debug-panel-enlarged .blork-tabs-header {
332
- padding: 14px 18px;
324
+ /* Close button inside enlarged log */
325
+ .blork-tabs-debug-log .blork-tabs-debug-clear-btn {
326
+ position: absolute;
327
+ top: 8px;
328
+ right: 8px;
329
+ display: none;
330
+ width: 32px;
331
+ height: 32px;
332
+ font-size: 20px;
333
+ background: rgba(0, 0, 0, 0.5);
334
+ border-radius: 4px;
335
+ z-index: 1;
336
+ }
337
+
338
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged {
339
+ position: fixed !important;
333
340
  }
334
341
 
335
- .blork-tabs-debug-panel-enlarged .blork-tabs-title {
336
- font-size: 20px;
342
+ .blork-tabs-debug-log.blork-tabs-debug-panel-enlarged .blork-tabs-debug-clear-btn {
343
+ display: flex;
344
+ position: sticky;
345
+ top: 0;
346
+ float: right;
347
+ margin-bottom: -32px;
337
348
  }
338
349
 
339
350
  /* Backdrop when enlarged */
@@ -344,7 +355,7 @@
344
355
  right: 0;
345
356
  bottom: 0;
346
357
  background: rgba(0, 0, 0, 0.5);
347
- z-index: 9999;
358
+ z-index: 99999;
348
359
  }
349
360
 
350
361
  /* Debug log scrollbar styling */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blorkfield/blork-tabs",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A framework-agnostic tab/panel management system with snapping and docking",
5
5
  "packageManager": "pnpm@10.28.2",
6
6
  "type": "module",
@@ -19,6 +19,7 @@ export class AutoHideManager {
19
19
  private classes: CSSClasses;
20
20
  private callbacks: AutoHideCallbacks;
21
21
  private hideTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
22
+ private pausedPanels: Set<string> = new Set();
22
23
  private boundActivityHandler: () => void;
23
24
  private listenersAttached = false;
24
25
 
@@ -49,6 +50,9 @@ export class AutoHideManager {
49
50
  */
50
51
  private handleActivity(): void {
51
52
  for (const panel of this.panels.values()) {
53
+ // Skip paused panels - they handle their own timing
54
+ if (this.pausedPanels.has(panel.id)) continue;
55
+
52
56
  // Only process panels that participate in auto-hide
53
57
  if (panel.resolvedAutoHideDelay !== undefined || panel.isHidden) {
54
58
  this.show(panel, 'activity');
@@ -62,7 +66,7 @@ export class AutoHideManager {
62
66
  /**
63
67
  * Schedule a panel to hide after its delay
64
68
  */
65
- private scheduleHide(panel: PanelState): void {
69
+ scheduleHide(panel: PanelState): void {
66
70
  this.clearTimer(panel.id);
67
71
  if (panel.resolvedAutoHideDelay === undefined) return;
68
72
 
@@ -75,7 +79,7 @@ export class AutoHideManager {
75
79
  /**
76
80
  * Clear hide timer for a panel
77
81
  */
78
- private clearTimer(panelId: string): void {
82
+ clearTimer(panelId: string): void {
79
83
  const timer = this.hideTimers.get(panelId);
80
84
  if (timer) {
81
85
  clearTimeout(timer);
@@ -83,6 +87,25 @@ export class AutoHideManager {
83
87
  }
84
88
  }
85
89
 
90
+ /**
91
+ * Pause auto-hide timer for a panel (e.g., during debug hover)
92
+ */
93
+ pauseTimer(panelId: string): void {
94
+ this.clearTimer(panelId);
95
+ this.pausedPanels.add(panelId);
96
+ }
97
+
98
+ /**
99
+ * Resume auto-hide timer for a panel
100
+ */
101
+ resumeTimer(panelId: string): void {
102
+ this.pausedPanels.delete(panelId);
103
+ const panel = this.panels.get(panelId);
104
+ if (panel && panel.resolvedAutoHideDelay !== undefined) {
105
+ this.scheduleHide(panel);
106
+ }
107
+ }
108
+
86
109
  /**
87
110
  * Show a panel
88
111
  */