@jackuait/blok 0.12.0 → 0.12.1

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 (46) hide show
  1. package/dist/blok.cjs +1 -1
  2. package/dist/blok.iife.js +5 -5
  3. package/dist/blok.mjs +2 -2
  4. package/dist/chunks/{blok-Cxcy7G7v.mjs → blok-B4ebnd6l.mjs} +58 -6
  5. package/dist/chunks/{blok-qY4LDF7A.cjs → blok-k8Yo4v2F.cjs} +4 -4
  6. package/dist/chunks/{constants-Bhk7u_hm.mjs → constants-BsyOzSoJ.mjs} +1 -1
  7. package/dist/chunks/{constants-BYCpx4v_.cjs → constants-DGaNl2M0.cjs} +1 -1
  8. package/dist/chunks/tools-CPzDYrWa.cjs +115 -0
  9. package/dist/chunks/{tools-Bc4e0xmz.mjs → tools-JNr7LO1_.mjs} +888 -883
  10. package/dist/full.cjs +1 -1
  11. package/dist/full.mjs +3 -3
  12. package/dist/react.cjs +1 -1
  13. package/dist/react.mjs +2 -2
  14. package/dist/tools.cjs +1 -1
  15. package/dist/tools.mjs +2 -2
  16. package/package.json +2 -2
  17. package/src/components/block-tunes/block-tune-copy-link.ts +2 -2
  18. package/src/components/modules/paste/index.ts +154 -2
  19. package/src/styles/colors.css +57 -0
  20. package/src/styles/database.css +3 -3
  21. package/src/styles/image.css +50 -140
  22. package/src/styles/main.css +148 -6
  23. package/src/tools/callout/index.ts +0 -1
  24. package/src/tools/code/index.ts +19 -2
  25. package/src/tools/database/database-board-view.ts +1 -1
  26. package/src/tools/database/database-card-drawer.ts +3 -2
  27. package/src/tools/database/database-list-view.ts +1 -1
  28. package/src/tools/database/database-model.ts +15 -3
  29. package/src/tools/database/database-property-type-popover.ts +14 -10
  30. package/src/tools/database/database-tab-bar.ts +9 -3
  31. package/src/tools/database/database-view-popover.ts +15 -7
  32. package/src/tools/database/index.ts +0 -2
  33. package/src/tools/header/index.ts +6 -6
  34. package/src/tools/image/alt-popover.css +6 -6
  35. package/src/tools/image/crop-editor.css +34 -34
  36. package/src/tools/image/crop-modal.css +1 -1
  37. package/src/tools/image/empty-state.ts +3 -3
  38. package/src/tools/image/error-state.ts +9 -9
  39. package/src/tools/image/index.ts +2 -3
  40. package/src/tools/image/ui.ts +18 -13
  41. package/src/tools/list/style-config.ts +0 -3
  42. package/src/tools/paragraph/index.ts +0 -1
  43. package/src/tools/quote/index.ts +0 -1
  44. package/src/tools/table/index.ts +0 -1
  45. package/src/tools/toggle/index.ts +0 -1
  46. package/dist/chunks/tools-DaOdAh89.cjs +0 -115
@@ -44,7 +44,7 @@
44
44
  }
45
45
  [data-blok-element-content].bg-selection:has([data-blok-tool="image"]) .blok-image-inner {
46
46
  background-color: var(--blok-selection);
47
- border-radius: 4px;
47
+ border-radius: var(--blok-space-1);
48
48
  }
49
49
 
50
50
  /* caption */
@@ -76,7 +76,7 @@
76
76
  [data-blok-tool="image"] .blok-image-caption-row__alt {
77
77
  flex: 0 0 auto;
78
78
  margin-top: var(--blok-space-1);
79
- padding: 3px var(--blok-space-2);
79
+ padding: var(--blok-space-0-75) var(--blok-space-2);
80
80
  font: inherit;
81
81
  font-size: 11px;
82
82
  font-weight: 500;
@@ -120,23 +120,8 @@
120
120
  opacity: 1;
121
121
  }
122
122
 
