@jackuait/blok 0.19.0 → 0.19.2

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 (35) hide show
  1. package/dist/blok.cjs +1 -1
  2. package/dist/blok.iife.js +2 -2
  3. package/dist/blok.mjs +2 -2
  4. package/dist/blok.umd.js +2 -2
  5. package/dist/chunks/{blok-BosfCfPX.cjs → blok-C8_pY9Bg.cjs} +3 -3
  6. package/dist/chunks/{blok-CXB-exXU.mjs → blok-DCc23rzA.mjs} +4 -5
  7. package/dist/chunks/{constants-BmCvHbkL.mjs → constants-C3-Lh6Fs.mjs} +1 -1
  8. package/dist/chunks/{constants-ChsAFZ8F.cjs → constants-R70ypRAK.cjs} +1 -1
  9. package/dist/chunks/{tools-DmEzyrn3.cjs → tools-DcbePIHF.cjs} +2 -2
  10. package/dist/chunks/{tools-BdoYHyV0.mjs → tools-fEI-EVng.mjs} +65 -34
  11. package/dist/full.cjs +1 -1
  12. package/dist/full.mjs +3 -3
  13. package/dist/react.cjs +1 -1
  14. package/dist/react.mjs +1 -1
  15. package/dist/tools.cjs +1 -1
  16. package/dist/tools.mjs +2 -2
  17. package/package.json +3 -3
  18. package/src/blok.ts +7 -3
  19. package/src/components/modules/api/blocks.ts +1 -1
  20. package/src/components/modules/blockManager/blockManager.ts +2 -2
  21. package/src/components/modules/yjs/serializer.ts +1 -1
  22. package/src/components/utils/data-model-transform.ts +15 -15
  23. package/src/components/utils/popover/popover-desktop.ts +6 -3
  24. package/src/components/utils/tooltip.ts +5 -38
  25. package/src/markdown/markdown-handler.ts +1 -2
  26. package/src/styles/audio.css +23 -3
  27. package/src/tools/audio/index.ts +76 -21
  28. package/src/tools/audio/ui.ts +6 -1
  29. package/types/api/tooltip.d.ts +40 -1
  30. package/types/data-formats/output-data.d.ts +5 -2
  31. package/types/index.d.ts +28 -5
  32. package/types/tools/database.d.ts +1 -1
  33. package/types/tools/header.d.ts +2 -1
  34. package/types/tools/list.d.ts +2 -1
  35. package/types/utils/popover/popover.d.ts +42 -1
