@inkami/blx 0.17.3

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 (114) hide show
  1. package/README.md +98 -0
  2. package/dist/attributed-string/attributed-string.d.ts +37 -0
  3. package/dist/attributed-string/index.d.ts +2 -0
  4. package/dist/attributed-string/types.d.ts +13 -0
  5. package/dist/autocomplete/autocomplete-menu.d.ts +24 -0
  6. package/dist/autocomplete/index.d.ts +2 -0
  7. package/dist/autocomplete/types.d.ts +29 -0
  8. package/dist/blob-store/index.d.ts +32 -0
  9. package/dist/blob-store/types.d.ts +24 -0
  10. package/dist/block/block-controller.d.ts +205 -0
  11. package/dist/block/index.d.ts +2 -0
  12. package/dist/block/types.d.ts +15 -0
  13. package/dist/block-action-menu/block-action-menu.d.ts +62 -0
  14. package/dist/block-action-menu/index.d.ts +2 -0
  15. package/dist/block-context-menu/block-context-menu.d.ts +33 -0
  16. package/dist/block-context-menu/index.d.ts +2 -0
  17. package/dist/blx.js +16245 -0
  18. package/dist/blx.js.map +1 -0
  19. package/dist/caret/custom-caret.d.ts +30 -0
  20. package/dist/clipboard/clipboard-manager.d.ts +28 -0
  21. package/dist/clipboard/index.d.ts +2 -0
  22. package/dist/clipboard/paste-parser.d.ts +30 -0
  23. package/dist/commands/command-registry.d.ts +26 -0
  24. package/dist/commands/index.d.ts +2 -0
  25. package/dist/crdt/automerge-sync.d.ts +157 -0
  26. package/dist/crdt/crdt-log.d.ts +57 -0
  27. package/dist/crdt/index.d.ts +8 -0
  28. package/dist/crdt/remote-cursor-renderer.d.ts +28 -0
  29. package/dist/crdt/replay-ui.d.ts +34 -0
  30. package/dist/crdt/types.d.ts +47 -0
  31. package/dist/date-picker/agenda-utils.d.ts +37 -0
  32. package/dist/date-picker/date-parser.d.ts +25 -0
  33. package/dist/date-picker/date-picker.d.ts +115 -0
  34. package/dist/date-picker/index.d.ts +6 -0
  35. package/dist/drag-handle/drag-handle.d.ts +105 -0
  36. package/dist/drag-handle/index.d.ts +1 -0
  37. package/dist/editor.d.ts +864 -0
  38. package/dist/emoji-picker/emoji-data.d.ts +22 -0
  39. package/dist/emoji-picker/emoji-picker.d.ts +43 -0
  40. package/dist/emoji-picker/index.d.ts +3 -0
  41. package/dist/event-bus/event-bus.d.ts +14 -0
  42. package/dist/event-bus/index.d.ts +2 -0
  43. package/dist/event-bus/types.d.ts +320 -0
  44. package/dist/fold/fold-manager.d.ts +27 -0
  45. package/dist/fold/index.d.ts +2 -0
  46. package/dist/grapheme.d.ts +28 -0
  47. package/dist/index.d.ts +62 -0
  48. package/dist/inline-toolbar/highlight-picker.d.ts +41 -0
  49. package/dist/inline-toolbar/index.d.ts +5 -0
  50. package/dist/inline-toolbar/inline-toolbar.d.ts +97 -0
  51. package/dist/inline-toolbar/link-popover.d.ts +24 -0
  52. package/dist/keybindings/defaults.d.ts +2 -0
  53. package/dist/keybindings/index.d.ts +4 -0
  54. package/dist/keybindings/key-matcher.d.ts +41 -0
  55. package/dist/keybindings/types.d.ts +6 -0
  56. package/dist/keybindings/vim.d.ts +2 -0
  57. package/dist/markdown/block-shortcuts.d.ts +35 -0
  58. package/dist/markdown/emoticon-substitutions.d.ts +22 -0
  59. package/dist/markdown/export.d.ts +5 -0
  60. package/dist/markdown/index.d.ts +4 -0
  61. package/dist/markdown/inline-shortcuts.d.ts +13 -0
  62. package/dist/markdown/types.d.ts +29 -0
  63. package/dist/plugins/blockquote.d.ts +6 -0
  64. package/dist/plugins/checklist.d.ts +18 -0
  65. package/dist/plugins/code.d.ts +50 -0
  66. package/dist/plugins/excalidraw.d.ts +26 -0
  67. package/dist/plugins/heading.d.ts +20 -0
  68. package/dist/plugins/horizontal-rule.d.ts +5 -0
  69. package/dist/plugins/image.d.ts +19 -0
  70. package/dist/plugins/index.d.ts +28 -0
  71. package/dist/plugins/list.d.ts +18 -0
  72. package/dist/plugins/mermaid.d.ts +11 -0
  73. package/dist/plugins/paragraph.d.ts +18 -0
  74. package/dist/plugins/prism-loader.d.ts +16 -0
  75. package/dist/plugins/render-utils.d.ts +43 -0
  76. package/dist/plugins/table.d.ts +22 -0
  77. package/dist/plugins/youtube.d.ts +21 -0
  78. package/dist/schema/index.d.ts +2 -0
  79. package/dist/schema/schema-registry.d.ts +35 -0
  80. package/dist/schema/types.d.ts +67 -0
  81. package/dist/search/index.d.ts +4 -0
  82. package/dist/search/search-engine.d.ts +39 -0
  83. package/dist/search/search-ui.d.ts +59 -0
  84. package/dist/selection/index.d.ts +3 -0
  85. package/dist/selection/selection-manager.d.ts +41 -0
  86. package/dist/selection/types.d.ts +20 -0
  87. package/dist/slash-menu/index.d.ts +2 -0
  88. package/dist/slash-menu/slash-menu.d.ts +27 -0
  89. package/dist/table-of-contents/index.d.ts +1 -0
  90. package/dist/table-of-contents/table-of-contents.d.ts +74 -0
  91. package/dist/transaction/index.d.ts +2 -0
  92. package/dist/transaction/transaction.d.ts +29 -0
  93. package/dist/transaction/types.d.ts +40 -0
  94. package/dist/types.d.ts +21 -0
  95. package/dist/ui/context-menu.d.ts +103 -0
  96. package/dist/ui/dismiss.d.ts +33 -0
  97. package/dist/ui/dropdown.d.ts +81 -0
  98. package/dist/ui/floating-scroll-manager.d.ts +50 -0
  99. package/dist/ui/index.d.ts +18 -0
  100. package/dist/ui/modal.d.ts +109 -0
  101. package/dist/ui/popover.d.ts +59 -0
  102. package/dist/ui/position-fixed.d.ts +35 -0
  103. package/dist/ui/toast.d.ts +60 -0
  104. package/dist/ui/tooltip.d.ts +64 -0
  105. package/dist/undo/index.d.ts +5 -0
  106. package/dist/undo/jump-list.d.ts +37 -0
  107. package/dist/undo/types.d.ts +49 -0
  108. package/dist/undo/undo-manager.d.ts +47 -0
  109. package/dist/util/icons.d.ts +13 -0
  110. package/dist/util/id.d.ts +10 -0
  111. package/dist/viewport/block-viewport.d.ts +106 -0
  112. package/dist/viewport/index.d.ts +2 -0
  113. package/package.json +66 -0
  114. package/style.css +4670 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Modal dialog widget.
