@necrolab/dashboard 0.5.5 → 0.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@necrolab/dashboard",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rm -rf dist && npx workbox-cli generateSW workbox-config.cjs && vite build",
package/src/App.vue CHANGED
@@ -220,19 +220,52 @@ document.onkeydown = function (evt) {
220
220
  }
221
221
  };
222
222
 
223
+ // Prevent ALL keyboard zoom combinations
223
224
  document.addEventListener("keydown", function (event) {
225
+ // Prevent Ctrl/Cmd + Plus/Minus/0 (various keycodes for different browsers)
224
226
  if (
225
227
  (event.ctrlKey || event.metaKey) &&
226
- (event.which === 61 ||
227
- event.which === 107 ||
228
- event.which === 173 ||
229
- event.which === 109 ||
230
- event.which === 187 ||
231
- event.which === 189)
228
+ (event.which === 61 || // = key
229
+ event.which === 107 || // Numpad +
230
+ event.which === 173 || // Firefox -
231
+ event.which === 109 || // Numpad -
232
+ event.which === 187 || // = key (Chrome)
233
+ event.which === 189 || // - key
234
+ event.keyCode === 61 ||
235
+ event.keyCode === 107 ||
236
+ event.keyCode === 173 ||
237
+ event.keyCode === 109 ||
238
+ event.keyCode === 187 ||
239
+ event.keyCode === 189 ||
240
+ event.key === '+' ||
241
+ event.key === '-' ||
242
+ event.key === '=' ||
243
+ event.key === '0')
232
244
  ) {
233
245
  event.preventDefault();
234
246
  }
235
- });
247
+ }, { passive: false });
248
+
249
+ // Prevent Ctrl/Cmd + Mouse wheel zoom (desktop)
250
+ document.addEventListener("wheel", function (event) {
251
+ if (event.ctrlKey || event.metaKey) {
252
+ event.preventDefault();
253
+ }
254
+ }, { passive: false });
255
+
256
+ // Also block mousewheel for older browsers
257
+ document.addEventListener("mousewheel", function (event) {
258
+ if (event.ctrlKey || event.metaKey) {
259
+ event.preventDefault();
260
+ }
261
+ }, { passive: false });
262
+
263
+ // Block DOMMouseScroll for Firefox
264
+ document.addEventListener("DOMMouseScroll", function (event) {
265
+ if (event.ctrlKey || event.metaKey) {
266
+ event.preventDefault();
267
+ }
268
+ }, { passive: false });
236
269
  // Nuclear iOS keyboard prevention - lock everything down
237
270
  let isIOSDevice =
238
271
  /iPad|iPhone|iPod/.test(navigator.platform) ||
@@ -260,108 +293,12 @@ if (isIOSDevice) {
260
293
  }
261
294
  }
262
295
 
