@jackuait/blok 0.4.1-beta.11 → 0.4.1-beta.12

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.
Files changed (28) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-oNSQ3HA6.mjs → blok-BU6NwVkN.mjs} +423 -401
  3. package/dist/chunks/{i18next-loader-BdNRw4n4.mjs → i18next-loader-D8GzSwio.mjs} +1 -1
  4. package/dist/chunks/{index-DHgXmfki.mjs → index-C5e_WLFg.mjs} +1 -1
  5. package/dist/chunks/{inline-tool-convert-CRqgjRim.mjs → inline-tool-convert-CLUxkCe_.mjs} +2 -1
  6. package/dist/full.mjs +2 -2
  7. package/dist/tools.mjs +16 -7
  8. package/package.json +1 -1
  9. package/src/components/inline-tools/inline-tool-convert.ts +1 -0
  10. package/src/components/inline-tools/inline-tool-link.ts +1 -0
  11. package/src/components/modules/toolbar/blockSettings.ts +2 -1
  12. package/src/components/modules/toolbar/index.ts +97 -116
  13. package/src/components/modules/ui.ts +1 -1
  14. package/src/components/ui/toolbox.ts +14 -5
  15. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +1 -1
  16. package/src/components/utils/popover/components/popover-item/popover-item.ts +11 -0
  17. package/src/components/utils/popover/popover-abstract.ts +1 -1
  18. package/src/components/utils/popover/popover-desktop.ts +8 -2
  19. package/src/stories/Popover.stories.ts +0 -85
  20. package/src/styles/main.css +7 -4
  21. package/src/tools/header/index.ts +1 -0
  22. package/src/tools/list/index.ts +11 -4
  23. package/types/configs/sanitizer-config.d.ts +25 -1
  24. package/types/index.d.ts +1 -0
  25. package/types/tools/block-tool.d.ts +2 -2
  26. package/types/tools/tool-settings.d.ts +7 -0
  27. package/types/utils/popover/popover-item.d.ts +6 -0
  28. package/types/utils/popover/popover.d.ts +6 -0
