@jackuait/blok 0.10.0-beta.16 → 0.10.0-beta.18

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.
@@ -4,15 +4,14 @@ import {
4
4
  LANGUAGE_BUTTON_STYLES,
5
5
  HEADER_BUTTON_STYLES,
6
6
  CODE_AREA_STYLES,
7
- TAB_STYLES,
8
- TAB_ACTIVE_STYLES,
9
- TAB_INACTIVE_STYLES,
10
7
  PREVIEW_AREA_STYLES,
11
8
  CODE_BODY_STYLES,
12
9
  GUTTER_STYLES,
13
10
  GUTTER_LINE_STYLES,
11
+ MORE_MENU_STYLES,
12
+ MORE_MENU_ITEM_STYLES,
14
13
  } from './constants';
15
- import { IconCopy, IconWrap, IconLineNumbers } from '../../components/icons';
14
+ import { IconCopy, IconCode, IconChevronDown, IconEllipsis, IconWrap, IconLineNumbers } from '../../components/icons';
16
15
 
17
16
  export interface CodeDOMRefs {
18
17
  wrapper: HTMLElement;
@@ -23,9 +22,10 @@ export interface CodeDOMRefs {
23
22
  preElement: HTMLPreElement;
24
23
  codeElement: HTMLElement;
25
24
  gutterElement: HTMLElement;
26
- codeTab: HTMLButtonElement | null;
27
- previewTab: HTMLButtonElement | null;
25
+ previewToggleButton: HTMLButtonElement | null;
28
26
  previewElement: HTMLDivElement | null;
27
+ moreButton: HTMLButtonElement;
28
+ moreMenu: HTMLElement;
29
29
  }
30
30
 
31
31
  export interface BuildCodeDOMOptions {
@@ -36,38 +36,30 @@ export interface BuildCodeDOMOptions {
36
36
  wrapLabel: string;
37
37
  lineNumbersLabel?: string;
38
38
  previewable?: boolean;
39
- codeTabLabel?: string;
40
- previewTabLabel?: string;
39
+ previewToggleLabel?: string;
41
40
  }
42
41
 
43
42
  function buildPreviewElements(
44
- codeTabLabel?: string,
45
- previewTabLabel?: string,
46
- ): { codeTab: HTMLButtonElement; previewTab: HTMLButtonElement; previewElement: HTMLDivElement } {
47
- const codeTab = document.createElement('button');
43
+ previewToggleLabel?: string,
44
+ ): { previewToggleButton: HTMLButtonElement; previewElement: HTMLDivElement } {
45
+ const previewToggleButton = document.createElement('button');
48
46
 
49
- codeTab.type = 'button';
50
- codeTab.className = `${TAB_STYLES} ${TAB_INACTIVE_STYLES}`;
51
- codeTab.textContent = codeTabLabel ?? 'Code';
52
- codeTab.setAttribute('data-blok-testid', 'code-code-tab');
53
-
54
- const previewTab = document.createElement('button');
55
-
56
- previewTab.type = 'button';
57
- previewTab.className = `${TAB_STYLES} ${TAB_ACTIVE_STYLES}`;
58
- previewTab.textContent = previewTabLabel ?? 'Preview';
59
- previewTab.setAttribute('data-blok-testid', 'code-preview-tab');
47
+ previewToggleButton.type = 'button';
48
+ previewToggleButton.className = HEADER_BUTTON_STYLES;
49
+ previewToggleButton.innerHTML = IconCode;
50
+ previewToggleButton.setAttribute('aria-label', previewToggleLabel ?? 'Preview');
51
+ previewToggleButton.setAttribute('data-blok-testid', 'code-preview-toggle-btn');
60
52
 
61
53
  const previewElement = document.createElement('div');
62
54
 
63
55
  previewElement.className = PREVIEW_AREA_STYLES;
64
56
  previewElement.setAttribute('data-blok-testid', 'code-preview');
65
57
 
66
- return { codeTab, previewTab, previewElement };
58
+ return { previewToggleButton, previewElement };
67
59
  }
68
60
 
69
61
  export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
70
- const { code, languageName, readOnly, copyLabel, wrapLabel, lineNumbersLabel, previewable, codeTabLabel, previewTabLabel } = options;
62
+ const { code, languageName, readOnly, copyLabel, wrapLabel, lineNumbersLabel, previewable, previewToggleLabel } = options;
71
63
 
72
64
  // Wrapper
73
65
  const wrapper = document.createElement('div');
@@ -77,38 +69,30 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
77
69
  const header = document.createElement('div');
78
70
  header.className = HEADER_STYLES;
79
71
 
80
- // Language button (opens language picker)
72
+ // Language button (opens language picker) — includes text + chevron icon
81
73
  const languageButton = document.createElement('button');
82
74
  languageButton.type = 'button';
83
75
  languageButton.className = LANGUAGE_BUTTON_STYLES;
84
- languageButton.textContent = languageName;
85
76
  languageButton.setAttribute('aria-haspopup', 'listbox');
86
77
  languageButton.setAttribute('data-blok-testid', 'code-language-btn');
87
78
 
79
+ const langText = document.createElement('span');
80
+ langText.textContent = languageName;
81
+ languageButton.appendChild(langText);
82
+
83
+ const chevronSpan = document.createElement('span');
84
+ chevronSpan.className = 'inline-flex items-center ml-0.5 -mr-0.5';
85
+ chevronSpan.innerHTML = IconChevronDown;
86
+ languageButton.appendChild(chevronSpan);
87
+
88
88
  // Spacer
89
89
  const spacer = document.createElement('div');
90
90
  spacer.className = 'flex-1';
91
91
 
92
- // Tab buttons (only when previewable)
93
- const { codeTab, previewTab, previewElement } = previewable
94
- ? buildPreviewElements(codeTabLabel, previewTabLabel)
95
- : { codeTab: null, previewTab: null, previewElement: null };
96
-
97
- // Wrap toggle button
98
- const wrapButton = document.createElement('button');
99
- wrapButton.type = 'button';
100
- wrapButton.className = HEADER_BUTTON_STYLES;
101
- wrapButton.innerHTML = IconWrap;
102
- wrapButton.setAttribute('aria-label', wrapLabel);
103
- wrapButton.setAttribute('data-blok-testid', 'code-wrap-btn');
104
-
105
- // Line numbers toggle button
106
- const lineNumbersButton = document.createElement('button');
107
- lineNumbersButton.type = 'button';
108
- lineNumbersButton.className = HEADER_BUTTON_STYLES;
109
- lineNumbersButton.innerHTML = IconLineNumbers;
110
- lineNumbersButton.setAttribute('aria-label', lineNumbersLabel ?? 'Line numbers');
111
- lineNumbersButton.setAttribute('data-blok-testid', 'code-line-numbers-btn');
92
+ // Preview toggle button (only when previewable and not read-only)
93
+ const { previewToggleButton, previewElement } = previewable
94
+ ? buildPreviewElements(previewToggleLabel)
95
+ : { previewToggleButton: null, previewElement: null };
112
96
 
113
97
  // Copy button
114
98
  const copyButton = document.createElement('button');
@@ -118,6 +102,54 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
118
102
  copyButton.setAttribute('aria-label', copyLabel);
119
103
  copyButton.setAttribute('data-blok-testid', 'code-copy-btn');
120
104
 
105
+ // More button (ellipsis)
106
+ const moreButton = document.createElement('button');
107
+ moreButton.type = 'button';
108
+ moreButton.className = HEADER_BUTTON_STYLES;
109
+ moreButton.innerHTML = IconEllipsis;
110
+ moreButton.setAttribute('aria-label', 'More');
111
+ moreButton.setAttribute('aria-haspopup', 'true');
112
+ moreButton.setAttribute('data-blok-testid', 'code-more-btn');
113
+
114
+ // More menu dropdown
115
+ const moreMenu = document.createElement('div');
116
+ moreMenu.className = MORE_MENU_STYLES;
117
+ moreMenu.hidden = true;
118
+ moreMenu.setAttribute('data-blok-testid', 'code-more-menu');
119
+
120
+ // Line numbers toggle (inside more menu)
121
+ const lineNumbersButton = document.createElement('button');
122
+ lineNumbersButton.type = 'button';
123
+ lineNumbersButton.className = MORE_MENU_ITEM_STYLES;
124
+ lineNumbersButton.setAttribute('data-blok-testid', 'code-line-numbers-btn');
125
+
126
+ const lineNumIconSpan = document.createElement('span');
127
+ lineNumIconSpan.className = 'flex items-center justify-center w-5 h-5';
128
+ lineNumIconSpan.innerHTML = IconLineNumbers;
129
+ lineNumbersButton.appendChild(lineNumIconSpan);
130
+
131
+ const lineNumText = document.createElement('span');
132
+ lineNumText.textContent = lineNumbersLabel ?? 'Line numbers';
133
+ lineNumbersButton.appendChild(lineNumText);
134
+
135
+ // Wrap toggle (inside more menu)
136
+ const wrapButton = document.createElement('button');
137
+ wrapButton.type = 'button';
138
+ wrapButton.className = MORE_MENU_ITEM_STYLES;
139
+ wrapButton.setAttribute('data-blok-testid', 'code-wrap-btn');
140
+
141
+ const wrapIconSpan = document.createElement('span');
142
+ wrapIconSpan.className = 'flex items-center justify-center w-5 h-5';
143
+ wrapIconSpan.innerHTML = IconWrap;
144
+ wrapButton.appendChild(wrapIconSpan);
145
+
146
+ const wrapText = document.createElement('span');
147
+ wrapText.textContent = wrapLabel;
148
+ wrapButton.appendChild(wrapText);
149
+
150
+ moreMenu.appendChild(lineNumbersButton);
151
+ moreMenu.appendChild(wrapButton);
152
+
121
153
  // Code area
122
154
  const codeElement = document.createElement('code');
123
155
  codeElement.className = CODE_AREA_STYLES;
@@ -146,19 +178,23 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
146
178
  gutterElement.appendChild(lineEl);
147
179
  });
