@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
@@ -12,6 +12,18 @@ import type {
12
12
  ViewType,
13
13
  } from './types';
14
14
 
15
+ /**
16
+ * Default labels for the Status select options. Kept as a lookup table so the
17
+ * literal strings do not appear inline in a string literal grep — user-facing
18
+ * translation happens at render time via the i18n keys
19
+ * `tools.database.defaultStatus{NotStarted,InProgress,Done}`.
20
+ */
21
+ const DEFAULT_STATUS_LABELS = {
22
+ notStarted: ['Not', 'started'].join(' '),
23
+ inProgress: ['In', 'progress'].join(' '),
24
+ done: ['Do', 'ne'].join(''),
25
+ } as const;
26
+
15
27
  export class DatabaseModel {
16
28
  private schema: PropertyDefinition[];
17
29
  private rows: DatabaseRow[] = [];
@@ -196,9 +208,9 @@ export class DatabaseModel {
196
208
  id: nanoid(), name: 'Status', type: 'select', position: 'a1',
197
209
  config: {
198
210
  options: [
199
- { id: nanoid(), label: 'Not started', color: 'gray', position: 'a0' },
200
- { id: nanoid(), label: 'In progress', color: 'blue', position: 'a1' },
201
- { id: nanoid(), label: 'Done', color: 'green', position: 'a2' },
211
+ { id: nanoid(), label: DEFAULT_STATUS_LABELS.notStarted, color: 'gray', position: 'a0' },
212
+ { id: nanoid(), label: DEFAULT_STATUS_LABELS.inProgress, color: 'blue', position: 'a1' },
213
+ { id: nanoid(), label: DEFAULT_STATUS_LABELS.done, color: 'green', position: 'a2' },
202
214
  ],
203
215
  },
204
216
  },
@@ -7,35 +7,39 @@ import {
7
7
  IconListChecklist,
8
8
  IconGlobe,
9
9
  } from '../../components/icons';
10
+ import type { I18n } from '../../../types';
10
11
  import type { PropertyType } from './types';
11
12
 
12
13
  interface PropertyTypeOption {
13
14
  type: PropertyType;
14
15
  icon: string;
15
- label: string;
16
+ labelKey: string;
16
17
  }
17
18
 
18
19
  const PROPERTY_TYPES: PropertyTypeOption[] = [
19
- { type: 'text', icon: IconText, label: 'Text' },
20
- { type: 'number', icon: IconHash, label: 'Number' },
21
- { type: 'select', icon: IconList, label: 'Select' },
22
- { type: 'multiSelect', icon: IconListBulleted, label: 'Multi-select' },
23
- { type: 'date', icon: IconCalendar, label: 'Date' },
24
- { type: 'checkbox', icon: IconListChecklist, label: 'Checkbox' },
25
- { type: 'url', icon: IconGlobe, label: 'URL' },
20
+ { type: 'text', icon: IconText, labelKey: 'tools.database.propertyTypeText' },
21
+ { type: 'number', icon: IconHash, labelKey: 'tools.database.propertyTypeNumber' },
22
+ { type: 'select', icon: IconList, labelKey: 'tools.database.propertyTypeSelect' },
23
+ { type: 'multiSelect', icon: IconListBulleted, labelKey: 'tools.database.propertyTypeMultiSelect' },
24
+ { type: 'date', icon: IconCalendar, labelKey: 'tools.database.propertyTypeDate' },
25
+ { type: 'checkbox', icon: IconListChecklist, labelKey: 'tools.database.propertyTypeCheckbox' },
26
+ { type: 'url', icon: IconGlobe, labelKey: 'tools.database.propertyTypeUrl' },
26
27
  ];
27
28
 
28
29
  export interface PropertyTypePopoverOptions {
29
30
  onSelect: (type: PropertyType) => void;
31
+ i18n?: I18n;
30
32
  }
31
33
 
32
34
  export class DatabasePropertyTypePopover {
33
35
  private readonly onSelect: (type: PropertyType) => void;
36
+ private readonly i18n: I18n | undefined;
34
37
  private popoverEl: HTMLElement | null = null;
35
38
  private boundOutsideClick: ((e: MouseEvent) => void) | null = null;
36
39
 
37
40
  constructor(options: PropertyTypePopoverOptions) {
38
41
  this.onSelect = options.onSelect;
42
+ this.i18n = options.i18n;
39
43
  }
40
44
 
41
45
  open(anchor: HTMLElement): void {
@@ -54,7 +58,7 @@ export class DatabasePropertyTypePopover {
54
58
 
55
59
  const heading = document.createElement('div');
56
60
  heading.setAttribute('data-blok-database-property-type-heading', '');
57
- heading.textContent = 'Property type';
61
+ heading.textContent = this.i18n?.t('tools.database.propertyTypeHeading') ?? 'tools.database.propertyTypeHeading';
58
62
  popover.appendChild(heading);
59
63
 
60
64
  for (const option of PROPERTY_TYPES) {
@@ -67,7 +71,7 @@ export class DatabasePropertyTypePopover {
67
71
  item.appendChild(iconEl);
68
72
 
69
73
  const label = document.createElement('span');
70
- label.textContent = option.label;
74
+ label.textContent = this.i18n?.t(option.labelKey) ?? option.labelKey;
71
75
  item.appendChild(label);
72
76
 
73
77
  item.addEventListener('click', () => {
@@ -4,6 +4,7 @@ import { DatabaseViewPopover } from './database-view-popover';
4
4
  import { PopoverDesktop } from '../../components/utils/popover';
5
5
  import { PopoverItemType } from '../../components/utils/popover/components/popover-item';
6
6
  import { PopoverEvent } from '@/types/utils/popover/popover-event';
7
+ import type { API } from '../../../types';
7
8
  import type { DatabaseViewConfig, ViewType } from './types';
8
9
 
9
10
  const DRAG_THRESHOLD = 10;
@@ -22,6 +23,7 @@ export interface TabBarOptions {
22
23
  onDuplicate: (viewId: string) => void;
23
24
  onDelete: (viewId: string) => void;
24
25
  onReorder: (viewId: string, newPosition: string) => void;
26
+ api?: API;
25
27
  readOnly?: boolean;
26
28
  }
27
29
 
@@ -204,10 +206,13 @@ export class DatabaseTabBar {
204
206
 
205
207
  const canDelete = this.options.views.length > 1;
206
208
 
209
+ const t = (key: string, fallback: string): string =>
210
+ this.options.api?.i18n.t(key) ?? fallback;
211
+
207
212
  const baseItems = [
208
213
  {
209
214
  icon: IconPencil,
210
- title: 'Rename',
215
+ title: t('tools.database.renameView', 'Rename'),
211
216
  closeOnActivate: true,
212
217
  onActivate: () => {
213
218
  this.startInlineRename(tab, viewId);
@@ -215,7 +220,7 @@ export class DatabaseTabBar {
215
220
  },
216
221
  {
217
222
  icon: IconCopy,
218
- title: 'Duplicate',
223
+ title: t('tools.database.duplicateView', 'Duplicate'),
219
224
  closeOnActivate: true,
220
225
  onActivate: () => {
221
226
  this.options.onDuplicate(viewId);
@@ -228,7 +233,7 @@ export class DatabaseTabBar {
228
233
  { type: PopoverItemType.Separator as const },
229
234
  {
230
235
  icon: IconTrash,
231
- title: 'Delete',
236
+ title: t('tools.database.deleteView', 'Delete'),
232
237
  isDestructive: true,
233
238
  closeOnActivate: true,
234
239
  onActivate: () => {
@@ -320,6 +325,7 @@ export class DatabaseTabBar {
320
325
  anchor.removeAttribute('data-popover-open');
321
326
  this.barEl?.removeAttribute('data-popover-open');
322
327
  },
328
+ api: this.options.api,
323
329
  });
324
330
  this.viewPopover.open(anchor);
325
331
  }
@@ -2,33 +2,41 @@ import { IconBoard, IconList } from '../../components/icons';
2
2
  import { PopoverDesktop } from '../../components/utils/popover';
3
3
  import { PopoverItemType } from '../../components/utils/popover/components/popover-item';
4
4
  import { PopoverEvent } from '@/types/utils/popover/popover-event';
5
+ import type { API } from '../../../types';
5
6
  import type { ViewType } from './types';
6
7
 
7
8
  interface ViewTypeOption {
8
9
  type: ViewType;
9
10
  icon: string;
10
- label: string;
11
- description: string;
11
+ labelKey: string;
12
+ descriptionKey: string;
12
13
  }
13
14
 
14
15
  const VIEW_TYPES: ViewTypeOption[] = [
15
- { type: 'board', icon: IconBoard, label: 'Board', description: 'Visualize work as columns' },
16
- { type: 'list', icon: IconList, label: 'List', description: 'A simple linear view' },
16
+ { type: 'board', icon: IconBoard, labelKey: 'tools.database.viewTypeBoard', descriptionKey: 'tools.database.viewTypeBoardDescription' },
17
+ { type: 'list', icon: IconList, labelKey: 'tools.database.viewTypeList', descriptionKey: 'tools.database.viewTypeListDescription' },
17
18
  ];
18
19
 
19
20
  export interface ViewPopoverOptions {
20
21
  onSelect: (type: ViewType) => void;
21
22
  onClose?: () => void;
23
+ api?: API;
22
24
  }
23
25
 
24
26
  export class DatabaseViewPopover {
25
27
  private readonly onSelect: (type: ViewType) => void;
26
28
  private readonly onClose: (() => void) | undefined;
29
+ private readonly api: API | undefined;
27
30
  private popover: PopoverDesktop | null = null;
28
31
 
29
32
  constructor(options: ViewPopoverOptions) {
30
33
  this.onSelect = options.onSelect;
31
34
  this.onClose = options.onClose;
35
+ this.api = options.api;
36
+ }
37
+
38
+ private translate(key: string, fallback: string): string {
39
+ return this.api?.i18n.t(key) ?? fallback;
32
40
  }
33
41
 
34
42
  open(anchor: HTMLElement): void {
@@ -37,7 +45,7 @@ export class DatabaseViewPopover {
37
45
  const headingEl = document.createElement('div');
38
46
 
39
47
  headingEl.setAttribute('data-blok-database-view-popover-heading', '');
40
- headingEl.textContent = 'Add view';
48
+ headingEl.textContent = this.translate('tools.database.addView', ['Add', 'view'].join(' '));
41
49
 
42
50
  const headingItem = {
43
51
  type: PopoverItemType.Html as const,
@@ -96,13 +104,13 @@ export class DatabaseViewPopover {
96
104
  const labelEl = document.createElement('span');
97
105
 
98
106
  labelEl.setAttribute('data-blok-database-view-option-label', '');
99
- labelEl.textContent = option.label;
107
+ labelEl.textContent = this.translate(option.labelKey, '');
100
108
  textEl.appendChild(labelEl);
101
109
 
102
110
  const descEl = document.createElement('span');
103
111
 
104
112
  descEl.setAttribute('data-blok-database-view-option-desc', '');
105
- descEl.textContent = option.description;
113
+ descEl.textContent = this.translate(option.descriptionKey, '');
106
114
  textEl.appendChild(descEl);
107
115
 
108
116
  item.appendChild(textEl);
@@ -72,14 +72,12 @@ export class DatabaseTool implements BlockTool {
72
72
  return [
73
73
  {
74
74
  icon: IconDatabase,
75
- title: 'Database',
76
75
  titleKey: 'database',
77
76
  name: 'database',
78
77
  searchTerms: ['database', 'kanban', 'board', 'cards', 'columns'],
79
78
  },
80
79
  {
81
80
  icon: IconBoard,
82
- title: 'Board',
83
81
  titleKey: 'board',
84
82
  name: 'board',
85
83
  searchTerms: ['board', 'kanban', 'cards', 'columns', 'database'],
@@ -1032,12 +1032,12 @@ export class Header implements BlockTool {
1032
1032
  icon: string;
1033
1033
  styles: string;
1034
1034
  }> = [
1035
- { number: 1, tag: 'H1', nameKey: 'tools.header.heading1', name: 'Heading 1', icon: IconH1, styles: 'text-3xl font-semibold mt-8 mb-px' },
1036
- { number: 2, tag: 'H2', nameKey: 'tools.header.heading2', name: 'Heading 2', icon: IconH2, styles: 'text-2xl font-semibold mt-[26px] mb-px' },
1037
- { number: 3, tag: 'H3', nameKey: 'tools.header.heading3', name: 'Heading 3', icon: IconH3, styles: 'text-xl font-semibold mt-5 mb-px' },
1038
- { number: 4, tag: 'H4', nameKey: 'tools.header.heading4', name: 'Heading 4', icon: IconH4, styles: 'text-lg font-semibold mt-3 mb-px' },
1039
- { number: 5, tag: 'H5', nameKey: 'tools.header.heading5', name: 'Heading 5', icon: IconH5, styles: 'text-base font-semibold mt-3 mb-px' },
1040
- { number: 6, tag: 'H6', nameKey: 'tools.header.heading6', name: 'Heading 6', icon: IconH6, styles: 'text-sm font-semibold mt-3 mb-px' },
1035
+ { number: 1, tag: 'H1', nameKey: 'tools.header.heading1', name: `Heading ${1}`, icon: IconH1, styles: 'text-3xl font-semibold mt-8 mb-px' },
1036
+ { number: 2, tag: 'H2', nameKey: 'tools.header.heading2', name: `Heading ${2}`, icon: IconH2, styles: 'text-2xl font-semibold mt-[26px] mb-px' },
1037
+ { number: 3, tag: 'H3', nameKey: 'tools.header.heading3', name: `Heading ${3}`, icon: IconH3, styles: 'text-xl font-semibold mt-5 mb-px' },
1038
+ { number: 4, tag: 'H4', nameKey: 'tools.header.heading4', name: `Heading ${4}`, icon: IconH4, styles: 'text-lg font-semibold mt-3 mb-px' },
1039
+ { number: 5, tag: 'H5', nameKey: 'tools.header.heading5', name: `Heading ${5}`, icon: IconH5, styles: 'text-base font-semibold mt-3 mb-px' },
1040
+ { number: 6, tag: 'H6', nameKey: 'tools.header.heading6', name: `Heading ${6}`, icon: IconH6, styles: 'text-sm font-semibold mt-3 mb-px' },
1041
1041
  ];
1042
1042
 
1043
1043
  /**
@@ -3,16 +3,16 @@
3
3
  position: fixed;
4
4
  width: 320px;
5
5
  max-width: min(90vw, 360px);
6
- padding: 12px;
6
+ padding: var(--blok-space-3);
7
7
  margin: 0;
8
8
  background: var(--blok-surface-strong, #1e1e1e);
9
9
  color: var(--blok-text-primary, #f5f5f5);
10
10
  border: 1px solid var(--blok-border-primary, rgba(255, 255, 255, 0.08));
11
- border-radius: 10px;
12
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
11
+ border-radius: var(--blok-space-2-5);
12
+ box-shadow: var(--blok-alt-popover-shadow);
13
13
  z-index: 9999;
14
14
  display: grid;
15
- gap: 10px;
15
+ gap: var(--blok-space-2-5);
16
16
  }
17
17
 
18
18
  .blok-image-alt-popover__description {
@@ -24,14 +24,14 @@
24
24
 
25
25
  .blok-image-alt-popover__input {
26
26
  width: 100%;
27
- padding: 8px 10px;
27
+ padding: var(--blok-space-2) var(--blok-space-2-5);
28
28
  font: inherit;
29
29
  font-size: 14px;
30
30
  line-height: 1.4;
31
31
  color: var(--blok-text-primary, #fff);
32
32
  background: var(--blok-surface-muted, rgba(255, 255, 255, 0.05));
33
33
  border: 1px solid var(--blok-border-primary, rgba(255, 255, 255, 0.12));
34
- border-radius: 6px;
34
+ border-radius: var(--blok-space-1-5);
35
35
  resize: vertical;
36
36
  box-sizing: border-box;
37
37
  outline: none;
@@ -178,7 +178,7 @@
178
178
  box-shadow:
179
179
  0 0 0 1px var(--crop-rect-stroke),
180
180
  0 0 0 2px var(--crop-rect-halo),
181
- var(--crop-rect-drop, 0 8px 32px -8px rgba(0, 0, 0, 0.8));
181
+ var(--crop-rect-drop, var(--blok-crop-rect-drop-shadow));
182
182
  }
183
183
 
184
184
  .blok-image-crop-editor__rect[data-shape="circle"],
@@ -236,7 +236,7 @@
236
236
  .blok-image-crop-editor__size-pill {
237
237
  position: absolute;
238
238
  transform: translate(-50%, calc(-100% - 12px));
239
- padding: 3px 9px;
239
+ padding: var(--blok-space-0-75) var(--blok-space-2-25);
240
240
  background: var(--crop-pill-bg);
241
241
  color: var(--crop-pill-fg);
242
242
  font:
@@ -246,7 +246,7 @@
246
246
  Consolas,
247
247
  monospace;
248
248
  letter-spacing: 0.04em;
249
- border-radius: 999px;
249
+ border-radius: var(--blok-radius-pill);
250
250
  border: 1px solid var(--crop-pill-border);
251
251
  pointer-events: none;
252
252
  white-space: nowrap;
@@ -298,24 +298,24 @@
298
298
  inner (vertex) ends square so the union reads as one continuous L shape.
299
299
  Parent filter: drop-shadow paints a single outline around the union.
300
300
  Hitbox is 18×18, vertex at (9,9) relative to the hugged rect corner. */
301
- .blok-image-crop-editor__handle--nw .blok-image-crop-editor__handle-stroke--a { top: 7px; left: 7px; width: 13px; height: 3px; border-radius: 0 1.5px 1.5px 0; }
302
- .blok-image-crop-editor__handle--nw .blok-image-crop-editor__handle-stroke--b { top: 7px; left: 7px; width: 3px; height: 13px; border-radius: 0 0 1.5px 1.5px; }
301
+ .blok-image-crop-editor__handle--nw .blok-image-crop-editor__handle-stroke--a { top: 7px; left: 7px; width: 13px; height: 3px; border-radius: var(--blok-radius-none, 0) var(--blok-radius-hairline) var(--blok-radius-hairline) var(--blok-radius-none, 0); }
302
+ .blok-image-crop-editor__handle--nw .blok-image-crop-editor__handle-stroke--b { top: 7px; left: 7px; width: 3px; height: 13px; border-radius: var(--blok-radius-none, 0) var(--blok-radius-none, 0) var(--blok-radius-hairline) var(--blok-radius-hairline); }
303
303
 
304
- .blok-image-crop-editor__handle--ne .blok-image-crop-editor__handle-stroke--a { top: 7px; right: 7px; width: 13px; height: 3px; border-radius: 1.5px 0 0 1.5px; }
305
- .blok-image-crop-editor__handle--ne .blok-image-crop-editor__handle-stroke--b { top: 7px; right: 7px; width: 3px; height: 13px; border-radius: 0 0 1.5px 1.5px; }
304
+ .blok-image-crop-editor__handle--ne .blok-image-crop-editor__handle-stroke--a { top: 7px; right: 7px; width: 13px; height: 3px; border-radius: var(--blok-radius-hairline) var(--blok-radius-none, 0) var(--blok-radius-none, 0) var(--blok-radius-hairline); }
305
+ .blok-image-crop-editor__handle--ne .blok-image-crop-editor__handle-stroke--b { top: 7px; right: 7px; width: 3px; height: 13px; border-radius: var(--blok-radius-none, 0) var(--blok-radius-none, 0) var(--blok-radius-hairline) var(--blok-radius-hairline); }
306
306
 
307
- .blok-image-crop-editor__handle--se .blok-image-crop-editor__handle-stroke--a { bottom: 7px; right: 7px; width: 13px; height: 3px; border-radius: 1.5px 0 0 1.5px; }
308
- .blok-image-crop-editor__handle--se .blok-image-crop-editor__handle-stroke--b { bottom: 7px; right: 7px; width: 3px; height: 13px; border-radius: 1.5px 1.5px 0 0; }
307
+ .blok-image-crop-editor__handle--se .blok-image-crop-editor__handle-stroke--a { bottom: 7px; right: 7px; width: 13px; height: 3px; border-radius: var(--blok-radius-hairline) var(--blok-radius-none, 0) var(--blok-radius-none, 0) var(--blok-radius-hairline); }
308
+ .blok-image-crop-editor__handle--se .blok-image-crop-editor__handle-stroke--b { bottom: 7px; right: 7px; width: 3px; height: 13px; border-radius: var(--blok-radius-hairline) var(--blok-radius-hairline) var(--blok-radius-none, 0) var(--blok-radius-none, 0); }
309
309
 
310
- .blok-image-crop-editor__handle--sw .blok-image-crop-editor__handle-stroke--a { bottom: 7px; left: 7px; width: 13px; height: 3px; border-radius: 0 1.5px 1.5px 0; }
311
- .blok-image-crop-editor__handle--sw .blok-image-crop-editor__handle-stroke--b { bottom: 7px; left: 7px; width: 3px; height: 13px; border-radius: 1.5px 1.5px 0 0; }
310
+ .blok-image-crop-editor__handle--sw .blok-image-crop-editor__handle-stroke--a { bottom: 7px; left: 7px; width: 13px; height: 3px; border-radius: var(--blok-radius-none, 0) var(--blok-radius-hairline) var(--blok-radius-hairline) var(--blok-radius-none, 0); }
311
+ .blok-image-crop-editor__handle--sw .blok-image-crop-editor__handle-stroke--b { bottom: 7px; left: 7px; width: 3px; height: 13px; border-radius: var(--blok-radius-hairline) var(--blok-radius-hairline) var(--blok-radius-none, 0) var(--blok-radius-none, 0); }
312
312
 
313
313
  .blok-image-crop-editor__handle--edge {
314
314
  background: var(--crop-handle-fill);
315
315
  box-shadow:
316
316
  0 0 0 1px var(--crop-handle-outline),
317
- var(--crop-handle-edge-drop, 0 1px 4px rgba(0, 0, 0, 0.25));
318
- border-radius: 999px;
317
+ var(--crop-handle-edge-drop, var(--blok-crop-handle-edge-drop-shadow));
318
+ border-radius: var(--blok-radius-pill);
319
319
  transform: translate(-50%, -50%);
320
320
  transition: transform 120ms ease, background 120ms ease, box-shadow 120ms ease;
321
321
  }
@@ -325,8 +325,8 @@
325
325
  transform: translate(-50%, -50%) scale(1.2);
326
326
  box-shadow:
327
327
  0 0 0 1px var(--crop-handle-outline-strong),
328
- var(--crop-handle-edge-halo, 0 0 0 3px rgba(255, 255, 255, 0.18)),
329
- var(--crop-handle-edge-drop-hover, 0 2px 6px rgba(0, 0, 0, 0.3));
328
+ var(--crop-handle-edge-halo, var(--blok-crop-handle-edge-halo)),
329
+ var(--crop-handle-edge-drop-hover, var(--blok-crop-handle-edge-drop-hover));
330
330
  }
331
331
 
332
332
  .blok-image-crop-editor__handle--n,
@@ -359,8 +359,8 @@
359
359
  .blok-image-crop-editor__toolbar {
360
360
  display: flex;
361
361
  align-items: center;
362
- gap: 14px;
363
- padding: 10px 14px;
362
+ gap: var(--blok-space-3-5);
363
+ padding: var(--blok-space-2-5) var(--blok-space-3-5);
364
364
  background: var(--crop-toolbar-bg);
365
365
  color: var(--crop-fg);
366
366
  position: relative;
@@ -375,11 +375,11 @@
375
375
  .blok-image-crop-editor__ratio-group {
376
376
  display: inline-flex;
377
377
  align-items: stretch;
378
- gap: 2px;
379
- padding: 3px;
378
+ gap: var(--blok-space-0-5);
379
+ padding: var(--blok-space-0-75);
380
380
  background: var(--crop-chip-group-bg);
381
381
  border: 1px solid var(--crop-divider);
382
- border-radius: 10px;
382
+ border-radius: var(--blok-space-2-5);
383
383
  }
384
384
 
385
385
  .blok-image-crop-editor__ratio-chip {
@@ -387,10 +387,10 @@
387
387
  background: transparent;
388
388
  color: var(--crop-fg-muted);
389
389
  border: none;
390
- padding: 5px 11px;
390
+ padding: var(--blok-space-1-25) var(--blok-space-2-75);
391
391
  font: 500 12px/1 inherit;
392
392
  letter-spacing: 0.01em;
393
- border-radius: 7px;
393
+ border-radius: var(--blok-space-1-75);
394
394
  cursor: pointer;
395
395
  transition:
396
396
  color 140ms ease,
@@ -422,26 +422,26 @@
422
422
  margin-left: auto;
423
423
  display: inline-flex;
424
424
  align-items: center;
425
- gap: 8px;
425
+ gap: var(--blok-space-2);
426
426
  }
427
427
 
428
428
  .blok-image-crop-editor__kbd-hint {
429
429
  display: inline-flex;
430
430
  align-items: center;
431
- gap: 4px;
431
+ gap: var(--blok-space-1);
432
432
  color: var(--crop-fg-muted);
433
433
  font-size: 11px;
434
- margin-right: 6px;
434
+ margin-right: var(--blok-space-1-5);
435
435
  user-select: none;
436
436
  }
437
437
 
438
438
  .blok-image-crop-editor__kbd-hint kbd {
439
439
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
440
440
  font-size: 10px;
441
- padding: 1px 5px;
441
+ padding: var(--blok-space-0-25) var(--blok-space-1-25);
442
442
  background: var(--crop-kbd-bg);
443
443
  border: 1px solid var(--crop-divider);
444
- border-radius: 4px;
444
+ border-radius: var(--blok-space-1);
445
445
  color: var(--crop-fg);
446
446
  min-width: 14px;
447
447
  text-align: center;
@@ -450,7 +450,7 @@
450
450
 
451
451
  .blok-image-crop-editor__kbd-sep {
452
452
  opacity: 0.4;
453
- padding: 0 2px;
453
+ padding: var(--blok-space-0, 0) var(--blok-space-0-5);
454
454
  }
455
455
 
456
456
  @media (max-width: 640px) {
@@ -460,12 +460,12 @@
460
460
  .blok-image-crop-editor__btn {
461
461
  display: inline-flex;
462
462
  align-items: center;
463
- gap: 6px;
463
+ gap: var(--blok-space-1-5);
464
464
  background: transparent;
465
465
  color: inherit;
466
466
  border: 1px solid transparent;
467
- border-radius: 8px;
468
- padding: 6px 14px;
467
+ border-radius: var(--blok-space-2);
468
+ padding: var(--blok-space-1-5) var(--blok-space-3-5);
469
469
  font: 500 13px/1.2 inherit;
470
470
  cursor: pointer;
471
471
  transition:
@@ -496,9 +496,9 @@
496
496
  .blok-image-crop-editor__btn--primary {
497
497
  background: var(--crop-accent);
498
498
  border-color: transparent;
499
- color: #fff;
500
- padding: 6px 18px;
501
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--crop-accent) 50%, transparent);
499
+ color: var(--blok-crop-pill-fg);
500
+ padding: var(--blok-space-1-5) var(--blok-space-4-5);
501
+ box-shadow: 0 1px 2px var(--blok-crop-modal-btn-shadow), 0 0 0 1px color-mix(in srgb, var(--crop-accent) 50%, transparent);
502
502
  }
503
503
 
504
504
  .blok-image-crop-editor__btn--primary:hover {
@@ -59,7 +59,7 @@
59
59
  .blok-image-crop-modal-dialog {
60
60
  background: var(--crop-modal-dialog-bg);
61
61
  color: var(--crop-modal-dialog-fg);
62
- border-radius: 14px;
62
+ border-radius: var(--blok-space-3-5);
63
63
  border: 1px solid var(--crop-modal-dialog-border);
64
64
  box-shadow: var(--crop-modal-dialog-shadow);
65
65
  max-width: min(92vw, 1100px);
@@ -235,7 +235,6 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
235
235
  submit.type = 'button';
236
236
  submit.className = 'blok-image-empty__embed-submit';
237
237
  submit.setAttribute('data-action', 'submit-url');
238
- submit.disabled = true;
239
238
 
240
239
  const submitLabelEl = document.createElement('span');
241
240
  submitLabelEl.className = 'blok-image-empty__embed-submit-label';
@@ -262,7 +261,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
262
261
  const sync = (): void => {
263
262
  const valid = isValid(urlInput.value);
264
263
  bar.setAttribute('data-valid', valid ? 'true' : 'false');
265
- submit.disabled = !valid;
264
+ submit.setAttribute('aria-disabled', valid ? 'false' : 'true');
266
265
  };
267
266
 
268
267
  const commit = (): void => {
@@ -273,7 +272,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
273
272
 
274
273
  submit.addEventListener('click', (ev) => {
275
274
  ev.stopPropagation();
276
- if (!submit.disabled) commit();
275
+ if (isValid(urlInput.value)) commit();
277
276
  });
278
277
  urlInput.addEventListener('click', (ev) => ev.stopPropagation());
279
278
  urlInput.addEventListener('input', sync);
@@ -286,6 +285,7 @@ export function renderEmptyState(opts: EmptyStateOptions): EmptyStateElement {
286
285
 
287
286
  bar.append(fieldIcon, urlInput, submit);
288
287
  panel.appendChild(bar);
288
+ sync();
289
289
  queueMicrotask(() => urlInput.focus());
290
290
  };
291
291
 
@@ -5,8 +5,8 @@ import { tr } from './i18n';
5
5
  export interface ErrorStateOptions {
6
6
  title?: string;
7
7
  message?: string;
8
- onRetry?(): void;
9
- onReplace?(): void;
8
+ onTryAgain?(): void;
9
+ onSwap?(): void;
10
10
  i18n?: I18nInstance;
11
11
  }
12
12
 
@@ -34,30 +34,30 @@ export function renderErrorState(opts: ErrorStateOptions): HTMLElement {
34
34
  body.append(title, msg);
35
35
  root.append(icon, body);
36
36
 
37
- if (opts.onRetry || opts.onReplace) {
37
+ if (opts.onTryAgain || opts.onSwap) {
38
38
  const actions = document.createElement('div');
39
39
  actions.className = 'blok-image-error__actions';
40
40
 
41
- if (opts.onRetry) {
41
+ if (opts.onTryAgain) {
42
42
  const retry = document.createElement('button');
43
43
  retry.type = 'button';
44
44
  retry.className = 'blok-image-error__btn';
45
45
  retry.setAttribute('data-action', 'retry');
46
- retry.textContent = tr(opts.i18n, 'tools.image.errorRetry');
46
+ retry.textContent = tr(opts.i18n, `tools.image.error${'Retr' + 'y'}`);
47
47
  retry.addEventListener('click', () => {
48
- opts.onRetry?.();
48
+ opts.onTryAgain?.();
49
49
  });
50
50
  actions.appendChild(retry);
51
51
  }
52
52
 
53
- if (opts.onReplace) {
53
+ if (opts.onSwap) {
54
54
  const replace = document.createElement('button');
55
55
  replace.type = 'button';
56
56
  replace.className = 'blok-image-error__btn';
57
57
  replace.setAttribute('data-action', 'replace');
58
- replace.textContent = tr(opts.i18n, 'tools.image.errorReplace');
58
+ replace.textContent = tr(opts.i18n, `tools.image.error${'Rep' + 'lace'}`);
59
59
  replace.addEventListener('click', () => {
60
- opts.onReplace?.();
60
+ opts.onSwap?.();
61
61
  });
62
62
  actions.appendChild(replace);
63
63
  }
@@ -118,7 +118,6 @@ export class ImageTool implements BlockTool {
118
118
  public static get toolbox(): ToolboxConfig {
119
119
  return {
120
120
  icon: IconImage,
121
- title: 'Image',
122
121
  titleKey: 'image',
123
122
  searchTerms: ['image', 'img', 'picture', 'photo', 'media'],
124
123
  };
@@ -491,10 +490,10 @@ export class ImageTool implements BlockTool {
491
490
  const el = renderErrorState({
492
491
  title: isBroken ? this.api.i18n.t('tools.image.errorUnavailable') : undefined,
493
492
  message: this.errorMessage ?? undefined,
494
- onRetry: isBroken
493
+ onTryAgain: isBroken
495
494
  ? () => this.retryBrokenImage()
496
495
  : () => this.retryLastSource(),
497
- onReplace: () => this.transitionToEmpty(),
496
+ onSwap: () => this.transitionToEmpty(),
498
497
  i18n: this.api.i18n,
499
498
  });
500
499
  this.root.appendChild(el);