@@ -1,4 +1,4 @@
1
- import { e as i } from "./blok-oNSQ3HA6.mjs";
1
+ import { e as i } from "./blok-BU6NwVkN.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-CRqgjRim.mjs";
1
+ import { t as f, q as i } from "./inline-tool-convert-CLUxkCe_.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.1-beta.11", Ct = {
21
+ const rt = () => "0.4.1-beta.12", Ct = {
22
22
  BACKSPACE: 8,
23
23
  TAB: 9,
24
24
  ENTER: 13,
@@ -1895,6 +1895,7 @@ const te = `
1895
1895
  },
1896
1896
  children: {
1897
1897
  items: s,
1898
+ width: "auto",
1898
1899
  onOpen: () => {
1899
1900
  c && (this.selectionAPI.setFakeBackground(), this.selectionAPI.save());
1900
1901
  },
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-oNSQ3HA6.mjs";
13
+ import { B as v, v as A } from "./chunks/blok-BU6NwVkN.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-CRqgjRim.mjs";
16
+ import { D as _ } from "./chunks/inline-tool-convert-CLUxkCe_.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 ? nt(f, t, { enumerable: !0, configurable: !0, writa
10
10
  it.call(t, e) && U(f, e, t[e]);
11
11
  return f;
12
12
  }, P = (f, t) => rt(f, st(t));
13
- import { t as x, D as m, a9 as et, aa as at, ab as lt, A as ct, ac as dt, ad as ut, ae as ht, af as ft, ag as pt, ah as mt, ai as G, aj as j, ak as $, f as A, al as gt, am as Et, S as H, P as Tt, an as Ct, l as At, J as yt } from "./chunks/inline-tool-convert-CRqgjRim.mjs";
14
- import { a0 as Dt } from "./chunks/inline-tool-convert-CRqgjRim.mjs";
13
+ import { t as x, D as m, a9 as et, aa as at, ab as lt, A as ct, ac as dt, ad as ut, ae as ht, af as ft, ag as pt, ah as mt, ai as G, aj as j, ak as $, f as A, al as gt, am as Et, S as H, P as Tt, an as Ct, l as At, J as yt } from "./chunks/inline-tool-convert-CLUxkCe_.mjs";
14
+ import { a0 as Dt } from "./chunks/inline-tool-convert-CLUxkCe_.mjs";
15
15
  const W = [
16
16
  "empty:before:pointer-events-none",
17
17
  "empty:before:text-gray-text",
@@ -531,7 +531,8 @@ const L = class L {
531
531
  titleKey: t.nameKey,
532
532
  name: `header-${t.number}`,
533
533
  data: { level: t.number },
534
- searchTerms: [`h${t.number}`, "title", "header", "heading"]
534
+ searchTerms: [`h${t.number}`, "title", "header", "heading"],
535
+ shortcut: "#".repeat(t.number)
535
536
  }));
536
537
  }
537
538
  };
@@ -1342,7 +1343,11 @@ const u = class u {
1342
1343
  return {
1343
1344
  text: {
1344
1345
  br: !0,
1345
- a: !0,
1346
+ a: {
1347
+ href: !0,
1348
+ target: "_blank",
1349
+ rel: "nofollow"
1350
+ },
1346
1351
  b: !0,
1347
1352
  i: !0,
1348
1353
  mark: !0
@@ -1421,7 +1426,8 @@ const u = class u {
1421
1426
  titleKey: "bulletedList",
1422
1427
  data: { style: "unordered" },
1423
1428
  name: "bulleted-list",
1424
- searchTerms: ["ul", "bullet", "unordered", "list"]
1429
+ searchTerms: ["ul", "bullet", "unordered", "list"],
1430
+ shortcut: "-"
1425
1431
  },
1426
1432
  {
1427
1433
  icon: j,
@@ -1429,7 +1435,8 @@ const u = class u {
1429
1435
  titleKey: "numberedList",
1430
1436
  data: { style: "ordered" },
1431
1437
  name: "numbered-list",
1432
- searchTerms: ["ol", "ordered", "number", "list"]
1438
+ searchTerms: ["ol", "ordered", "number", "list"],
1439
+ shortcut: "1."
1433
1440
  },
1434
1441
  {
1435
1442
  icon: $,
@@ -1437,7 +1444,8 @@ const u = class u {
1437
1444
  titleKey: "todoList",
1438
1445
  data: { style: "checklist" },
1439
1446
  name: "check-list",
1440
- searchTerms: ["checkbox", "task", "todo", "check", "list"]
1447
+ searchTerms: ["checkbox", "task", "todo", "check", "list"],
1448
+ shortcut: "[]"
1441
1449
  }
1442
1450
  ];
1443
1451
  }
@@ -2907,6 +2915,7 @@ const D = class D {
2907
2915
  isActive: () => !!this.selection.findParentTag("A"),
2908
2916
  children: {
2909
2917
  hideChevron: !0,
2918
+ width: "200px",
2910
2919
  items: [
2911
2920
  {
2912
2921
  type: Tt.Html,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackuait/blok",
3
- "version": "0.4.1-beta.11",
3
+ "version": "0.4.1-beta.12",
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",
@@ -123,6 +123,7 @@ export class ConvertInlineTool implements InlineTool {
123
123
  },
124
124
  children: {
125
125
  items: convertToItems,
126
+ width: 'auto',
126
127
  onOpen: () => {
127
128
  if (isDesktop) {
128
129
  this.selectionAPI.setFakeBackground();
@@ -133,6 +133,7 @@ export class LinkInlineTool implements InlineTool {
133
133
  isActive: () => !!this.selection.findParentTag('A'),
134
134
  children: {
135
135
  hideChevron: true,
136
+ width: '200px',
136
137
  items: [
137
138
  {
138
139
  type: PopoverItemType.Html,
@@ -164,10 +164,11 @@ export class BlockSettings extends Module<BlockSettingsNodes> {
164
164
 
165
165
  const PopoverClass = isMobileScreen() ? PopoverMobile : PopoverDesktop;
166
166
  const popoverParams: PopoverParams & { flipper?: Flipper } = {
167
- searchable: true,
167
+ searchable: false,
168
168
  trigger: trigger || this.nodes.wrapper,
169
169
  items: await this.getTunesItems(block, commonTunes, toolTunes),
170
170
  scopeElement: this.Blok.API.methods.ui.nodes.redactor,
171
+ width: 'auto',
171
172
  messages: {
172
173
  nothingFound: this.Blok.I18n.t('popover.nothingFound'),
173
174
  search: this.Blok.I18n.t('popover.search'),
@@ -109,18 +109,6 @@ export class Toolbar extends Module<ToolbarNodes> {
109
109
  */
110
110
  private toolboxInstance: Toolbox | null = null;
111
111
 
112
- /**
113
- * Mouse position when mousedown occurred on settings toggler
114
- * Used to distinguish between click and drag
115
- */
116
- private settingsTogglerMouseDownPosition: { x: number; y: number } | null = null;
117
-
118
- /**
119
- * Mouse position when mousedown occurred on plus button
120
- * Used to distinguish between click and drag
121
- */
122
- private plusButtonMouseDownPosition: { x: number; y: number } | null = null;
123
-
124
112
  /**
125
113
  * Last calculated toolbar Y position
126
114
  * Used to avoid unnecessary repositioning when the position hasn't changed
@@ -133,6 +121,12 @@ export class Toolbar extends Module<ToolbarNodes> {
133
121
  */
134
122
  private ignoreNextSettingsMouseUp = false;
135
123
 
124
+ /**
125
+ * Set of pending document-level mouseup listeners that need cleanup on destroy.
126
+ * Each listener is added on mousedown and removed after mouseup fires.
127
+ */
128
+ private pendingMouseUpListeners: Set<(e: MouseEvent) => void> = new Set();
129
+
136
130
  /**
137
131
  * @class
138
132
  * @param moduleConfiguration - Module Configuration
@@ -218,7 +212,6 @@ export class Toolbar extends Module<ToolbarNodes> {
218
212
  'not-mobile:w-6'
219
213
  ),
220
214
  settingsTogglerHidden: 'hidden',
221
- settingsTogglerOpened: '',
222
215
  };
223
216
  }
224
217
 
@@ -660,60 +653,25 @@ export class Toolbar extends Module<ToolbarNodes> {
660
653
 
661
654
  /**
662
655
  * Plus button mousedown handler
663
- * Stores the initial mouse position and sets up a document-level mouseup listener.
664
- * Using document-level mouseup ensures we catch the event even if the mouse
665
- * moves slightly off the button element during the click.
656
+ * Uses click-vs-drag detection to distinguish clicks from drags.
666
657
  */
667
658
  this.readOnlyMutableListeners.on(plusButton, 'mousedown', (e) => {
668
659
  hide();
669
660
 
670
- const mouseEvent = e as MouseEvent;
671
-
672
- /**
673
- * Store the mouse position when mousedown occurs
674
- * This will be used to determine if the user dragged or clicked
675
- */
676
- this.plusButtonMouseDownPosition = {
677
- x: mouseEvent.clientX,
678
- y: mouseEvent.clientY,
679
- };
680
-
681
- /**
682
- * Add document-level mouseup listener to catch the event even if mouse
683
- * moves slightly off the button. This is removed after firing once.
684
- */
685
- const onMouseUp = (mouseUpEvent: MouseEvent): void => {
686
- document.removeEventListener('mouseup', onMouseUp, true);
687
-
688
- const mouseDownPos = this.plusButtonMouseDownPosition;
689
-
690
- this.plusButtonMouseDownPosition = null;
691
-
692
- if (mouseDownPos === null) {
693
- return;
694
- }
695
-
696
- const wasDragged = (
697
- Math.abs(mouseUpEvent.clientX - mouseDownPos.x) > DRAG_THRESHOLD ||
698
- Math.abs(mouseUpEvent.clientY - mouseDownPos.y) > DRAG_THRESHOLD
699
- );
661
+ this.setupClickVsDrag(
662
+ e as MouseEvent,
663
+ (mouseUpEvent) => {
664
+ /**
665
+ * Check for modifier key to determine insert direction:
666
+ * - Option/Alt on Mac, Ctrl on Windows → insert above
667
+ * - No modifier → insert below (default)
668
+ */
669
+ const userOS = getUserOS();
670
+ const insertAbove = userOS.win ? mouseUpEvent.ctrlKey : mouseUpEvent.altKey;
700
671
 
701
- if (wasDragged) {
702
- return;
672
+ this.plusButtonClicked(insertAbove);
703
673
  }
704
-
705
- /**
706
- * Check for modifier key to determine insert direction:
707
- * - Option/Alt on Mac, Ctrl on Windows → insert above
708
- * - No modifier → insert below (default)
709
- */
710
- const userOS = getUserOS();
711
- const insertAbove = userOS.win ? mouseUpEvent.ctrlKey : mouseUpEvent.altKey;
712
-
713
- this.plusButtonClicked(insertAbove);
714
- };
715
-
716
- document.addEventListener('mouseup', onMouseUp, true);
674
+ );
717
675
  }, true);
718
676
 
719
677
  /**
@@ -931,66 +889,35 @@ export class Toolbar extends Module<ToolbarNodes> {
931
889
  if (settingsToggler) {
932
890
  /**
933
891
  * Settings toggler mousedown handler
934
- * Stores the initial mouse position and sets up a document-level mouseup listener.
935
- * Using document-level mouseup ensures we catch the event even if the mouse
936
- * moves slightly off the toggler element during the click.
892
+ * Uses click-vs-drag detection to distinguish clicks from drags.
937
893
  */
938
894
  this.readOnlyMutableListeners.on(settingsToggler, 'mousedown', (e) => {
939
895
  hide();
940
896
 
941
- const mouseEvent = e as MouseEvent;
942
-
943
- /**
944
- * Store the mouse position when mousedown occurs
945
- * This will be used to determine if the user dragged or clicked
946
- */
947
- this.settingsTogglerMouseDownPosition = {
948
- x: mouseEvent.clientX,
949
- y: mouseEvent.clientY,
950
- };
951
-
952
- /**
953
- * Add document-level mouseup listener to catch the event even if mouse
954
- * moves slightly off the toggler. This is removed after firing once.
955
- */
956
- const onMouseUp = (mouseUpEvent: MouseEvent): void => {
957
- document.removeEventListener('mouseup', onMouseUp, true);
958
-
959
- /**
960
- * Ignore mouseup after a block drop to prevent settings menu from opening
961
- */
962
- if (this.ignoreNextSettingsMouseUp) {
963
- this.ignoreNextSettingsMouseUp = false;
964
- this.settingsTogglerMouseDownPosition = null;
965
-
966
- return;
967
- }
968
-
969
- const mouseDownPos = this.settingsTogglerMouseDownPosition;
970
-
971
- this.settingsTogglerMouseDownPosition = null;
972
-
973
- if (mouseDownPos === null) {
974
- return;
975
- }
976
-
977
- const wasDragged = (
978
- Math.abs(mouseUpEvent.clientX - mouseDownPos.x) > DRAG_THRESHOLD ||
979
- Math.abs(mouseUpEvent.clientY - mouseDownPos.y) > DRAG_THRESHOLD
980
- );
981
-
982
- if (wasDragged) {
983
- return;
984
- }
985
-
986
- this.settingsTogglerClicked();
987
-
988
- if (this.toolboxInstance?.opened) {
989
- this.toolboxInstance.close();
897
+ this.setupClickVsDrag(
898
+ e as MouseEvent,
899
+ () => {
900
+ this.settingsTogglerClicked();
901
+
902
+ if (this.toolboxInstance?.opened) {
903
+ this.toolboxInstance.close();
904
+ }
905
+ },
906
+ {
907
+ /**
908
+ * Check if we should ignore this mouseup (e.g., after a block drop)
909
+ */
910
+ beforeCallback: () => {
911
+ if (this.ignoreNextSettingsMouseUp) {
912
+ this.ignoreNextSettingsMouseUp = false;
913
+
914
+ return false;
915
+ }
916
+
917
+ return true;
918
+ },
990
919
  }
991
- };
992
-
993
- document.addEventListener('mouseup', onMouseUp, true);
920
+ );
994
921
  }, true);
995
922
  }
996
923
 
@@ -1302,6 +1229,50 @@ export class Toolbar extends Module<ToolbarNodes> {
1302
1229
  return container;
1303
1230
  }
1304
1231
 
1232
+ /**
1233
+ * Sets up a click-vs-drag detection pattern on an element.
1234
+ * Tracks mousedown position and fires callback only if mouse didn't move beyond threshold.
1235
+ * Uses document-level mouseup to catch events even if mouse moves off element.
1236
+ * @param element - Element to attach mousedown listener to
1237
+ * @param mouseEvent - The mousedown event
1238
+ * @param onClickCallback - Callback to fire if it was a click (not a drag)
1239
+ * @param options - Optional configuration
1240
+ * @param options.beforeCallback - Function called before click callback, return false to abort
1241
+ */
1242
+ private setupClickVsDrag(
1243
+ mouseEvent: MouseEvent,
1244
+ onClickCallback: (mouseUpEvent: MouseEvent) => void,
1245
+ options?: { beforeCallback?: () => boolean }
1246
+ ): void {
1247
+ const startPosition = {
1248
+ x: mouseEvent.clientX,
1249
+ y: mouseEvent.clientY,
1250
+ };
1251
+
1252
+ const onMouseUp = (mouseUpEvent: MouseEvent): void => {
1253
+ document.removeEventListener('mouseup', onMouseUp, true);
1254
+ this.pendingMouseUpListeners.delete(onMouseUp);
1255
+
1256
+ if (options?.beforeCallback && !options.beforeCallback()) {
1257
+ return;
1258
+ }
1259
+
1260
+ const wasDragged = (
1261
+ Math.abs(mouseUpEvent.clientX - startPosition.x) > DRAG_THRESHOLD ||
1262
+ Math.abs(mouseUpEvent.clientY - startPosition.y) > DRAG_THRESHOLD
1263
+ );
1264
+
1265
+ if (wasDragged) {
1266
+ return;
1267
+ }
1268
+
1269
+ onClickCallback(mouseUpEvent);
1270
+ };
1271
+
1272
+ this.pendingMouseUpListeners.add(onMouseUp);
1273
+ document.addEventListener('mouseup', onMouseUp, true);
1274
+ }
1275
+
1305
1276
  /**
1306
1277
  * Removes all created and saved HTMLElements
1307
1278
  * It is used in Read-Only mode
@@ -1311,5 +1282,15 @@ export class Toolbar extends Module<ToolbarNodes> {
1311
1282
  if (this.toolboxInstance) {
1312
1283
  this.toolboxInstance.destroy();
1313
1284
  }
1285
+
1286
+ /**
1287
+ * Clean up any pending document-level mouseup listeners.
1288
+ * These are added on mousedown and normally removed on mouseup,
1289
+ * but if the component is destroyed mid-click, they need manual cleanup.
1290
+ */
1291
+ for (const listener of this.pendingMouseUpListeners) {
1292
+ document.removeEventListener('mouseup', listener, true);
1293
+ }
1294
+ this.pendingMouseUpListeners.clear();
1314
1295
  }
1315
1296
  }
@@ -468,7 +468,7 @@ export class UI extends Module<UINodes> {
468
468
  };
469
469
 
470
470
  const handleBlockHovered = (event: Event): void => {
471
- if (!(event instanceof MouseEvent)) {
471
+ if (typeof MouseEvent === 'undefined' || !(event instanceof MouseEvent)) {
472
472
  return;
473
473
  }
474
474
 
@@ -145,6 +145,12 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
145
145
  */
146
146
  private currentBlockForSearch: HTMLElement | null = null;
147
147
 
148
+ /**
149
+ * Cached contentEditable element for the current block being searched.
150
+ * Avoids repeated DOM queries on each input event.
151
+ */
152
+ private currentContentEditable: Element | null = null;
153
+
148
154
  /**
149
155
  * Toolbox constructor
150
156
  * @param options - available parameters
@@ -383,6 +389,9 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
383
389
  const userSearchTerms = tool.searchTerms ?? [];
384
390
  const mergedSearchTerms = [...new Set([...librarySearchTerms, ...userSearchTerms])];
385
391
 
392
+ // Use entry-level shortcut if available, otherwise fall back to tool-level shortcut (for first entry only)
393
+ const shortcut = toolboxItem.shortcut ?? (displaySecondaryLabel ? tool.shortcut : undefined);
394
+
386
395
  return {
387
396
  icon: toolboxItem.icon,
388
397
  title: translateToolTitle(this.i18n, toolboxItem, capitalize(tool.name)),
@@ -390,7 +399,7 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
390
399
  onActivate: (): void => {
391
400
  void this.toolButtonActivated(tool.name, toolboxItem.data);
392
401
  },
393
- secondaryLabel: (tool.shortcut && displaySecondaryLabel) ? beautifyShortcut(tool.shortcut) : '',
402
+ secondaryLabel: shortcut ? beautifyShortcut(shortcut) : '',
394
403
  englishTitle,
395
404
  searchTerms: mergedSearchTerms,
396
405
  };
@@ -545,6 +554,7 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
545
554
  }
546
555
 
547
556
  this.currentBlockForSearch = currentBlock.holder;
557
+ this.currentContentEditable = this.currentBlockForSearch.querySelector('[contenteditable="true"]');
548
558
  this.listeners.on(this.currentBlockForSearch, 'input', this.handleBlockInput);
549
559
  }
550
560
 
@@ -555,6 +565,7 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
555
565
  if (this.currentBlockForSearch !== null) {
556
566
  this.listeners.off(this.currentBlockForSearch, 'input', this.handleBlockInput);
557
567
  this.currentBlockForSearch = null;
568
+ this.currentContentEditable = null;
558
569
  }
559
570
 
560
571
  this.popover?.filterItems('');
@@ -565,13 +576,11 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
565
576
  * Extracts text after "/" and applies it as a filter query.
566
577
  */
567
578
  private handleBlockInput = (): void => {
568
- if (this.currentBlockForSearch === null) {
579
+ if (this.currentContentEditable === null) {
569
580
  return;
570
581
  }
571
582
 
572
- // Get text from the contenteditable element inside the block
573
- const contentEditable = this.currentBlockForSearch.querySelector('[contenteditable="true"]');
574
- const text = contentEditable?.textContent || '';
583
+ const text = this.currentContentEditable.textContent || '';
575
584
  const slashIndex = text.lastIndexOf('/');
576
585
 
577
586
  if (slashIndex === -1) {
@@ -265,7 +265,7 @@ export class PopoverItemDefault extends PopoverItem {
265
265
  if (params.secondaryLabel) {
266
266
  const secondaryEl = document.createElement('div');
267
267
 
268
- secondaryEl.className = 'whitespace-nowrap pr-1.5 text-xs -tracking-widest text-text-secondary opacity-60';
268
+ secondaryEl.className = 'whitespace-nowrap pr-1.5 text-xs font-light tracking-[0.25px] text-text-secondary opacity-60';
269
269
  secondaryEl.setAttribute(DATA_ATTR.popoverItemSecondaryTitle, '');
270
270
  secondaryEl.setAttribute('data-blok-testid', 'popover-item-secondary-title');
271
271
  secondaryEl.textContent = params.secondaryLabel;
@@ -158,6 +158,17 @@ export abstract class PopoverItem {
158
158
  return this.params !== undefined && 'children' in this.params && this.params.children?.searchable === true;
159
159
  }
160
160
 
161
+ /**
162
+ * Returns the width for children popover, if specified
163
+ */
164
+ public get childrenWidth(): string | undefined {
165
+ if (this.params === undefined || !('children' in this.params)) {
166
+ return undefined;
167
+ }
168
+
169
+ return this.params.children?.width;
170
+ }
171
+
161
172
  /**
162
173
  * True if popover should close once item is activated
163
174
  */
@@ -399,7 +399,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
399
399
  popover.setAttribute('data-blok-testid', 'popover');
400
400
 
401
401
  // Set CSS variables
402
- popover.style.setProperty('--width', '200px');
402
+ popover.style.setProperty('--width', this.params.width ?? '280px');
403
403
  popover.style.setProperty('--item-padding', '3px');
404
404
  popover.style.setProperty('--item-height', 'calc(1.25rem + 2 * var(--item-padding))');
405
405
  popover.style.setProperty('--popover-top', 'calc(100% + 0.5rem)');
@@ -401,6 +401,7 @@ export class PopoverDesktop extends PopoverAbstract {
401
401
  flippable: item.isChildrenFlippable,
402
402
  messages: this.messages,
403
403
  onNavigateBack: this.destroyNestedPopoverIfExists.bind(this),
404
+ width: item.childrenWidth,
404
405
  });
405
406
 
406
407
  item.onChildrenOpen();
@@ -452,14 +453,19 @@ export class PopoverDesktop extends PopoverAbstract {
452
453
  // Apply position: absolute for nested container
453
454
  nestedContainer.style.position = 'absolute';
454
455
 
456
+ // Get parent width - use computed width if --width is 'auto'
457
+ const parentWidth = this.params.width === 'auto'
458
+ ? `${this.nodes.popoverContainer.offsetWidth}px`
459
+ : 'var(--width)';
460
+
455
461
  // Calculate --popover-left based on nesting level and parent open direction
456
462
  // Set on the actual popover element to override its default value
457
463
  if (isParentOpenLeft) {
458
464
  // Position to the left
459
- actualPopoverEl.style.setProperty(CSSVariables.PopoverLeft, 'calc(-1 * (var(--nesting-level) + 1) * var(--width) + 100%)');
465
+ actualPopoverEl.style.setProperty(CSSVariables.PopoverLeft, `calc(-1 * (var(--nesting-level) + 1) * ${parentWidth} + 100%)`);
460
466
  } else {
461
467
  // Position to the right
462
- actualPopoverEl.style.setProperty(CSSVariables.PopoverLeft, 'calc(var(--nesting-level) * (var(--width) - var(--nested-popover-overlap)))');
468
+ actualPopoverEl.style.setProperty(CSSVariables.PopoverLeft, `calc(var(--nesting-level) * (${parentWidth} - var(--nested-popover-overlap)))`);
463
469
  }
464
470
 
465
471
  // Calculate top position based on parent open direction
@@ -21,7 +21,6 @@ const POPOVER_ITEM_TESTID = '[data-blok-testid="popover-item"]';
21
21
  const POPOVER_OPENED_SELECTOR = '[data-blok-popover-opened="true"]';
22
22
  const ITEM_FOCUSED_SELECTOR = '[data-blok-focused=\"true\"]';
23
23
  const CONFIRMATION_SELECTOR = '[data-blok-popover-item-confirmation="true"]';
24
- const NOTHING_FOUND_SELECTOR = '[data-blok-nothing-found-displayed="true"]';
25
24
  const DELETE_BUTTON_SELECTOR = '[data-blok-item-name="delete"]';
26
25
  const CONVERT_TO_SELECTOR = '[data-blok-item-name="convert-to"]';
27
26
  const NESTED_POPOVER_SELECTOR = '[data-blok-nested="true"]';
@@ -451,90 +450,6 @@ export const SearchFiltering: Story = {
451
450
  },
452
451
  };
453
452
 
454
- /**
455
- * Popover nothing found message (in block settings).
456
- */
457
- export const NothingFoundMessage: Story = {
458
- args: {
459
- data: sampleData,
460
- },
461
- play: async ({ canvasElement, step }) => {
462
- await step('Wait for editor and toolbar to initialize', async () => {
463
- await waitFor(
464
- () => {
465
- const block = canvasElement.querySelector(BLOCK_TESTID);
466
-
467
- expect(block).toBeInTheDocument();
468
- },
469
- TIMEOUT_INIT
470
- );
471
- // Wait for toolbar to be created (happens in requestIdleCallback)
472
- await waitForToolbar(canvasElement);
473
- });
474
-
475
- await step('Click block to show toolbar', async () => {
476
- const block = canvasElement.querySelector(BLOCK_TESTID);
477
-
478
- if (block) {
479
- simulateClick(block);
480
- }
481
-
482
- await waitFor(
483
- () => {
484
- const toolbar = canvasElement.querySelector(TOOLBAR_TESTID);
485
-
486
- expect(toolbar).toHaveAttribute('data-blok-opened', 'true');
487
- },
488
- TIMEOUT_ACTION
489
- );
490
- });
491
-
492
- await step('Open block settings', async () => {
493
- const settingsButton = canvasElement.querySelector(SETTINGS_BUTTON_TESTID);
494
-
495
- if (settingsButton) {
496
- await userEvent.click(settingsButton);
497
- }
498
-
499
- await waitFor(
500
- () => {
501
- // Block tunes popover is appended to document.body
502
- const blockTunesPopover = document.querySelector(BLOCK_TUNES_POPOVER_TESTID);
503
-
504
- expect(blockTunesPopover).toBeInTheDocument();
505
- },
506
- TIMEOUT_ACTION
507
- );
508
- });
509
-
510
- await step('Search for non-existent item', async () => {
511
- // Wait for popover to be fully rendered and search input to be available
512
- await new Promise((resolve) => setTimeout(resolve, 100));
513
-
514
- // Find and focus the search input inside the popover
515
- const searchInput = document.querySelector('[data-blok-testid="popover-search-input"]') as HTMLInputElement;
516
-
517
- if (searchInput) {
518
- searchInput.focus();
519
- // Type directly into the input
520
- searchInput.value = 'xyznonexistent';
521
- // Trigger input event to notify the search handler
522
- searchInput.dispatchEvent(new Event('input', { bubbles: true }));
523
- }
524
-
525
- await waitFor(
526
- () => {
527
- // Nothing found message is inside the popover which is in document.body
528
- const nothingFound = document.querySelector(NOTHING_FOUND_SELECTOR);
529
-
530
- expect(nothingFound).toBeInTheDocument();
531
- },
532
- TIMEOUT_ACTION
533
- );
534
- });
535
- },
536
- };
537
-
538
453
  /**
539
454
  * Popover item in disabled state.
540
455
  */