@jacksontian/mwt 1.0.0 → 1.1.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/public/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <body>
12
12
  <div id="toolbar">
13
13
  <div class="toolbar-left">
14
- <button id="btn-new-terminal" title="New Terminal (Ctrl+Shift+T)">
14
+ <button id="btn-new-terminal" title="New Terminal">
15
15
  <span class="btn-icon">+</span> New Terminal
16
16
  </button>
17
17
  </div>
@@ -32,7 +32,13 @@
32
32
  </div>
33
33
  </div>
34
34
  <div class="toolbar-right">
35
- <button id="btn-fullscreen" title="Toggle fullscreen (F11)">
35
+ <button id="btn-settings" title="Settings">
36
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 10a2 2 0 100-4 2 2 0 000 4z" stroke="currentColor" stroke-width="1.4"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M2.93 2.93l1.06 1.06M12.01 12.01l1.06 1.06M2.93 13.07l1.06-1.06M12.01 3.99l1.06-1.06" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
37
+ </button>
38
+ <button id="btn-shortcuts" title="Keyboard Shortcuts">
39
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="3" width="14" height="10" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path d="M4 7h1M7 7h1M10 7h1M4 9.5h8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
40
+ </button>
41
+ <button id="btn-fullscreen" title="Toggle fullscreen">
36
42
  <svg class="icon-enter-fs" width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
37
43
  <svg class="icon-exit-fs" width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M6 2v4H2M10 6h4V2M10 14v-4h4M6 10H2v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
38
44
  </button>
@@ -50,6 +56,55 @@
50
56
  <div id="terminal-container" class="layout-side-by-side"></div>
51
57
  </div>
52
58
 
59
+ <div id="settings-modal" class="modal-overlay hidden">
60
+ <div class="modal">
61
+ <div class="modal-header">
62
+ <span class="modal-title">Settings</span>
63
+ <button id="btn-settings-close" class="modal-close">&times;</button>
64
+ </div>
65
+ <div class="modal-body">
66
+ <div class="settings-group">
67
+ <div class="settings-group-title">Font</div>
68
+ <div class="settings-row">
69
+ <label class="settings-label" for="input-font-size">Size <span id="font-size-value"></span></label>
70
+ <input type="range" id="input-font-size" min="8" max="32" step="1">
71
+ </div>
72
+ <div class="settings-row">
73
+ <label class="settings-label" for="input-font-family">Family</label>
74
+ <input type="text" id="input-font-family" class="settings-text-input" placeholder="e.g. Menlo, monospace">
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <div id="shortcuts-modal" class="modal-overlay hidden">
82
+ <div class="modal">
83
+ <div class="modal-header">
84
+ <span class="modal-title">Keyboard Shortcuts</span>
85
+ <button id="btn-shortcuts-close" class="modal-close">&times;</button>
86
+ </div>
87
+ <div class="modal-body">
88
+ <div class="shortcut-group">
89
+ <div class="shortcut-group-title">Terminals</div>
90
+ <div class="shortcut-row"><span class="shortcut-desc">New Terminal</span><kbd class="shortcut-key" data-shortcut="Ctrl+`"></kbd></div>
91
+ <div class="shortcut-row"><span class="shortcut-desc">Close Terminal</span><kbd class="shortcut-key" data-shortcut="Ctrl+Shift+`"></kbd></div>
92
+ <div class="shortcut-row"><span class="shortcut-desc">Maximize / Restore Terminal</span><kbd class="shortcut-key" data-shortcut="Ctrl+Shift+M"></kbd></div>
93
+ </div>
94
+ <div class="shortcut-group">
95
+ <div class="shortcut-group-title">Navigation</div>
96
+ <div class="shortcut-row"><span class="shortcut-desc">Next Terminal</span><kbd class="shortcut-key" data-shortcut="Ctrl+PageDown"></kbd></div>
97
+ <div class="shortcut-row"><span class="shortcut-desc">Previous Terminal</span><kbd class="shortcut-key" data-shortcut="Ctrl+PageUp"></kbd></div>
98
+ <div class="shortcut-row"><span class="shortcut-desc">Switch to Terminal 1–9</span><kbd class="shortcut-key" data-shortcut="Ctrl+Shift+1~9"></kbd></div>
99
+ </div>
100
+ <div class="shortcut-group">
101
+ <div class="shortcut-group-title">Window</div>
102
+ <div class="shortcut-row"><span class="shortcut-desc">Toggle Fullscreen</span><kbd class="shortcut-key" data-shortcut="F11"></kbd></div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
53
108
  <script src="/vendor/xterm/xterm.js"></script>