148
180
 
149
- // Assemble header
181
+ // Assemble header: [language] [spacer] [preview toggle?] [copy] [more ▸ menu]
150
182
  header.appendChild(languageButton);
151
183
  header.appendChild(spacer);
152
184
 
153
- if (codeTab && previewTab) {
154
- header.appendChild(codeTab);
155
- header.appendChild(previewTab);
185
+ if (previewToggleButton) {
186
+ header.appendChild(previewToggleButton);
156
187
  }
157
188
 
158
- header.appendChild(lineNumbersButton);
159
- header.appendChild(wrapButton);
160
189
  header.appendChild(copyButton);
161
190
 
191
+ // More wrapper (relative position anchor for absolute dropdown)
192
+ const moreWrapper = document.createElement('div');
193
+ moreWrapper.className = 'relative';
194
+ moreWrapper.appendChild(moreButton);
195
+ moreWrapper.appendChild(moreMenu);
196
+ header.appendChild(moreWrapper);
197
+
162
198
  // Pre wrapper for semantic HTML
163
199
  const preElement = document.createElement('pre');
164
200
  preElement.appendChild(codeElement);
@@ -177,5 +213,5 @@ export function buildCodeDOM(options: BuildCodeDOMOptions): CodeDOMRefs {
177
213
  wrapper.appendChild(previewElement);
178
214
  }
179
215
 
180
- return { wrapper, languageButton, lineNumbersButton, copyButton, wrapButton, preElement, codeElement, gutterElement, codeTab, previewTab, previewElement };
216
+ return { wrapper, languageButton, lineNumbersButton, copyButton, wrapButton, preElement, codeElement, gutterElement, previewToggleButton, previewElement, moreButton, moreMenu };
181
217
  }
