@jackuait/blok 0.4.3-beta.7 → 0.4.3-beta.9

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.
@@ -1,4 +1,4 @@
1
- import { e as i } from "./blok-B-fFKCc3.mjs";
1
+ import { e as i } from "./blok-CU-oiynu.mjs";
2
2
  const l = async (e, r) => {
3
3
  const n = (await import("./i18next-CugVlwWp.mjs")).default.createInstance(), s = {
4
4
  lng: e,
@@ -1,4 +1,4 @@
1
- import { t as f, q as i } from "./inline-tool-convert-BQzo9tk1.mjs";
1
+ import { t as f, q as i } from "./inline-tool-convert-rak34pCi.mjs";
2
2
  const a = {
3
3
  wrapper: i(
4
4
  "fixed z-[2] bottom-5 left-5",
@@ -18,7 +18,7 @@ let nt = (o = 21) => {
18
18
  return t;
19
19
  };
20
20
  var ot = /* @__PURE__ */ ((o) => (o.VERBOSE = "VERBOSE", o.INFO = "INFO", o.WARN = "WARN", o.ERROR = "ERROR", o))(ot || {});
21
- const rt = () => "0.4.3-beta.7", Ct = {
21
+ const rt = () => "0.4.3-beta.9", Ct = {
22
22
  BACKSPACE: 8,
23
23
  TAB: 9,
24
24
  ENTER: 13,
package/dist/full.mjs CHANGED
@@ -10,10 +10,10 @@ var e = (a, l, o) => l in a ? n(a, l, { enumerable: !0, configurable: !0, writab
10
10
  d.call(l, o) && e(a, o, l[o]);
11
11
  return a;
12
12
  }, r = (a, l) => t(a, c(l));
13
- import { B as v, v as A } from "./chunks/blok-B-fFKCc3.mjs";
13
+ import { B as v, v as A } from "./chunks/blok-CU-oiynu.mjs";
14
14
  import { List as p, Header as f, Paragraph as I, Link as k, Italic as u, Bold as B } from "./tools.mjs";
15
15
  import { defaultBlockTools as H, defaultInlineTools as P } from "./tools.mjs";
16
- import { D as _ } from "./chunks/inline-tool-convert-BQzo9tk1.mjs";
16
+ import { D as _ } from "./chunks/inline-tool-convert-rak34pCi.mjs";
17
17
  const m = {
18
18
  paragraph: {
19
19
  class: I,
package/dist/tools.mjs CHANGED
@@ -10,8 +10,8 @@ var U = (f, t, e) => t in f ? rt(f, t, { enumerable: !0, configurable: !0, writa
10
10
  at.call(t, e) && U(f, e, t[e]);
11
11
  return f;
12
12
  }, P = (f, t) => st(f, ot(t));
13
- import { t as L, D as g, a9 as nt, aa as lt, ab as G, ac as ct, ad as dt, ae as ut, af as ht, ag as ft, ah as pt, ai as j, aj as $, ak as X, f as A, al as gt, am as mt, S as H, P as Et, an as Tt, l as Ct, J as At } from "./chunks/inline-tool-convert-BQzo9tk1.mjs";
14
- import { a0 as Ot } from "./chunks/inline-tool-convert-BQzo9tk1.mjs";
13
+ import { t as L, D as g, a9 as nt, aa as lt, ab as G, ac as ct, ad as dt, ae as ut, af as ht, ag as ft, ah as pt, ai as j, aj as $, ak as X, f as A, al as gt, am as mt, S as H, P as Et, an as Tt, l as Ct, J as At } from "./chunks/inline-tool-convert-rak34pCi.mjs";
14
+ import { a0 as Ot } from "./chunks/inline-tool-convert-rak34pCi.mjs";
15
15
  const W = [
16
16
  "empty:before:pointer-events-none",
17
17
  "empty:before:text-gray-text",
@@ -288,17 +288,17 @@ const x = class x {
288
288
  * @returns MenuConfig array
289
289
  */
290
290
  renderSettings() {
291
- const t = this._settings._toolboxEntries;
292
- return t !== void 0 && t.length > 0 ? this.buildSettingsFromToolboxEntries(t) : this.levels.map((e) => {
293
- const n = this.api.i18n.t(e.nameKey), r = n !== e.nameKey ? n : e.name;
291
+ const t = this._settings._toolboxEntries, e = (t == null ? void 0 : t.length) === 1 && t[0].data === void 0;
292
+ return t !== void 0 && t.length > 0 && !e ? this.buildSettingsFromToolboxEntries(t) : this.levels.map((n) => {
293
+ const r = this.api.i18n.t(n.nameKey), s = r !== n.nameKey ? r : n.name;
294
294
  return {
295
- icon: e.icon,
296
- title: r,
297
- onActivate: () => this.setLevel(e.number),
295
+ icon: n.icon,
296
+ title: s,
297
+ onActivate: () => this.setLevel(n.number),
298
298
  closeOnActivate: !0,
299
- isActive: this.currentLevel.number === e.number,
299
+ isActive: this.currentLevel.number === n.number,
300
300
  dataset: {
301
- "blok-header-level": String(e.number)
301
+ "blok-header-level": String(n.number)
302
302
  }
303
303
  };
304
304
  });
@@ -3093,9 +3093,9 @@ const D = class D {
3093
3093
  D.isInline = !0, D.title = "Link", D.titleKey = "link", D.shortcut = "CMD+K";
3094
3094
  let et = D;
3095
3095
  const xt = {
3096
- paragraph: { inlineToolbar: !0, config: { preserveBlank: !0 } },
3097
- header: { inlineToolbar: !0 },
3098
- list: { inlineToolbar: !0 }
3096
+ paragraph: { preserveBlank: !0 },
3097
+ header: {},
3098
+ list: {}
3099
3099
  }, vt = {
3100
3100
  bold: {},
3101
3101
  italic: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackuait/blok",
3
- "version": "0.4.3-beta.7",
3
+ "version": "0.4.3-beta.9",
4
4
  "description": "Blok — headless, highly extensible rich text editor built for developers who need to implement a block-based editing experience (similar to Notion) without building it from scratch",
5
5
  "module": "dist/blok.mjs",
6
6
  "types": "./types/index.d.ts",
@@ -35,6 +35,8 @@ const DRAG_CONFIG = {
35
35
  autoScrollSpeed: 10,
36
36
  /** Throttle delay for drop position announcements (ms) */
37
37
  announcementThrottleMs: 300,
38
+ /** Horizontal distance to the left of blocks where drop is still valid */
39
+ leftDropZone: 50,
38
40
  };
39
41
 
40
42
  /**
@@ -424,20 +426,10 @@ export class DragManager extends Module {
424
426
  return;
425
427
  }
426
428
 
427
- // Find block holder
428
- const blockHolder = elementUnderCursor.closest(createSelector(DATA_ATTR.element)) as HTMLElement | null;
429
+ // Find block holder - first try direct hit, then left drop zone fallback
430
+ const { block: targetBlock, holder: blockHolder } = this.findDropTargetBlock(elementUnderCursor, clientX, clientY);
429
431
 
430
- if (!blockHolder) {
431
- this.dragState.targetBlock = null;
432
- this.dragState.targetEdge = null;
433
-
434
- return;
435
- }
436
-
437
- // Find the block instance
438
- const targetBlock = this.Blok.BlockManager.blocks.find(b => b.holder === blockHolder);
439
-
440
- if (!targetBlock || targetBlock === this.dragState.sourceBlock) {
432
+ if (!blockHolder || !targetBlock || targetBlock === this.dragState.sourceBlock) {
441
433
  this.dragState.targetBlock = null;
442
434
  this.dragState.targetEdge = null;
443
435
 
@@ -1132,6 +1124,72 @@ export class DragManager extends Module {
1132
1124
  return collectDescendants(blockIndex + 1, []);
1133
1125
  }
1134
1126
 
1127
+ /**
1128
+ * Finds the drop target block from an element or by checking the left drop zone.
1129
+ * @param elementUnderCursor - Element directly under the cursor
1130
+ * @param clientX - Cursor X position
1131
+ * @param clientY - Cursor Y position
1132
+ * @returns Object with block and holder, or nulls if no valid target found
1133
+ */
1134
+ private findDropTargetBlock(
1135
+ elementUnderCursor: Element,
1136
+ clientX: number,
1137
+ clientY: number
1138
+ ): { block: Block | undefined; holder: HTMLElement | null } {
1139
+ // First try: find block holder directly under cursor
1140
+ const directHolder = elementUnderCursor.closest(createSelector(DATA_ATTR.element)) as HTMLElement | null;
1141
+
1142
+ if (directHolder) {
1143
+ const block = this.Blok.BlockManager.blocks.find(b => b.holder === directHolder);
1144
+
1145
+ return { block, holder: directHolder };
1146
+ }
1147
+
1148
+ // Fallback: check if cursor is in the left drop zone
1149
+ const leftZoneBlock = this.findBlockInLeftDropZone(clientX, clientY);
1150
+
1151
+ if (leftZoneBlock) {
1152
+ return { block: leftZoneBlock, holder: leftZoneBlock.holder };
1153
+ }
1154
+
1155
+ return { block: undefined, holder: null };
1156
+ }
1157
+
1158
+ /**
1159
+ * Finds a block by vertical position when cursor is in the left drop zone.
1160
+ * Used as a fallback when elementFromPoint doesn't find a block directly.
1161
+ * @param clientX - Cursor X position
1162
+ * @param clientY - Cursor Y position
1163
+ * @returns Block at the vertical position, or null if not in left zone or no block found
1164
+ */
1165
+ private findBlockInLeftDropZone(clientX: number, clientY: number): Block | null {
1166
+ const contentRect = this.Blok.UI.contentRect;
1167
+ const leftEdge = contentRect.left;
1168
+
1169
+ // Check if cursor is within left drop zone (between leftEdge - leftDropZone and leftEdge)
1170
+ const distanceFromEdge = leftEdge - clientX;
1171
+
1172
+ if (distanceFromEdge < 0 || distanceFromEdge > DRAG_CONFIG.leftDropZone) {
1173
+ return null;
1174
+ }
1175
+
1176
+ // Find block by Y position
1177
+ for (const block of this.Blok.BlockManager.blocks) {
1178
+ // Skip source blocks
1179
+ if (this.dragState?.sourceBlocks.includes(block)) {
1180
+ continue;
1181
+ }
1182
+
1183
+ const rect = block.holder.getBoundingClientRect();
1184
+
1185
+ if (clientY >= rect.top && clientY <= rect.bottom) {
1186
+ return block;
1187
+ }
1188
+ }
1189
+
1190
+ return null;
1191
+ }
1192
+
1135
1193
  /**
1136
1194
  * Module destruction
1137
1195
  */
@@ -116,11 +116,29 @@ export class RectangleSelection extends Module {
116
116
  * Init rect params
117
117
  * @param {number} pageX - X coord of mouse
118
118
  * @param {number} pageY - Y coord of mouse
119
+ * @param {boolean} shiftKey - whether Shift key is held for additive selection
119
120
  */
120
- public startSelection(pageX: number, pageY: number): void {
121
- const scrollLeft = this.getScrollLeft();
121
+ public startSelection(pageX: number, pageY: number, shiftKey = false): void {
122
+ const { UI } = this.Blok;
123
+ const redactor = UI.nodes.redactor;
124
+
125
+ if (!redactor) {
126
+ return;
127
+ }
128
+
129
+ const editorRect = redactor.getBoundingClientRect();
122
130
  const scrollTop = this.getScrollTop();
123
- const elemWhereSelectionStart = document.elementFromPoint(pageX - scrollLeft, pageY - scrollTop);
131
+ const pointerY = pageY - scrollTop;
132
+
133
+ // Check if pointer is within editor's vertical bounds
134
+ const withinEditorVertically = pointerY >= editorRect.top && pointerY <= editorRect.bottom;
135
+
136
+ if (!withinEditorVertically) {
137
+ return;
138
+ }
139
+
140
+ const scrollLeft = this.getScrollLeft();
141
+ const elemWhereSelectionStart = document.elementFromPoint(pageX - scrollLeft, pointerY);
124
142
 
125
143
  if (!elemWhereSelectionStart) {
126
144
  return;
@@ -132,7 +150,11 @@ export class RectangleSelection extends Module {
132
150
  */
133
151
  const startsInsideToolbar = elemWhereSelectionStart.closest(createSelector(DATA_ATTR.toolbar));
134
152
 
135
- if (!startsInsideToolbar) {
153
+ /**
154
+ * When Shift is held, preserve existing selection for additive behavior.
155
+ * Otherwise, clear selection state.
156
+ */
157
+ if (!startsInsideToolbar && !shiftKey) {
136
158
  this.Blok.BlockSelection.allBlocksSelected = false;
137
159
  this.clearSelection();
138
160
  this.stackOfSelected = [];
@@ -144,13 +166,12 @@ export class RectangleSelection extends Module {
144
166
  INLINE_TOOLBAR_INTERFACE_SELECTOR,
145
167
  ];
146
168
 
147
- const startsInsideBlok = elemWhereSelectionStart.closest(createSelector(DATA_ATTR.editor));
148
169
  const startsInSelectorToAvoid = selectorsToAvoid.some((selector) => !!elemWhereSelectionStart.closest(selector));
149
170
 
150
171
  /**
151
- * If selection starts outside of the blok or inside the blocks or on Blok UI elements, do not handle it
172
+ * If selection starts inside the blocks content or on Blok UI elements, do not handle it
152
173
  */
153
- if (!startsInsideBlok || startsInSelectorToAvoid) {
174
+ if (startsInSelectorToAvoid) {
154
175
  return;
155
176
  }
156
177
 
@@ -194,9 +215,9 @@ export class RectangleSelection extends Module {
194
215
  * Sets Module necessary event handlers
195
216
  */
196
217
  private enableModuleBindings(): void {
197
- const { container } = this.genHTML();
218
+ this.genHTML();
198
219
 
199
- this.listeners.on(container, 'mousedown', (event: Event) => {
220
+ this.listeners.on(document.body, 'mousedown', (event: Event) => {
200
221
  this.processMouseDown(event as MouseEvent);
201
222
  }, false);
202
223
 
@@ -245,7 +266,7 @@ export class RectangleSelection extends Module {
245
266
  const startedFromContentEditable = (mouseEvent.target as Element).closest($.allInputsSelector) !== null;
246
267
 
247
268
  if (!startedFromContentEditable) {
248
- this.startSelection(mouseEvent.pageX, mouseEvent.pageY);
269
+ this.startSelection(mouseEvent.pageX, mouseEvent.pageY, mouseEvent.shiftKey);
249
270
  }
250
271
  }
251
272
 
@@ -402,13 +423,12 @@ export class RectangleSelection extends Module {
402
423
  this.mouseY = event.pageY;
403
424
  }
404
425
 
405
- const { rightPos, leftPos, index } = this.genInfoForMouseSelection();
406
- // There is not new block in selection
426
+ const { index } = this.genInfoForMouseSelection();
407
427
 
408
- const rectIsOnRighSideOfredactor = this.startX > rightPos && this.mouseX > rightPos;
409
- const rectISOnLeftSideOfRedactor = this.startX < leftPos && this.mouseX < leftPos;
410
-
411
- this.rectCrossesBlocks = !(rectIsOnRighSideOfredactor || rectISOnLeftSideOfRedactor);
428
+ // For page-wide selection: always consider the rectangle as crossing blocks
429
+ // if we have a valid block index. The vertical check in startSelection()
430
+ // already ensures we're within the editor's vertical bounds.
431
+ this.rectCrossesBlocks = index !== undefined;
412
432
 
413
433
  if (!this.isRectSelectionActivated) {
414
434
  this.rectCrossesBlocks = false;
@@ -517,6 +537,7 @@ export class RectangleSelection extends Module {
517
537
 
518
538
  /**
519
539
  * Collects information needed to determine the behavior of the rectangle
540
+ * For page-wide selection, we check blocks at the center X position but at the actual mouse Y position
520
541
  * @returns {object} index - index next Block, leftPos - start of left border of Block, rightPos - right border
521
542
  */
522
543
  private genInfoForMouseSelection(): {index: number | undefined; leftPos: number; rightPos: number} {
@@ -524,6 +545,9 @@ export class RectangleSelection extends Module {
524
545
  const centerOfRedactor = widthOfRedactor / 2;
525
546
  const scrollTop = this.getScrollTop();
526
547
  const y = this.mouseY - scrollTop;
548
+
549
+ // For page-wide selection: check what block is at the center X, but at the mouse's Y position
550
+ // This allows selection to work even when mouse is in the left/right margins
527
551
  const elementUnderMouse = document.elementFromPoint(centerOfRedactor, y);
528
552
  const lastBlockHolder = this.Blok.BlockManager.lastBlock?.holder;
529
553
  const contentElement = lastBlockHolder?.querySelector(createSelector(DATA_ATTR.elementContent));
@@ -334,18 +334,18 @@ export class Tools extends Module {
334
334
  .map(([toolName, settings]): ChainData => {
335
335
  const toolData: ToolPrepareData = {
336
336
  toolName,
337
- config: (settings.config ?? {}) as ToolConfig,
337
+ config: this.extractToolConfig(settings),
338
338
  };
339
339
 
340
340
  const prepareFunction: ChainData['function'] = async (payload?: unknown) => {
341
- const constructable = settings.class;
341
+ const constructable = settings.class as ToolConstructable | undefined;
342
342
 
343
343
  if (!constructable || !isFunction(constructable.prepare)) {
344
344
  return;
345
345
  }
346
346
 
347
347
  const data = (payload ?? toolData) as ToolPrepareData;
348
- const prepareMethod = constructable.prepare as unknown as ToolPrepareFunction;
348
+ const prepareMethod = constructable.prepare as ToolPrepareFunction;
349
349
 
350
350
  return prepareMethod.call(constructable, data);
351
351
  };
@@ -357,6 +357,42 @@ export class Tools extends Module {
357
357
  });
358
358
  }
359
359
 
360
+ /**
361
+ * Keys that are Blok-level settings (not passed to tool constructor)
362
+ */
363
+ private static readonly BLOK_SETTINGS_KEYS = new Set([
364
+ 'class',
365
+ 'inlineToolbar',
366
+ 'tunes',
367
+ 'shortcut',
368
+ 'toolbox',
369
+ 'config',
370
+ 'isInternal',
371
+ ]);
372
+
373
+ /**
374
+ * Extracts tool configuration from settings.
375
+ * Merges nested config with flat tool-specific options (flat takes precedence).
376
+ * @param settings - Tool settings from user config
377
+ * @returns Merged tool configuration
378
+ */
379
+ private extractToolConfig(settings: ToolSettings): ToolConfig {
380
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- Internal: reading legacy config for backwards compatibility
381
+ const nestedConfig = (settings.config ?? {}) as ToolConfig;
382
+
383
+ // Extract non-Blok keys as tool-specific config
384
+ const flatConfig: Record<string, unknown> = {};
385
+
386
+ for (const key of Object.keys(settings)) {
387
+ if (!Tools.BLOK_SETTINGS_KEYS.has(key)) {
388
+ flatConfig[key] = settings[key as keyof typeof settings];
389
+ }
390
+ }
391
+
392
+ // Merge: nested config first, flat config overrides
393
+ return { ...nestedConfig, ...flatConfig } as ToolConfig;
394
+ }
395
+
360
396
  /**
361
397
  * Assign enabled Inline Tools and Block Tunes for Block Tool
362
398
  */
@@ -7,6 +7,19 @@ import type { InlineToolAdapter as InlineToolAdapterInterface } from '@/types/to
7
7
  import type { BlockToolAdapter as BlockToolAdapterInterface } from '@/types/tools/adapters/block-tool-adapter';
8
8
  import type { BlockTuneAdapter as BlockTuneAdapterInterface } from '@/types/tools/adapters/block-tune-adapter';
9
9
 
10
+ /**
11
+ * Keys that are Blok-level settings (not passed to tool constructor)
12
+ */
13
+ const BLOK_SETTINGS_KEYS = new Set([
14
+ 'class',
15
+ 'inlineToolbar',
16
+ 'tunes',
17
+ 'shortcut',
18
+ 'toolbox',
19
+ 'config',
20
+ 'isInternal',
21
+ ]);
22
+
10
23
  /**
11
24
  * Enum of Tool options provided by user
12
25
  */
@@ -188,10 +201,24 @@ export abstract class BaseToolAdapter<Type extends ToolType = ToolType, ToolClas
188
201
  }
189
202
 
190
203
  /**
191
- * Returns Tool user configuration
204
+ * Returns Tool user configuration.
205
+ * Extracts tool-specific options from flat config and merges with nested config.
192
206
  */
193
207
  public get settings(): ToolConfig {
194
- const config = (this.config[UserSettings.Config] ?? {}) as ToolConfig;
208
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- Internal: reading legacy config for backwards compatibility
209
+ const nestedConfig = (this.config[UserSettings.Config] ?? {}) as ToolConfig;
210
+
211
+ // Extract non-Blok keys as tool-specific config
212
+ const flatConfig: Record<string, unknown> = {};
213
+
214
+ for (const key of Object.keys(this.config)) {
215
+ if (!BLOK_SETTINGS_KEYS.has(key)) {
216
+ flatConfig[key] = this.config[key as keyof typeof this.config];
217
+ }
218
+ }
219
+
220
+ // Merge: nested config first, flat config overrides
221
+ const config = { ...nestedConfig, ...flatConfig } as ToolConfig;
195
222
 
196
223
  if (this.isDefault && !('placeholder' in config) && this.defaultPlaceholder) {
197
224
  config.placeholder = this.defaultPlaceholder;
@@ -203,10 +203,18 @@ export class BlockToolAdapter extends BaseToolAdapter<ToolType.Block, IBlockTool
203
203
  }
204
204
 
205
205
  /**
206
- * Returns enabled inline tools for Tool
206
+ * Returns enabled inline tools for Tool.
207
+ * Defaults to true (all inline tools) unless explicitly set to false or array.
207
208
  */
208
209
  public get enabledInlineTools(): boolean | string[] {
209
- return this.config[UserSettings.EnabledInlineTools] || false;
210
+ const setting = this.config[UserSettings.EnabledInlineTools];
211
+
212
+ // Default to true if not specified
213
+ if (setting === undefined) {
214
+ return true;
215
+ }
216
+
217
+ return setting;
210
218
  }
211
219
 
212
220
  /**
@@ -206,8 +206,16 @@ export class Header implements BlockTool {
206
206
  /**
207
207
  * If user provided custom toolbox entries, use them to build settings menu.
208
208
  * This ensures block settings match the toolbox configuration.
209
+ * Entries without explicit level data will default to the configured defaultLevel.
210
+ *
211
+ * Only fall back to levels config when _toolboxEntries is not provided or empty,
212
+ * or when using the default single "Heading" toolbox entry (detected by having
213
+ * exactly one entry with no level data).
209
214
  */
210
- if (toolboxEntries !== undefined && toolboxEntries.length > 0) {
215
+ const isDefaultToolboxEntry = toolboxEntries?.length === 1 &&
216
+ toolboxEntries[0].data === undefined;
217
+
218
+ if (toolboxEntries !== undefined && toolboxEntries.length > 0 && !isDefaultToolboxEntry) {
211
219
  return this.buildSettingsFromToolboxEntries(toolboxEntries);
212
220
  }
213
221
 
@@ -6,10 +6,16 @@
6
6
  * // Import specific tools
7
7
  * import { Paragraph, Header, List, Bold, Italic, Link } from '@jackuait/blok/tools';
8
8
  *
9
- * // Use in Blok configuration
9
+ * // Use in Blok configuration (flat config style)
10
10
  * new Blok({
11
- * tools: { paragraph: Paragraph, header: Header, list: List },
12
- * inlineTools: { bold: Bold, italic: Italic, link: Link }
11
+ * tools: {
12
+ * paragraph: Paragraph,
13
+ * header: { class: Header, levels: [1, 2, 3] },
14
+ * list: List,
15
+ * bold: Bold,
16
+ * italic: Italic,
17
+ * link: Link,
18
+ * }
13
19
  * });
14
20
  */
15
21
 
@@ -25,10 +31,11 @@ export { LinkInlineTool as Link } from '../components/inline-tools/inline-tool-l
25
31
  export { ConvertInlineTool as Convert } from '../components/inline-tools/inline-tool-convert';
26
32
 
27
33
  // Default tools configuration for convenience
34
+ // Note: inlineToolbar defaults to true, so it doesn't need to be specified
28
35
  export const defaultBlockTools = {
29
- paragraph: { inlineToolbar: true, config: { preserveBlank: true } },
30
- header: { inlineToolbar: true },
31
- list: { inlineToolbar: true },
36
+ paragraph: { preserveBlank: true },
37
+ header: {},
38
+ list: {},
32
39
  } as const;
33
40
 
34
41
  export const defaultInlineTools = {
@@ -1,6 +1,12 @@
1
1
  import { ToolConfig } from './tool-config';
2
2
  import { ToolConstructable, BlockToolData, MenuConfig, MenuConfigItem } from './index';
3
3
 
4
+ /**
5
+ * Permissive type for tool class - accepts any constructor.
6
+ * Runtime validation ensures the tool has required methods.
7
+ */
8
+ export type ToolClass = new (...args: any[]) => any;
9
+
4
10
  /**
5
11
  * Tool may specify its toolbox configuration
6
12
  * It may include several entries as well
@@ -49,9 +55,9 @@ export interface ToolboxConfigEntry {
49
55
  export interface ExternalToolSettings<Config extends object = any> {
50
56
 
51
57
  /**
52
- * Tool's class
58
+ * Tool's class - accepts any constructor, validated at runtime
53
59
  */
54
- class: ToolConstructable;
60
+ class: ToolConstructable | ToolClass;
55
61
 
56
62
  /**
57
63
  * User configuration object that will be passed to the Tool's constructor
@@ -88,6 +94,58 @@ export interface ExternalToolSettings<Config extends object = any> {
88
94
  export type InternalToolSettings<Config extends object = any> = Omit<ExternalToolSettings<Config>, 'class'> & Partial<Pick<ExternalToolSettings<Config>, 'class'>>;
89
95
 
90
96
  /**
91
- * Union of external and internal Tools settings
97
+ * Keys that Blok extracts from tool settings (not passed to tool constructor)
98
+ */
99
+ export type BlokToolSettingsKeys = 'class' | 'inlineToolbar' | 'tunes' | 'shortcut' | 'toolbox' | 'config';
100
+
101
+ /**
102
+ * Flat tool settings - tool-specific options at top level.
103
+ * Blok extracts known keys and passes the rest as `config` to the tool.
104
+ */
105
+ export interface FlatToolSettings<Config extends object = any> {
106
+ /**
107
+ * Tool's class
108
+ */
109
+ class: ToolConstructable | ToolClass;
110
+
111
+ /**
112
+ * Is need to show Inline Toolbar.
113
+ * Defaults to true for block tools.
114
+ */
115
+ inlineToolbar?: boolean | string[];
116
+
117
+ /**
118
+ * BlockTunes for Tool
119
+ */
120
+ tunes?: boolean | string[];
121
+
122
+ /**
123
+ * Define shortcut that will render Tool
124
+ */
125
+ shortcut?: string;
126
+
127
+ /**
128
+ * Tool's Toolbox settings
129
+ */
130
+ toolbox?: ToolboxConfig | false;
131
+
132
+ /**
133
+ * Legacy nested config - merged with top-level tool options
134
+ * @deprecated Use flat config instead
135
+ */
136
+ config?: ToolConfig<Config>;
137
+
138
+ /**
139
+ * Tool-specific options (placeholder, levels, etc.)
140
+ * These are passed to the tool constructor as `config`
141
+ */
142
+ [key: string]: unknown;
143
+ }
144
+
145
+ /**
146
+ * Union of all tool settings formats
92
147
  */
93
- export type ToolSettings<Config extends object = any> = InternalToolSettings<Config> | ExternalToolSettings<Config>;
148
+ export type ToolSettings<Config extends object = any> =
149
+ | InternalToolSettings<Config>
150
+ | ExternalToolSettings<Config>
151
+ | FlatToolSettings<Config>;