@markii/stdlib 0.12.1 → 0.14.0
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/diagnostics.d.ts +45 -0
- package/dist/diagnostics.js +34 -0
- package/dist/editor/complete/completion.d.ts +15 -0
- package/dist/editor/complete/completion.js +322 -0
- package/dist/editor/complete/directive-context.d.ts +79 -0
- package/dist/editor/complete/directive-context.js +326 -0
- package/dist/editor/complete/documentation.d.ts +31 -0
- package/dist/editor/complete/documentation.js +92 -0
- package/dist/editor/complete/index.d.ts +9 -0
- package/dist/editor/complete/index.js +1 -0
- package/dist/editor/complete/types.d.ts +82 -0
- package/dist/editor/complete/types.js +12 -0
- package/dist/editor/fences/container-fences.d.ts +130 -0
- package/dist/editor/fences/container-fences.js +306 -0
- package/dist/editor/index.d.ts +32 -0
- package/dist/editor/index.js +6 -0
- package/dist/editor/insert/component-catalog.d.ts +95 -0
- package/dist/editor/insert/component-catalog.js +51 -0
- package/dist/editor/insert/component-skeleton.d.ts +58 -0
- package/dist/editor/insert/component-skeleton.js +85 -0
- package/dist/editor/insert/first-sentence.d.ts +19 -0
- package/dist/editor/insert/first-sentence.js +24 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/interactive.d.ts +15 -0
- package/dist/interactive.js +15 -0
- package/package.json +6 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Insert Component" (GitHub issue #17, slice 1): given a component's
|
|
3
|
+
* directive form and its required attributes, produces the exact directive
|
|
4
|
+
* text a host inserts at the cursor, plus where the cursor should land
|
|
5
|
+
* afterward so the author's next keystroke lands somewhere useful — inside
|
|
6
|
+
* the body for a component with nothing required, inside the first required
|
|
7
|
+
* attribute's quotes otherwise, since that is the first thing the author
|
|
8
|
+
* must fill in.
|
|
9
|
+
*
|
|
10
|
+
* Pure and host-neutral: no editor API, no knowledge of `vscode` or
|
|
11
|
+
* `obsidian`. Both hosts turn `ComponentSkeleton.cursorOffset` into their
|
|
12
|
+
* own editor's line/column (or offset) type using `offsetToLineColumn`
|
|
13
|
+
* below.
|
|
14
|
+
*/
|
|
15
|
+
import type { ComponentKind } from '../../contracts.js';
|
|
16
|
+
/** The text to insert, and where the cursor should land within it. */
|
|
17
|
+
export interface ComponentSkeleton {
|
|
18
|
+
/** The exact text to insert at the cursor. */
|
|
19
|
+
readonly text: string;
|
|
20
|
+
/** Character offset within `text` where the cursor should land after insertion. */
|
|
21
|
+
readonly cursorOffset: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds the skeleton for one directive. `requiredAttributes` is the
|
|
25
|
+
* contract's required attribute names, in the contract's own declared
|
|
26
|
+
* order (`@markii/stdlib`'s `ComponentContract.attributes` key order) —
|
|
27
|
+
* only required attributes are ever pre-filled; an optional attribute is
|
|
28
|
+
* never emitted, keeping the inserted text as small as the directive can
|
|
29
|
+
* legally be.
|
|
30
|
+
*
|
|
31
|
+
* Directive forms match docs/format.md's three spellings:
|
|
32
|
+
* - `container`: `:::NAME{...}\n\n:::`
|
|
33
|
+
* - `leaf`: `::NAME{...}`
|
|
34
|
+
* - `inline`: `:NAME[...]{...}`
|
|
35
|
+
*
|
|
36
|
+
* A directive with no required attributes of its own gets no `{}` clause at
|
|
37
|
+
* all, in every form: an empty brace pair is pointless noise the author
|
|
38
|
+
* would otherwise have to delete by hand, the same reasoning the inline
|
|
39
|
+
* form already applied (`[...]` is already its content slot). A container
|
|
40
|
+
* with nothing required lands the cursor in its empty body, between the
|
|
41
|
+
* fences; a leaf lands it right after the name, since there is nowhere else
|
|
42
|
+
* useful to put it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function componentSkeleton(directiveName: string, kind: ComponentKind, requiredAttributes: readonly string[]): ComponentSkeleton;
|
|
45
|
+
/** Zero-based line and column, both counting from 0. */
|
|
46
|
+
export interface LineColumn {
|
|
47
|
+
readonly line: number;
|
|
48
|
+
readonly column: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Converts a character offset within `text` into a zero-based line/column,
|
|
52
|
+
* counting `\n` as the only line terminator (matching every directive
|
|
53
|
+
* skeleton this module produces, which never contains `\r`). An
|
|
54
|
+
* out-of-range offset (negative, or past the end of `text`) is clamped to
|
|
55
|
+
* the end of `text` rather than throwing — a defensive posture matching
|
|
56
|
+
* this package's other pure helpers.
|
|
57
|
+
*/
|
|
58
|
+
export declare function offsetToLineColumn(text: string, offset: number): LineColumn;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the skeleton for one directive. `requiredAttributes` is the
|
|
3
|
+
* contract's required attribute names, in the contract's own declared
|
|
4
|
+
* order (`@markii/stdlib`'s `ComponentContract.attributes` key order) —
|
|
5
|
+
* only required attributes are ever pre-filled; an optional attribute is
|
|
6
|
+
* never emitted, keeping the inserted text as small as the directive can
|
|
7
|
+
* legally be.
|
|
8
|
+
*
|
|
9
|
+
* Directive forms match docs/format.md's three spellings:
|
|
10
|
+
* - `container`: `:::NAME{...}\n\n:::`
|
|
11
|
+
* - `leaf`: `::NAME{...}`
|
|
12
|
+
* - `inline`: `:NAME[...]{...}`
|
|
13
|
+
*
|
|
14
|
+
* A directive with no required attributes of its own gets no `{}` clause at
|
|
15
|
+
* all, in every form: an empty brace pair is pointless noise the author
|
|
16
|
+
* would otherwise have to delete by hand, the same reasoning the inline
|
|
17
|
+
* form already applied (`[...]` is already its content slot). A container
|
|
18
|
+
* with nothing required lands the cursor in its empty body, between the
|
|
19
|
+
* fences; a leaf lands it right after the name, since there is nowhere else
|
|
20
|
+
* useful to put it.
|
|
21
|
+
*/
|
|
22
|
+
export function componentSkeleton(directiveName, kind, requiredAttributes) {
|
|
23
|
+
const attributesClause = attributeClause(requiredAttributes);
|
|
24
|
+
if (kind === 'container') {
|
|
25
|
+
if (requiredAttributes.length === 0) {
|
|
26
|
+
const prefix = `:::${directiveName}\n`;
|
|
27
|
+
return { text: `${prefix}\n:::`, cursorOffset: prefix.length };
|
|
28
|
+
}
|
|
29
|
+
const prefix = `:::${directiveName}${attributesClause}\n`;
|
|
30
|
+
return {
|
|
31
|
+
text: `${prefix}\n:::`,
|
|
32
|
+
cursorOffset: firstAttributeQuoteOffset(prefix),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (kind === 'leaf') {
|
|
36
|
+
if (requiredAttributes.length === 0) {
|
|
37
|
+
const text = `::${directiveName}`;
|
|
38
|
+
return { text, cursorOffset: text.length };
|
|
39
|
+
}
|
|
40
|
+
const text = `::${directiveName}${attributesClause}`;
|
|
41
|
+
return { text, cursorOffset: firstAttributeQuoteOffset(text) };
|
|
42
|
+
}
|
|
43
|
+
// inline
|
|
44
|
+
if (requiredAttributes.length === 0) {
|
|
45
|
+
const text = `:${directiveName}[]`;
|
|
46
|
+
return { text, cursorOffset: text.length - 1 };
|
|
47
|
+
}
|
|
48
|
+
const text = `:${directiveName}[]${attributesClause}`;
|
|
49
|
+
return { text, cursorOffset: firstAttributeQuoteOffset(text) };
|
|
50
|
+
}
|
|
51
|
+
/** `{attr=""}` for one or more required attribute names, space-separated in the given order. Empty input never reaches this — callers branch on `requiredAttributes.length === 0` first. */
|
|
52
|
+
function attributeClause(requiredAttributes) {
|
|
53
|
+
const parts = requiredAttributes.map((name) => `${name}=""`);
|
|
54
|
+
return `{${parts.join(' ')}}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The offset of the character right after the first `="` in `text` — i.e.
|
|
58
|
+
* inside the first attribute's quotes. `text` is always one this module
|
|
59
|
+
* just built from `attributeClause`, so `="` is always present.
|
|
60
|
+
*/
|
|
61
|
+
function firstAttributeQuoteOffset(text) {
|
|
62
|
+
const marker = '="';
|
|
63
|
+
const index = text.indexOf(marker);
|
|
64
|
+
return index + marker.length;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Converts a character offset within `text` into a zero-based line/column,
|
|
68
|
+
* counting `\n` as the only line terminator (matching every directive
|
|
69
|
+
* skeleton this module produces, which never contains `\r`). An
|
|
70
|
+
* out-of-range offset (negative, or past the end of `text`) is clamped to
|
|
71
|
+
* the end of `text` rather than throwing — a defensive posture matching
|
|
72
|
+
* this package's other pure helpers.
|
|
73
|
+
*/
|
|
74
|
+
export function offsetToLineColumn(text, offset) {
|
|
75
|
+
const clamped = Math.max(0, Math.min(offset, text.length));
|
|
76
|
+
let line = 0;
|
|
77
|
+
let lastNewlineEnd = 0;
|
|
78
|
+
for (let i = 0; i < clamped; i++) {
|
|
79
|
+
if (text[i] === '\n') {
|
|
80
|
+
line++;
|
|
81
|
+
lastNewlineEnd = i + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { line, column: clamped - lastNewlineEnd };
|
|
85
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates `description` to its first sentence, for a picker row that has
|
|
3
|
+
* no room for a contract's full prose. Shared by `./component-catalog.ts`
|
|
4
|
+
* (a component's picker-row description) and `../complete/documentation.ts`
|
|
5
|
+
* (an attribute's completion-row detail), so the sentence-boundary rule
|
|
6
|
+
* lives in exactly one place.
|
|
7
|
+
*
|
|
8
|
+
* A sentence boundary is a period followed by a space AND an uppercase
|
|
9
|
+
* letter, NOT merely ". ". Almost every contract description in
|
|
10
|
+
* `@markii/stdlib` contains "e.g. " before its first real sentence break,
|
|
11
|
+
* and a bare ". " split truncates 19 of the 20 standard components to a
|
|
12
|
+
* row reading "A titled panel, e.g." — the example, which is the useful
|
|
13
|
+
* half, cut off mid-phrase. The uppercase requirement steps over "e.g. `"
|
|
14
|
+
* and "... :::`" alike, because neither is followed by a capital.
|
|
15
|
+
*
|
|
16
|
+
* When no such boundary exists the whole string is returned unchanged: a
|
|
17
|
+
* description that is already one sentence is not worth mangling further.
|
|
18
|
+
*/
|
|
19
|
+
export declare function firstSentence(description: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncates `description` to its first sentence, for a picker row that has
|
|
3
|
+
* no room for a contract's full prose. Shared by `./component-catalog.ts`
|
|
4
|
+
* (a component's picker-row description) and `../complete/documentation.ts`
|
|
5
|
+
* (an attribute's completion-row detail), so the sentence-boundary rule
|
|
6
|
+
* lives in exactly one place.
|
|
7
|
+
*
|
|
8
|
+
* A sentence boundary is a period followed by a space AND an uppercase
|
|
9
|
+
* letter, NOT merely ". ". Almost every contract description in
|
|
10
|
+
* `@markii/stdlib` contains "e.g. " before its first real sentence break,
|
|
11
|
+
* and a bare ". " split truncates 19 of the 20 standard components to a
|
|
12
|
+
* row reading "A titled panel, e.g." — the example, which is the useful
|
|
13
|
+
* half, cut off mid-phrase. The uppercase requirement steps over "e.g. `"
|
|
14
|
+
* and "... :::`" alike, because neither is followed by a capital.
|
|
15
|
+
*
|
|
16
|
+
* When no such boundary exists the whole string is returned unchanged: a
|
|
17
|
+
* description that is already one sentence is not worth mangling further.
|
|
18
|
+
*/
|
|
19
|
+
export function firstSentence(description) {
|
|
20
|
+
const boundary = /\.\s+(?=[A-Z])/.exec(description);
|
|
21
|
+
if (boundary === null)
|
|
22
|
+
return description;
|
|
23
|
+
return description.slice(0, boundary.index + 1);
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export { ALIGN_PRESETS, LAYOUT_ATTRIBUTE_KEYS, LAYOUT_ATTRIBUTES, WIDTH_PRESETS,
|
|
|
3
3
|
export { TEXT_ALIGN_ATTRIBUTE, TEXT_ALIGN_COMPONENTS, TEXT_ALIGN_PRESETS, type TextAlignPreset, } from './text-align.js';
|
|
4
4
|
export { formatValue, isNumericLike, FORMAT_ATTRIBUTE, DECIMALS_ATTRIBUTE, type ValueFormat, } from './value-format.js';
|
|
5
5
|
export { deriveTableShape, type TableShape } from './table-shape.js';
|
|
6
|
+
export { reportDiagnostic, type DiagnosticEvent, type DiagnosticKind, type OnDiagnostic, } from './diagnostics.js';
|
|
7
|
+
export { INTERACTIVE_ATTRIBUTE } from './interactive.js';
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,5 @@ export { ALIGN_PRESETS, LAYOUT_ATTRIBUTE_KEYS, LAYOUT_ATTRIBUTES, WIDTH_PRESETS,
|
|
|
3
3
|
export { TEXT_ALIGN_ATTRIBUTE, TEXT_ALIGN_COMPONENTS, TEXT_ALIGN_PRESETS, } from './text-align.js';
|
|
4
4
|
export { formatValue, isNumericLike, FORMAT_ATTRIBUTE, DECIMALS_ATTRIBUTE, } from './value-format.js';
|
|
5
5
|
export { deriveTableShape } from './table-shape.js';
|
|
6
|
+
export { reportDiagnostic, } from './diagnostics.js';
|
|
7
|
+
export { INTERACTIVE_ATTRIBUTE } from './interactive.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The interactive-element marker contract (docs/integration.md): an editor
|
|
3
|
+
* host that renders Markii output as a live-preview widget needs to tell,
|
|
4
|
+
* from a DOM click target, whether it landed on a genuinely interactive
|
|
5
|
+
* control (a tab button, a details/script-marker summary) versus plain text
|
|
6
|
+
* — text reveals the raw source for editing, a control acts instead.
|
|
7
|
+
*
|
|
8
|
+
* `@markii/react` and `@markii/html` both put this attribute on every
|
|
9
|
+
* element they emit that is a real `<button>`, carries `role="tab"` or
|
|
10
|
+
* `role="button"`, or is a `<summary>`. It is also the contract a pack's own
|
|
11
|
+
* component is expected to follow for its own interactive elements — this
|
|
12
|
+
* package carries no enforcement (a pack is arbitrary code), only the one
|
|
13
|
+
* name every renderer and pack should agree on.
|
|
14
|
+
*/
|
|
15
|
+
export declare const INTERACTIVE_ATTRIBUTE = "data-mk-interactive";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The interactive-element marker contract (docs/integration.md): an editor
|
|
3
|
+
* host that renders Markii output as a live-preview widget needs to tell,
|
|
4
|
+
* from a DOM click target, whether it landed on a genuinely interactive
|
|
5
|
+
* control (a tab button, a details/script-marker summary) versus plain text
|
|
6
|
+
* — text reveals the raw source for editing, a control acts instead.
|
|
7
|
+
*
|
|
8
|
+
* `@markii/react` and `@markii/html` both put this attribute on every
|
|
9
|
+
* element they emit that is a real `<button>`, carries `role="tab"` or
|
|
10
|
+
* `role="button"`, or is a `<summary>`. It is also the contract a pack's own
|
|
11
|
+
* component is expected to follow for its own interactive elements — this
|
|
12
|
+
* package carries no enforcement (a pack is arbitrary code), only the one
|
|
13
|
+
* name every renderer and pack should agree on.
|
|
14
|
+
*/
|
|
15
|
+
export const INTERACTIVE_ATTRIBUTE = 'data-mk-interactive';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markii/stdlib",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Neutral, framework-agnostic standard component contracts for Mark (.mk.md) — the kind/attribute-schema seam every renderer (e.g. @markii/react) implements against. No React, zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
29
|
"import": "./dist/index.js",
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./editor": {
|
|
33
|
+
"types": "./dist/editor/index.d.ts",
|
|
34
|
+
"import": "./dist/editor/index.js",
|
|
35
|
+
"default": "./dist/editor/index.js"
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
38
|
"files": [
|