@markii/stdlib 0.13.0 → 0.15.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.
@@ -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.13.0",
3
+ "version": "0.15.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": [