54
109
  <script type="module" src="/js/app.js"></script>
55
110
  </body>
package/public/js/app.js CHANGED
@@ -2,14 +2,31 @@ import { WSClient } from './ws-client.js';
2
2
  import { TerminalManager } from './terminal-manager.js';
3
3
  import { LayoutManager } from './layout-manager.js';
4
4
  import { ThemeManager } from './theme-manager.js';
5
+ import { FontManager } from './font-manager.js';
6
+
7
+ const isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform);
8
+ const cmdKey = isMac ? 'Cmd' : 'Ctrl';
9
+
5
10
  const wsClient = new WSClient();
6
11
  const container = document.getElementById('terminal-container');
7
12
  const tabBar = document.getElementById('tab-bar');
8
13
  const terminalCount = document.getElementById('terminal-count');
9
14
 
10
15
  const themeManager = new ThemeManager();
16
+ const fontManager = new FontManager();
11
17
  const layoutManager = new LayoutManager(container, tabBar);
12
- const terminalManager = new TerminalManager(wsClient, container, themeManager);
18
+ const terminalManager = new TerminalManager(wsClient, container, themeManager, cmdKey, fontManager);
19
+
20
+ layoutManager.onTabActivate = (id) => {
21
+ terminalManager.focusTerminal(id);
22
+ };
23
+
24
+ layoutManager.onLayoutApplied = () => {
25
+ const id = terminalManager.activeId || terminalManager.getIds()[0];
26
+ if (id) {
27
+ terminalManager.focusTerminal(id);
28
+ }
29
+ };
13
30
 
14
31
  // Theme toggle