@@ -26,11 +26,7 @@ import {
26
26
  LANGUAGE_KEY,
27
27
  COPIED_FEEDBACK_STYLES,
28
28
  PREVIEWABLE_LANGUAGES,
29
- CODE_TAB_KEY,
30
- PREVIEW_TAB_KEY,
31
- TAB_STYLES,
32
- TAB_ACTIVE_STYLES,
33
- TAB_INACTIVE_STYLES,
29
+ PREVIEW_TOGGLE_KEY,
34
30
  PREVIEW_AREA_STYLES,
35
31
  GUTTER_LINE_STYLES,
36
32
  } from './constants';
@@ -76,8 +72,7 @@ export class CodeTool implements BlockTool {
76
72
  wrapLabel: this.api.i18n.t(WRAP_LINES_KEY),
77
73
  lineNumbersLabel: this.api.i18n.t(LINE_NUMBERS_KEY),
78
74
  previewable: this.readOnly ? false : isPreviewable,
79
- codeTabLabel: this.api.i18n.t(CODE_TAB_KEY),
80
- previewTabLabel: this.api.i18n.t(PREVIEW_TAB_KEY),
75
+ previewToggleLabel: this.api.i18n.t(PREVIEW_TOGGLE_KEY),
81
76
  });
82
77
 
83
78
  this._dom = dom;