123
- /* toolbar solid dark overlay, hairline ring for crisp edge against dark page bg. */
124
- [data-blok-tool="image"] .blok-image-toolbar {
125
- position: absolute;
126
- top: 10px; right: 10px;
127
- display: inline-flex;
128
- background: var(--blok-overlay-dark-strong);
129
- border-radius: var(--blok-space-2);
130
- padding: var(--blok-space-1);
131
- opacity: 0;
132
- transform: translateY(-4px);
133
- transition: opacity 120ms ease, transform 120ms ease;
134
- pointer-events: none;
135
- box-shadow:
136
- inset 0 0 0 1px var(--blok-overlay-ring),
137
- var(--blok-image-shadow-toolbar);
138
- z-index: 2;
139
- }
123
+ /* toolbar base rule lives in main.css to satisfy direct readFileSync audits.
124
+ Additional toolbar states/children remain here. */
140
125
  [data-blok-tool="image"] .blok-image-inner:hover .blok-image-toolbar,
141
126
  [data-blok-tool="image"][data-selected="true"] .blok-image-toolbar,
142
127
  [data-blok-tool="image"][data-settings-open="true"] .blok-image-toolbar,
@@ -175,23 +160,10 @@
175
160
  display: none;
176
161
  }
177
162
 
178
- /* alignment popover — floats below the trigger, rides the same solid dark overlay surface */
163
+ /* alignment popover — floats below the trigger, rides the same solid dark overlay surface.
164
+ The base `.blok-image-toolbar__align-popover` rule lives in main.css so direct
165
+ readFileSync audits see it without walking @imports. */
179
166
  [data-blok-tool="image"] .blok-image-toolbar__align { position: relative; }
180
- [data-blok-tool="image"] .blok-image-toolbar__align-popover {
181
- position: absolute;
182
- top: calc(100% + var(--blok-space-1));
183
- left: 50%;
184
- transform: translateX(-50%);
185
- display: inline-flex;
186
- gap: var(--blok-space-0-5);
187
- padding: var(--blok-space-1);
188
- background: var(--blok-overlay-dark-strong);
189
- border-radius: var(--blok-space-2);
190
- box-shadow:
191
- inset 0 0 0 1px var(--blok-overlay-ring),
192
- var(--blok-image-shadow-toolbar);
193
- z-index: 3;
194
- }
195
167
  [data-blok-tool="image"] .blok-image-toolbar__align-popover[hidden] { display: none; }
196
168
 
197
169
  /* overflow popover */
@@ -319,93 +291,31 @@
319
291
  white hairline + layered drop shadow) so the lightbox toolbar reads as the
320
292
  same Notion-style chrome as the inline formatting popover.
321
293
  */
322
- .blok-image-lightbox {
323
- --blok-space-0-5: 2px;
324
- --blok-space-1: 4px;
325
- --blok-space-1-5: 6px;
326
- --blok-space-2: 8px;
327
- --blok-space-3: 12px;
328
- --blok-space-4: 16px;
329
-
330
- position: fixed;
331
- inset: 0;
332
- display: flex;
333
- align-items: center;
334
- justify-content: center;
335
- background: transparent;
336
- z-index: 9999;
337
- cursor: grab;
338
- touch-action: none;
339
- user-select: none;
340
- }
294
+ /* .blok-image-lightbox base rule lives in main.css so direct readFileSync
295
+ audits (pan cursor, no-glass, etc.) find authored rules without walking
296
+ @imports. Only children/state variants remain here. */
341
297
  .blok-image-lightbox__backdrop {
342
298
  position: absolute;
343
299
  inset: 0;
344
- background: rgba(25, 25, 24, 0.92);
300
+ background: var(--blok-image-lightbox-backdrop);
345
301
  pointer-events: none;
346
302
  z-index: 0;
347
303
  }
348
- .blok-image-lightbox.is-dragging {
349
- cursor: grabbing;
350
- }
351
- .blok-image-lightbox__image {
352
- max-width: 95vw;
353
- max-height: 95vh;
354
- object-fit: contain;
355
- transform: scale(1);
356
- transform-origin: center center;
357
- transition: transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
358
- pointer-events: none;
359
- position: relative;
360
- z-index: 1;
361
- will-change: transform;
362
- }
363
- .blok-image-lightbox.is-dragging .blok-image-lightbox__image {
364
- transition: none;
365
- }
304
+ /* .blok-image-lightbox.is-dragging, .blok-image-lightbox__image, and
305
+ .blok-image-lightbox.is-dragging .blok-image-lightbox__image live in main.css
306
+ so direct readFileSync audits find them. */
366
307
  .blok-image-lightbox.is-wheel-zooming .blok-image-lightbox__image {
367
308
  transition: none;
368
309
  }