@@ -52,6 +52,8 @@ export class AudioTool implements BlockTool {
52
52
  private controlsHandle: ControlsHandle | null = null;
53
53
  private waveformHandle: WaveformHandle | null = null;
54
54
  private destroyed = false;
55
+ /** Pending fallback timer for the caption collapse-out animation. */
56
+ private captionExitTimer: ReturnType<typeof setTimeout> | null = null;
55
57
 
56
58
  constructor(options: BlockToolConstructorOptions<AudioData, AudioConfig>) {
57
59
  this.api = options.api;
@@ -199,6 +201,7 @@ export class AudioTool implements BlockTool {
199
201
 
200
202
  public removed(): void {
201
203
  this.destroyed = true;
204
+ if (this.captionExitTimer) { clearTimeout(this.captionExitTimer); this.captionExitTimer = null; }
202
205
  this.controlsHandle?.destroy();
203
206
  this.controlsHandle = null;
204
207
  this.waveformHandle?.destroy();
@@ -299,6 +302,7 @@ export class AudioTool implements BlockTool {
299
302
 
300
303
  private renderState(): void {
301
304
  if (!this.root) return;
305
+ if (this.captionExitTimer) { clearTimeout(this.captionExitTimer); this.captionExitTimer = null; }
302
306
  this.controlsHandle?.destroy();
303
307
  this.controlsHandle = null;
304
308
  this.waveformHandle?.destroy();
@@ -393,14 +397,9 @@ export class AudioTool implements BlockTool {
393
397
  body.appendChild(this.controlsHandle.element);
394
398
 
395
399
  // Caption row — outside body, below the card
396
- const placeholder = this.config.captionPlaceholder ?? DEFAULT_CAPTION_PLACEHOLDER;
397
400
  const captionVisible = this.data.captionVisible !== false;
398
- if (captionVisible || !this.readOnly) {
399
- figure.appendChild(renderCaptionRow({
400
- value: this.data.caption ?? '',
401
- placeholder,
402
- editable: !this.readOnly,
403
- }));
401
+ if (captionVisible) {
402
+ figure.appendChild(this.createCaptionRow());
404
403
  }
405
404
 
406
405
  // Wire title/artist blur handlers
@@ -419,21 +418,32 @@ export class AudioTool implements BlockTool {
419
418
  }
420
419
  });
421
420
 
422
- // Wire caption blur handler
423
- const captionEl = figure.querySelector<HTMLElement>('[data-role="audio-caption"]');
424
- if (captionEl) {
425
- captionEl.addEventListener('blur', () => {
426
- const next = captionEl.textContent ?? '';
427
- if (next !== this.data.caption) {
428
- this.data.caption = next || undefined;
429
- this.block.dispatchChange();
430
- }
431
- });
432
- }
433
-
434
421
  this.root.appendChild(figure);
435
422
  }
436
423
 
424
+ /** Build a caption row (with its blur handler wired) ready to mount. */
425
+ private createCaptionRow(): HTMLElement {
426
+ const placeholder = this.config.captionPlaceholder ?? DEFAULT_CAPTION_PLACEHOLDER;
427
+ const row = renderCaptionRow({
428
+ value: this.data.caption ?? '',
429
+ placeholder,
430
+ editable: !this.readOnly,
431
+ });
432
+ const captionEl = row.querySelector<HTMLElement>('[data-role="audio-caption"]');
433
+ captionEl?.addEventListener('blur', () => {
434
+ const next = captionEl.textContent ?? '';
435
+ if (next !== this.data.caption) {
436
+ this.data.caption = next || undefined;
437
+ this.block.dispatchChange();
438
+ }
439
+ });
440
+ return row;
441
+ }
442
+
443
+ private prefersReducedMotion(): boolean {
444
+ return globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
445
+ }
446
+
437
447
  private setAlignment(next: AudioAlignment): void {
438
448
  if (this.data.alignment === next) return;
439
449
  this.data.alignment = next;
@@ -442,9 +452,54 @@ export class AudioTool implements BlockTool {
442
452
  }
443
453
 
444
454
  private toggleCaption(): void {
445
- this.data.captionVisible = this.data.captionVisible === false;
455
+ const enabling = this.data.captionVisible === false;
456
+ this.data.captionVisible = enabling;
457
+ // Removing the caption clears its text too, so re-enabling starts fresh
458
+ // rather than resurrecting the old text.
459
+ if (!enabling) this.data.caption = undefined;
446
460
  this.block.dispatchChange();
447
- this.renderState();
461
+
462
+ const figure = this.root?.querySelector<HTMLElement>('[data-role="audio-figure"]');
463
+ // No live card to animate (or motion is off) — fall back to an instant
464
+ // re-render so the caption snaps in/out.
465
+ if (!figure || this.prefersReducedMotion()) {
466
+ this.renderState();
467
+ return;
468
+ }
469
+ this.syncRootAttributes();
470
+ if (enabling) this.animateCaptionIn(figure);
471
+ else this.animateCaptionOut(figure);
472
+ }
473
+
474
+ private animateCaptionIn(figure: HTMLElement): void {
475
+ const existing = figure.querySelector<HTMLElement>('[data-role="audio-caption-row"]');
476
+ if (existing) {
477
+ // A collapse-out was mid-flight — cancel it and re-open in place.
478
+ if (this.captionExitTimer) { clearTimeout(this.captionExitTimer); this.captionExitTimer = null; }
479
+ existing.classList.remove('is-collapsed');
480
+ return;
481
+ }
482
+ const row = this.createCaptionRow();
483
+ row.classList.add('is-collapsed');
484
+ figure.appendChild(row);
485
+ // Force a reflow so the collapsed state is committed, then expand into view.
486
+ void row.offsetHeight;
487
+ row.classList.remove('is-collapsed');
488
+ }
489
+
490
+ private animateCaptionOut(figure: HTMLElement): void {
491
+ const row = figure.querySelector<HTMLElement>('[data-role="audio-caption-row"]');
492
+ if (!row) return;
493
+ row.classList.add('is-collapsed');
494
+ const finish = (): void => {
495
+ if (this.captionExitTimer) { clearTimeout(this.captionExitTimer); this.captionExitTimer = null; }
496
+ row.remove();
497
+ };
498
+ row.addEventListener('transitionend', (e) => {
499
+ if (e.propertyName === 'grid-template-rows') finish();
500
+ }, { once: true });
501
+ // Fallback in case transitionend never fires (display:none, interrupted, etc.).
502
+ this.captionExitTimer = setTimeout(finish, 360);
448
503
  }
449
504
 
450
505
  private toggleLoop(): void {
@@ -97,8 +97,13 @@ export function renderCaptionRow(opts: CaptionRowOptions): HTMLElement {
97
97
  const row = document.createElement('div');
98
98
  row.className = 'blok-audio-caption-row';
99
99
  row.setAttribute('data-role', 'audio-caption-row');
100
+ // Inner wrapper carries the visible layout (padding/border) so the row itself
101
+ // can collapse cleanly via grid-template-rows when toggled.
102
+ const inner = document.createElement('div');
103
+ inner.className = 'blok-audio-caption-row__inner';
100
104
  const cap = editableLine('audio-caption', opts.value, opts.editable, opts.placeholder);
101
105
  cap.className = 'blok-audio-caption';
102
- row.appendChild(cap);
106
+ inner.appendChild(cap);
107
+ row.appendChild(inner);
103
108
  return row;
104
109
  }
@@ -1,7 +1,46 @@
1
1
  /**
2
2
  * Tooltip API
3
3
  */
4
- import {TooltipContent, TooltipOptions} from '../../src/components/utils/tooltip';
4
+
5
+ /**
6
+ * Tooltip supported content
7
+ */
8
+ export type TooltipContent = HTMLElement | DocumentFragment | Node | string;
9
+
10
+ /**
11
+ * Base options interface for tooltips
12
+ */
13
+ export interface TooltipOptions {
14
+ /**
15
+ * Tooltip placement: top|bottom|left|right
16
+ */
17
+ placement?: string;
18
+
19
+ /**
20
+ * Tooltip top margin
21
+ */
22
+ marginTop?: number;
23
+
24
+ /**
25
+ * Tooltip left margin
26
+ */
27
+ marginLeft?: number;
28
+
29
+ /**
30
+ * Tooltip right margin
31
+ */
32
+ marginRight?: number;
33
+
34
+ /**
35
+ * Tooltip bottom margin
36
+ */
37
+ marginBottom?: number;
38
+
39
+ /**
40
+ * Timeout before showing
41
+ */
42
+ delay?: number;
43
+ }
5
44
 
6
45
  export interface Tooltip {
7
46
  /**
@@ -6,9 +6,12 @@ import { BlockId } from './block-id';
6
6
  * Output of one Tool
7
7
  *
8
8
  * @template Type - the string literal describing a tool type
9
- * @template Data - the structure describing a data object supported by the tool
9
+ * @template Data - the structure describing a data object supported by the tool.
10
+ * Defaults to `Record<string, unknown>` (matching {@link BlockToolData}) so
11
+ * reading saved block data is type-guarded: property access yields `unknown`,
12
+ * not `any`. Pass a concrete shape for fully-typed access.
10
13
  */
11
- export interface OutputBlockData<Type extends string = string, Data extends object = any> {
14
+ export interface OutputBlockData<Type extends string = string, Data extends object = Record<string, unknown>> {
12
15
  /**
13
16
  * Unique Id of the block
14
17
  */
package/types/index.d.ts CHANGED
@@ -5,12 +5,11 @@
5
5
  */
6
6
 
7
7
  import {
8
- Dictionary,
9
- DictValue,
10
8
  BlokConfig,
11
9
  I18nConfig,
12
10
  I18nDictionary,
13
11
  } from './configs';
12
+ import { InlineToolConstructable, InlineToolConstructorOptions } from './tools';
14
13
 
15
14
  import {
16
15
  Blocks,
@@ -81,8 +80,6 @@ export {
81
80
  LogLevels,
82
81
  ConversionConfig,
83
82
  I18nDictionary,
84
- Dictionary,
85
- DictValue,
86
83
  I18nConfig,
87
84
  UserInfo,
88
85
  } from './configs';
@@ -207,11 +204,37 @@ export function wrapLegacyInlineTool(
207
204
  }
208
205
  ): InlineToolConstructable;
209
206
 
207
+ /**
208
+ * The surface of a Blok instance that is guaranteed to exist synchronously,
209
+ * immediately after `new Blok()` and before `isReady` has resolved.
210
+ *
211
+ * Blok constructs its module APIs (`blocks`, `caret`, `history`, `readOnly`, …)
212
+ * asynchronously: they only become available once `isReady` resolves. Accessing
213
+ * them earlier returns `undefined` at runtime. Type a reference you hold during
214
+ * that window as `PendingBlok` so the not-yet-available members are unreachable,
215
+ * then await `isReady` to obtain the fully-initialized {@link Blok}:
216
+ *
217
+ * @example
218
+ * const pending: PendingBlok = new Blok(config);
219
+ * const editor = await pending.isReady; // editor is a ready Blok
220
+ * editor.blocks.render(data);
221
+ */
222
+ export interface PendingBlok {
223
+ /** Resolves with the fully-initialized Blok instance once core modules are ready. */
224
+ isReady: Promise<Blok>;
225
+ /** Destroy the instance. Safe to call before `isReady` resolves. */
226
+ destroy(): void;
227
+ /** Theme API, exposed immediately after construction. */
228
+ theme: Theme;
229
+ /** Width API, exposed immediately after construction. */
230
+ width: Width;
231
+ }
232
+
210
233
  /**
211
234
  * Main Blok class
212
235
  */
213
236
  export class Blok {
214
- public isReady: Promise<void>;
237
+ public isReady: Promise<Blok>;
215
238
 
216
239
  public blocks: Blocks;
217
240
  public caret: Caret;
@@ -1,4 +1,4 @@
1
- import { BlockToolData } from './block-tool';
1
+ import { BlockToolData } from './block-tool-data';
2
2
  import { OutputData } from '../data-formats/output-data';
3
3
 
4
4
  // ─── Property types ───
@@ -1,4 +1,5 @@
1
- import { BlockTool, BlockToolConstructable, BlockToolConstructorOptions, BlockToolData } from './block-tool';
1
+ import { BlockTool, BlockToolConstructable, BlockToolConstructorOptions } from './block-tool';
2
+ import { BlockToolData } from './block-tool-data';
2
3
  import { MenuConfig } from './menu-config';
3
4
 
4
5
  /**
@@ -1,4 +1,5 @@
1
- import { BlockTool, BlockToolConstructable, BlockToolConstructorOptions, BlockToolData } from './block-tool';
1
+ import { BlockTool, BlockToolConstructable, BlockToolConstructorOptions } from './block-tool';
2
+ import { BlockToolData } from './block-tool-data';
2
3
  import { MenuConfig } from './menu-config';
3
4
 
4
5
  /**
@@ -1,7 +1,48 @@
1
1
  import { PopoverItemParams } from './popover-item';
2
- import type { Flipper } from '../../../src/components/flipper';
3
2
  import { PopoverEvent } from './popover-event';
4
3
 
4
+ /**
5
+ * Public surface of the internal keyboard-navigation Flipper that a popover
6
+ * exposes for reuse via {@link PopoverParams.flipper}.
7
+ *
8
+ * Declared structurally here (rather than imported from raw src) so the
9
+ * published declarations stay self-contained: a bare `import` of the package
10
+ * must never drag Blok's `.ts` source into a consumer's type program. The
11
+ * concrete Flipper is internal — consumers never construct it; it is only
12
+ * passed between Blok's own popovers.
13
+ */
14
+ export interface Flipper {
15
+ /** True while the flipper is handling keyboard navigation. */
16
+ readonly isActivated: boolean;
17
+
18
+ /** Begin handling keyboard navigation over the given items. */
19
+ activate(items?: HTMLElement[], cursorPosition?: number): void;
20
+
21
+ /** Stop handling keyboard navigation. */
22
+ deactivate(): void;
23
+
24
+ /** Focus the first navigable item. */
25
+ focusFirst(): void;
26
+
27
+ /** Focus the item at the given position. */
28
+ focusItem(position: number, options?: { skipNextTab?: boolean }): void;
29
+
30
+ /** True if a navigable item currently has focus. */
31
+ hasFocus(): boolean;
32
+
33
+ /** Register a callback fired on each flip (navigation step). */
34
+ onFlip(cb: () => void): void;
35
+
36
+ /** Remove a previously registered flip callback. */
37
+ removeOnFlip(cb: () => void): void;
38
+
39
+ /** Toggle handling of keydown events originating from contenteditable targets. */
40
+ setHandleContentEditableTargets(value: boolean): void;
41
+
42
+ /** Whether keydown events from contenteditable targets are handled. */
43
+ getHandleContentEditableTargets(): boolean;
44
+ }
45
+
5
46
  /**
6
47
  * Params required to render popover
7
48
  */