15
32
  document.getElementById('btn-theme-toggle').addEventListener('click', () => {
@@ -19,6 +36,7 @@ document.getElementById('btn-theme-toggle').addEventListener('click', () => {
19
36
  // Cross-wire layout manager and terminal manager
20
37
  layoutManager.fitAll = () => terminalManager.fitAll();
21
38
  layoutManager.closeTerminal = (id) => terminalManager.closeTerminal(id);
39
+ layoutManager.swapTerminals = (id1, id2) => terminalManager.swapTerminals(id1, id2);
22
40
 
23
41
  // Activity notification: visual indicators + browser notifications + title flash
24
42
  const originalTitle = document.title;
@@ -60,7 +78,6 @@ terminalManager.onActivity((id, cleared) => {
60
78
  });
61
79
  n.onclick = () => {
62
80
  window.focus();
63
- terminalManager.activeId = id;
64
81
  terminalManager.focusTerminal(id);
65
82
  if (layoutManager.currentLayout === 'tabs') {
66
83
  layoutManager.activateTab(id);
@@ -104,6 +121,10 @@ terminalManager.onChange((event) => {
104
121
  layoutManager.onTerminalRemoved(event.id);
105
122
  } else if (event.type === 'title') {
106
123
  layoutManager.updateTabTitle(event.id, event.title);
124
+ } else if (event.type === 'maximize') {
125
+ layoutManager.onTerminalMaximized();
126
+ } else if (event.type === 'restore') {
127
+ layoutManager.onTerminalRestored();
107
128
  }
108
129
  updateCount();
109
130
  });
@@ -113,6 +134,75 @@ document.getElementById('btn-new-terminal').addEventListener('click', () => {
113
134
  terminalManager.createTerminal();
114
135
  });
115
136
 
137
+ // Settings modal
138
+ const settingsModal = document.getElementById('settings-modal');
139
+ const inputFontSize = document.getElementById('input-font-size');
140
+ const inputFontFamily = document.getElementById('input-font-family');
141
+ const fontSizeValue = document.getElementById('font-size-value');
142
+
143
+ function syncSettingsUI() {
144
+ inputFontSize.value = fontManager.fontSize;
145
+ fontSizeValue.textContent = `${fontManager.fontSize}px`;
146
+ inputFontFamily.value = fontManager.fontFamily;
147
+ }
148
+
149
+ document.getElementById('btn-settings').addEventListener('click', () => {
150
+ syncSettingsUI();
151
+ settingsModal.classList.remove('hidden');
152
+ });
153
+
154
+ document.getElementById('btn-settings-close').addEventListener('click', () => {
155
+ settingsModal.classList.add('hidden');
156
+ });
157
+
158
+ settingsModal.addEventListener('click', (e) => {
159
+ if (e.target === settingsModal) settingsModal.classList.add('hidden');
160
+ });
161
+
162
+ inputFontSize.addEventListener('input', () => {
163
+ fontSizeValue.textContent = `${inputFontSize.value}px`;
164
+ fontManager.setFontSize(parseInt(inputFontSize.value, 10));
165
+ });
166
+
167
+ inputFontFamily.addEventListener('change', () => {
168
+ const val = inputFontFamily.value.trim();
169
+ if (val) fontManager.setFontFamily(val);
170
+ });
171
+
172
+ // Update button titles with platform-appropriate shortcut hints
173
+ document.getElementById('btn-new-terminal').title = `New Terminal (${cmdKey}+\`)`;
174
+ document.getElementById('btn-fullscreen').title = `Toggle fullscreen (F11)`;
175
+ document.getElementById('btn-shortcuts').title = `Keyboard Shortcuts`;
176
+
177
+ // Shortcuts modal
178
+ const shortcutsModal = document.getElementById('shortcuts-modal');
179
+
180
+ document.getElementById('btn-shortcuts').addEventListener('click', () => {
181
+ shortcutsModal.classList.remove('hidden');
182
+ });
183
+
184
+ document.getElementById('btn-shortcuts-close').addEventListener('click', () => {
185
+ shortcutsModal.classList.add('hidden');
186
+ });
187
+
188
+ shortcutsModal.addEventListener('click', (e) => {
189
+ if (e.target === shortcutsModal) {
190
+ shortcutsModal.classList.add('hidden');
191
+ }
192
+ });
193
+
194
+ document.addEventListener('keydown', (e) => {
195
+ if (e.key === 'Escape') {
196
+ if (!shortcutsModal.classList.contains('hidden')) shortcutsModal.classList.add('hidden');
197
+ if (!settingsModal.classList.contains('hidden')) settingsModal.classList.add('hidden');
198
+ }
199
+ }, true);
200
+
201
+ // Fill platform-appropriate shortcut keys in modal
202
+ document.querySelectorAll('.shortcut-key[data-shortcut]').forEach(el => {
203
+ el.textContent = el.dataset.shortcut.replace(/^Ctrl/, cmdKey);
204
+ });
205
+
116
206
  // Layout persistence
117
207
  const LAYOUT_KEY = 'myterminal-layout';
118
208
  const ACTIVE_TAB_KEY = 'myterminal-active-tab';
@@ -129,6 +219,11 @@ document.querySelectorAll('.layout-btn').forEach(btn => {
129
219
  btn.addEventListener('click', () => {
130
220
  document.querySelectorAll('.layout-btn').forEach(b => b.classList.remove('active'));
131
221
  btn.classList.add('active');
222
+ // Sync active terminal to layout manager before switching, so tabs
223
+ // activates the same terminal the user is currently focused on.
224
+ if (terminalManager.activeId) {
225
+ layoutManager.activeTabId = terminalManager.activeId;
226
+ }
132
227
  layoutManager.setLayout(btn.dataset.layout);
133
228
  saveLayoutState();
134
229
  });
@@ -151,24 +246,23 @@ document.addEventListener('fullscreenchange', () => {
151
246
  document.addEventListener('keydown', (e) => {
152
247
  const ctrl = e.ctrlKey || e.metaKey;
153
248
  const shift = e.shiftKey;
154
- const alt = e.altKey;
155
249
 
156
- // Ctrl+Shift+T: new terminal
157
- if (ctrl && shift && e.key === 'T') {
250
+ // Ctrl+`: new terminal
251
+ if (ctrl && !shift && e.key === '`') {
158
252
  e.preventDefault();
159
253
  terminalManager.createTerminal();
160
254
  return;
161
255
  }
162
256
 
163
- // Ctrl+Shift+W: close active terminal
164
- if (ctrl && shift && e.key === 'W') {
257
+ // Ctrl+Shift+`: close active terminal
258
+ if (ctrl && shift && e.key === '`') {
165
259
  e.preventDefault();
166
260
  terminalManager.closeActiveTerminal();
167
261
  return;
168
262
  }
169
263
 
170
- // Ctrl+Shift+]: next terminal
171
- if (ctrl && shift && e.key === '}') {
264
+ // Ctrl+PageDown: next terminal
265
+ if (ctrl && e.key === 'PageDown') {
172
266
  e.preventDefault();
173
267
  const nextId = terminalManager.focusNext();
174
268
  if (nextId) {
@@ -180,8 +274,8 @@ document.addEventListener('keydown', (e) => {
180
274
  return;
181
275
  }
182
276
 
183
- // Ctrl+Shift+[: previous terminal
184
- if (ctrl && shift && e.key === '{') {
277
+ // Ctrl+PageUp: previous terminal
278
+ if (ctrl && e.key === 'PageUp') {
185
279
  e.preventDefault();
186
280
  const prevId = terminalManager.focusPrev();
187
281
  if (prevId) {
@@ -193,14 +287,13 @@ document.addEventListener('keydown', (e) => {
193
287
  return;
194
288
  }
195
289
 
196
- // Alt+1~9: switch to terminal N
197
- if (alt && e.key >= '1' && e.key <= '9') {
290
+ // Ctrl+Shift+1~9: switch to terminal N
291
+ if (ctrl && shift && e.key >= '1' && e.key <= '9') {
198
292
  e.preventDefault();
199
293
  const ids = terminalManager.getIds();
200
294
  const idx = parseInt(e.key, 10) - 1;
201
295
  if (idx < ids.length) {
202
296
  const targetId = ids[idx];
203
- terminalManager.activeId = targetId;
204
297
  terminalManager.focusTerminal(targetId);
205
298
  terminalManager.clearActivity(targetId);
206
299
  if (layoutManager.currentLayout === 'tabs') {
@@ -0,0 +1,43 @@
1
+ const STORAGE_KEY_SIZE = 'myterminal-font-size';
2
+ const STORAGE_KEY_FAMILY = 'myterminal-font-family';
3
+
4
+ const DEFAULT_FONT_SIZE = 14;
5
+ const DEFAULT_FONT_FAMILY = "'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Menlo', monospace";
6
+
7
+ export class FontManager {
8
+ constructor() {
9
+ this._listeners = [];
10
+ this._fontSize = parseInt(localStorage.getItem(STORAGE_KEY_SIZE), 10) || DEFAULT_FONT_SIZE;
11
+ this._fontFamily = localStorage.getItem(STORAGE_KEY_FAMILY) || DEFAULT_FONT_FAMILY;
12
+ }
13
+
14
+ get fontSize() {
15
+ return this._fontSize;
16
+ }
17
+
18
+ get fontFamily() {
19
+ return this._fontFamily;
20
+ }
21
+
22
+ setFontSize(size) {
23
+ this._fontSize = size;
24
+ localStorage.setItem(STORAGE_KEY_SIZE, size);
25
+ this._notify();
26
+ }
27
+
28
+ setFontFamily(family) {
29
+ this._fontFamily = family;
30
+ localStorage.setItem(STORAGE_KEY_FAMILY, family);
31
+ this._notify();
32
+ }
33
+
34
+ onChange(callback) {
35
+ this._listeners.push(callback);
36
+ }
37
+
38
+ _notify() {
39
+ for (const cb of this._listeners) {
40
+ cb({ fontSize: this._fontSize, fontFamily: this._fontFamily });
41
+ }
42
+ }
43
+ }
@@ -4,12 +4,20 @@ export class LayoutManager {
4
4
  this.tabBar = tabBar;
5
5
  this.currentLayout = 'side-by-side';
6
6
  this.activeTabId = null;
7
+ /** @type {((id: string) => void) | null} Called after tab becomes active (focus PTY). */
8
+ this.onTabActivate = null;
9
+ /** @type {((layout: string) => void) | null} After refit when layout mode changes (focus PTY). */
10
+ this.onLayoutApplied = null;
7
11
  // These will be set by app.js after construction
8
12
  this.fitAll = () => {};
9
13
  this.closeTerminal = () => {};
14
+ this.swapTerminals = null;
10
15
  }
11
16
 
12
17
  setLayout(mode) {
18
+ // Clear maximized state before switching — layout takes priority
19
+ this._clearMaximizedState();
20
+
13
21
  // Remove all layout classes
14
22
  this.container.classList.remove('layout-side-by-side', 'layout-grid', 'layout-tabs');
15
23
  this.container.classList.add(`layout-${mode}`);
@@ -37,8 +45,15 @@ export class LayoutManager {
37
45
  }
38
46
  }
39
47
 
40
- // Refit after layout change
41
- requestAnimationFrame(() => this.fitAll());
48
+ // Refit after layout change, then restore keyboard focus to the active terminal
49
+ requestAnimationFrame(() => {
50
+ this.fitAll();
51
+ requestAnimationFrame(() => {
52
+ if (this.onLayoutApplied) {
53
+ this.onLayoutApplied(this.currentLayout);
54
+ }
55
+ });
56
+ });
42
57
  }
43
58
 
44
59
  onTerminalAdded(id) {
@@ -53,14 +68,29 @@ export class LayoutManager {
53
68
 
54
69
  onTerminalRemoved(id) {
55
70
  if (this.currentLayout === 'tabs') {
71
+ let nextTabId = null;
72
+ if (this.activeTabId === id) {
73
+ const tabs = [...this.tabBar.querySelectorAll('.tab')];
74
+ const tidx = tabs.findIndex(t => t.dataset.id === id);
75
+ if (tidx >= 0) {
76
+ if (tidx < tabs.length - 1) {
77
+ nextTabId = tabs[tidx + 1].dataset.id;
78
+ } else if (tidx > 0) {
79
+ nextTabId = tabs[tidx - 1].dataset.id;
80
+ }
81
+ }
82
+ }
56
83
  this._removeTab(id);
57
84
  if (this.activeTabId === id) {
58
- // Activate another tab
59
- const firstTab = this.tabBar.querySelector('.tab');
60
- if (firstTab) {
61
- this.activateTab(firstTab.dataset.id);
85
+ if (nextTabId) {
86
+ this.activateTab(nextTabId);
62
87
  } else {
63
- this.activeTabId = null;
88
+ const firstTab = this.tabBar.querySelector('.tab');
89
+ if (firstTab) {
90
+ this.activateTab(firstTab.dataset.id);
91
+ } else {
92
+ this.activeTabId = null;
93
+ }
64
94
  }
65
95
  }
66
96
  } else if (this.currentLayout === 'grid') {
@@ -85,21 +115,25 @@ export class LayoutManager {
85
115
  // Clear activity indicator for the activated tab
86
116
  this.clearActivity(id);
87
117
 
118
+ if (this.onTabActivate) {
119
+ this.onTabActivate(id);
120
+ }
121
+
88
122
  requestAnimationFrame(() => this.fitAll());
89
123
  }
90
124
 
91
125
  markActivity(id) {
92
126
  const tab = this.tabBar.querySelector(`.tab[data-id="${id}"]`);
93
- if (tab) tab.classList.add('has-activity');
127
+ if (tab) {tab.classList.add('has-activity');}
94
128
  const pane = this.container.querySelector(`.terminal-pane[data-id="${id}"]`);
95
- if (pane) pane.classList.add('has-activity');
129
+ if (pane) {pane.classList.add('has-activity');}
96
130
  }
97
131
 
98
132
  clearActivity(id) {
99
133
  const tab = this.tabBar.querySelector(`.tab[data-id="${id}"]`);
100
- if (tab) tab.classList.remove('has-activity');
134
+ if (tab) {tab.classList.remove('has-activity');}
101
135
  const pane = this.container.querySelector(`.terminal-pane[data-id="${id}"]`);
102
- if (pane) pane.classList.remove('has-activity');
136
+ if (pane) {pane.classList.remove('has-activity');}
103
137
  }
104
138
 
105
139
  _rebuildTabBar() {
@@ -115,6 +149,7 @@ export class LayoutManager {
115
149
  const tab = document.createElement('div');
116
150
  tab.className = 'tab';
117
151
  tab.dataset.id = id;
152
+ tab.draggable = true;
118
153
 
119
154
  const title = document.createElement('span');
120
155
  title.className = 'tab-title';
@@ -136,6 +171,35 @@ export class LayoutManager {
136
171
  this.activateTab(id);
137
172
  });
138
173
 
174
+ tab.addEventListener('dragstart', (e) => {
175
+ e.dataTransfer.effectAllowed = 'move';
176
+ e.dataTransfer.setData('text/plain', id);
177
+ });
178
+ tab.addEventListener('dragover', (e) => {
179
+ e.preventDefault();
180
+ e.dataTransfer.dropEffect = 'move';
181
+ tab.classList.add('drag-over');
182
+ });
183
+ tab.addEventListener('dragleave', () => {
184
+ tab.classList.remove('drag-over');
185
+ });
186
+ tab.addEventListener('drop', (e) => {
187
+ e.preventDefault();
188
+ tab.classList.remove('drag-over');
189
+ const sourceId = e.dataTransfer.getData('text/plain');
190
+ if (sourceId && sourceId !== id && this.swapTerminals) {
191
+ this.swapTerminals(sourceId, id);
192
+ // Reorder tab DOM to match
193
+ const sourceTab = this.tabBar.querySelector(`.tab[data-id="${sourceId}"]`);
194
+ if (sourceTab) {
195
+ const placeholder = document.createComment('tab-swap');
196
+ sourceTab.replaceWith(placeholder);
197
+ tab.replaceWith(sourceTab);
198
+ placeholder.replaceWith(tab);
199
+ }
200
+ }
201
+ });
202
+
139
203
  this.tabBar.appendChild(tab);
140
204
  }
141
205
 
@@ -149,12 +213,29 @@ export class LayoutManager {
149
213
 
150
214
  _removeTab(id) {
151
215
  const tab = this.tabBar.querySelector(`.tab[data-id="${id}"]`);
152
- if (tab) tab.remove();
216
+ if (tab) {tab.remove();}
217
+ }
218
+
219
+ _clearMaximizedState() {
220
+ this.container.querySelectorAll('.terminal-pane.maximized').forEach(el => el.classList.remove('maximized'));
221
+ this.container.querySelectorAll('.terminal-pane.hidden-by-maximize').forEach(el => el.classList.remove('hidden-by-maximize'));
222
+ }
223
+
224
+ onTerminalMaximized() {
225
+ if (this.currentLayout === 'grid') {
226
+ this.container.style.gridTemplateColumns = '1fr';
227
+ }
228
+ }
229
+
230
+ onTerminalRestored() {
231
+ if (this.currentLayout === 'grid') {
232
+ this._updateGridColumns();
233
+ }
153
234
  }
154
235
 
155
236
  _updateGridColumns() {
156
237
  const count = this.container.querySelectorAll('.terminal-pane').length;
157
- const cols = count <= 1 ? 1 : count <= 4 ? 2 : count <= 9 ? 3 : 4;
238
+ const cols = Math.ceil(Math.sqrt(count));
158
239
  this.container.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
159
240
  }
160
241
  }