3
+ *
4
+ * Provides both static factory helpers (Modal.confirm, Modal.alert) and an
5
+ * instance-based API (new Modal()) for custom content dialogs.
6
+ *
7
+ * Uses the native `<dialog>` element with `showModal()` for:
8
+ * - Built-in focus trap (browser-managed)
9
+ * - Top-layer rendering (above all other content)
10
+ * - Native Escape key handling
11
+ *
12
+ * Accessibility:
13
+ * - `role="dialog"` + `aria-modal="true"` on the dialog element
14
+ * - `aria-labelledby` points to the title element when a title is provided
15
+ * - Scroll lock applied to body while dialog is open
16
+ * - Focus returned to triggering element on close
17
+ *
18
+ * @example
19
+ * // Confirm dialog
20
+ * const confirmed = await Modal.confirm("Delete this block?", { title: "Confirm Delete" });
21
+ *
22
+ * // Alert dialog
23
+ * await Modal.alert("Your changes have been saved.");
24
+ *
25
+ * // Custom content modal
26
+ * const modal = new Modal({ title: "Settings" });
27
+ * modal.contentEl.appendChild(myForm);
28
+ * modal.open();
29
+ */
30
+ /** Construction / configuration options for Modal instances and static helpers. */
31
+ export interface ModalOptions {
32
+ /** Dialog title displayed in a header `<h3>`. Used for `aria-labelledby`. */
33
+ title?: string;
34
+ /** Additional CSS class for the dialog element. */
35
+ className?: string;
36
+ /** Whether a click on the backdrop area closes the dialog (default: true). */
37
+ closeOnBackdropClick?: boolean;
38
+ }
39
+ export declare class Modal {
40
+ /** The native `<dialog>` element. */
41
+ readonly dialogEl: HTMLDialogElement;
42
+ /**
43
+ * Container element for custom content.
44
+ * Append child elements here when using the instance API.
45
+ */
46
+ readonly contentEl: HTMLElement;
47
+ private opts;
48
+ private previousFocus;
49
+ /**
50
+ * Create a new Modal instance for custom content dialogs.
51
+ * The dialog is NOT appended to the DOM until `.open()` is called.
52
+ *
53
+ * @param options - Modal configuration.
54
+ */
55
+ constructor(options?: ModalOptions);
56
+ /**
57
+ * Open the modal: append to DOM, apply scroll lock, call showModal().
58
+ * Records the currently focused element to restore focus on close.
59
+ */
60
+ open(): void;
61
+ /**
62
+ * Close the modal programmatically.
63
+ * Calls `dialog.close()` which fires the "close" event and removes scroll lock.
64
+ */
65
+ close(): void;
66
+ /**
67
+ * Remove the dialog element from the DOM.
68
+ * Should be called when you no longer need the modal instance.
69
+ */
70
+ destroy(): void;
71
+ /**
72
+ * Show a confirmation dialog.
73
+ *
74
+ * @param message - Body text for the dialog.
75
+ * @param opts - Optional title and configuration.
76
+ * @returns Promise that resolves to `true` (confirmed) or `false` (cancelled).
77
+ */
78
+ static confirm(message: string, opts?: Pick<ModalOptions, "title" | "closeOnBackdropClick">): Promise<boolean>;
79
+ /**
80
+ * Show an informational alert dialog with a single OK button.
81
+ *
82
+ * @param message - Body text for the dialog.
83
+ * @param opts - Optional title.
84
+ * @returns Promise that resolves when the dialog is dismissed.
85
+ */
86
+ static alert(message: string, opts?: Pick<ModalOptions, "title">): Promise<void>;
87
+ /**
88
+ * Build a `<dialog>` element with the standard modal structure:
89
+ * optional header (if title provided), body div, footer div.
90
+ * Sets ARIA attributes: role, aria-modal, aria-labelledby.
91
+ */
92
+ private static buildDialog;
93
+ /**
94
+ * Attach a backdrop click listener to a dialog.
95
+ * Backdrop clicks are detected by checking if the click target is the dialog
96
+ * element itself (not a child — those have a different target).
97
+ */
98
+ private static attachBackdropListener;
99
+ /**
100
+ * Apply scroll lock: hide body overflow and compensate for scrollbar width
101
+ * to prevent layout shift.
102
+ */
103
+ private static applyScrollLock;
104
+ /**
105
+ * Remove scroll lock previously applied by applyScrollLock().
106
+ */
107
+ private static removeScrollLock;
108
+ }
109
+ export type { ModalOptions as ModalConfirmOptions };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Reusable floating container with positioning and dismiss logic.
3
+ * Content is appended by the consumer via `popover.el`.
4
+ *
5
+ * Used internally by LinkPopover, HighlightPicker, and InlineToolbar.
6
+ */
7
+ /** Construction options for {@link Popover}. */
8
+ export interface PopoverOptions {
9
+ /** Additional CSS class (e.g. "blx-link-popover") */
10
+ className?: string;
11
+ /** CSS display value when shown (default: "flex") */
12
+ displayMode?: string;
13
+ /** Pixel gap between anchor and popover (default: 6) */
14
+ gap?: number;
15
+ /** Horizontal alignment (default: "center") */
16
+ align?: "start" | "center";
17
+ /** Preferred vertical placement (default: "below") */
18
+ placement?: "above" | "below";
19
+ /** Dismiss on click outside (default: true) */
20
+ dismissOnClickOutside?: boolean;
21
+ /** Dismiss on Escape key (default: true) */
22
+ dismissOnEscape?: boolean;
23
+ }
24
+ export declare class Popover {
25
+ /** The root DOM element of the popover. Append to document before calling {@link show}. */
26
+ readonly el: HTMLElement;
27
+ private _visible;
28
+ private opts;
29
+ private dismissHandle;
30
+ /** Called after the popover is hidden (by any means). */
31
+ onHide: (() => void) | null;
32
+ /**
33
+ * Create a new Popover.
34
+ * @param options - Optional configuration overrides.
35
+ */
36
+ constructor(options?: PopoverOptions);
37
+ /** Whether the popover is currently visible. */
38
+ get visible(): boolean;
39
+ /**
40
+ * Show the popover anchored to `anchorRect`.
41
+ * Installs click-outside and/or Escape-key dismiss handlers as configured.
42
+ * @param anchorRect - Bounding rect of the element to anchor to.
43
+ */
44
+ show(anchorRect: DOMRect): void;
45
+ /**
46
+ * Reposition to a new anchor rect without toggling visibility or dismiss handlers.
47
+ * No-op if the popover is not currently visible.
48
+ * @param anchorRect - New bounding rect to anchor to.
49
+ */
50
+ reposition(anchorRect: DOMRect): void;
51
+ private positionTo;
52
+ /**
53
+ * Hide the popover and remove all dismiss handlers.
54
+ * Fires {@link onHide} if it was previously visible.
55
+ */
56
+ hide(): void;
57
+ /** Hide the popover and remove its element from the DOM. */
58
+ destroy(): void;
59
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared positioning utility for floating UI elements.
3
+ * Sets `position: fixed` and places the element relative to an anchor rect,
4
+ * flipping vertically if it overflows and clamping horizontally.
5
+ * Constrains max-height to available viewport space so menus scroll
6
+ * instead of bleeding outside the viewport.
7
+ */
8
+ /** Options for {@link positionFixed}. */
9
+ export interface PositionFixedOptions {
10
+ /** Pixel gap between anchor and element (default: 4) */
11
+ gap?: number;
12
+ /** Horizontal alignment relative to anchor (default: "start") */
13
+ align?: "start" | "center";
14
+ /** Preferred vertical placement (default: "below") */
15
+ placement?: "above" | "below";
16
+ /** Viewport edge padding in px (default: 4) */
17
+ viewportPadding?: number;
18
+ /**
19
+ * Called after final placement is determined.
20
+ * Receives the actual placement used (`"above"` or `"below"`), which may
21
+ * differ from `options.placement` if a flip was necessary.
22
+ */
23
+ onPlaced?: (placement: "above" | "below") => void;
24
+ }
25
+ /**
26
+ * Position `el` with `position: fixed` relative to `anchor`.
27
+ *
28
+ * Measures the element and places it in a single synchronous pass so the
29
+ * final position is painted on the first frame (no flash/jump).
30
+ *
31
+ * @param el - The floating element to position (must be in the DOM and visible).
32
+ * @param anchor - The anchor bounding rect (e.g. from `getBoundingClientRect()`).
33
+ * @param options - Placement preferences and callbacks.
34
+ */
35
+ export declare function positionFixed(el: HTMLElement, anchor: DOMRect, options?: PositionFixedOptions): void;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Toast notification widget.
3
+ *
4
+ * Displays brief, auto-dismissing notifications at the bottom-center of the
5
+ * viewport. Uses static factory methods — callers never manage instances.
6
+ *
7
+ * Two aria-live containers are maintained:
8
+ * - Polite container (info/success): does not interrupt screen readers
9
+ * - Assertive container (error): interrupts screen readers immediately
10
+ *
11
+ * Both containers are created lazily on first use and persist indefinitely
12
+ * so that aria-live regions are pre-registered before content is injected
13
+ * (which is required for reliable screen reader announcements).
14
+ *
15
+ * @example
16
+ * Toast.show("File saved");
17
+ * Toast.show("Connection failed", { type: "error" });
18
+ * Toast.show("Copied!", { type: "success", duration: 2000 });
19
+ */
20
+ /** Visual variant of a toast notification. */
21
+ export type ToastType = "info" | "error" | "success";
22
+ /** Options for {@link Toast.show}. */
23
+ export interface ToastShowOptions {
24
+ /** Toast variant (default: "info"). */
25
+ type?: ToastType;
26
+ /** Auto-dismiss duration in milliseconds (default: 4000). */
27
+ duration?: number;
28
+ }
29
+ export declare class Toast {
30
+ /** Polite aria-live container (info / success toasts). Created on first use. */
31
+ private static container;
32
+ /** Assertive aria-live container (error toasts). Created on first use. */
33
+ private static assertiveContainer;
34
+ private constructor();
35
+ /**
36
+ * Display a toast notification.
37
+ *
38
+ * @param message - Text content to show in the toast.
39
+ * @param opts - Optional configuration.
40
+ */
41
+ static show(message: string, opts?: ToastShowOptions): void;
42
+ /**
43
+ * Return the appropriate aria-live container for the given toast type,
44
+ * creating and appending it to document.body if it doesn't exist yet.
45
+ *
46
+ * The container is created BEFORE any toast is appended so that
47
+ * screen readers register the live region in advance.
48
+ */
49
+ private static getOrCreateContainer;
50
+ /**
51
+ * Create a new toast container element and append it to document.body.
52
+ */
53
+ private static createContainer;
54
+ /**
55
+ * Dismiss a toast element with an exit animation.
56
+ * Removes the element from DOM after the animation completes.
57
+ * If prefers-reduced-motion is set, removes immediately.
58
+ */
59
+ private static dismiss;
60
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Accessible Tooltip widget.
3
+ *
4
+ * Shows a short text label near a trigger element on hover (with configurable
5
+ * delay) or focus (immediately). Uses `positionFixed` for viewport-aware placement.
6
+ *
7
+ * Follows WAI-ARIA Tooltip pattern: the trigger gets `aria-describedby` pointing
8
+ * to the tooltip element which has `role="tooltip"`. The tooltip element itself
9
+ * is never focusable (`pointer-events: none` set in CSS).
10
+ */
11
+ /** Construction options for {@link Tooltip}. */
12
+ export interface TooltipOptions {
13
+ /** Tooltip text content. */
14
+ content: string;
15
+ /** Preferred placement relative to trigger (default: "above"). */
16
+ placement?: "above" | "below";
17
+ /** Show delay in ms on mouseenter (default: 350). Focus always shows instantly. */
18
+ delay?: number;
19
+ }
20
+ export declare class Tooltip {
21
+ /** The tooltip DOM element. Has `role="tooltip"` and class `blx-tooltip`. */
22
+ readonly el: HTMLElement;
23
+ private opts;
24
+ private _visible;
25
+ private trigger;
26
+ private showTimer;
27
+ private onMouseEnter;
28
+ private onMouseLeave;
29
+ private onFocus;
30
+ private onBlur;
31
+ private onKeyDown;
32
+ private onVisibilityChange;
33
+ /**
34
+ * Create a new Tooltip.
35
+ * The element is appended to `document.body` immediately so it is available
36
+ * for positioning. It starts hidden (`display: none`).
37
+ * @param options - Tooltip configuration.
38
+ */
39
+ constructor(options: TooltipOptions);
40
+ /**
41
+ * Attach the tooltip to a trigger element.
42
+ * Sets `aria-describedby` on the trigger and installs event listeners.
43
+ * Call at most once per Tooltip instance — create a new Tooltip to re-attach.
44
+ * @param trigger - The element that triggers the tooltip.
45
+ */
46
+ attachTo(trigger: HTMLElement): void;
47
+ /**
48
+ * Update the tooltip's text content.
49
+ * Can be called at any time, including while the tooltip is visible.
50
+ * @param content - New text content.
51
+ */
52
+ updateContent(content: string): void;
53
+ /**
54
+ * Remove the tooltip element from the DOM and clean up all event listeners
55
+ * and timers. Removes `aria-describedby` from the trigger.
56
+ * After calling destroy, do not use this Tooltip instance.
57
+ */
58
+ destroy(): void;
59
+ private startShowTimer;
60
+ private cancelAndHide;
61
+ private cancelTimer;
62
+ private showNow;
63
+ private hide;
64
+ }
@@ -0,0 +1,5 @@
1
+ export { UndoManager } from "./undo-manager";
2
+ export type { UndoManagerOptions } from "./undo-manager";
3
+ export { JumpList } from "./jump-list";
4
+ export type { JumpEntry, JumpListOptions } from "./jump-list";
5
+ export type { UndoAction, UndoEntry, CursorState, BlockSnapshot } from "./types";
@@ -0,0 +1,37 @@
1
+ /** A position in the editor (block + cursor offset) */
2
+ export interface JumpEntry {
3
+ blockId: string;
4
+ offset: number;
5
+ }
6
+ export interface JumpListOptions {
7
+ /** Maximum number of entries (default 100) */
8
+ maxSize?: number;
9
+ }
10
+ /**
11
+ * Vim-style navigation history (jumplist).
12
+ *
13
+ * Records cursor positions before "jumps" — significant position changes
14
+ * such as search navigation, internal link clicks, or scrollToBlock calls.
15
+ * Navigate with Ctrl-O (back) and Ctrl-I (forward).
16
+ */
17
+ export declare class JumpList {
18
+ private entries;
19
+ private index;
20
+ private readonly maxSize;
21
+ constructor(options?: JumpListOptions);
22
+ /**
23
+ * Record the current position before jumping away.
24
+ * Discards forward history (like vim's jumplist).
25
+ */
26
+ push(entry: JumpEntry): void;
27
+ /** Go back (Ctrl-O). Returns the position to jump to, or null. */
28
+ back(): JumpEntry | null;
29
+ /** Go forward (Ctrl-I). Returns the position to jump to, or null. */
30
+ forward(): JumpEntry | null;
31
+ /** Current position in the jump list */
32
+ current(): JumpEntry | null;
33
+ canGoBack(): boolean;
34
+ canGoForward(): boolean;
35
+ clear(): void;
36
+ get size(): number;
37
+ }
@@ -0,0 +1,49 @@
1
+ import type { AttributedString } from "../attributed-string";
2
+ import type { BlockMetadata } from "../schema";
3
+ /** Snapshot of a block's full state for undo/redo */
4
+ export interface BlockSnapshot {
5
+ id: string;
6
+ type: string;
7
+ content: AttributedString;
8
+ metadata: BlockMetadata;
9
+ }
10
+ /** Cursor position within the editor */
11
+ export interface CursorState {
12
+ blockId: string;
13
+ offset: number;
14
+ }
15
+ /** A single reversible action within an undo entry */
16
+ export type UndoAction = {
17
+ type: "contentChange";
18
+ blockId: string;
19
+ before: {
20
+ content: AttributedString;
21
+ metadata: BlockMetadata;
22
+ blockType: string;
23
+ };
24
+ after: {
25
+ content: AttributedString;
26
+ metadata: BlockMetadata;
27
+ blockType: string;
28
+ };
29
+ } | {
30
+ type: "addBlock";
31
+ block: BlockSnapshot;
32
+ index: number;
33
+ } | {
34
+ type: "removeBlock";
35
+ block: BlockSnapshot;
36
+ index: number;
37
+ } | {
38
+ type: "moveBlock";
39
+ blockId: string;
40
+ fromIndex: number;
41
+ toIndex: number;
42
+ };
43
+ /** A group of actions that form a single undoable unit */
44
+ export interface UndoEntry {
45
+ actions: UndoAction[];
46
+ beforeCursor: CursorState | null;
47
+ afterCursor: CursorState | null;
48
+ timestamp: number;
49
+ }
@@ -0,0 +1,47 @@
1
+ import type { UndoAction, UndoEntry, CursorState } from "./types";
2
+ export interface UndoManagerOptions {
3
+ /** Maximum entries in the undo stack (default 200) */
4
+ maxEntries?: number;
5
+ /** Max ms between content changes on the same block to merge them (default 500) */
6
+ mergeWindow?: number;
7
+ }
8
+ /**
9
+ * Transaction-aware undo/redo history manager.
10
+ *
11
+ * Records individual actions or compound groups (e.g., block split = content
12
+ * change + block add). Consecutive content changes on the same block within
13
+ * `mergeWindow` ms are automatically coalesced into a single entry.
14
+ */
15
+ export declare class UndoManager {
16
+ private undoStack;
17
+ private redoStack;
18
+ private readonly maxEntries;
19
+ private readonly mergeWindow;
20
+ private compoundActions;
21
+ private compoundBeforeCursor;
22
+ constructor(options?: UndoManagerOptions);
23
+ /** Start grouping actions into a single compound entry */
24
+ beginGroup(beforeCursor: CursorState | null): void;
25
+ /** Whether a compound group is currently active */
26
+ get isGrouping(): boolean;
27
+ /**
28
+ * Record an undoable action.
29
+ *
30
+ * If a compound group is active, the action is appended to the group.
31
+ * Otherwise, consecutive content changes on the same block within
32
+ * `mergeWindow` are merged into the last entry.
33
+ */
34
+ record(action: UndoAction, beforeCursor: CursorState | null, afterCursor: CursorState | null): void;
35
+ /** End compound group and push as a single entry */
36
+ endGroup(afterCursor: CursorState | null): void;
37
+ private pushEntry;
38
+ /** Pop the last undo entry. Returns null if nothing to undo. */
39
+ popUndo(): UndoEntry | null;
40
+ /** Pop the last redo entry. Returns null if nothing to redo. */
41
+ popRedo(): UndoEntry | null;
42
+ canUndo(): boolean;
43
+ canRedo(): boolean;
44
+ clear(): void;
45
+ get undoSize(): number;
46
+ get redoSize(): number;
47
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Icon utilities — thin wrapper around lucide icon definitions.
3
+ *
4
+ * Usage:
5
+ * import { iconHtml, X, Plus } from "../util/icons";
6
+ * el.innerHTML = iconHtml(X, 14);
7
+ */
8
+ import type { IconNode } from "lucide";
9
+ export { Check, ChevronDown, CircleCheckBig, ChevronsLeft, ChevronsRight, Clipboard, ClipboardPaste, ClockFading, ClockPlus, Code, Copy, GripVertical, Heading1, Heading2, Heading3, Heading4, History, Image, Link, List, ListChecks, ListOrdered, Pause, Pencil, Pilcrow, Play, Plus, Repeat2, Scissors, Sparkles, TextQuote, Trash2, Ban, X, } from "lucide";
10
+ /**
11
+ * Return an HTML string for a lucide icon (for innerHTML contexts).
12
+ */
13
+ export declare function iconHtml(def: IconNode, size?: number, attrs?: Record<string, string>): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generate a unique block ID using UUID v4.
3
+ *
4
+ * Uses `crypto.randomUUID()` when available (all modern browsers and
5
+ * Node 19+). Falls back to a manual implementation for older runtimes
6
+ * (e.g. jsdom in tests).
7
+ */
8
+ export declare function generateId(): string;
9
+ /** Validate whether a string is a valid UUID v4 */
10
+ export declare function isUUID(id: string): boolean;
@@ -0,0 +1,106 @@
1
+ import type { BlockController } from "../block";
2
+ export interface BlockViewportOptions {
3
+ /**
4
+ * Number of pixels above/below the viewport to keep blocks fully active
5
+ * (listeners attached, content visible). Defaults to 1x viewport height,
6
+ * giving a total active zone of ~3x the viewport (1 above + visible + 1 below).
7
+ *
8
+ * Blocks outside this zone are *ejected*: listeners detached and the
9
+ * browser skips painting via `content-visibility: hidden`. The DOM stays
10
+ * intact so the container height is perfectly preserved — zero layout shift.
11
+ */
12
+ bufferPx?: number;
13
+ /**
14
+ * Estimated height in px for blocks whose content has not yet been
15
+ * rendered. Used as `minHeight` on the container to maintain scroll
16
+ * position stability. Default: 32
17
+ */
18
+ estimatedBlockHeight?: number;
19
+ }
20
+ /**
21
+ * Manages virtual rendering of blocks using IntersectionObserver.
22
+ *
23
+ * Two-tier lifecycle:
24
+ *
25
+ * 1. **First mount** — Block has never been rendered. When it enters the
26
+ * buffer zone, `mountContent()` is called to run the plugin's `render()`
27
+ * and attach listeners. This is the only transition that may cause a
28
+ * layout shift (estimated height → real height); it is compensated
29
+ * with a scroll anchor.
30
+ *
31
+ * 2. **Eject / Restore** — Once rendered, blocks are never unmounted.
32
+ * When they leave the buffer, `ejectContent()` detaches listeners and
33
+ * sets `content-visibility: hidden` (browser skips painting but
34
+ * remembers the size). When they re-enter, `restoreContent()` reverses
35
+ * this. Because the DOM is untouched, there is zero layout shift.
36
+ *
37
+ * When IntersectionObserver is unavailable (e.g. jsdom in tests), all blocks
38
+ * are mounted immediately, preserving existing behavior.
39
+ */
40
+ export declare class BlockViewport {
41
+ private observer;
42
+ private controllers;
43
+ private forceMounted;
44
+ /** Measured content heights — used as `minHeight` for blocks that have
45
+ * never been rendered, so first-mount layout shifts are minimized. */
46
+ private heightCache;
47
+ private readonly blocksContainer;
48
+ private readonly estimatedHeight;
49
+ private readonly hasIO;
50
+ /** Guard against re-entrant intersection callbacks caused by scrollBy */
51
+ private handlingIntersection;
52
+ /** Callback invoked after a batch of blocks is mounted by the viewport */
53
+ onContentMounted: ((blockId: string) => void) | null;
54
+ constructor(container: HTMLElement, options?: BlockViewportOptions);
55
+ /**
56
+ * Register a block controller with the viewport manager.
57
+ * If IntersectionObserver is available, the block is observed and content
58
+ * is mounted lazily. Otherwise, content is mounted immediately.
59
+ */
60
+ register(controller: BlockController): void;
61
+ /**
62
+ * Register multiple block controllers in a single batch.
63
+ * More efficient than individual register() calls during initial load.
64
+ */
65
+ registerBatch(controllers: BlockController[]): void;
66
+ /** Unregister a block and stop observing it */
67
+ unregister(blockId: string): void;
68
+ /**
69
+ * Ensure a block's content is mounted and active (e.g., before focusing
70
+ * or searching). The block is also marked as force-mounted so it won't
71
+ * be ejected when scrolling away.
72
+ */
73
+ ensureMounted(blockId: string): void;
74
+ /**
75
+ * Remove the force-mount flag from a block. If the block is outside the
76
+ * viewport buffer, it may be ejected on the next intersection check.
77
+ */
78
+ releaseForceMounted(blockId: string): void;
79
+ /** Whether a block is currently content-mounted */
80
+ isContentMounted(blockId: string): boolean;
81
+ /** Whether a block is force-mounted (won't be ejected by intersection) */
82
+ isForceMounted(blockId: string): boolean;
83
+ /**
84
+ * Synchronously mount content for all blocks currently visible in the
85
+ * viewport. Call this after the initial block load to avoid a flash of
86
+ * empty containers.
87
+ */
88
+ mountInitialViewport(): void;
89
+ /**
90
+ * Ensure a block is mounted, then scroll its container into view.
91
+ */
92
+ scrollIntoView(blockId: string, behavior?: ScrollBehavior): void;
93
+ /**
94
+ * Recalculate the average height from measured blocks and apply it to
95
+ * all never-rendered block placeholders.
96
+ */
97
+ private updateEstimatedHeights;
98
+ destroy(): void;
99
+ /** Check if an element is within the viewport (synchronous fallback) */
100
+ private isInViewport;
101
+ private handleIntersection;
102
+ /**
103
+ * Find a block container visible in the viewport to use as a scroll anchor.
104
+ */
105
+ private pickScrollAnchor;
106
+ }
@@ -0,0 +1,2 @@
1
+ export { BlockViewport } from "./block-viewport";
2
+ export type { BlockViewportOptions } from "./block-viewport";
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@inkami/blx",
3
+ "version": "0.17.3",
4
+ "description": "A block-based editor library",
5
+ "files": [
6
+ "dist",
7
+ "style.css"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/blx.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/blx.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./style.css": "./style.css"
18
+ },
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org",
21
+ "access": "public"
22
+ },
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "lucide": "^0.575.0",
26
+ "prismjs": "^1.30.0"
27
+ },
28
+ "peerDependencies": {
29
+ "@automerge/automerge": "^3.0.0",
30
+ "mermaid": "^11.0.0",
31
+ "react": "^18.0.0 || ^19.0.0",
32
+ "react-dom": "^18.0.0 || ^19.0.0",
33
+ "@excalidraw/excalidraw": ">=0.18.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "@automerge/automerge": {
37
+ "optional": true
38
+ },
39
+ "mermaid": {
40
+ "optional": true
41
+ },
42
+ "react": {
43
+ "optional": true
44
+ },
45
+ "react-dom": {
46
+ "optional": true
47
+ },
48
+ "@excalidraw/excalidraw": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "devDependencies": {
53
+ "@types/prismjs": "^1.26.5",
54
+ "jsdom": "^28.0.0",
55
+ "typescript": "^5.9.3",
56
+ "vite": "^7.3.1",
57
+ "vite-plugin-wasm": "^3.5.0",
58
+ "vitest": "^4.0.18"
59
+ },
60
+ "scripts": {
61
+ "build": "vite build && pnpm run build:types",
62
+ "build:types": "tsc -p tsconfig.build.json",
63
+ "test": "vitest run",
64
+ "test:watch": "vitest"
65
+ }
66
+ }