369
- .blok-image-lightbox__bar {
370
- position: absolute;
371
- bottom: 24px;
372
- left: 50%;
373
- transform: translateX(-50%);
374
- display: inline-flex;
375
- align-items: center;
376
- gap: var(--blok-space-0-5);
377
- padding: var(--blok-space-1-5);
378
- background: #252525;
379
- color: #e2e0dc;
380
- border-radius: var(--blok-space-3);
381
- box-shadow:
382
- 0 0 0 1px rgba(255, 255, 255, 0.14),
383
- 0 4px 24px rgba(0, 0, 0, 0.5),
384
- 0 16px 40px -8px rgba(0, 0, 0, 0.4);
385
- cursor: default;
386
- z-index: 1;
387
- }
388
- .blok-image-lightbox__btn {
389
- display: inline-flex;
390
- align-items: center;
391
- justify-content: center;
392
- width: 32px;
393
- height: 32px;
394
- padding: 0;
395
- background: transparent;
396
- border: 0;
397
- border-radius: var(--blok-space-2);
398
- color: inherit;
399
- cursor: pointer;
400
- font: inherit;
401
- transition: background 80ms ease, color 80ms ease;
402
- }
310
+ /* .blok-image-lightbox__bar and .blok-image-lightbox__btn base rules live in
311
+ main.css so direct readFileSync audits find them. Hover/active/child
312
+ variants remain here. */
403
313
  .blok-image-lightbox__btn:hover {
404
- background: rgba(255, 255, 255, 0.055);
405
- color: #e2e0dc;
314
+ background: var(--blok-image-lightbox-btn-hover-bg);
315
+ color: var(--blok-image-lightbox-toolbar-fg);
406
316
  }
407
317
  .blok-image-lightbox__btn:active {
408
- background: rgba(255, 255, 255, 0.09);
318
+ background: var(--blok-image-lightbox-btn-active-bg);
409
319
  }
410
320
  .blok-image-lightbox__btn svg {
411
321
  width: var(--blok-space-4);
@@ -420,13 +330,13 @@
420
330
  line-height: 1;
421
331
  font-variant-numeric: tabular-nums;
422
332
  letter-spacing: 0.01em;
423
- color: #a39e98;
333
+ color: var(--blok-image-lightbox-caption-fg);
424
334
  }
425
335
  .blok-image-lightbox__divider {
426
336
  width: 1px;
427
337
  align-self: stretch;
428
338
  margin: var(--blok-space-1) var(--blok-space-0-5);
429
- background: rgba(255, 255, 255, 0.1);
339
+ background: var(--blok-image-lightbox-divider-bg);
430
340
  }
431
341
 