263
- // Precise mouse wheel control - only allow scrolling within specific scrollable elements
264
- window.addEventListener(
265
- "mousewheel",
266
- function (event) {
267
- // Check if we're on a scrollable textarea
268
- const isScrollableTextarea =
269
- event.target.tagName === "TEXTAREA" &&
270
- (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
296
+ // Simplified scroll control - let scrollable elements handle their own scrolling
297
+ // Page scroll is already prevented by overflow: hidden on html/body
271
298
 
272
- // Check if we're in a table but only allow scrolling on actual scrollable content
273
- const isInTable = event.target.closest(".table-component");
274
- const isScrollableTableContent =
275
- isInTable &&
276
- (event.target.closest(".grid") || // Table rows
277
- event.target.closest(".table-row") ||
278
- (event.target.closest(".table-component") && !event.target.closest(".table-header")));
299
+ // DOMMouseScroll handler removed - CSS overflow handles scroll prevention
279
300
 
280
- // Allow scrolling in navbar and modals
281
- const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
282
- const isInModal = event.target.closest('[role="dialog"]');
283
-
284
- // Only allow these specific cases
285
- if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
286
- // Stop propagation to prevent page scroll
287
- if (isScrollableTableContent || isScrollableTextarea) {
288
- event.stopPropagation();
289
- }
290
- return;
291
- }
292
-
293
- event.preventDefault();
294
- },
295
- { passive: false }
296
- );
297
-
298
- window.addEventListener(
299
- "DOMMouseScroll",
300
- function (event) {
301
- // Use same logic as mousewheel
302
- const isScrollableTextarea =
303
- event.target.tagName === "TEXTAREA" &&
304
- (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
305
-
306
- const isInTable = event.target.closest(".table-component");
307
- const isScrollableTableContent =
308
- isInTable &&
309
- (event.target.closest(".grid") ||
310
- event.target.closest(".table-row") ||
311
- (event.target.closest(".table-component") && !event.target.closest(".table-header")));
312
-
313
- const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
314
- const isInModal = event.target.closest('[role="dialog"]');
315
-
316
- if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
317
- if (isScrollableTableContent || isScrollableTextarea) {
318
- event.stopPropagation();
319
- }
320
- return;
321
- }
322
-
323
- event.preventDefault();
324
- },
325
- { passive: false }
326
- );
327
-
328
- window.addEventListener(
329
- "wheel",
330
- function (event) {
331
- // Use same logic as mousewheel
332
- const isScrollableTextarea =
333
- event.target.tagName === "TEXTAREA" &&
334
- (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
335
-
336
- const isInTable = event.target.closest(".table-component");
337
- const isScrollableTableContent =
338
- isInTable &&
339
- (event.target.closest(".grid") ||
340
- event.target.closest(".table-row") ||
341
- (event.target.closest(".table-component") && !event.target.closest(".table-header")));
342
-
343
- const isInNavbar = event.target.closest(".navbar") || event.target.closest(".mobile-menu");
344
- const isInModal = event.target.closest('[role="dialog"]');
345
-
346
- if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
347
- if (isScrollableTableContent || isScrollableTextarea) {
348
- event.stopPropagation();
349
- }
350
- return;
351
- }
352
-
353
- event.preventDefault();
354
- },
355
- { passive: false }
356
- );
357
-
358
- window.addEventListener(
359
- "scroll",
360
- function (event) {
361
- event.preventDefault();
362
- },
363
- { passive: false }
364
- );
301
+ // Wheel and scroll handlers removed - CSS overflow: hidden on html/body prevents page scroll
365
302
 
366
303
  // Precise scroll control - only allow scrolling within specific scrollable elements
