@mrakomor/core 0.0.11 → 0.0.13
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.
- package/dist/blockChrome.mjs +2 -2
- package/dist/blockInsertMenu.mjs +1 -1
- package/dist/chunk-2C6UDFD4.mjs +1 -0
- package/dist/chunk-4WTPDNPA.mjs +1 -0
- package/dist/chunk-5AD5MQXI.mjs +1 -0
- package/dist/{chunk-K2DMHAUD.mjs → chunk-7CJLTNHZ.mjs} +1 -1
- package/dist/{chunk-VYTSZ4S6.mjs → chunk-B2UFH244.mjs} +1 -1
- package/dist/chunk-BWK2S4UI.mjs +2 -0
- package/dist/chunk-D6K2L5O3.mjs +4 -0
- package/dist/{chunk-NR2BAMSW.mjs → chunk-DHFIX7FW.mjs} +2 -2
- package/dist/chunk-ERNLRJPB.mjs +1 -0
- package/dist/chunk-EXDY5TZH.mjs +1 -0
- package/dist/chunk-J4TTWQ6J.mjs +2 -0
- package/dist/{chunk-2VURZA2E.mjs → chunk-SG5PCGIV.mjs} +1 -1
- package/dist/chunk-ZHOZPQZH.mjs +1 -0
- package/dist/clipboard.cjs +3 -3
- package/dist/clipboard.mjs +3 -3
- package/dist/footer.cjs +3 -3
- package/dist/footer.mjs +2 -2
- package/dist/headline.cjs +2 -2
- package/dist/headline.mjs +1 -1
- package/dist/help.cjs +1 -1
- package/dist/help.mjs +2 -2
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +4 -4
- package/dist/link.mjs +2 -2
- package/dist/list.cjs +3 -3
- package/dist/list.mjs +1 -1
- package/dist/paragraph.cjs +2 -2
- package/dist/paragraph.mjs +1 -1
- package/dist/slashCommand.cjs +2 -2
- package/dist/slashCommand.mjs +1 -1
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.mjs +2 -2
- package/dist/tts.cjs +8 -8
- package/dist/tts.mjs +3 -3
- package/dist/types/Mrakomor.d.mts +230 -0
- package/dist/types/component/AbstractComponent.d.mts +69 -0
- package/dist/types/component/Editor.d.mts +8 -0
- package/dist/types/component/block/BlockComponent.d.mts +105 -0
- package/dist/types/component/block/BlockRegister.d.mts +29 -0
- package/dist/types/component/block/ExternalComponent.d.mts +42 -0
- package/dist/types/component/block/UnknownComponent.d.mts +3 -0
- package/dist/types/component/block/blockItemEquals.d.mts +6 -0
- package/dist/types/component/block/helpers.d.mts +319 -0
- package/dist/types/component/editorContext.d.mts +4 -0
- package/dist/types/component/entityRange/EntityRangeElement.d.mts +15 -0
- package/dist/types/component/inlineStyle/Bold.d.mts +4 -0
- package/dist/types/component/inlineStyle/InlineElement.d.mts +10 -0
- package/dist/types/component/inlineStyle/Italic.d.mts +4 -0
- package/dist/types/component/inlineStyle/Subscript.d.mts +4 -0
- package/dist/types/component/inlineStyle/Superscript.d.mts +4 -0
- package/dist/types/component/inlineStyle/Underline.d.mts +4 -0
- package/dist/types/component/inlineStyle/index.d.mts +5 -0
- package/dist/types/component/overlayMount.d.mts +46 -0
- package/dist/types/dom.d.mts +169 -0
- package/dist/types/emitter.d.mts +38 -0
- package/dist/types/index.d.mts +7 -0
- package/dist/types/namespaces.d.mts +17 -0
- package/dist/types/plugin/blockChrome/BlockChrome.d.mts +11 -0
- package/dist/types/plugin/blockChrome/index.d.mts +1 -0
- package/dist/types/plugin/blockInsertMenu/BlockInsertMenu.d.mts +6 -0
- package/dist/types/plugin/blockInsertMenu/index.d.mts +1 -0
- package/dist/types/plugin/clipboard/clipboardHelpers.d.mts +151 -0
- package/dist/types/plugin/clipboard/clipboardPlugin.d.mts +6 -0
- package/dist/types/plugin/clipboard/index.d.mts +1 -0
- package/dist/types/plugin/default/defaultMrakomorPlugin.d.mts +21 -0
- package/dist/types/plugin/dialog/Dialog.d.mts +23 -0
- package/dist/types/plugin/footer/footerPlugin.d.mts +7 -0
- package/dist/types/plugin/footer/index.d.mts +1 -0
- package/dist/types/plugin/headline/Headline.d.mts +29 -0
- package/dist/types/plugin/headline/index.d.mts +1 -0
- package/dist/types/plugin/help/HelpRegister.d.mts +15 -0
- package/dist/types/plugin/help/helpPlugin.d.mts +26 -0
- package/dist/types/plugin/help/helpSchema.d.mts +11 -0
- package/dist/types/plugin/help/index.d.mts +1 -0
- package/dist/types/plugin/linkEntity/LinkEntity.d.mts +37 -0
- package/dist/types/plugin/linkEntity/index.d.mts +1 -0
- package/dist/types/plugin/list/List.d.mts +61 -0
- package/dist/types/plugin/list/index.d.mts +1 -0
- package/dist/types/plugin/list/listHelpers.d.mts +166 -0
- package/dist/types/plugin/menu/Menu.d.mts +22 -0
- package/dist/types/plugin/menu/menuPlugin.d.mts +6 -0
- package/dist/types/plugin/paragraph/Paragraph.d.mts +16 -0
- package/dist/types/plugin/paragraph/index.d.mts +1 -0
- package/dist/types/plugin/slashCommand/SlashActionRegister.d.mts +12 -0
- package/dist/types/plugin/slashCommand/index.d.mts +1 -0
- package/dist/types/plugin/slashCommand/slashActionSchema.d.mts +29 -0
- package/dist/types/plugin/slashCommand/slashCommandHelpers.d.mts +77 -0
- package/dist/types/plugin/slashCommand/slashCommandPlugin.d.mts +11 -0
- package/dist/types/plugin/slashCommand/slashHelp.d.mts +12 -0
- package/dist/types/plugin/tooltip/Tooltip.d.mts +16 -0
- package/dist/types/plugin/tooltip/index.d.mts +1 -0
- package/dist/types/plugin/tts/TtsHeadline.d.mts +27 -0
- package/dist/types/plugin/tts/TtsList.d.mts +19 -0
- package/dist/types/plugin/tts/TtsParagraph.d.mts +9 -0
- package/dist/types/plugin/tts/TtsVoice.d.mts +52 -0
- package/dist/types/plugin/tts/index.d.mts +4 -0
- package/dist/types/register/Register.d.mts +50 -0
- package/dist/types/register/sortRegisterEntries.d.mts +7 -0
- package/dist/types/schema/blockContent.d.mts +34 -0
- package/dist/types/schema/blockRegister.d.mts +14 -0
- package/dist/types/schema/config.d.mts +6 -0
- package/dist/types/types.d.mts +636 -0
- package/package.json +36 -22
- package/dist/chunk-5VPYPKGA.mjs +0 -1
- package/dist/chunk-BCE45I7Q.mjs +0 -2
- package/dist/chunk-N4I2WZWF.mjs +0 -1
- package/dist/chunk-SCFD3XBH.mjs +0 -1
- package/dist/chunk-T7OO472O.mjs +0 -1
- package/dist/chunk-WQWE5UCN.mjs +0 -1
- package/dist/chunk-YO4ZA6PJ.mjs +0 -2
- package/dist/chunk-ZDMD7SRT.mjs +0 -4
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export class AbstractComponent extends Component {
|
|
2
|
+
static get NAMESPACE(): string;
|
|
3
|
+
static getStateSchema(): import("@esmj/schema").SchemaInterface<{
|
|
4
|
+
component: string;
|
|
5
|
+
properties: {
|
|
6
|
+
[x: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
settings: {
|
|
9
|
+
[x: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
}, {
|
|
12
|
+
component: string;
|
|
13
|
+
properties: {
|
|
14
|
+
[x: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
settings: {
|
|
17
|
+
[x: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
static getPropsSchema(): import("@esmj/schema").SchemaInterface<{
|
|
21
|
+
state: {
|
|
22
|
+
component: string;
|
|
23
|
+
properties: {
|
|
24
|
+
[x: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
settings: {
|
|
27
|
+
[x: string]: unknown;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
observeDOMChanges: boolean;
|
|
31
|
+
}, {
|
|
32
|
+
state: {
|
|
33
|
+
component: string;
|
|
34
|
+
properties: {
|
|
35
|
+
[x: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
settings: {
|
|
38
|
+
[x: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
observeDOMChanges: boolean;
|
|
42
|
+
}>;
|
|
43
|
+
constructor(props: any);
|
|
44
|
+
/** @type {import('#src/types.mjs').Signal<import('#src/types.mjs').BlockState>} */
|
|
45
|
+
state: import("#src/types.mjs").Signal<import("#src/types.mjs").BlockState>;
|
|
46
|
+
/** @type {Element | null} */
|
|
47
|
+
element: Element | null;
|
|
48
|
+
/** @type {import('#src/types.mjs').Mrakomor} */
|
|
49
|
+
mrakomor: import("#src/types.mjs").Mrakomor;
|
|
50
|
+
_stale: boolean;
|
|
51
|
+
_observer: MutationObserver | null;
|
|
52
|
+
/** @type {(() => void) | null} */
|
|
53
|
+
_offInputStale: (() => void) | null;
|
|
54
|
+
/** @type {((event: import('#src/types.mjs').EmitterEvent) => void) | null} */
|
|
55
|
+
_onInputMarkStale: ((event: import("#src/types.mjs").EmitterEvent) => void) | null;
|
|
56
|
+
_observeDOMChanges: any;
|
|
57
|
+
parseFromHTML(node: any): any;
|
|
58
|
+
getState(): any;
|
|
59
|
+
/**
|
|
60
|
+
* Whether live DOM may differ from the item signal (`getState()` parses until re-render).
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
isStale(): boolean;
|
|
64
|
+
onMount(): void;
|
|
65
|
+
onUnmount(): void;
|
|
66
|
+
#private;
|
|
67
|
+
}
|
|
68
|
+
export { AbstractComponent as SAbstractComponent };
|
|
69
|
+
import { Component } from '@esmj/dom';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editor shell: tools overlay + keyed block list (`Each` by `componentId`).
|
|
3
|
+
* @param {{ mrakomor: import('#src/types.mjs').Signal<import('#src/types.mjs').Mrakomor> }} props
|
|
4
|
+
* @returns {*}
|
|
5
|
+
*/
|
|
6
|
+
export function Editor({ mrakomor: mrakomorSignal }: {
|
|
7
|
+
mrakomor: import("#src/types.mjs").Signal<import("#src/types.mjs").Mrakomor>;
|
|
8
|
+
}): any;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for content blocks (paragraph, headline, list, figure, …).
|
|
3
|
+
* Subclasses override `onKeydown` for block-owned keys and may override
|
|
4
|
+
* `setCursor` / `isAtomic` for neighbor Backspace behavior.
|
|
5
|
+
*/
|
|
6
|
+
export class BlockComponent extends AbstractComponent {
|
|
7
|
+
/**
|
|
8
|
+
* Visual label for insert menus / tooltip toolbar (string or renderable).
|
|
9
|
+
* @returns {string | Function}
|
|
10
|
+
*/
|
|
11
|
+
static get LABEL(): string | Function;
|
|
12
|
+
/**
|
|
13
|
+
* Extra slash-filter tokens (label and namespace leaf are added when empty).
|
|
14
|
+
* @returns {string[]}
|
|
15
|
+
*/
|
|
16
|
+
static get KEYWORDS(): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Menu group for insert / slash menus (`register` row `group`).
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
static get GROUP(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Convert this block type's own state into the generic cross-component
|
|
24
|
+
* exchange shape: `properties.items` with exactly one entry (this block
|
|
25
|
+
* type has no per-item DOM identity). Records this block type's own
|
|
26
|
+
* `NAMESPACE` as `originComponent` for provenance (authoritative — not
|
|
27
|
+
* read from `state.component`, which a caller could omit or get wrong).
|
|
28
|
+
* Normalizes the item's text via `normalizeBlockText` so the empty-
|
|
29
|
+
* contenteditable `'\n'`/`'\r\n'` DOM sentinel never leaks into the
|
|
30
|
+
* exchange state as a fake 1-character text. `inlineStyleRanges`/
|
|
31
|
+
* `entityRanges` are included on the item ONLY when the real
|
|
32
|
+
* `state.properties` actually has that key — never fabricated to `[]` —
|
|
33
|
+
* so generic callers (`resolveActiveExchangeItem`/insert-range handlers)
|
|
34
|
+
* can tell "supports this range kind" (key present, even if empty) apart
|
|
35
|
+
* from "doesn't support it at all" (key absent), e.g. a plain Headline
|
|
36
|
+
* has neither key, `TtsHeadline` has `entityRanges` only. Override for
|
|
37
|
+
* blocks whose properties should not leak into the exchange state (e.g.
|
|
38
|
+
* atomic blocks), or that have more than one item (e.g. List).
|
|
39
|
+
* @param {import('#src/types.mjs').BlockState} state
|
|
40
|
+
* @returns {import('#src/types.mjs').ExchangeBlockState}
|
|
41
|
+
*/
|
|
42
|
+
static toExchangeState(state: import("#src/types.mjs").BlockState): import("#src/types.mjs").ExchangeBlockState;
|
|
43
|
+
/**
|
|
44
|
+
* Convert a generic cross-component exchange state into this block type's
|
|
45
|
+
* own `properties`/`settings` fragment: unwraps `properties.items[0]` back
|
|
46
|
+
* into a flat `{ text, inlineStyleRanges, entityRanges }` (this block type
|
|
47
|
+
* has no per-item identity, so any additional incoming items are dropped —
|
|
48
|
+
* matches `unwrapListItem`'s per-item transform semantics, transform
|
|
49
|
+
* always targets ONE item). Override to reshape it (e.g. List wraps every
|
|
50
|
+
* item into its own row). Callers always finish with
|
|
51
|
+
* `Constructor.getStateSchema().parse(...)` for final validation/defaults.
|
|
52
|
+
* @param {import('#src/types.mjs').ExchangeBlockState} exchangeState
|
|
53
|
+
* @returns {import('#src/types.mjs').BlockStateInput}
|
|
54
|
+
*/
|
|
55
|
+
static fromExchangeState(exchangeState: import("#src/types.mjs").ExchangeBlockState): import("#src/types.mjs").BlockStateInput;
|
|
56
|
+
/**
|
|
57
|
+
* Append an incoming exchange state onto this block's own state (Backspace
|
|
58
|
+
* merge join, previous block keeps its own type). Default text-concats via
|
|
59
|
+
* `deepMergeBlockState`, which keeps `state`'s own `component`/`componentId`
|
|
60
|
+
* and merges `properties`/`settings`. The incoming exchange state's items
|
|
61
|
+
* are joined into one flat item first (`joinExchangeItems`, `'\n'` between
|
|
62
|
+
* items) since this block type can only hold flat text — matters when the
|
|
63
|
+
* removed block is multi-item (e.g. a List being merged into a preceding
|
|
64
|
+
* Paragraph). Override to reshape the join (e.g. List appends the incoming
|
|
65
|
+
* text as a new item) or return `null` to opt out of merging entirely
|
|
66
|
+
* (e.g. atomic external blocks — see `ExternalComponent.appendExchangeState`).
|
|
67
|
+
* @param {import('#src/types.mjs').BlockState} state
|
|
68
|
+
* @param {import('#src/types.mjs').ExchangeBlockState} exchangeState
|
|
69
|
+
* @returns {{ state: import('#src/types.mjs').BlockState, cursor: number } | null}
|
|
70
|
+
*/
|
|
71
|
+
static appendExchangeState(state: import("#src/types.mjs").BlockState, exchangeState: import("#src/types.mjs").ExchangeBlockState): {
|
|
72
|
+
state: import("#src/types.mjs").BlockState;
|
|
73
|
+
cursor: number;
|
|
74
|
+
} | null;
|
|
75
|
+
/**
|
|
76
|
+
* Whether Backspace on an empty neighbor should remove this block.
|
|
77
|
+
* Non-atomic blocks receive the cursor via `setCursor` instead.
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
isAtomic(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Place the cursor in this block’s selectable root.
|
|
83
|
+
* Omitting `cursor` places it at the end of `properties.text`.
|
|
84
|
+
* Restores immediately and again in `afterRender`.
|
|
85
|
+
* @param {{ cursor?: number, itemIndex?: number }} [options] `itemIndex`
|
|
86
|
+
* targets a specific item for multi-item blocks (e.g. `List.setCursor`);
|
|
87
|
+
* this flat/single-item base has only one item, so it accepts
|
|
88
|
+
* `itemIndex` for a consistent cross-block `setCursor` contract (generic
|
|
89
|
+
* callers like `restoreCaretAfterMerge` forward it blindly) but ignores it.
|
|
90
|
+
* @returns {void}
|
|
91
|
+
*/
|
|
92
|
+
setCursor({ cursor }?: {
|
|
93
|
+
cursor?: number;
|
|
94
|
+
itemIndex?: number;
|
|
95
|
+
}): void;
|
|
96
|
+
/**
|
|
97
|
+
* Handle keydown while this block is focused. Override in subclasses.
|
|
98
|
+
* Call `event.preventDefault()` when the block owns the key so default
|
|
99
|
+
* Backspace merge / insert is skipped (`unlessPrevented`).
|
|
100
|
+
* @param {import('#src/types.mjs').KeydownEvent} event
|
|
101
|
+
* @returns {void}
|
|
102
|
+
*/
|
|
103
|
+
onKeydown(_event: any): void;
|
|
104
|
+
}
|
|
105
|
+
import { AbstractComponent } from '../AbstractComponent.mjs';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block insert/slash register with namespace → constructor lookup.
|
|
3
|
+
*/
|
|
4
|
+
export class BlockRegister extends Register {
|
|
5
|
+
/**
|
|
6
|
+
* NAMESPACE → Constructor from block register rows (first registration wins).
|
|
7
|
+
* @param {import('#src/types.mjs').BlockComponentRegistration[]} registrations
|
|
8
|
+
* @returns {Map<string, import('#src/types.mjs').BlockConstructor>}
|
|
9
|
+
*/
|
|
10
|
+
static buildNamespaceMap(registrations: import("#src/types.mjs").BlockComponentRegistration[]): Map<string, import("#src/types.mjs").BlockConstructor>;
|
|
11
|
+
/**
|
|
12
|
+
* @param {import('#src/types.mjs').Mrakomor} mrakomor
|
|
13
|
+
*/
|
|
14
|
+
constructor(mrakomor: import("#src/types.mjs").Mrakomor);
|
|
15
|
+
/**
|
|
16
|
+
* @param {*} raw
|
|
17
|
+
* @param {number} index
|
|
18
|
+
* @returns {import('#src/types.mjs').BlockComponentRegistration | null}
|
|
19
|
+
*/
|
|
20
|
+
normalize(raw: any, index: number): import("#src/types.mjs").BlockComponentRegistration | null;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve block component by dotted namespace (first register row wins).
|
|
23
|
+
* @param {string} namespace
|
|
24
|
+
* @returns {import('#src/types.mjs').BlockConstructor | undefined}
|
|
25
|
+
*/
|
|
26
|
+
getByNamespace(namespace: string): import("#src/types.mjs").BlockConstructor | undefined;
|
|
27
|
+
#private;
|
|
28
|
+
}
|
|
29
|
+
import Register from '#src/register/Register.mjs';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-text embed block (CMS external widget). Hosts opaque content in
|
|
3
|
+
* `this.root` and a thin contenteditable strip so the block can take focus.
|
|
4
|
+
* Atomic: empty neighbor Backspace removes this block.
|
|
5
|
+
*/
|
|
6
|
+
export class ExternalComponent extends BlockComponent {
|
|
7
|
+
/**
|
|
8
|
+
* Schema for external block state (`component`, `componentId`, empty props/settings).
|
|
9
|
+
* @param {Element | null | undefined} node
|
|
10
|
+
* @returns {*}
|
|
11
|
+
*/
|
|
12
|
+
static getStateSchema(node: Element | null | undefined): any;
|
|
13
|
+
/**
|
|
14
|
+
* Atomic blocks never merge with a neighbor (Backspace removes them via
|
|
15
|
+
* `isAtomic` instead of appending text into them).
|
|
16
|
+
* @returns {null}
|
|
17
|
+
*/
|
|
18
|
+
static appendExchangeState(): null;
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} props
|
|
21
|
+
*/
|
|
22
|
+
constructor(props: object);
|
|
23
|
+
/** @type {Element | null} host for embed content */
|
|
24
|
+
root: Element | null;
|
|
25
|
+
/**
|
|
26
|
+
* Current signal value (external blocks do not re-parse from DOM).
|
|
27
|
+
* @returns {import('#src/types.mjs').BlockState}
|
|
28
|
+
*/
|
|
29
|
+
getState(): import("#src/types.mjs").BlockState;
|
|
30
|
+
/**
|
|
31
|
+
* Opaque container plus a focusable strip for selection / Enter.
|
|
32
|
+
* @returns {*}
|
|
33
|
+
*/
|
|
34
|
+
render(): any;
|
|
35
|
+
/**
|
|
36
|
+
* Suppress most typing; Enter inserts an empty paragraph below.
|
|
37
|
+
* @param {import('#src/types.mjs').KeydownEvent} event
|
|
38
|
+
* @returns {void}
|
|
39
|
+
*/
|
|
40
|
+
onKeydown(event: import("#src/types.mjs").KeydownEvent): void;
|
|
41
|
+
}
|
|
42
|
+
import { BlockComponent } from './BlockComponent.mjs';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Each` item equals for `#content` blocks: `deepEqual`, unless the live instance is stale.
|
|
3
|
+
* @param {import('#src/types.mjs').Mrakomor} mrakomor
|
|
4
|
+
* @returns {(prev: import('#src/types.mjs').BlockState, next: import('#src/types.mjs').BlockState) => boolean}
|
|
5
|
+
*/
|
|
6
|
+
export function createBlockItemEquals(mrakomor: import("#src/types.mjs").Mrakomor): (prev: import("#src/types.mjs").BlockState, next: import("#src/types.mjs").BlockState) => boolean;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('#src/types.mjs').BlockState} BlockState
|
|
3
|
+
* @typedef {import('#src/types.mjs').RichTextState} RichTextState
|
|
4
|
+
* @typedef {import('#src/types.mjs').InlineStyleRange} InlineStyleRange
|
|
5
|
+
* @typedef {import('#src/types.mjs').EntityRange} EntityRange
|
|
6
|
+
* @typedef {import('#src/types.mjs').RichTextComponents} RichTextComponents
|
|
7
|
+
* @typedef {import('#src/types.mjs').SplitBlockResult} SplitBlockResult
|
|
8
|
+
* @typedef {import('#src/types.mjs').ContentMutation} ContentMutation
|
|
9
|
+
* @typedef {import('#src/types.mjs').ClipboardSelection} ClipboardSelection
|
|
10
|
+
* @typedef {import('#src/types.mjs').RichTextBlockInstance} RichTextBlockInstance
|
|
11
|
+
* @typedef {import('#src/types.mjs').RangeMeta} RangeMeta
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Empty contenteditable often serializes as a lone newline; treat that as `''`.
|
|
15
|
+
* @param {string | null | undefined} text
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeBlockText(text: string | null | undefined): string;
|
|
19
|
+
/**
|
|
20
|
+
* Build a generic cross-component exchange state (see
|
|
21
|
+
* `BlockComponent.toExchangeState` / `fromExchangeState`). The only
|
|
22
|
+
* text-shaped field is `properties.items` — always an array. Pass
|
|
23
|
+
* `properties.items` directly to preserve it as-is (real multi-item
|
|
24
|
+
* producers like `List.toExchangeState`, including an intentionally empty
|
|
25
|
+
* array); otherwise (`items` absent) `properties.text`/`inlineStyleRanges`/
|
|
26
|
+
* `entityRanges` (or their absence) are wrapped into a single-item array —
|
|
27
|
+
* this is the convenience path most callers use to build a flat exchange
|
|
28
|
+
* state. `component`/`originComponent` record the target/source namespace.
|
|
29
|
+
* @param {Partial<import('#src/types.mjs').ExchangeBlockState> & { properties?: { items?: import('#src/types.mjs').ExchangeItem[] } & Record<string, *> }} [overrides]
|
|
30
|
+
* @returns {import('#src/types.mjs').ExchangeBlockState}
|
|
31
|
+
*/
|
|
32
|
+
export function createExchangeState({ component, originComponent, properties, settings, }?: Partial<import("#src/types.mjs").ExchangeBlockState> & {
|
|
33
|
+
properties?: {
|
|
34
|
+
items?: import("#src/types.mjs").ExchangeItem[];
|
|
35
|
+
} & Record<string, any>;
|
|
36
|
+
}): import("#src/types.mjs").ExchangeBlockState;
|
|
37
|
+
/**
|
|
38
|
+
* Collapse multiple exchange items into one flat text-shaped item:
|
|
39
|
+
* concatenates each item's text (`'\n'` between items) and shifts each
|
|
40
|
+
* item's inline/entity ranges by the joined text's length so far. Used by
|
|
41
|
+
* `BlockComponent`'s default `appendExchangeState` to merge a possibly
|
|
42
|
+
* multi-item incoming exchange state (e.g. a List being merged into a flat
|
|
43
|
+
* text block) down to the single flat shape a text block can hold. A
|
|
44
|
+
* single-item input (the common case — merging two flat blocks) passes
|
|
45
|
+
* through unchanged (no separator, no shift).
|
|
46
|
+
* @param {import('#src/types.mjs').ExchangeItem[] | null | undefined} items
|
|
47
|
+
* @returns {{ text: string, inlineStyleRanges: InlineStyleRange[], entityRanges: EntityRange[] }}
|
|
48
|
+
*/
|
|
49
|
+
export function joinExchangeItems(items: import("#src/types.mjs").ExchangeItem[] | null | undefined): {
|
|
50
|
+
text: string;
|
|
51
|
+
inlineStyleRanges: InlineStyleRange[];
|
|
52
|
+
entityRanges: EntityRange[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Wrap a single item's `text`/`inlineStyleRanges`/`entityRanges` (e.g. one
|
|
56
|
+
* List row) as the flat rich-text shape accepted by
|
|
57
|
+
* `splitStateByCursor`/`deepMergeBlockState`'s per-item helpers. Distinct
|
|
58
|
+
* from `createExchangeState`, which builds a full `properties.items[]`
|
|
59
|
+
* `ExchangeBlockState` — this is the single-item building block used to
|
|
60
|
+
* construct one entry of that array, or to feed the flat-shape line-split
|
|
61
|
+
* helpers with one item's data (e.g. splitting one list item at the caret).
|
|
62
|
+
* @param {Partial<import('#src/types.mjs').ExchangeItem> | null | undefined} item
|
|
63
|
+
* @returns {RichTextState}
|
|
64
|
+
*/
|
|
65
|
+
export function wrapExchangeItem(item: Partial<import("#src/types.mjs").ExchangeItem> | null | undefined): RichTextState;
|
|
66
|
+
/**
|
|
67
|
+
* Write a patched item's ranges back into a block's own persisted state by
|
|
68
|
+
* round-tripping through its own `toExchangeState`/`fromExchangeState` pair
|
|
69
|
+
* (same-type, in-place patch — NOT a cross-type conversion). Shared by the
|
|
70
|
+
* generic INSERT_INLINE_STYLE_RANGE/INSERT_ENTITY_RANGE handlers so every
|
|
71
|
+
* block type (flat text blocks and List) writes through one path instead of
|
|
72
|
+
* each implementing its own mutation. Relies on `Constructor.toExchangeState`
|
|
73
|
+
* including a stable per-item `componentId` for blocks with per-item DOM
|
|
74
|
+
* identity (e.g. List's `<li>`s), so `fromExchangeState` doesn't mint fresh
|
|
75
|
+
* ids on every keystroke. Returns `state` unchanged when `itemIndex` is out
|
|
76
|
+
* of range.
|
|
77
|
+
* @param {import('#src/types.mjs').BlockConstructor} Constructor
|
|
78
|
+
* @param {import('#src/types.mjs').BlockState} state
|
|
79
|
+
* @param {number} itemIndex
|
|
80
|
+
* @param {Partial<import('#src/types.mjs').ExchangeItem>} patch
|
|
81
|
+
* @returns {import('#src/types.mjs').BlockState}
|
|
82
|
+
*/
|
|
83
|
+
export function patchBlockExchangeItem(Constructor: import("#src/types.mjs").BlockConstructor, state: import("#src/types.mjs").BlockState, itemIndex: number, patch: Partial<import("#src/types.mjs").ExchangeItem>): import("#src/types.mjs").BlockState;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the active exchange item (and its element/index/Constructor) for
|
|
86
|
+
* the block at `blockElement`/`range`, via the generic `toExchangeState` +
|
|
87
|
+
* `getSelectableItemForRange` contract. Works for ANY block type without a
|
|
88
|
+
* block-specific special case: a flat text block (Paragraph/Headline)
|
|
89
|
+
* always resolves item 0; a List resolves the active `<li>`'s own index.
|
|
90
|
+
* Falls back to item 0 when `range` doesn't resolve to a marked item at all
|
|
91
|
+
* (e.g. a stale/lost selection) — a best-effort default appropriate for
|
|
92
|
+
* single-item flat blocks; List's own key handlers use
|
|
93
|
+
* `getSelectableItemForRange` directly instead and bail (no-op) on the same
|
|
94
|
+
* failure, since guessing item 0 there would silently apply an action to
|
|
95
|
+
* the wrong `<li>`. Shared by the default plugin's insert-range/
|
|
96
|
+
* formatting-query handlers AND `Tooltip` (called directly, not through an
|
|
97
|
+
* emitter round trip — this is a pure read/compute with no plugin ever
|
|
98
|
+
* needing to intercept it; a block that needs different resolution
|
|
99
|
+
* overrides `toExchangeState` instead, the existing per-block extension
|
|
100
|
+
* point). See `patchBlockExchangeItem` for the matching write-back half of
|
|
101
|
+
* this contract.
|
|
102
|
+
* @param {import('#src/types.mjs').Mrakomor} mrakomor
|
|
103
|
+
* @param {Element} blockElement
|
|
104
|
+
* @param {Range | null} range
|
|
105
|
+
* @returns {{ Constructor: import('#src/types.mjs').BlockConstructor, current: import('#src/types.mjs').BlockState, element: Element, itemIndex: number, item: import('#src/types.mjs').ExchangeItem } | null}
|
|
106
|
+
*/
|
|
107
|
+
export function resolveActiveExchangeItem(mrakomor: import("#src/types.mjs").Mrakomor, blockElement: Element, range: Range | null): {
|
|
108
|
+
Constructor: import("#src/types.mjs").BlockConstructor;
|
|
109
|
+
current: import("#src/types.mjs").BlockState;
|
|
110
|
+
element: Element;
|
|
111
|
+
itemIndex: number;
|
|
112
|
+
item: import("#src/types.mjs").ExchangeItem;
|
|
113
|
+
} | null;
|
|
114
|
+
/**
|
|
115
|
+
* Restore caret after a block merge (`afterRender` is a macrotask). Goes
|
|
116
|
+
* through the block instance's own `setCursor` (e.g. `List.setCursor`
|
|
117
|
+
* targets a specific `<li>`) instead of `getBlockContentRoot` +
|
|
118
|
+
* `setCursorPosition` directly — a multi-item block's content root (e.g.
|
|
119
|
+
* List's `<ul>/<ol>`) counts characters from the START of the whole
|
|
120
|
+
* concatenated text, landing in the wrong item otherwise. Shared by
|
|
121
|
+
* `defaultMrakomorPlugin`'s `onMergeBlockElement` and the list plugin's own
|
|
122
|
+
* adjacent-list re-merge Backspace handler.
|
|
123
|
+
* @param {{
|
|
124
|
+
* mrakomor: import('#src/types.mjs').Mrakomor,
|
|
125
|
+
* resultIndex: number,
|
|
126
|
+
* cursor?: number,
|
|
127
|
+
* fallbackBlockElement?: Element | null,
|
|
128
|
+
* itemIndex?: number,
|
|
129
|
+
* }} options `resultIndex` indexes the merged block in the (already
|
|
130
|
+
* committed) content array; `cursor` is the caret offset in the merged
|
|
131
|
+
* block at the join; `fallbackBlockElement` is used before `afterRender`
|
|
132
|
+
* has synced `#blockElements`, or if `resultIndex` cannot resolve one;
|
|
133
|
+
* `itemIndex` is for multi-item blocks (e.g. List) — the index of the
|
|
134
|
+
* item the join landed in, passed through as `setCursor`'s `itemIndex`
|
|
135
|
+
* field (part of `BlockComponent.setCursor`'s shared contract, ignored by
|
|
136
|
+
* single-item blocks) so a merge that also appended items after the
|
|
137
|
+
* joined one still targets the right item instead of the block's
|
|
138
|
+
* default (e.g. last item)
|
|
139
|
+
* @returns {void}
|
|
140
|
+
*/
|
|
141
|
+
export function restoreCaretAfterMerge({ mrakomor, resultIndex, cursor, fallbackBlockElement, itemIndex, }: {
|
|
142
|
+
mrakomor: import("#src/types.mjs").Mrakomor;
|
|
143
|
+
resultIndex: number;
|
|
144
|
+
cursor?: number;
|
|
145
|
+
fallbackBlockElement?: Element | null;
|
|
146
|
+
itemIndex?: number;
|
|
147
|
+
}): void;
|
|
148
|
+
/**
|
|
149
|
+
* Paragraph or headline — blocks with `properties.text`.
|
|
150
|
+
* @param {BlockState} state
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
export function isTextBlock(state: BlockState): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* List block — `properties.items` is an array.
|
|
156
|
+
* @param {BlockState} state
|
|
157
|
+
* @returns {boolean}
|
|
158
|
+
*/
|
|
159
|
+
export function isListBlock(state: BlockState): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Two text blocks of the same component can merge on paste/delete.
|
|
162
|
+
* @param {BlockState} left
|
|
163
|
+
* @param {BlockState} right
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
166
|
+
export function canMergeBlocks(left: BlockState, right: BlockState): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* Nested vDOM for one text segment with all active inline styles and entity
|
|
169
|
+
* ranges applied (entity wraps inline).
|
|
170
|
+
*
|
|
171
|
+
* @param {string} substring
|
|
172
|
+
* @param {InlineStyleRange[]} inlineRanges
|
|
173
|
+
* @param {EntityRange[]} entityRanges
|
|
174
|
+
* @param {RichTextComponents} components
|
|
175
|
+
* @returns {string | Node | object}
|
|
176
|
+
*/
|
|
177
|
+
export function buildRichTextSegment(substring: string, inlineRanges: InlineStyleRange[], entityRanges: EntityRange[], { inlineStyleComponents, entityRangeComponents }: RichTextComponents): string | Node | object;
|
|
178
|
+
/**
|
|
179
|
+
* Render paragraph rich text without duplicating overlapping ranges.
|
|
180
|
+
*
|
|
181
|
+
* @param {RichTextState} state block state with `properties.text` / ranges
|
|
182
|
+
* @param {RichTextComponents} components
|
|
183
|
+
* @returns {DocumentFragment}
|
|
184
|
+
*/
|
|
185
|
+
export function buildRichTextFragment(state: RichTextState, { inlineStyleComponents, entityRangeComponents }: RichTextComponents): DocumentFragment;
|
|
186
|
+
/**
|
|
187
|
+
* Walk block child nodes and collect inline/entity ranges with correct offsets.
|
|
188
|
+
* Recurses into matched inline/entity elements so nested markup round-trips.
|
|
189
|
+
*
|
|
190
|
+
* @param {NodeList | Node[]} childNodes
|
|
191
|
+
* @param {RichTextState} state mutated; ranges are pushed onto `properties`
|
|
192
|
+
* @param {number} offset starting character index
|
|
193
|
+
* @param {RichTextComponents} components
|
|
194
|
+
* @returns {number} character index after the last consumed node
|
|
195
|
+
*/
|
|
196
|
+
export function parseRichTextChildNodes(childNodes: NodeList | Node[], state: RichTextState, offset: number, { inlineStyleComponents, entityRangeComponents }: RichTextComponents): number;
|
|
197
|
+
/**
|
|
198
|
+
* Concatenate two text-block states: `original` text then `newData` text,
|
|
199
|
+
* shifting `newData` ranges by the original length.
|
|
200
|
+
*
|
|
201
|
+
* @param {RichTextState} original
|
|
202
|
+
* @param {RichTextState} newData
|
|
203
|
+
* @returns {{ state: RichTextState, cursor: number }} merged state and caret at the join
|
|
204
|
+
*/
|
|
205
|
+
export function deepMergeBlockState(original: RichTextState, newData: RichTextState): {
|
|
206
|
+
state: RichTextState;
|
|
207
|
+
cursor: number;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Trim selection across blocks and merge remnants into one block.
|
|
211
|
+
* Keeps the start block's identity; removes indexes startIndex..endIndex.
|
|
212
|
+
*
|
|
213
|
+
* @param {BlockState[]} content
|
|
214
|
+
* @param {ClipboardSelection} selection
|
|
215
|
+
* @returns {ContentMutation}
|
|
216
|
+
*/
|
|
217
|
+
export function mergeBlocksAcrossSelection(content: BlockState[], { startIndex, endIndex, startOffset, endOffset }: ClipboardSelection): ContentMutation;
|
|
218
|
+
/**
|
|
219
|
+
* Split a text block at `cursor`: before keeps the original `componentId`,
|
|
220
|
+
* after gets a new one. Inline and entity ranges are clipped/shifted.
|
|
221
|
+
*
|
|
222
|
+
* @param {RichTextState} state
|
|
223
|
+
* @param {number} cursor
|
|
224
|
+
* @returns {SplitBlockResult}
|
|
225
|
+
*/
|
|
226
|
+
export function splitStateByCursor(state: RichTextState, cursor: number): SplitBlockResult;
|
|
227
|
+
/**
|
|
228
|
+
* Split a text block at the caret and insert the remainder as a new block.
|
|
229
|
+
* Used by Paragraph and Headline on Enter — not a BlockComponent protocol.
|
|
230
|
+
*
|
|
231
|
+
* @param {RichTextBlockInstance} block
|
|
232
|
+
* @param {import('#src/types.mjs').KeydownEvent} event
|
|
233
|
+
* @param {{ namespace?: string }} [options]
|
|
234
|
+
* @returns {boolean} whether Enter was handled
|
|
235
|
+
*/
|
|
236
|
+
export function splitAtCursorAndInsert(block: RichTextBlockInstance, event: import("#src/types.mjs").KeydownEvent, { namespace }?: {
|
|
237
|
+
namespace?: string;
|
|
238
|
+
}): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Insert an empty paragraph below the current selection (non-splitting blocks).
|
|
241
|
+
* @param {{ mrakomor: import('#src/types.mjs').Mrakomor }} block
|
|
242
|
+
* @param {import('#src/types.mjs').KeydownEvent} event
|
|
243
|
+
* @returns {boolean} whether Enter was handled
|
|
244
|
+
*/
|
|
245
|
+
export function insertEmptyParagraphBelow(block: {
|
|
246
|
+
mrakomor: import("#src/types.mjs").Mrakomor;
|
|
247
|
+
}, event: import("#src/types.mjs").KeydownEvent): boolean;
|
|
248
|
+
/**
|
|
249
|
+
* Parse a rich-text block from DOM: schema defaults plus inline/entity ranges
|
|
250
|
+
* from child nodes.
|
|
251
|
+
*
|
|
252
|
+
* @param {RichTextBlockInstance} block
|
|
253
|
+
* @param {Element | null | undefined} node
|
|
254
|
+
* @returns {RichTextState}
|
|
255
|
+
*/
|
|
256
|
+
export function parseRichTextBlock(block: RichTextBlockInstance, node: Element | null | undefined): RichTextState;
|
|
257
|
+
/**
|
|
258
|
+
* Computes text-range metadata (`offset` and `length`) for a DOM `Range`
|
|
259
|
+
* relative to the concatenated text content of a root element.
|
|
260
|
+
*
|
|
261
|
+
* @function getEntityRangeMeta
|
|
262
|
+
* @param {Element} element - Root element used as the text traversal boundary.
|
|
263
|
+
* @param {Range} range - DOM Range whose start position is used to compute the offset.
|
|
264
|
+
* @returns {RangeMeta} Range metadata:
|
|
265
|
+
* - `offset`: zero-based character index from the start of `element` text content to `range.startContainer + range.startOffset`
|
|
266
|
+
* - `length`: selected character count (`range.endOffset - range.startOffset`)
|
|
267
|
+
*
|
|
268
|
+
*/
|
|
269
|
+
export function getEntityRangeMeta(element: Element, range: Range): RangeMeta;
|
|
270
|
+
/**
|
|
271
|
+
* Toggle an inline style over a selection: remove a covering same-style range,
|
|
272
|
+
* otherwise append a new range.
|
|
273
|
+
* @param {InlineStyleRange[]} ranges
|
|
274
|
+
* @param {InlineStyleRange} next
|
|
275
|
+
* @returns {InlineStyleRange[]}
|
|
276
|
+
*/
|
|
277
|
+
export function toggleInlineStyleRange(ranges: InlineStyleRange[], { style, offset, length }: InlineStyleRange): InlineStyleRange[];
|
|
278
|
+
/**
|
|
279
|
+
* Toggle or apply an entity range exclusively: exact same-span+same-type removes
|
|
280
|
+
* that range; otherwise carve overlapping ranges then append. Adjacent spans are
|
|
281
|
+
* allowed. Returns `null` when length is 0.
|
|
282
|
+
* @param {EntityRange[]} ranges
|
|
283
|
+
* @param {EntityRange} entityRangeState
|
|
284
|
+
* @returns {EntityRange[] | null}
|
|
285
|
+
*/
|
|
286
|
+
export function toggleEntityRange(ranges: EntityRange[], entityRangeState: EntityRange): EntityRange[] | null;
|
|
287
|
+
/**
|
|
288
|
+
* Toggle an inline style range for a selection against `element`. Returns the
|
|
289
|
+
* next `inlineStyleRanges` array, or `null` when the selection is collapsed.
|
|
290
|
+
* @param {InlineStyleRange[] | undefined} ranges
|
|
291
|
+
* @param {Element} element - root used to compute the range's text offset/length
|
|
292
|
+
* @param {Range} range
|
|
293
|
+
* @param {string} style
|
|
294
|
+
* @returns {InlineStyleRange[] | null}
|
|
295
|
+
*/
|
|
296
|
+
export function applyInlineStyleRange(ranges: InlineStyleRange[] | undefined, element: Element, range: Range, style: string): InlineStyleRange[] | null;
|
|
297
|
+
/**
|
|
298
|
+
* Toggle an entity range for a selection against `element`. Returns the next
|
|
299
|
+
* `entityRanges` array, or `null` when the selection is collapsed.
|
|
300
|
+
* @param {EntityRange[] | undefined} ranges
|
|
301
|
+
* @param {Element} element - root used to compute the range's text offset/length
|
|
302
|
+
* @param {Range} range
|
|
303
|
+
* @param {{ namespace: string, entityData?: object }} options
|
|
304
|
+
* @returns {EntityRange[] | null}
|
|
305
|
+
*/
|
|
306
|
+
export function applyEntityRange(ranges: EntityRange[] | undefined, element: Element, range: Range, { namespace, entityData }: {
|
|
307
|
+
namespace: string;
|
|
308
|
+
entityData?: object;
|
|
309
|
+
}): EntityRange[] | null;
|
|
310
|
+
export type BlockState = import("#src/types.mjs").BlockState;
|
|
311
|
+
export type RichTextState = import("#src/types.mjs").RichTextState;
|
|
312
|
+
export type InlineStyleRange = import("#src/types.mjs").InlineStyleRange;
|
|
313
|
+
export type EntityRange = import("#src/types.mjs").EntityRange;
|
|
314
|
+
export type RichTextComponents = import("#src/types.mjs").RichTextComponents;
|
|
315
|
+
export type SplitBlockResult = import("#src/types.mjs").SplitBlockResult;
|
|
316
|
+
export type ContentMutation = import("#src/types.mjs").ContentMutation;
|
|
317
|
+
export type ClipboardSelection = import("#src/types.mjs").ClipboardSelection;
|
|
318
|
+
export type RichTextBlockInstance = import("#src/types.mjs").RichTextBlockInstance;
|
|
319
|
+
export type RangeMeta = import("#src/types.mjs").RangeMeta;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class EntityRangeElement extends Component {
|
|
2
|
+
static get nodeName(): string;
|
|
3
|
+
static get NAMESPACE(): string;
|
|
4
|
+
static match(node: any): boolean;
|
|
5
|
+
static getStateSchema(_node: any): import("@esmj/schema").SchemaInterface<unknown, unknown>;
|
|
6
|
+
constructor(props: any);
|
|
7
|
+
mrakomor: any;
|
|
8
|
+
element: {
|
|
9
|
+
get: () => null;
|
|
10
|
+
set: (element: any) => null;
|
|
11
|
+
[Symbol.toPrimitive](): string;
|
|
12
|
+
};
|
|
13
|
+
render(): any;
|
|
14
|
+
}
|
|
15
|
+
import { Component } from '@esmj/dom';
|