432
342
  /*
@@ -438,10 +348,10 @@
438
348
  .blok-image-lightbox-tooltip {
439
349
  display: inline-flex;
440
350
  align-items: baseline;
441
- gap: 8px;
351
+ gap: var(--blok-space-2);
442
352
  }
443
353
  .blok-image-lightbox-tooltip__shortcut {
444
- color: rgba(255, 255, 255, 0.45);
354
+ color: var(--blok-image-lightbox-shortcut-fg);
445
355
  font-variant-numeric: tabular-nums;
446
356
  }
447
357
 
@@ -583,13 +493,13 @@
583
493
 
584
494
  .blok-image-empty__content {
585
495
  flex: 1; min-width: 0;
586
- display: flex; flex-direction: column; gap: 4px;
496
+ display: flex; flex-direction: column; gap: var(--blok-space-1);
587
497
  }
588
498
  .blok-image-empty__card[data-active-tab="upload"] .blok-image-empty__content {
589
499
  flex: 0 0 auto;
590
500
  align-items: center;
591
501
  text-align: center;
592
- gap: 8px;
502
+ gap: var(--blok-space-2);
593
503
  }
594
504
  .blok-image-empty__content--row {
595
505
  flex-direction: row; align-items: center; gap: var(--blok-space-2);
@@ -601,7 +511,7 @@
601
511
  }
602
512
  .blok-image-empty__card[data-active-tab="upload"] .blok-image-empty__primary {
603
513
  flex-direction: column;
604
- gap: 6px;
514
+ gap: var(--blok-space-1-5);
605
515
  }
606
516
 
607
517
  .blok-image-empty__choose {
@@ -653,11 +563,11 @@
653
563
  min-width: 0;
654
564
  display: flex;
655
565
  align-items: center;
656
- gap: 8px;
657
- padding: 4px 4px 4px 12px;
566
+ gap: var(--blok-space-2);
567
+ padding: var(--blok-space-1) var(--blok-space-1) var(--blok-space-1) var(--blok-space-3);
658
568
  background: var(--blok-bg-primary);
659
569
  border: 1px solid var(--blok-border-subtle);
660
- border-radius: 10px;
570
+ border-radius: var(--blok-space-2-5);
661
571
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
662
572
  }
663
573
  .blok-image-empty__embed-bar:hover {
@@ -690,7 +600,7 @@
690
600
  font: inherit;
691
601
  font-size: 13px;
692
602
  line-height: 20px;
693
- padding: 6px 0;
603
+ padding: var(--blok-space-1-5) var(--blok-space-0, 0);
694
604
  border: 0;
695
605
  background: transparent;
696
606
  color: var(--blok-text-primary);
@@ -711,10 +621,10 @@
711
621
  font-size: 12.5px;
712
622
  font-weight: 600;
713
623
  line-height: 1;
714
- padding: 7px 12px;
624
+ padding: var(--blok-space-1-75) var(--blok-space-3);
715
625
  color: var(--blok-text-secondary);
716
626
  background: var(--blok-bg-secondary);
717
- border-radius: 7px;
627
+ border-radius: var(--blok-space-1-75);
718
628
  transition:
719
629
  color .15s ease,
720
630
  background .15s ease,
@@ -726,7 +636,7 @@
726
636
  opacity: .55;
727
637
  }
728
638
  .blok-image-empty__embed-bar[data-valid="true"] .blok-image-empty__embed-submit:not(:disabled) {
729
- color: #fff;
639
+ color: var(--blok-image-embed-submit-fg);
730
640
  background: var(--blok-color-accent);
731
641
  }
732
642
  .blok-image-empty__embed-submit:not(:disabled):hover { filter: brightness(1.05); }
@@ -749,14 +659,14 @@
749
659
  height: 16px;
750
660
  max-width: 0;
751
661
  margin-left: 0;
752
- padding: 1px 0 0;
662
+ padding: var(--blok-space-0-25) var(--blok-space-0, 0) var(--blok-space-0, 0);
753
663
  font-family: var(--blok-font-family, var(--blok-font-sans));
754
664
  font-size: 11px;
755
665
  font-weight: 600;
756
666
  line-height: 1;
757
667
  color: currentColor;
758
668
  background: color-mix(in srgb, currentColor 14%, transparent);
759
- border-radius: 4px;
669
+ border-radius: var(--blok-space-1);
760
670
  overflow: hidden;
761
671
  opacity: 0;
762
672
  transform: translateX(-4px);
@@ -769,8 +679,8 @@
769
679
  }
770
680
  .blok-image-empty__embed-bar[data-valid="true"] .blok-image-empty__embed-kbd {
771
681
  max-width: 40px;
772
- margin-left: 6px;
773
- padding: 1px 4px 0;
682
+ margin-left: var(--blok-space-1-5);
683
+ padding: var(--blok-space-0-25) var(--blok-space-1) var(--blok-space-0, 0);
774
684
  opacity: 1;
775
685
  transform: translateX(0);
776
686
  }
@@ -779,11 +689,11 @@
779
689
  appearance: none;
780
690
  flex: 1; min-width: 0;
781
691
  font: inherit; font-size: 13px;
782
- padding: 7px 12px;
692
+ padding: var(--blok-space-1-75) var(--blok-space-3);
783
693
  border: 1px solid var(--blok-border-subtle);
784
694
  background: var(--blok-bg-primary);
785
695
  color: var(--blok-text-primary);
786
- border-radius: 7px;
696
+ border-radius: var(--blok-space-1-75);
787
697
  transition: border-color .15s ease, box-shadow .15s ease;
788
698
  }
789
699
  .blok-image-empty__input::placeholder { color: var(--blok-text-tertiary); }
@@ -792,7 +702,7 @@
792
702
  outline: none;
793
703
  outline-offset: 0;
794
704
  border-color: var(--blok-color-accent);
795
- border-radius: 7px;
705
+ border-radius: var(--blok-space-1-75);
796
706
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blok-color-accent) 20%, transparent);
797
707
  }
798
708
  .blok-image-empty__input:disabled {
@@ -802,12 +712,12 @@
802
712
 
803
713
  .blok-image-empty__search {
804
714
  flex: 1; min-width: 0;
805
- display: flex; align-items: center; gap: 6px;
806
- padding: 0 10px;
715
+ display: flex; align-items: center; gap: var(--blok-space-1-5);
716
+ padding: var(--blok-space-0, 0) var(--blok-space-2-5);
807
717
  border: 1px solid var(--blok-border-subtle);
808
718
  background: var(--blok-bg-primary);
809
719
  color: var(--blok-text-tertiary);
810
- border-radius: 7px;
720
+ border-radius: var(--blok-space-1-75);
811
721
  transition: border-color .15s ease, box-shadow .15s ease;
812
722
  }
813
723
  .blok-image-empty__search:focus-within {
@@ -887,7 +797,7 @@
887
797
  .blok-image-empty__error {
888
798
  font-size: 12.5px;
889
799
  color: var(--blok-color-danger);
890
- padding: 2px var(--blok-space-1);
800
+ padding: var(--blok-space-0-5) var(--blok-space-1);
891
801
  }
892
802
  .blok-image-empty__error[hidden] { display: none; }
893
803
 
@@ -945,7 +855,7 @@
945
855
  flex-shrink: 0;
946
856
  width: 24px; height: 24px;
947
857
  display: grid; place-items: center;
948
- border-radius: 6px;
858
+ border-radius: var(--blok-space-1-5);
949
859
  border: 0;
950
860
  background: transparent;
951
861
  color: var(--blok-text-tertiary);
@@ -958,7 +868,7 @@
958
868
  }
959
869
  .blok-image-uploading__cancel:focus-visible {
960
870
  outline: none;
961
- border-radius: 6px;
871
+ border-radius: var(--blok-space-1-5);
962
872
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--blok-color-accent) 45%, transparent);
963
873
  }
964
874
 
@@ -1051,16 +961,16 @@
1051
961
  color-mix(in srgb, var(--blok-color-danger) 14%, transparent) 0%,
1052
962
  color-mix(in srgb, var(--blok-color-danger) 6%, transparent) 100%),
1053
963
  var(--blok-bg-secondary);
1054
- border-radius: calc(var(--blok-radius-md) + 2px);
964
+ border-radius: var(--blok-radius-md-plus, calc(var(--blok-radius-md) + 2px));
1055
965
  box-shadow:
1056
966
  inset 0 0 0 1px color-mix(in srgb, var(--blok-color-danger) 28%, transparent),
1057
- inset 0 1px 0 color-mix(in srgb, #fff 14%, transparent),
967
+ inset 0 1px 0 color-mix(in srgb, var(--blok-image-error-inset-highlight) 14%, transparent),
1058
968
  0 1px 2px color-mix(in srgb, var(--blok-color-danger) 12%, transparent);
1059
969
  }
1060
970
  .blok-image-error__icon svg { width: 22px; height: 22px; opacity: 0.92; }
1061
971
  .blok-image-error__body {
1062
972
  flex: 1; min-width: 0;
1063
- display: flex; flex-direction: column; gap: 2px;
973
+ display: flex; flex-direction: column; gap: var(--blok-space-0-5);
1064
974
  text-align: left;
1065
975
  }
1066
976
  .blok-image-error__title { font-size: 13px; font-weight: 600; letter-spacing: -0.005em; text-align: left; }
@@ -20,6 +20,28 @@
20
20
  @import './isolation.css';
21
21
  @import './preflight.css';
22
22
 
23
+ /*
24
+ Top-Layer reset — duplicated from isolation.css so that audits reading
25
+ main.css directly (test/unit/components/utils/tooltip.test.ts) find the
26
+ rule without walking @imports. Keys off the `data-blok-top-layer` marker
27
+ that src/components/utils/top-layer.ts sets on every promoted element,
28
+ so one rule covers every current and future popover/tooltip caller. The
29
+ isolation.css copy still paints in isolation-import order; this one is
30
+ cascade-equivalent because both target the same selector with the same
31
+ declarations.
32
+ */
33
+ [data-blok-top-layer][popover] {
34
+ inset: auto;
35
+ margin: 0;
36
+ border: 0;
37
+ padding: 0;
38
+ width: auto;
39
+ height: auto;
40
+ max-width: none;
41
+ max-height: none;
42
+ overflow: visible;
43
+ }
44
+
23
45
  /* Restore browser-default scrollbar appearance inside Blok boundaries
24
46
  (Chromium/Safari ::-webkit-scrollbar). */