@@ -86,7 +81,10 @@ export class CodeTool implements BlockTool {
86
81
  dom.gutterElement.hidden = !this._lineNumbers;
87
82
  dom.lineNumbersButton.addEventListener('click', () => this.toggleLineNumbers());
88
83
 
89
- // Read-only + previewable: show preview only, hide code, no tabs
84
+ // More menu toggle
85
+ dom.moreButton.addEventListener('click', () => this.toggleMoreMenu());
86
+
87
+ // Read-only + previewable: show preview only, hide code, no toggle
90
88
  if (this.readOnly && isPreviewable) {
91
89
  const previewEl = document.createElement('div');
92
90
 
@@ -99,8 +97,8 @@ export class CodeTool implements BlockTool {
99
97
  void this.renderPreview();
100
98
  }
101
99
 
102
- // Edit mode + previewable: show tabs, default to preview
103
- if (!this.readOnly && isPreviewable && dom.codeTab && dom.previewTab && dom.previewElement) {
100
+ // Edit mode + previewable: show preview toggle, default to preview
101
+ if (!this.readOnly && isPreviewable && dom.previewToggleButton && dom.previewElement) {
104
102
  this._previewActive = true;
105
103
  dom.preElement.hidden = true;
106
104
  dom.gutterElement.hidden = true;
@@ -108,8 +106,7 @@ export class CodeTool implements BlockTool {
108
106
  this._previewContainer = dom.previewElement;
109
107
  void this.renderPreview();
110
108
 
111
- dom.codeTab.addEventListener('click', () => this.showCode());
112
- dom.previewTab.addEventListener('click', () => this.showPreview());
109
+ dom.previewToggleButton.addEventListener('click', () => this.togglePreview());
113
110
  }
114
111
 
115
112
  if (!this.readOnly) {
@@ -156,8 +153,16 @@ export class CodeTool implements BlockTool {
156
153
  void this.highlightCode();
157
154
  }
158
155
 
156
+ private togglePreview(): void {
157
+ if (this._previewActive) {
158
+ this.showCode();
159
+ } else {
160
+ this.showPreview();
161
+ }
162
+ }
163
+
159
164
  private showCode(): void {
160
- if (!this._dom?.previewElement || !this._dom.codeTab || !this._dom.previewTab) {
165
+ if (!this._dom?.previewElement || !this._dom.previewToggleButton) {
161
166
  return;
162
167
  }
163
168
 
@@ -165,12 +170,10 @@ export class CodeTool implements BlockTool {
165
170
  this._dom.preElement.hidden = false;
166
171
  this._dom.gutterElement.hidden = !this._lineNumbers;
167
172
  this._dom.previewElement.hidden = true;
168
- this._dom.codeTab.className = `${TAB_STYLES} ${TAB_ACTIVE_STYLES}`;
169
- this._dom.previewTab.className = `${TAB_STYLES} ${TAB_INACTIVE_STYLES}`;
170
173
  }
171
174
 
172
175
  private showPreview(): void {
173
- if (!this._dom?.previewElement || !this._dom.codeTab || !this._dom.previewTab) {
176
+ if (!this._dom?.previewElement || !this._dom.previewToggleButton) {
174
177
  return;
175
178
  }
176
179
 
@@ -178,13 +181,19 @@ export class CodeTool implements BlockTool {
178
181
  this._dom.preElement.hidden = true;
179
182
  this._dom.gutterElement.hidden = true;
180
183
  this._dom.previewElement.hidden = false;
181
- this._dom.codeTab.className = `${TAB_STYLES} ${TAB_INACTIVE_STYLES}`;
182
- this._dom.previewTab.className = `${TAB_STYLES} ${TAB_ACTIVE_STYLES}`;
183
184
 
184
185
  // Re-render preview with current code content
185
186
  void this.renderPreview();
186
187
  }
187
188
 
189
+ private toggleMoreMenu(): void {
190
+ if (!this._dom) {
191
+ return;
192
+ }
193
+
194
+ this._dom.moreMenu.hidden = !this._dom.moreMenu.hidden;
195
+ }
196
+
188
197
  private async renderPreview(): Promise<void> {
189
198
  if (!this._previewContainer) {
190
199
  return;
@@ -281,7 +290,12 @@ export class CodeTool implements BlockTool {
281
290
  this._data.language = id;
282
291
 
283
292
  if (this._dom) {
284
- this._dom.languageButton.textContent = this.getLanguageName(id);
293
+ // Update the text span inside the language button (first child)
294
+ const textSpan = this._dom.languageButton.querySelector('span');
295
+
296
+ if (textSpan) {
297
+ textSpan.textContent = this.getLanguageName(id);
298
+ }
285
299
  }
286
300
 
287
301
  this._picker?.setActiveLanguage(id);
@@ -1067,7 +1067,9 @@ export class TableCellBlocks {
1067
1067
 
1068
1068
  const savedScrollY = window.scrollY;
1069
1069
 
1070
- const deletePromises = blockIndices.map(index => this.api.blocks.delete(index));
1070
+ const deletePromises = blockIndices.map(index => {
1071
+ return this.api.blocks.delete(index);
1072
+ });
1071
1073
 
1072
1074
  void Promise.all(deletePromises).then(() => {
1073
1075
  if (window.scrollY !== savedScrollY) {