367
304
  window.addEventListener(
@@ -372,12 +309,21 @@ window.addEventListener(
372
309
  event.target.tagName === "TEXTAREA" &&
373
310
  (event.target.classList.contains("code-editor") || event.target.classList.contains("proxy-editor"));
374
311
 
312
+ // Check if we're in a scrollable container
313
+ const isInScrollableContainer =
314
+ event.target.closest(".stop-pan") ||
315
+ event.target.closest(".overflow-y-auto") ||
316
+ event.target.closest(".vue-recycle-scroller") ||
317
+ event.target.closest(".scroller") ||
318
+ event.target.closest(".scrollable");
319
+
375
320
  // Check if we're in a table but only allow scrolling on actual scrollable content
376
321
  const isInTable = event.target.closest(".table-component");
377
322
  const isScrollableTableContent =
378
323
  isInTable &&
379
324
  (event.target.closest(".grid") || // Table rows
380
325
  event.target.closest(".table-row") ||
326
+ isInScrollableContainer ||
381
327
  (event.target.closest(".table-component") && !event.target.closest(".table-header")));
382
328
 
383
329
  // Allow scrolling in navbar and modals (they handle their own boundaries)
@@ -385,9 +331,9 @@ window.addEventListener(
385
331
  const isInModal = event.target.closest('[role="dialog"]');
386
332
 
387
333
  // Only allow these specific cases
388
- if (isScrollableTextarea || isScrollableTableContent || isInNavbar || isInModal) {
334
+ if (isScrollableTextarea || isScrollableTableContent || isInScrollableContainer || isInNavbar || isInModal) {
389
335
  // For table content, ensure we stop propagation to prevent page scroll
390
- if (isScrollableTableContent || isScrollableTextarea) {
336
+ if (isScrollableTableContent || isScrollableTextarea || isInScrollableContainer) {
391
337
  event.stopPropagation();
392
338
  }
393
339
  return;
@@ -749,11 +695,26 @@ body {
749
695
  touch-action: none !important;
750
696
  }
751
697
 
752
- // Prevent any element from being zoomable
698
+ // Prevent zoom but allow scrolling on specific elements
753
699
  * {
754
700
  touch-action: manipulation !important;
755
701
  }
756
702
 
703
+ // Override for scrollable containers - allow proper touch scrolling
704
+ .table-component,
705
+ .stop-pan,
706
+ .scrollable,
707
+ .overflow-y-auto,
708
+ .vue-recycle-scroller,
709
+ .console,
710
+ [class*="scroll"] {
711
+ touch-action: pan-y !important;
712
+ }
713
+
714
+ .table-component {
715
+ touch-action: pan-x pan-y !important;
716
+ }
717
+
757
718
  .dropdown {
758
719
  position: relative;
759
720
  display: inline-block;
@@ -2,6 +2,14 @@
2
2
  GLOBAL RESETS
3
3
  ========================================================================== */
4
4
 
5
+ /* Comprehensive zoom prevention */
6
+ * {
7
+ -webkit-text-size-adjust: 100%;
8
+ -moz-text-size-adjust: 100%;
9
+ -ms-text-size-adjust: 100%;
10
+ text-size-adjust: 100%;
11
+ }
12
+
5
13
  input,
6
14
  textarea,
7
15
  select,
@@ -9,6 +17,8 @@ button {
9
17
  -webkit-tap-highlight-color: transparent;
10
18
  -webkit-touch-callout: none;
11
19
  outline: none;
20
+ /* Prevent zoom on input focus (mobile) */
21
+ font-size: 16px;
12
22
  }
13
23
 
14
24
  input,
@@ -7,6 +7,11 @@ html {
7
7
  overscroll-behavior: none !important;
8
8
  height: 100% !important;
9
9
  width: 100% !important;
10
+ /* Additional zoom prevention at CSS level */
11
+ -webkit-text-size-adjust: 100%;
12
+ -moz-text-size-adjust: 100%;
13
+ -ms-text-size-adjust: 100%;
14
+ text-size-adjust: 100%;
10
15
  }
11
16
 
12
17
  * {
@@ -31,6 +31,17 @@
31
31
  &::placeholder {
32
32
  color: oklch(0.50 0 0);
33
33
  }
34
+
35
+ // Hide default number input spinners - use custom .input-incrementer instead
36
+ &[type="number"]::-webkit-inner-spin-button,
37
+ &[type="number"]::-webkit-outer-spin-button {
38
+ -webkit-appearance: none;
39
+ margin: 0;
40
+ }
41
+
42
+ &[type="number"] {
43
+ -moz-appearance: textfield;
44
+ }
34
45
  }
35
46
 
36
47
  .input-incrementer {
@@ -73,6 +73,31 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
73
73
  fill: oklch(0.9 0 0) !important;
74
74
  }
75
75
 
76
+ /* Badge icon color exceptions - override ALL global white rules */
77
+ .enabled-badge svg,
78
+ .enabled-badge-large svg {
79
+ color: oklch(0.72 0.15 145) !important;
80
+ fill: oklch(0.72 0.15 145) !important;
81
+ }
82
+
83
+ .disabled-badge svg,
84
+ .disabled-badge-large svg {
85
+ color: oklch(0.60 0.20 25) !important;
86
+ fill: oklch(0.60 0.20 25) !important;
87
+ }
88
+
89
+ .enabled-badge svg *,
90
+ .enabled-badge-large svg * {
91
+ fill: oklch(0.72 0.15 145) !important;
92
+ stroke: oklch(0.72 0.15 145) !important;
93
+ }
94
+
95
+ .disabled-badge svg *,
96
+ .disabled-badge-large svg * {
97
+ fill: oklch(0.60 0.20 25) !important;
98
+ stroke: oklch(0.60 0.20 25) !important;
99
+ }
100
+
76
101
  /* ==========================================================================
77
102
  COMPONENT UTILITIES
78
103
  ========================================================================== */
@@ -21,12 +21,12 @@
21
21
  </h4>
22
22
  </div>
23
23
  <div class="col-span-1">
24
- <h4 v-if="props.account.enabled" class="flex justify-center text-green-400">
25
- <img class="green h-3 w-3" src="/img/controls/enable.svg" />
26
- </h4>
27
- <h4 v-else class="flex justify-center text-red-400">
28
- <img class="h-3 w-3 fill-red-400" src="/img/close.svg" />
29
- </h4>
24
+ <div v-if="props.account.enabled" class="enabled-badge">
25
+ <CheckmarkIcon class="w-3.5 h-3.5" />
26
+ </div>
27
+ <div v-else class="disabled-badge">
28
+ <CloseXIcon class="w-3.5 h-3.5" />
29
+ </div>
30
30
  </div>
31
31
 
32
32
  <div class="col-span-1 hidden lg:block">
@@ -57,30 +57,80 @@
57
57
  </Row>
58
58
  </template>
59
59
  <style lang="scss" scoped>
60
+ .enabled-badge {
61
+ @apply flex items-center justify-center mx-auto rounded-full;
62
+ background: oklch(0.72 0.15 145 / 0.12);
63
+ border: 1.5px solid oklch(0.72 0.15 145);
64
+ width: 24px;
65
+ height: 24px;
66
+ padding: 0;
67
+
68
+ svg {
69
+ color: oklch(0.72 0.15 145) !important;
70
+ width: 12px !important;
71
+ height: auto !important;
72
+ max-height: 12px !important;
73
+ display: block;
74
+ margin: 0;
75
+ }
76
+
77
+ svg path {
78
+ stroke: oklch(0.72 0.15 145) !important;
79
+ fill: oklch(0.72 0.15 145) !important;
80
+ }
81
+ }
82
+
83
+ .disabled-badge {
84
+ @apply flex items-center justify-center mx-auto rounded-full;
85
+ background: oklch(0.60 0.20 25 / 0.12);
86
+ border: 1.5px solid oklch(0.60 0.20 25);
87
+ width: 24px;
88
+ height: 24px;
89
+ padding: 0;
90
+ color: oklch(0.60 0.20 25) !important;
91
+
92
+ svg {
93
+ color: oklch(0.60 0.20 25) !important;
94
+ width: 12px !important;
95
+ height: 12px !important;
96
+ display: block;
97
+ margin: 0;
98
+ fill: oklch(0.60 0.20 25) !important;
99
+ }
100
+
101
+ svg path {
102
+ stroke: oklch(0.60 0.20 25) !important;
103
+ fill: oklch(0.60 0.20 25) !important;
104
+ }
105
+ }
106
+
60
107
  h4 {
61
108
  @apply text-center;
62
109
  }
63
110
  .account-buttons {
64
- @apply mx-auto flex items-center justify-center rounded border border-dark-650 bg-dark-500;
111
+ @apply mx-auto flex items-center justify-center rounded;
112
+ background: oklch(0.2046 0 0);
113
+ border: 2px solid oklch(0.2809 0 0);
65
114
  padding: 3px;
66
115
  gap: 2px;
116
+ flex-shrink: 0;
117
+ overflow: visible;
67
118
 
68
119
  button {
69
120
  @apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
70
121
  background: transparent;
71
122
  width: 28px;
72
123
  height: 28px;
73
- color: oklch(0.82 0 0);
124
+ color: oklch(0.90 0 0);
125
+ border-radius: 6px;
74
126
 
75
127
  &:hover {
76
- background: rgba(255, 255, 255, 0.1);
77
- color: #ffffff;
78
- transform: scale(1.05);
128
+ background: oklch(0.72 0.15 145 / 0.15);
129
+ color: oklch(1 0 0);
79
130
  }
80
131
 
81
132
  &:active {
82
- background: rgba(255, 255, 255, 0.2);
83
- transform: scale(0.95);
133
+ background: oklch(0.72 0.15 145 / 0.25);
84
134
  }
85
135
  }
86
136
 
@@ -97,19 +147,17 @@ h4 {
97
147
  }
98
148
  }
99
149
 
100
- // Tablet optimization
101
- @media (max-width: 1024px) {
102
- h4 {
103
- font-size: 10px !important;
104
- }
105
-
150
+ // Tablet sizing - medium buttons
151
+ @media (min-width: 768px) and (max-width: 1023px) {
106
152
  .account-buttons {
107
- padding: 3px;
108
- gap: 2px;
153
+ padding: 2px;
154
+ gap: 1px;
155
+ border-radius: 6px;
109
156
 
110
157
  button {
111
158
  width: 26px;
112
159
  height: 26px;
160
+ border-radius: 5px;
113
161
  }
114
162
 
115
163
  svg,
@@ -118,59 +166,74 @@ h4 {
118
166
  height: 14px;
119
167
  }
120
168
  }
121
-
122
- .account-id {
123
- font-size: 6px !important;
124
- margin-right: -12px;
125
- margin-top: 20px;
126
- }
127
169
  }
128
170
 
129
- // Mobile optimization
130
- @media (max-width: 768px) {
171
+ // Desktop sizing - large buttons
172
+ @media (min-width: 1024px) {
131
173
  .account-buttons {
132
- padding: 2px;
133
- gap: 1px;
174
+ padding: 3px;
175
+ gap: 2px;
176
+ border-radius: 8px;
134
177
 
135
178
  button {
136
- width: 22px;
137
- height: 22px;
179
+ width: 28px;
180
+ height: 28px;
181
+ border-radius: 6px;
138
182
  }
139
183
 
140
184
  svg,
141
185
  img {
142
- width: 12px;
143
- height: 12px;
186
+ width: 16px;
187
+ height: 16px;
144
188
  }
145
189
  }
146
190
  }
147
191
 
148
- // iPhone vertical (portrait) specific
149
- @media (max-width: 480px) and (orientation: portrait) {
192
+ // Mobile specific styling
193
+ @media (max-width: 640px) {
150
194
  .account-buttons {
151
- padding: 2px;
195
+ padding: 1px;
152
196
  gap: 1px;
197
+ border-radius: 4px;
198
+ border: 2px solid oklch(0.2809 0 0) !important;
199
+ max-width: 100%;
200
+ min-height: 28px;
201
+ height: auto;
202
+ flex-wrap: wrap;
203
+ justify-content: center;
204
+ align-items: center;
205
+ background: oklch(0.2046 0 0);
153
206
 
154
207
  button {
155
- width: 18px;
156
- height: 18px;
208
+ width: 20px;
209
+ height: 20px;
210
+ border-radius: 3px;
211
+ min-width: 20px;
212
+ border: none !important;
213
+ flex-shrink: 0;
214
+ margin: 0.5px;
215
+ background: transparent;
157
216
 
158
217
  &:hover {
159
- transform: scale(1.1);
218
+ background: oklch(0.72 0.15 145 / 0.15);
219
+ }
220
+
221
+ &:active {
222
+ background: oklch(0.72 0.15 145 / 0.25);
160
223
  }
161
224
  }
162
225
 
163
226
  svg,
164
227
  img {
165
- width: 10px;
166
- height: 10px;
228
+ width: 12px;
229
+ height: 12px;
167
230
  }
168
231
  }
169
232
  }
170
233
  </style>
171
234
  <script setup>
172
235
  import { Row } from "@/components/Table";
173
- import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/components/icons";
236
+ import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon, CheckmarkIcon, CloseXIcon } from "@/components/icons";
174
237
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
175
238
  import { useUIStore } from "@/stores/ui";
176
239
  import TagLabel from "@/components/Editors/TagLabel.vue";
@@ -31,22 +31,13 @@
31
31
  </Header>
32
32
  <div
33
33
  v-if="toRender.length != 0"
34
- class="hidden-scrollbars stop-pan overflow-y-auto overflow-x-hidden"
34
+ class="hidden-scrollbars stop-pan flex flex-col divide-y divide-dark-650 overflow-y-auto overflow-x-hidden"
35
35
  :style="{ maxHeight: dynamicTableHeight }">
36
- <RecycleScroller
37
- :items="toRender"
38
- :item-size="64"
39
- key-field="index"
40
- class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650">
41
- <template #default="props">
42
- <div class="account" :key="`account-${props.item.id || props.item.index}`">
43
- <Account
44
- @click="i[props.item.index]++"
45
- :class="props.item.index % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
46
- :account="props.item" />
47
- </div>
48
- </template>
49
- </RecycleScroller>
36
+ <div v-for="(account, i) in toRender" :key="account.id || account.index" class="account-row-container">
37
+ <Account
38
+ :class="i % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
39
+ :account="account" />
40
+ </div>
50
41
  </div>
51
42
  <div v-else class="empty-state flex flex-col items-center justify-center bg-dark-400 py-8 text-center">
52
43
  <MailIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
@@ -56,9 +47,16 @@
56
47
  </Table>
57
48
  </template>
58
49
  <style lang="scss" scoped>
59
- .account {
60
- height: 64px;
50
+ .account-row-container {
51
+ min-height: 64px;
52
+ flex-shrink: 0;
53
+ transition: background-color 0.15s ease;
54
+
55
+ &:hover {
56
+ @apply bg-dark-550 !important;
57
+ }
61
58
  }
59
+
62
60
  h4 {
63
61
  @apply text-white;
64
62
  }