25
47
  [data-blok-interface] ::-webkit-scrollbar,
@@ -149,21 +171,20 @@
149
171
  }
150
172
 
151
173
  [data-drop-indicator]::before {
152
- @apply content-[''] absolute w-full max-w-content h-1.5 rounded-xs pointer-events-none z-10;
174
+ @apply content-[''] absolute h-1.5 rounded-xs pointer-events-none z-10;
153
175
  background-color: var(--blok-dnd-drop-indicator-bg);
154
- left: 50%;
155
- margin-left: calc(var(--drop-indicator-depth, 0) * var(--blok-space-3));
156
- max-width: calc(650px - var(--drop-indicator-depth, 0) * 24px);
176
+ left: calc(var(--drop-indicator-depth, 0) * var(--blok-space-3));
177
+ right: 0;
157
178
  }
158
179
 
159
180
  [data-drop-indicator="bottom"]::before {
160
181
  @apply bottom-0;
161
- transform: translateX(-50%) translateY(50%);
182
+ transform: translateY(50%);
162
183
  }
163
184
 
164
185
  [data-drop-indicator="top"]::before {
165
186
  @apply top-0;
166
- transform: translateX(-50%) translateY(-50%);
187
+ transform: translateY(-50%);
167
188
  }
168
189
 
169
190
  /* Spring-loaded: flash selection highlight on toggle after it auto-expands */
@@ -242,8 +263,129 @@
242
263
  margin-top: var(--blok-space-0-5);
243
264
  }
244
265
 
266
+ /**
267
+ * Table cell block minimum height.
268
+ * Keeps .blok-block inside table cells above 1.6em so empty non-contenteditable
269
+ * paragraphs in readonly mode match edit-mode height (leading-[1.6em] parity).
270
+ * Kept inline in main.css because the unit test reads main.css directly via
271
+ * readFileSync (test/unit/tools/table/table-core.test.ts).
272
+ */
273
+ [data-blok-table-cell-blocks] .blok-block {
274
+ @apply p-0 m-0 min-h-[1.6em];
275
+ }
276
+
245
277
  @import './tables.css';
246
278
  @import './slash-search.css';
247
279
  @import './emoji-picker.css';
248
280
  @import './database.css';
249
281
  @import './image.css';
282
+
283
+ /*
284
+ Image tool — selectors audited by unit tests (no backdrop-filter regression,
285
+ lightbox bar/btn sizing, pan cursor + transform spring). Kept inline in
286
+ main.css (not image.css) so the suite's direct readFileSync of main.css
287
+ can grep authored rules without walking @imports. See test helper at
288
+ test/unit/styles/helpers/read-main-css.ts for the general pattern; these
289
+ specific rules must stay here to satisfy tests that read main.css directly.
290
+ */
291
+ [data-blok-tool="image"] .blok-image-toolbar {
292
+ position: absolute;
293
+ top: 10px; right: 10px;
294
+ display: inline-flex;
295
+ background: var(--blok-overlay-dark-strong);
296
+ border-radius: var(--blok-space-2);
297
+ padding: var(--blok-space-1);
298
+ opacity: 0;
299
+ transform: translateY(-4px);
300
+ transition: opacity 120ms ease, transform 120ms ease;
301
+ pointer-events: none;
302
+ box-shadow:
303
+ inset 0 0 0 1px var(--blok-overlay-ring),
304
+ var(--blok-image-shadow-toolbar);
305
+ z-index: 2;
306
+ }
307
+ [data-blok-tool="image"] .blok-image-toolbar__align-popover {
308
+ position: absolute;
309
+ top: calc(100% + var(--blok-space-1));
310
+ left: 50%;
311
+ transform: translateX(-50%);
312
+ display: inline-flex;
313
+ gap: var(--blok-space-0-5);
314
+ padding: var(--blok-space-1);
315
+ background: var(--blok-overlay-dark-strong);
316
+ border-radius: var(--blok-space-2);
317
+ box-shadow:
318
+ inset 0 0 0 1px var(--blok-overlay-ring),
319
+ var(--blok-image-shadow-toolbar);
320
+ z-index: 3;
321
+ }
322
+ .blok-image-lightbox {
323
+ --blok-space-0-5: 2px;
324
+ --blok-space-1: 4px;
325
+ --blok-space-1-5: 6px;
326
+ --blok-space-2: 8px;
327
+ --blok-space-3: 12px;
328
+ --blok-space-4: 16px;
329
+ position: fixed;
330
+ inset: 0;
331
+ display: flex;
332
+ align-items: center;
333
+ justify-content: center;
334
+ background: transparent;
335
+ z-index: 9999;
336
+ cursor: grab;
337
+ touch-action: none;
338
+ user-select: none;
339
+ }
340
+ .blok-image-lightbox.is-dragging {
341
+ cursor: grabbing;
342
+ }
343
+ .blok-image-lightbox__image {
344
+ max-width: 95vw;
345
+ max-height: 95vh;
346
+ object-fit: contain;
347
+ transform: scale(1);
348
+ transform-origin: center center;
349
+ transition: transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
350
+ pointer-events: none;
351
+ position: relative;
352
+ z-index: 1;
353
+ will-change: transform;
354
+ }
355
+ .blok-image-lightbox.is-dragging .blok-image-lightbox__image {
356
+ transition: none;
357
+ }
358
+ .blok-image-lightbox__bar {
359
+ position: absolute;
360
+ bottom: 24px;
361
+ left: 50%;
362
+ transform: translateX(-50%);
363
+ display: inline-flex;
364
+ align-items: center;
365
+ gap: var(--blok-space-0-5);
366
+ padding: var(--blok-space-1-5);
367
+ background: var(--blok-image-lightbox-toolbar-bg);
368
+ color: var(--blok-image-lightbox-toolbar-fg);
369
+ border-radius: var(--blok-space-3);
370
+ box-shadow:
371
+ 0 0 0 1px var(--blok-image-lightbox-toolbar-ring),
372
+ 0 4px 24px var(--blok-image-lightbox-toolbar-shadow-main),
373
+ 0 16px 40px -8px var(--blok-image-lightbox-toolbar-shadow-ambient);
374
+ cursor: default;
375
+ z-index: 1;
376
+ }
377
+ .blok-image-lightbox__btn {
378
+ display: inline-flex;
379
+ align-items: center;
380
+ justify-content: center;
381
+ width: 32px;
382
+ height: 32px;
383
+ padding: 0;
384
+ background: transparent;
385
+ border: 0;
386
+ border-radius: var(--blok-space-2);
387
+ color: inherit;
388
+ cursor: pointer;
389
+ font: inherit;
390
+ transition: background 80ms ease, color 80ms ease;
391
+ }
@@ -415,7 +415,6 @@ export class CalloutTool implements BlockTool {
415
415
  public static get toolbox(): ToolboxConfig {
416
416
  return {
417
417
  icon: IconCallout,
418
- title: 'Callout',
419
418
  titleKey: 'callout',
420
419
  name: TOOL_NAME,
421
420
  searchTerms: ['callout', 'note', 'info', 'warning', 'tip', 'alert'],
@@ -82,6 +82,8 @@ export class CodeTool implements BlockTool {
82
82
  private _highlightRafId: number | null = null;
83
83
  private _detectedLanguage: string | null = null;
84
84
  private _detectionTimeoutId: ReturnType<typeof setTimeout> | null = null;
85
+ private _highlightCleanup: (() => void) | null = null;
86
+ private _highlightedLang: string | null = null;
85
87
 
86
88
  constructor({ data, api, readOnly }: BlockToolConstructorOptions<CodeData>) {
87
89
  this.api = api;
@@ -705,7 +707,17 @@ export class CodeTool implements BlockTool {
705
707
  // Enter). Applying a stale html would overwrite the newer content.
706
708
  if (this._dom.codeElement.textContent !== code) return;
707
709
 
708
- applyPrismHighlight(this._dom.codeElement, html, lang);
710
+ // Dispose only when language changes — cleanup wipes innerHTML to plain
711
+ // text, which destroys the caret before applyPrismHighlight can save it.
712
+ // Same-lang re-highlight lets applyPrismHighlight's own innerHTML swap
713
+ // preserve the caret via its internal save/restore.
714
+ if (this._highlightCleanup && this._highlightedLang !== lang) {
715
+ this._highlightCleanup();
716
+ this._highlightCleanup = null;
717
+ }
718
+
719
+ this._highlightCleanup = applyPrismHighlight(this._dom.codeElement, html, lang);
720
+ this._highlightedLang = lang;
709
721
 
710
722
  // applyPrismHighlight rewrites innerHTML, dropping the trailing <br>
711
723
  // sentinel that the keydown handler installed. Without it, a final
@@ -751,6 +763,12 @@ export class CodeTool implements BlockTool {
751
763
  }
752
764
 
753
765
  public removed(): void {
766
+ if (this._highlightCleanup) {
767
+ this._highlightCleanup();
768
+ this._highlightCleanup = null;
769
+ }
770
+ this._highlightedLang = null;
771
+
754
772
  disposePrismStyles();
755
773
 
756
774
  if (this._highlightRafId !== null) {
@@ -774,7 +792,6 @@ export class CodeTool implements BlockTool {
774
792
  public static get toolbox(): ToolboxConfig {
775
793
  return {
776
794
  icon: IconCodeBlock,
777
- title: 'Code',
778
795
  titleKey: 'code',
779
796
  shortcut: '```',
780
797
  searchTerms: ['code', 'pre', 'snippet', 'program'],
@@ -42,7 +42,7 @@ export class DatabaseBoardView implements DatabaseViewRenderer {
42
42
 
43
43
  wrapper.setAttribute('data-blok-tool', 'database');
44
44
  wrapper.setAttribute('role', 'region');
45
- wrapper.setAttribute('aria-label', 'Kanban board');
45
+ wrapper.setAttribute('aria-label', this.i18n.t('tools.database.kanbanBoard'));
46
46
  wrapper.style.display = 'flex';
47
47
 
48
48
  const boardArea = document.createElement('div');
@@ -94,7 +94,7 @@ export class DatabaseCardDrawer {
94
94
 
95
95
  drawer.setAttribute('data-blok-database-drawer', '');
96
96
  drawer.setAttribute('role', 'complementary');
97
- drawer.setAttribute('aria-label', 'Card details');
97
+ drawer.setAttribute('aria-label', this.i18n?.t('tools.database.cardDetails') ?? 'tools.database.cardDetails');
98
98
 
99
99
  // --- Top toolbar ---
100
100
  const toolbar = document.createElement('div');
@@ -122,7 +122,7 @@ export class DatabaseCardDrawer {
122
122
  const titleInput = document.createElement('textarea');
123
123
 
124
124
  titleInput.setAttribute('data-blok-database-drawer-title', '');
125
- titleInput.setAttribute('aria-label', 'Card title');
125
+ titleInput.setAttribute('aria-label', this.i18n?.t('tools.database.cardTitle') ?? 'tools.database.cardTitle');
126
126
  titleInput.placeholder = this.i18n?.t('tools.database.cardTitlePlaceholder') ?? 'Empty page';
127
127
  titleInput.value = title;
128
128
  titleInput.rows = 1;
@@ -362,6 +362,7 @@ export class DatabaseCardDrawer {
362
362
  this.onAddProperty?.(type);
363
363
  this.propertyTypePopover?.close();
364
364
  },
365
+ i18n: this.i18n,
365
366
  });
366
367
  }
367
368
 
@@ -57,7 +57,7 @@ export class DatabaseListView implements DatabaseViewRenderer {
57
57
  }
58
58
  } else {
59
59
  wrapper.setAttribute('role', 'list');
60
- wrapper.setAttribute('aria-label', 'List view');
60
+ wrapper.setAttribute('aria-label', this.i18n.t('tools.database.listView'));
61
61
 
62
62
  for (const row of this.rows) {
63
63
  const rowEl = this.createRowElement(row);