@markii/html 0.9.0 → 0.10.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/components/callout.d.ts +2 -1
- package/dist/components/callout.js +4 -2
- package/dist/components/card.d.ts +3 -1
- package/dist/components/card.js +5 -2
- package/dist/components/cell.d.ts +4 -1
- package/dist/components/cell.js +7 -3
- package/dist/components/divider.d.ts +11 -7
- package/dist/components/divider.js +20 -8
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +23 -26
- package/dist/components/layout-wrapper.d.ts +30 -12
- package/dist/components/layout-wrapper.js +45 -12
- package/dist/components/row.d.ts +7 -3
- package/dist/components/row.js +9 -6
- package/dist/doc-css.generated.js +1 -1
- package/dist/layout.d.ts +25 -1
- package/dist/layout.js +49 -6
- package/dist/registry.d.ts +37 -0
- package/dist/registry.js +25 -0
- package/dist/render.js +26 -6
- package/package.json +4 -4
|
@@ -3,7 +3,8 @@ export type CalloutType = 'info' | 'warning' | 'danger';
|
|
|
3
3
|
/**
|
|
4
4
|
* `:::callout{type=info|warning|danger title="..."}` — a colored box for an
|
|
5
5
|
* aside, warning, or danger note. Unknown/missing `type` falls back to
|
|
6
|
-
* `info` rather than throwing.
|
|
6
|
+
* `info` rather than throwing. `text` (`left | center | right`) aligns the
|
|
7
|
+
* box's own text, header and body alike. Matches `@markii/react`'s `Callout` markup
|
|
7
8
|
* byte-for-byte so one stylesheet covers both renderers. No outer margin:
|
|
8
9
|
* the document stylesheet owns spacing between this and its siblings.
|
|
9
10
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withTextClass } from '../layout.js';
|
|
1
2
|
const CALLOUT_TYPES = ['info', 'warning', 'danger'];
|
|
2
3
|
const CALLOUT_ICONS = {
|
|
3
4
|
info: 'ℹ',
|
|
@@ -10,7 +11,8 @@ function isCalloutType(value) {
|
|
|
10
11
|
/**
|
|
11
12
|
* `:::callout{type=info|warning|danger title="..."}` — a colored box for an
|
|
12
13
|
* aside, warning, or danger note. Unknown/missing `type` falls back to
|
|
13
|
-
* `info` rather than throwing.
|
|
14
|
+
* `info` rather than throwing. `text` (`left | center | right`) aligns the
|
|
15
|
+
* box's own text, header and body alike. Matches `@markii/react`'s `Callout` markup
|
|
14
16
|
* byte-for-byte so one stylesheet covers both renderers. No outer margin:
|
|
15
17
|
* the document stylesheet owns spacing between this and its siblings.
|
|
16
18
|
*/
|
|
@@ -21,7 +23,7 @@ export const Callout = (attributes, childrenHtml, ctx) => {
|
|
|
21
23
|
const titleHtml = title
|
|
22
24
|
? `<span class="mk-callout__title">${ctx.esc(title)}</span>`
|
|
23
25
|
: '';
|
|
24
|
-
return (`<div class="mk-callout mk-callout--${type}" role="note">` +
|
|
26
|
+
return (`<div class="${withTextClass(`mk-callout mk-callout--${type}`, attributes.text)}" role="note">` +
|
|
25
27
|
`<div class="mk-callout__header">` +
|
|
26
28
|
`<span class="mk-callout__icon" aria-hidden="true">${CALLOUT_ICONS[type]}</span>` +
|
|
27
29
|
`${titleHtml}</div>` +
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { HtmlComponent } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* `:::card{title="..."} ... :::` — a titled panel. `title` is optional; the
|
|
4
|
-
* title row is omitted entirely (not rendered empty) when absent.
|
|
4
|
+
* title row is omitted entirely (not rendered empty) when absent. `text`
|
|
5
|
+
* (`left | center | right`) aligns the panel's own text, title and body
|
|
6
|
+
* alike. Matches
|
|
5
7
|
* `@markii/react`'s `Card` markup byte-for-byte so one stylesheet covers
|
|
6
8
|
* both renderers. No outer margin: the document stylesheet owns spacing
|
|
7
9
|
* between this and its siblings.
|
package/dist/components/card.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { withTextClass } from '../layout.js';
|
|
1
2
|
/**
|
|
2
3
|
* `:::card{title="..."} ... :::` — a titled panel. `title` is optional; the
|
|
3
|
-
* title row is omitted entirely (not rendered empty) when absent.
|
|
4
|
+
* title row is omitted entirely (not rendered empty) when absent. `text`
|
|
5
|
+
* (`left | center | right`) aligns the panel's own text, title and body
|
|
6
|
+
* alike. Matches
|
|
4
7
|
* `@markii/react`'s `Card` markup byte-for-byte so one stylesheet covers
|
|
5
8
|
* both renderers. No outer margin: the document stylesheet owns spacing
|
|
6
9
|
* between this and its siblings.
|
|
@@ -10,6 +13,6 @@ export const Card = (attributes, childrenHtml, ctx) => {
|
|
|
10
13
|
const titleHtml = title
|
|
11
14
|
? `<div class="mk-card__title">${ctx.esc(title)}</div>`
|
|
12
15
|
: '';
|
|
13
|
-
return (`<div class="mk-card">${titleHtml}` +
|
|
16
|
+
return (`<div class="${withTextClass('mk-card', attributes.text)}">${titleHtml}` +
|
|
14
17
|
`<div class="mk-card__body">${childrenHtml}</div></div>`);
|
|
15
18
|
};
|
|
@@ -4,6 +4,9 @@ import type { HtmlComponent } from '../registry.js';
|
|
|
4
4
|
* letting several blocks count as ONE cell of `:::row`. Matches
|
|
5
5
|
* `@markii/react`'s `Cell` markup byte-for-byte: a plain `<div class="mk-cell">`
|
|
6
6
|
* with no look of its own (no border, background, padding, or outer margin).
|
|
7
|
-
*
|
|
7
|
+
* Its one attribute is `text` (`left | center | right`), which aligns the
|
|
8
|
+
* content inside this cell and overrides the enclosing `:::row{text=...}`
|
|
9
|
+
* by declaring a value where the row only offered an inherited one.
|
|
10
|
+
* Matches the React component.
|
|
8
11
|
*/
|
|
9
12
|
export declare const Cell: HtmlComponent;
|
package/dist/components/cell.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { withTextClass } from '../layout.js';
|
|
1
2
|
/**
|
|
2
3
|
* `:::cell ... :::` — a transparent grouping container whose ONLY job is
|
|
3
4
|
* letting several blocks count as ONE cell of `:::row`. Matches
|
|
4
5
|
* `@markii/react`'s `Cell` markup byte-for-byte: a plain `<div class="mk-cell">`
|
|
5
6
|
* with no look of its own (no border, background, padding, or outer margin).
|
|
6
|
-
*
|
|
7
|
+
* Its one attribute is `text` (`left | center | right`), which aligns the
|
|
8
|
+
* content inside this cell and overrides the enclosing `:::row{text=...}`
|
|
9
|
+
* by declaring a value where the row only offered an inherited one.
|
|
10
|
+
* Matches the React component.
|
|
7
11
|
*/
|
|
8
|
-
export const Cell = (
|
|
9
|
-
return `<div class="mk-cell">${childrenHtml}</div>`;
|
|
12
|
+
export const Cell = (attributes, childrenHtml) => {
|
|
13
|
+
return `<div class="${withTextClass('mk-cell', attributes.text)}">${childrenHtml}</div>`;
|
|
10
14
|
};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { HtmlComponent } from '../registry.js';
|
|
2
2
|
export type DividerVariant = 'line' | 'dots' | 'ornament';
|
|
3
|
+
export type DividerLabelAlign = 'left' | 'center' | 'right';
|
|
3
4
|
/**
|
|
4
|
-
* `::divider` / `::divider{label="..." variant="line|dots|ornament"
|
|
5
|
-
* leaf directive rendering a
|
|
6
|
-
* Unknown/missing/bare-null
|
|
7
|
-
* throwing, matching `callout`'s
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* `::divider` / `::divider{label="..." variant="line|dots|ornament"
|
|
6
|
+
* label-align="left|center|right"}` — a leaf directive rendering a
|
|
7
|
+
* horizontal separator, optionally labeled. Unknown/missing/bare-null
|
|
8
|
+
* `variant` falls back to `line` rather than throwing, matching `callout`'s
|
|
9
|
+
* posture for its `type` attribute; `label-align` falls back to `center`
|
|
10
|
+
* the same way, and is component-scoped, distinct from the reserved
|
|
11
|
+
* `width`/`align` layout attributes. Matches `@markii/react`'s `Divider`
|
|
12
|
+
* markup byte-for-byte so one stylesheet covers both renderers. No outer
|
|
13
|
+
* margin: the document stylesheet owns spacing between this and its
|
|
14
|
+
* siblings.
|
|
11
15
|
*/
|
|
12
16
|
export declare const Divider: HtmlComponent;
|
|
@@ -7,14 +7,21 @@ const ORNAMENT_GLYPH = '❖';
|
|
|
7
7
|
function isDividerVariant(value) {
|
|
8
8
|
return DIVIDER_VARIANTS.includes(value);
|
|
9
9
|
}
|
|
10
|
+
const LABEL_ALIGNS = ['left', 'center', 'right'];
|
|
11
|
+
function isDividerLabelAlign(value) {
|
|
12
|
+
return LABEL_ALIGNS.includes(value);
|
|
13
|
+
}
|
|
10
14
|
/**
|
|
11
|
-
* `::divider` / `::divider{label="..." variant="line|dots|ornament"
|
|
12
|
-
* leaf directive rendering a
|
|
13
|
-
* Unknown/missing/bare-null
|
|
14
|
-
* throwing, matching `callout`'s
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* `::divider` / `::divider{label="..." variant="line|dots|ornament"
|
|
16
|
+
* label-align="left|center|right"}` — a leaf directive rendering a
|
|
17
|
+
* horizontal separator, optionally labeled. Unknown/missing/bare-null
|
|
18
|
+
* `variant` falls back to `line` rather than throwing, matching `callout`'s
|
|
19
|
+
* posture for its `type` attribute; `label-align` falls back to `center`
|
|
20
|
+
* the same way, and is component-scoped, distinct from the reserved
|
|
21
|
+
* `width`/`align` layout attributes. Matches `@markii/react`'s `Divider`
|
|
22
|
+
* markup byte-for-byte so one stylesheet covers both renderers. No outer
|
|
23
|
+
* margin: the document stylesheet owns spacing between this and its
|
|
24
|
+
* siblings.
|
|
18
25
|
*/
|
|
19
26
|
export const Divider = (attributes, _childrenHtml, ctx) => {
|
|
20
27
|
const rawVariant = attributes.variant ?? 'line';
|
|
@@ -23,6 +30,11 @@ export const Divider = (attributes, _childrenHtml, ctx) => {
|
|
|
23
30
|
: 'line';
|
|
24
31
|
const rawLabel = attributes.label ?? null;
|
|
25
32
|
const label = rawLabel ? rawLabel : null;
|
|
33
|
+
const rawLabelAlign = attributes['label-align'];
|
|
34
|
+
const labelAlign = rawLabelAlign && isDividerLabelAlign(rawLabelAlign)
|
|
35
|
+
? rawLabelAlign
|
|
36
|
+
: 'center';
|
|
37
|
+
const labelAlignClass = labelAlign === 'center' ? '' : ` mk-divider--label-${labelAlign}`;
|
|
26
38
|
const ariaLabel = label ? ` aria-label="${ctx.esc(label)}"` : '';
|
|
27
39
|
const labelSpan = label
|
|
28
40
|
? `<span class="mk-divider__label">${ctx.esc(label)}</span>`
|
|
@@ -35,6 +47,6 @@ export const Divider = (attributes, _childrenHtml, ctx) => {
|
|
|
35
47
|
else {
|
|
36
48
|
inner = labelSpan;
|
|
37
49
|
}
|
|
38
|
-
return (`<div class="mk-divider mk-divider--${variantName}" role="separator"${ariaLabel}>` +
|
|
50
|
+
return (`<div class="mk-divider mk-divider--${variantName}${labelAlignClass}" role="separator"${ariaLabel}>` +
|
|
39
51
|
`${inner}</div>`);
|
|
40
52
|
};
|
|
@@ -11,7 +11,7 @@ export { Divider } from './divider.js';
|
|
|
11
11
|
export type { DividerVariant } from './divider.js';
|
|
12
12
|
export { Figure } from './figure.js';
|
|
13
13
|
export { Kbd } from './kbd.js';
|
|
14
|
-
export { createLayoutWrapper, LAYOUT_WRAPPER_PRESETS, } from './layout-wrapper.js';
|
|
14
|
+
export { createLayoutWrapper, layoutWrapperPresetAxis, LAYOUT_WRAPPER_PRESETS, } from './layout-wrapper.js';
|
|
15
15
|
export type { LayoutWrapperPreset } from './layout-wrapper.js';
|
|
16
16
|
export { Progress } from './progress.js';
|
|
17
17
|
export { Rating } from './rating.js';
|
package/dist/components/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Details } from './details.js';
|
|
|
9
9
|
import { Divider } from './divider.js';
|
|
10
10
|
import { Figure } from './figure.js';
|
|
11
11
|
import { Kbd } from './kbd.js';
|
|
12
|
-
import { createLayoutWrapper } from './layout-wrapper.js';
|
|
12
|
+
import { createLayoutWrapper, layoutWrapperPresetAxis, } from './layout-wrapper.js';
|
|
13
13
|
import { Progress } from './progress.js';
|
|
14
14
|
import { Rating } from './rating.js';
|
|
15
15
|
import { Row } from './row.js';
|
|
@@ -25,7 +25,7 @@ export { Details } from './details.js';
|
|
|
25
25
|
export { Divider } from './divider.js';
|
|
26
26
|
export { Figure } from './figure.js';
|
|
27
27
|
export { Kbd } from './kbd.js';
|
|
28
|
-
export { createLayoutWrapper, LAYOUT_WRAPPER_PRESETS, } from './layout-wrapper.js';
|
|
28
|
+
export { createLayoutWrapper, layoutWrapperPresetAxis, LAYOUT_WRAPPER_PRESETS, } from './layout-wrapper.js';
|
|
29
29
|
export { Progress } from './progress.js';
|
|
30
30
|
export { Rating } from './rating.js';
|
|
31
31
|
export { Row } from './row.js';
|
|
@@ -44,6 +44,20 @@ export { Tabs } from './tabs.js';
|
|
|
44
44
|
function inlineFromContract(name) {
|
|
45
45
|
return getContract(name)?.kind === 'inline';
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* One layout-wrapper registration: the shared wrapper component bound to
|
|
49
|
+
* `preset`, plus the `layout` axis that preset sets by its own name, which
|
|
50
|
+
* is what tells `render.ts` to drop a same-axis attribute and hand the other
|
|
51
|
+
* axis down through `ctx` instead of wrapping the component in a second
|
|
52
|
+
* `<div>`. Matches `@markii/react`'s `layoutWrapperEntry`.
|
|
53
|
+
*/
|
|
54
|
+
function layoutWrapperEntry(preset) {
|
|
55
|
+
return {
|
|
56
|
+
component: createLayoutWrapper(preset),
|
|
57
|
+
inline: inlineFromContract(preset),
|
|
58
|
+
layout: layoutWrapperPresetAxis(preset),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
47
61
|
/**
|
|
48
62
|
* The built-in standard components, pre-registered under their names —
|
|
49
63
|
* matching `@markii/react`'s `defaultRegistry` in full, including the
|
|
@@ -67,28 +81,11 @@ export const defaultHtmlRegistry = createHtmlRegistry({
|
|
|
67
81
|
stat: { component: Stat, inline: inlineFromContract('stat') },
|
|
68
82
|
progress: { component: Progress, inline: inlineFromContract('progress') },
|
|
69
83
|
chart: { component: Chart, inline: inlineFromContract('chart') },
|
|
70
|
-
center:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
right: {
|
|
79
|
-
component: createLayoutWrapper('right'),
|
|
80
|
-
inline: inlineFromContract('right'),
|
|
81
|
-
},
|
|
82
|
-
wide: {
|
|
83
|
-
component: createLayoutWrapper('wide'),
|
|
84
|
-
inline: inlineFromContract('wide'),
|
|
85
|
-
},
|
|
86
|
-
narrow: {
|
|
87
|
-
component: createLayoutWrapper('narrow'),
|
|
88
|
-
inline: inlineFromContract('narrow'),
|
|
89
|
-
},
|
|
90
|
-
full: {
|
|
91
|
-
component: createLayoutWrapper('full'),
|
|
92
|
-
inline: inlineFromContract('full'),
|
|
93
|
-
},
|
|
84
|
+
center: layoutWrapperEntry('center'),
|
|
85
|
+
left: layoutWrapperEntry('left'),
|
|
86
|
+
right: layoutWrapperEntry('right'),
|
|
87
|
+
wide: layoutWrapperEntry('wide'),
|
|
88
|
+
narrow: layoutWrapperEntry('narrow'),
|
|
89
|
+
full: layoutWrapperEntry('full'),
|
|
90
|
+
fit: layoutWrapperEntry('fit'),
|
|
94
91
|
});
|
|
@@ -1,23 +1,41 @@
|
|
|
1
|
+
import type { LayoutAxis } from '@markii/stdlib';
|
|
1
2
|
import type { HtmlComponent } from '../registry.js';
|
|
2
3
|
/**
|
|
3
|
-
* The closed set of layout-wrapper container names (docs/format.md):
|
|
4
|
+
* The closed set of layout-wrapper container names (docs/format.md):
|
|
4
5
|
* aliases of the one shared implementation below (`createLayoutWrapper`),
|
|
5
6
|
* matching `@markii/react`'s `layout-wrapper.tsx`. There is deliberately no
|
|
6
|
-
* `normal` alias
|
|
7
|
-
* attribute-bearing form.
|
|
7
|
+
* `normal` alias: the default needs no wrapper at all.
|
|
8
8
|
*/
|
|
9
|
-
export declare const LAYOUT_WRAPPER_PRESETS: readonly ["center", "left", "right", "wide", "narrow", "full"];
|
|
10
|
-
/** One of the
|
|
9
|
+
export declare const LAYOUT_WRAPPER_PRESETS: readonly ["center", "left", "right", "wide", "narrow", "full", "fit"];
|
|
10
|
+
/** One of the closed layout-wrapper preset names. */
|
|
11
11
|
export type LayoutWrapperPreset = (typeof LAYOUT_WRAPPER_PRESETS)[number];
|
|
12
12
|
/**
|
|
13
|
-
* Creates the registry component for one of docs/format.md's
|
|
13
|
+
* Creates the registry component for one of docs/format.md's layout-
|
|
14
14
|
* wrapper container names — `:::center`, `:::left`, `:::right`, `:::wide`,
|
|
15
|
-
* `:::narrow`, `:::full`. One shared implementation, bound to
|
|
16
|
-
* registration time, matching `@markii/react`'s
|
|
15
|
+
* `:::narrow`, `:::full`, and `:::fit`. One shared implementation, bound to
|
|
16
|
+
* `preset` at registration time, matching `@markii/react`'s
|
|
17
|
+
* `createLayoutWrapper`.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* A wrapper sets ONE of the two layout axes by its name and takes the OTHER
|
|
20
|
+
* as an attribute (docs/spec.md §3), so `:::center{width=fit}` is centered
|
|
21
|
+
* AND sized to its content. It still never reads `attributes`: `width` and
|
|
22
|
+
* `align` are reserved and stripped before any component sees them, so
|
|
23
|
+
* `render.ts` resolves the open axis and hands the result down as
|
|
24
|
+
* `ctx.layoutClassName`. That class is appended to this wrapper's own on the
|
|
25
|
+
* SAME `<div>`, so the two engines emit one element carrying both classes.
|
|
26
|
+
* An attribute for the wrapper's own axis never arrives at all: `render.ts`
|
|
27
|
+
* drops it, and the name wins.
|
|
28
|
+
*
|
|
29
|
+
* `ctx.layoutClassName` is composed of fixed class literals from
|
|
30
|
+
* `layout.ts`'s closed maps, never author text, so it is interpolated
|
|
31
|
+
* without escaping for the same reason `className` above is.
|
|
22
32
|
*/
|
|
23
33
|
export declare function createLayoutWrapper(preset: LayoutWrapperPreset): HtmlComponent;
|
|
34
|
+
/**
|
|
35
|
+
* The layout axis `preset` sets by its own name, read from
|
|
36
|
+
* `@markii/stdlib`'s one classification of the seven wrapper names.
|
|
37
|
+
* Used by `components/index.ts` to register each wrapper with the right
|
|
38
|
+
* `HtmlRegistryEntry.layout`. Mirrors `@markii/react`'s
|
|
39
|
+
* `layoutWrapperPresetAxis`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function layoutWrapperPresetAxis(preset: LayoutWrapperPreset): LayoutAxis;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { layoutWrapperAxis } from '@markii/stdlib';
|
|
1
2
|
/**
|
|
2
|
-
* The closed set of layout-wrapper container names (docs/format.md):
|
|
3
|
+
* The closed set of layout-wrapper container names (docs/format.md):
|
|
3
4
|
* aliases of the one shared implementation below (`createLayoutWrapper`),
|
|
4
5
|
* matching `@markii/react`'s `layout-wrapper.tsx`. There is deliberately no
|
|
5
|
-
* `normal` alias
|
|
6
|
-
* attribute-bearing form.
|
|
6
|
+
* `normal` alias: the default needs no wrapper at all.
|
|
7
7
|
*/
|
|
8
8
|
export const LAYOUT_WRAPPER_PRESETS = [
|
|
9
9
|
'center',
|
|
@@ -12,11 +12,12 @@ export const LAYOUT_WRAPPER_PRESETS = [
|
|
|
12
12
|
'wide',
|
|
13
13
|
'narrow',
|
|
14
14
|
'full',
|
|
15
|
+
'fit',
|
|
15
16
|
];
|
|
16
17
|
/**
|
|
17
18
|
* Preset -> class string. Null-prototype, mirroring `layout.ts`'s
|
|
18
19
|
* `WIDTH_CLASSES`/`ALIGN_CLASSES`. `center`/`left`/`right` reuse the
|
|
19
|
-
* existing `mk-align-*` classes; `wide`/`narrow`/`full` reuse the existing
|
|
20
|
+
* existing `mk-align-*` classes; `wide`/`narrow`/`full`/`fit` reuse the existing
|
|
20
21
|
* `mk-width-*` classes. `mk-layout` is the one class every preset adds on
|
|
21
22
|
* top. Matches `@markii/react`'s `WRAPPER_CLASSES` byte-for-byte.
|
|
22
23
|
*/
|
|
@@ -27,19 +28,51 @@ const WRAPPER_CLASSES = Object.assign(Object.create(null), {
|
|
|
27
28
|
wide: 'mk-layout mk-width-wide',
|
|
28
29
|
narrow: 'mk-layout mk-width-narrow',
|
|
29
30
|
full: 'mk-layout mk-width-full',
|
|
31
|
+
fit: 'mk-layout mk-width-fit',
|
|
30
32
|
});
|
|
31
33
|
/**
|
|
32
|
-
* Creates the registry component for one of docs/format.md's
|
|
34
|
+
* Creates the registry component for one of docs/format.md's layout-
|
|
33
35
|
* wrapper container names — `:::center`, `:::left`, `:::right`, `:::wide`,
|
|
34
|
-
* `:::narrow`, `:::full`. One shared implementation, bound to
|
|
35
|
-
* registration time, matching `@markii/react`'s
|
|
36
|
+
* `:::narrow`, `:::full`, and `:::fit`. One shared implementation, bound to
|
|
37
|
+
* `preset` at registration time, matching `@markii/react`'s
|
|
38
|
+
* `createLayoutWrapper`.
|
|
36
39
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* A wrapper sets ONE of the two layout axes by its name and takes the OTHER
|
|
41
|
+
* as an attribute (docs/spec.md §3), so `:::center{width=fit}` is centered
|
|
42
|
+
* AND sized to its content. It still never reads `attributes`: `width` and
|
|
43
|
+
* `align` are reserved and stripped before any component sees them, so
|
|
44
|
+
* `render.ts` resolves the open axis and hands the result down as
|
|
45
|
+
* `ctx.layoutClassName`. That class is appended to this wrapper's own on the
|
|
46
|
+
* SAME `<div>`, so the two engines emit one element carrying both classes.
|
|
47
|
+
* An attribute for the wrapper's own axis never arrives at all: `render.ts`
|
|
48
|
+
* drops it, and the name wins.
|
|
49
|
+
*
|
|
50
|
+
* `ctx.layoutClassName` is composed of fixed class literals from
|
|
51
|
+
* `layout.ts`'s closed maps, never author text, so it is interpolated
|
|
52
|
+
* without escaping for the same reason `className` above is.
|
|
41
53
|
*/
|
|
42
54
|
export function createLayoutWrapper(preset) {
|
|
43
55
|
const className = WRAPPER_CLASSES[preset] ?? 'mk-layout';
|
|
44
|
-
return (_attributes, childrenHtml) =>
|
|
56
|
+
return (_attributes, childrenHtml, ctx) => {
|
|
57
|
+
const full = ctx.layoutClassName
|
|
58
|
+
? `${className} ${ctx.layoutClassName}`
|
|
59
|
+
: className;
|
|
60
|
+
return `<div class="${full}">${childrenHtml}</div>`;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The layout axis `preset` sets by its own name, read from
|
|
65
|
+
* `@markii/stdlib`'s one classification of the seven wrapper names.
|
|
66
|
+
* Used by `components/index.ts` to register each wrapper with the right
|
|
67
|
+
* `HtmlRegistryEntry.layout`. Mirrors `@markii/react`'s
|
|
68
|
+
* `layoutWrapperPresetAxis`.
|
|
69
|
+
*/
|
|
70
|
+
export function layoutWrapperPresetAxis(preset) {
|
|
71
|
+
const axis = layoutWrapperAxis(preset);
|
|
72
|
+
if (axis === undefined) {
|
|
73
|
+
// Unreachable for the closed preset list above; kept as a narrowing
|
|
74
|
+
// branch rather than a non-null assertion.
|
|
75
|
+
throw new Error(`"${preset}" is not a layout-wrapper name`);
|
|
76
|
+
}
|
|
77
|
+
return axis;
|
|
45
78
|
}
|
package/dist/components/row.d.ts
CHANGED
|
@@ -2,8 +2,12 @@ import type { HtmlComponent } from '../registry.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* `:::row{cols=3} ... :::` — docs/format.md's one layout *container*. An
|
|
4
4
|
* absent or invalid `cols` value degrades to plain `mk-row` (auto-fit)
|
|
5
|
-
* rather than an error.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* rather than an error. `text` (`left | center | right`) aligns the content
|
|
6
|
+
* inside the row's cells, reaching them through ordinary CSS inheritance;
|
|
7
|
+
* the reserved `align` keeps its ordinary meaning of placing the row's own
|
|
8
|
+
* box, which a full-width grid has no room to act on. Matches
|
|
9
|
+
* `@markii/react`'s `Row` markup byte-for-byte so one stylesheet covers both
|
|
10
|
+
* renderers. No outer margin: the document stylesheet owns spacing between
|
|
11
|
+
* this and its siblings.
|
|
8
12
|
*/
|
|
9
13
|
export declare const Row: HtmlComponent;
|
package/dist/components/row.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withTextClass } from '../layout.js';
|
|
1
2
|
/** The exact `cols` values that select a fixed-column-count class; anything else degrades to auto-fit. */
|
|
2
3
|
const COLS_VALUES = ['2', '3', '4'];
|
|
3
4
|
function isColsValue(value) {
|
|
@@ -6,14 +7,16 @@ function isColsValue(value) {
|
|
|
6
7
|
/**
|
|
7
8
|
* `:::row{cols=3} ... :::` — docs/format.md's one layout *container*. An
|
|
8
9
|
* absent or invalid `cols` value degrades to plain `mk-row` (auto-fit)
|
|
9
|
-
* rather than an error.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* rather than an error. `text` (`left | center | right`) aligns the content
|
|
11
|
+
* inside the row's cells, reaching them through ordinary CSS inheritance;
|
|
12
|
+
* the reserved `align` keeps its ordinary meaning of placing the row's own
|
|
13
|
+
* box, which a full-width grid has no room to act on. Matches
|
|
14
|
+
* `@markii/react`'s `Row` markup byte-for-byte so one stylesheet covers both
|
|
15
|
+
* renderers. No outer margin: the document stylesheet owns spacing between
|
|
16
|
+
* this and its siblings.
|
|
12
17
|
*/
|
|
13
18
|
export const Row = (attributes, childrenHtml) => {
|
|
14
19
|
const rawCols = attributes.cols ?? '';
|
|
15
|
-
const className = isColsValue(rawCols)
|
|
16
|
-
? `mk-row mk-row--cols-${rawCols}`
|
|
17
|
-
: 'mk-row';
|
|
20
|
+
const className = withTextClass(isColsValue(rawCols) ? `mk-row mk-row--cols-${rawCols}` : 'mk-row', attributes.text);
|
|
18
21
|
return `<div class="${className}">${childrenHtml}</div>`;
|
|
19
22
|
};
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// Regenerate with: node scripts/generate-doc-css.ts
|
|
3
3
|
// Source of truth: packages/platforms/markii-react/src/doc.css
|
|
4
4
|
/** The shared document stylesheet (@markii/react's doc.css), embedded as a string for exportHtmlDocument's <style> block. */
|
|
5
|
-
export const DOC_CSS = "/*\n * Document rhythm: components own their insides only, never outer margins.\n * This single rule spaces every block-level child of `.doc` identically —\n * paragraphs, headings, and components alike — so new components always\n * sit correctly in the flow with zero per-component tuning.\n */\n.doc > * + * {\n margin-block-start: 1rem;\n}\n\n/*\n * ============================================================================\n * TIER 1 TOKENS — the theming contract\n * ============================================================================\n * Every host that wants a themed (in particular, a dark) `.doc` remaps ONLY\n * these custom properties, deliberately scoped to `.doc` and not `:root`:\n * `doc.css` is a library stylesheet embedded into other people's pages\n * (Obsidian injects it globally), so it must stay polite and never claim\n * `:root`. A host theme layer loaded after this one, at the same\n * specificity, simply redeclares the ones below on `.doc` (or a more\n * specific selector) to win the cascade — see `apps/vscode/src/webview/\n * theme.css` and `apps/obsidian/src/obsidian-theme.css`.\n *\n * Nine neutrals + five semantic hues. This list is deliberately small: a\n * host maps ~14 tokens instead of ~60 individual selectors, and a\n * derivation layer below (Tier 2) builds every finer shade FROM these, so\n * remapping these 14 makes every callout/badge/chart/etc. variant\n * theme-correct for free.\n */\n.doc {\n /* ---------- neutrals ---------- */\n --mk-bg: #fff; /* page ground */\n --mk-raised: #fff; /* raised surface for cards — distinct from ground; dark themes need real separation here where light themes get it from the border alone */\n --mk-fg: #1a1a1a; /* body text */\n --mk-surface: #f4f4f5; /* subtle fill one step off the ground: pre, kbd, script marker, details, default callout, zebra rows */\n --mk-surface-strong: #f0f0f2; /* second step off the ground: inline code, th, progress track, default badge background */\n --mk-border: #e4e4e7; /* hairlines */\n --mk-muted: #52525b; /* secondary text: captions, labels, script summary */\n --mk-faint: #94a3b8; /* tertiary/empty: missing values, empty-chart text, unfilled stars */\n --mk-accent: #3b82f6; /* the single interactive/brand color: active tab, progress bar, chart stroke/fill */\n --mk-on-accent: #fff; /* ink that sits ON a solid --mk-accent fill; a host must supply this because it cannot be derived: it depends on the accent's own lightness, not on the page palette */\n\n /* ---------- semantic hues ---------- */\n --mk-info: #3b82f6;\n --mk-success: #15803d;\n --mk-warning: #d97706;\n --mk-danger: #dc2626;\n --mk-limit: #7c3aed; /* the purple, used for the \"hit a resource limit\" failure kind */\n}\n\n/*\n * ============================================================================\n * TIER 2 DERIVATION FORMULAS\n * ============================================================================\n * Every finer shade `doc.css` needs (a callout's tinted background, a\n * badge's tinted text) is a MIX of a Tier 1 hue against `--mk-bg`/`--mk-fg`,\n * never a literal of its own. Because the mix targets those two tokens\n * specifically, a dark host palette flips the derived shade's polarity\n * correctly with no extra work on the host's part — the whole point of\n * this refactor.\n *\n * Exactly three named percentages are used anywhere in this file. A future\n * component reuses one of these three; it does not invent a fourth.\n *\n * --mk-mix-variant-fill: color-mix(in srgb, <hue> 14%, var(--mk-bg))\n * A quiet tinted background — callout body fill.\n * --mk-mix-strong-fill: color-mix(in srgb, <hue> 18%, var(--mk-bg))\n * A slightly stronger tinted background — badge background.\n * --mk-mix-ink: color-mix(in srgb, <hue> 85%, var(--mk-fg))\n * A hue nudged toward body text — usable as ink (badge text, star\n * color) or as a border (callout border).\n *\n * `color-mix()` is unsupported in most email clients, and a custom property\n * whose value fails to parse does not fall back — the declaration goes\n * invalid-at-computed-value and effectively vanishes. `doc.css` is embedded\n * verbatim into `@markii/html`'s `exportHtmlDocument`, whose documented\n * targets include email and archive output, so every derived token below is\n * defined TWICE: first as a literal hex (today's existing light-mode\n * value), then, guarded by `@supports (color: color-mix(in srgb, red,\n * red))`, redefined via the real mix. A modern browser or either Electron\n * host gets live derivation that tracks a remapped Tier 1 palette; an old\n * email client silently keeps exactly today's light palette.\n */\n.doc {\n /* ---- literal light-mode fallback (used verbatim where color-mix is unsupported) ---- */\n --mk-info-fill: #eff6ff;\n --mk-info-strong-fill: #dbeafe;\n --mk-info-ink: #3b82f6;\n --mk-success-strong-fill: #dcfce7;\n --mk-success-ink: #15803d;\n --mk-warning-fill: #fffbeb;\n --mk-warning-strong-fill: #fef3c7;\n --mk-warning-ink: #d97706;\n --mk-danger-fill: #fef2f2;\n --mk-danger-strong-fill: #fee2e2;\n --mk-danger-ink: #dc2626;\n --mk-limit-ink: #7c3aed;\n /*\n * The keycap's inset depth line. Not a themed hue, but not theme-neutral\n * either: a low-alpha BLACK line is invisible on a dark surface, so the\n * literal below is only the no-`color-mix` fallback (where the palette is\n * the light one anyway). The derivation in the `@supports` block below\n * expresses it against `--mk-fg` instead, so it flips to a light line\n * when a host supplies a dark palette, which is the correct depth cue\n * there. Kept in this block, not the Tier 1 block above, so its literal\n * stays inside a block the no-raw-color-literal test allows.\n */\n --mk-shadow-sm: rgba(0, 0, 0, 0.05);\n}\n\n@supports (color: color-mix(in srgb, red, red)) {\n .doc {\n --mk-shadow-sm: color-mix(in srgb, var(--mk-fg) 8%, transparent);\n\n --mk-info-fill: color-mix(in srgb, var(--mk-info) 14%, var(--mk-bg));\n --mk-info-strong-fill: color-mix(in srgb, var(--mk-info) 18%, var(--mk-bg));\n --mk-info-ink: color-mix(in srgb, var(--mk-info) 85%, var(--mk-fg));\n\n --mk-success-strong-fill: color-mix(\n in srgb,\n var(--mk-success) 18%,\n var(--mk-bg)\n );\n --mk-success-ink: color-mix(in srgb, var(--mk-success) 85%, var(--mk-fg));\n\n --mk-warning-fill: color-mix(in srgb, var(--mk-warning) 14%, var(--mk-bg));\n --mk-warning-strong-fill: color-mix(\n in srgb,\n var(--mk-warning) 18%,\n var(--mk-bg)\n );\n --mk-warning-ink: color-mix(in srgb, var(--mk-warning) 85%, var(--mk-fg));\n\n --mk-danger-fill: color-mix(in srgb, var(--mk-danger) 14%, var(--mk-bg));\n --mk-danger-strong-fill: color-mix(\n in srgb,\n var(--mk-danger) 18%,\n var(--mk-bg)\n );\n --mk-danger-ink: color-mix(in srgb, var(--mk-danger) 85%, var(--mk-fg));\n\n --mk-limit-ink: color-mix(in srgb, var(--mk-limit) 85%, var(--mk-fg));\n }\n}\n\n.doc {\n color: var(--mk-fg);\n font-family:\n system-ui,\n -apple-system,\n 'Segoe UI',\n sans-serif;\n line-height: 1.6;\n}\n\n.doc pre {\n overflow-x: auto;\n background: var(--mk-surface);\n padding: 0.75rem 1rem;\n border-radius: 6px;\n}\n\n.doc code {\n background: var(--mk-surface-strong);\n border-radius: 3px;\n padding: 0.1em 0.35em;\n font-size: 0.9em;\n}\n\n.doc pre code {\n background: none;\n padding: 0;\n}\n\n/* ---------- GFM table ---------- */\n\n/*\n * `display: block` on the table itself (rather than wrapping it in an\n * extra element the renderer doesn't otherwise inject) is what makes a wide\n * table scroll horizontally instead of overflowing the page or the doc\n * column — the table box becomes independently scrollable content, the\n * same trick used by GitHub's own Markdown rendering.\n */\n.doc table {\n display: block;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.doc th,\n.doc td {\n border: 1px solid var(--mk-border);\n padding: 0.4rem 0.75rem;\n text-align: left;\n}\n\n.doc th {\n font-weight: 600;\n background: var(--mk-surface-strong);\n}\n\n.doc tr:nth-child(even) {\n background: var(--mk-surface);\n}\n\n/* ---------- GFM task list ---------- */\n\n/*\n * `li:has(> input[type=\"checkbox\"])` scopes bullet removal + checkbox\n * alignment to task-list items only — an ordinary `<ul>`/`<ol>` item keeps\n * its normal bullet/number, since GFM only adds a leading `<input>` to\n * items that used `- [ ]`/`- [x]` syntax.\n */\n.doc li:has(> input[type='checkbox']) {\n list-style: none;\n margin-inline-start: -1.5em;\n}\n\n.doc li > input[type='checkbox'] {\n margin-inline-end: 0.5em;\n vertical-align: middle;\n}\n\n/* ---------- callout ---------- */\n\n.mk-callout {\n border: 1px solid var(--mk-callout-border, var(--mk-border));\n border-left-width: 4px;\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-callout-bg, var(--mk-surface));\n}\n\n.mk-callout--info {\n --mk-callout-border: var(--mk-info-ink);\n --mk-callout-bg: var(--mk-info-fill);\n}\n\n.mk-callout--warning {\n --mk-callout-border: var(--mk-warning-ink);\n --mk-callout-bg: var(--mk-warning-fill);\n}\n\n.mk-callout--danger {\n --mk-callout-border: var(--mk-danger-ink);\n --mk-callout-bg: var(--mk-danger-fill);\n}\n\n.mk-callout__header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n}\n\n.mk-callout__icon {\n line-height: 1;\n}\n\n/*\n * `display: flex; flex-direction: column` is load-bearing here, not just\n * `> * + *` margin: several block-holding components (`stat`, `badge`)\n * declare their OWN outer display as `inline-flex`/`inline-block` (correct\n * for sitting inline in a sentence), so without a flex/grid parent they\n * flow side-by-side on the same line instead of stacking — a\n * `margin-block-start` on a same-line inline-level sibling creates no\n * visible gap. Making the body a column flex container forces every\n * child's *used* display to blockify (CSS Flexbox: a flex item's outer\n * display is always block-level), so they stack regardless of the\n * component's own declared display, and the margin rule below then has\n * something to actually separate.\n */\n.mk-callout__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-callout__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-callout__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- kbd ---------- */\n\n.mk-kbd {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.4em;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n border: 1px solid var(--mk-border);\n border-bottom-width: 2px;\n border-radius: 4px;\n background: var(--mk-surface);\n box-shadow: inset 0 -1px 0 var(--mk-shadow-sm);\n}\n\n/* ---------- rating ---------- */\n\n.mk-rating {\n display: inline-flex;\n gap: 0.15em;\n font-size: 1.1em;\n color: var(--mk-faint);\n}\n\n.mk-rating__star--filled {\n color: var(--mk-warning);\n}\n\n/* ---------- divider ---------- */\n\n/*\n * The `::before`/`::after` pseudo-elements are the flanking hairlines; the\n * label sits between them as ordinary flex children. The label carries its\n * own inline margin rather than a flex `gap` because an unlabeled divider\n * has no label element to create a gap around — with `gap` the rule would\n * split into two disconnected segments instead of staying one unbroken\n * line.\n */\n\n.mk-divider {\n display: flex;\n align-items: center;\n color: var(--mk-faint);\n}\n\n.mk-divider::before,\n.mk-divider::after {\n content: '';\n flex: 1 1 0;\n border-block-start: 1px solid var(--mk-border);\n}\n\n.mk-divider--dots::before,\n.mk-divider--dots::after {\n border-block-start-style: dotted;\n}\n\n.mk-divider--ornament::before,\n.mk-divider--ornament::after {\n border-block-start-color: transparent;\n}\n\n.mk-divider__label {\n margin-inline: 0.75em;\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-divider__ornament {\n margin-inline: 0.35em;\n font-size: 0.9em;\n}\n\n/* ---------- value interpolation ---------- */\n\n.mk-value {\n display: inline;\n vertical-align: baseline;\n}\n\n.mk-value--stale {\n color: var(--mk-warning-ink);\n border-bottom: 1px dashed var(--mk-warning-ink);\n}\n\n.mk-value--missing {\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.9em;\n color: var(--mk-faint);\n font-style: italic;\n}\n\n/*\n * Failure-kind modifiers (`@markii/runtime`'s `FailureKind`, docs/scripting.md) —\n * layered on top of `.mk-value--missing`, only ever added when the\n * resolution's root entry carried a `failureKind` on a genuine error (see\n * `ValueDirective`). Each just tints the dashed-underline treatment a\n * distinct hue so a reader can tell \"script bug\" apart from \"needs\n * permission\" / \"needs a manual run\" / \"hit a resource limit\" at a glance,\n * with the full message still available via the `title` tooltip.\n *\n * Failure kinds map onto the Tier 1 semantic hues: script-error -> danger,\n * capability-denied -> warning, tier-blocked -> info, limit -> limit.\n */\n.mk-value--script-error {\n border-bottom: 1px dashed var(--mk-danger-ink);\n}\n\n.mk-value--capability-denied {\n border-bottom: 1px dashed var(--mk-warning-ink);\n}\n\n.mk-value--tier-blocked {\n border-bottom: 1px dashed var(--mk-info-ink);\n}\n\n.mk-value--limit {\n border-bottom: 1px dashed var(--mk-limit-ink);\n}\n\n/* ---------- script marker ---------- */\n\n.mk-script {\n border: 1px solid var(--mk-border);\n border-radius: 6px;\n background: var(--mk-surface);\n}\n\n.mk-script__summary {\n cursor: pointer;\n padding: 0.5rem 0.75rem;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-script[open] > .mk-script__summary {\n border-bottom: 1px solid var(--mk-border);\n}\n\n.mk-script__code {\n margin: 0.75rem;\n margin-block-start: 0;\n}\n\n.mk-script__empty {\n margin: 0.75rem;\n margin-block-start: 0;\n font-size: 0.85em;\n font-style: italic;\n color: var(--mk-faint);\n}\n\n/* ---------- details ---------- */\n\n.mk-details {\n border: 1px solid var(--mk-border);\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-surface);\n}\n\n.mk-details__summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n.mk-details[open] > .mk-details__summary {\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-details__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-details__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-details__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- card ---------- */\n\n.mk-card {\n border: 1px solid var(--mk-border);\n border-radius: 8px;\n padding: 1rem;\n background: var(--mk-raised);\n}\n\n.mk-card__title {\n font-weight: 600;\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-card__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-card__body > :first-child {\n margin-block-start: 0;\n}\n\n.mk-card__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- badge ---------- */\n\n.mk-badge {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.6em;\n font-size: 0.8em;\n font-weight: 600;\n border-radius: 999px;\n color: var(--mk-badge-fg, var(--mk-fg));\n background: var(--mk-badge-bg, var(--mk-surface-strong));\n}\n\n.mk-badge--info {\n --mk-badge-fg: var(--mk-info-ink);\n --mk-badge-bg: var(--mk-info-strong-fill);\n}\n\n.mk-badge--success {\n --mk-badge-fg: var(--mk-success-ink);\n --mk-badge-bg: var(--mk-success-strong-fill);\n}\n\n.mk-badge--warning {\n --mk-badge-fg: var(--mk-warning-ink);\n --mk-badge-bg: var(--mk-warning-strong-fill);\n}\n\n.mk-badge--danger {\n --mk-badge-fg: var(--mk-danger-ink);\n --mk-badge-bg: var(--mk-danger-strong-fill);\n}\n\n/* ---------- figure ---------- */\n\n.mk-figure {\n margin: 0;\n}\n\n.mk-figure__img {\n display: block;\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n}\n\n.mk-figure__caption {\n margin-block-start: 0.5rem;\n font-size: 0.9em;\n color: var(--mk-muted);\n}\n\n.mk-figure__caption > :first-child {\n margin-block-start: 0;\n}\n\n.mk-figure__caption > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- tabs ---------- */\n\n.mk-tabs__list {\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--mk-border);\n}\n\n.mk-tabs__button {\n cursor: pointer;\n border: none;\n background: none;\n padding: 0.5rem 0.9rem;\n font: inherit;\n font-weight: 600;\n color: var(--mk-muted);\n border-bottom: 2px solid transparent;\n margin-block-end: -1px;\n}\n\n.mk-tabs__button--active {\n color: var(--mk-accent);\n border-bottom-color: var(--mk-accent);\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-tab {\n display: flex;\n flex-direction: column;\n padding-block-start: 0.75rem;\n}\n\n.mk-tab > :first-child {\n margin-block-start: 0;\n}\n\n.mk-tab > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- stat ---------- */\n\n.mk-stat {\n display: inline-flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n\n.mk-stat__value {\n font-size: 1.8em;\n font-weight: 700;\n line-height: 1.2;\n}\n\n.mk-stat__label {\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-stat__delta {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--mk-muted);\n}\n\n.mk-stat__delta--up {\n color: var(--mk-success-ink);\n}\n\n.mk-stat__delta--down {\n color: var(--mk-danger-ink);\n}\n\n.mk-stat__delta--flat {\n color: var(--mk-muted);\n}\n\n/* ---------- progress ---------- */\n\n.mk-progress {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n}\n\n.mk-progress__label {\n font-size: 0.85em;\n color: var(--mk-muted);\n flex: 0 0 auto;\n}\n\n.mk-progress__track {\n flex: 1 1 auto;\n height: 0.6rem;\n border-radius: 999px;\n background: var(--mk-border);\n overflow: hidden;\n}\n\n.mk-progress__bar {\n height: 100%;\n background: var(--mk-accent);\n border-radius: inherit;\n}\n\n.mk-progress__percent {\n flex: 0 0 auto;\n font-size: 0.85em;\n font-variant-numeric: tabular-nums;\n color: var(--mk-muted);\n}\n\n/* ---------- chart ---------- */\n\n.mk-chart {\n display: block;\n max-width: 100%;\n}\n\n.mk-chart__line {\n fill: none;\n stroke: var(--mk-accent);\n stroke-width: 2;\n stroke-linejoin: round;\n stroke-linecap: round;\n}\n\n.mk-chart__bar {\n fill: var(--mk-accent);\n}\n\n.mk-chart--empty {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.8em;\n font-style: italic;\n color: var(--mk-faint);\n border: 1px dashed var(--mk-border);\n border-radius: 6px;\n}\n\n/* ---------- data-binding state (docs/scripting.md) ---------- */\n\n/*\n * The block twins of the `.mk-value--*` markers above: a data-bound\n * component (`stat`, `progress`, `chart`) whose `data=` binding is stale or\n * failed keeps its ordinary QUIET body — `—`, a `0%` bar, `no data` — and\n * says so only through these hooks plus its `title` tooltip. No rule here\n * may add body text (`content:` is deliberately absent) or an outer margin;\n * the failure taxonomy's wording lives in one place only, `components/\n * failure-presentation.ts`.\n *\n * The hues match `.mk-value--*` exactly, so the same failing name reads the\n * same whether it surfaced inline via `:value[...]` or as a component.\n */\n.mk-stat--stale,\n.mk-progress--stale,\n.mk-chart--stale {\n opacity: 0.8;\n}\n\n.mk-stat--script-error,\n.mk-progress--script-error,\n.mk-chart--script-error {\n border-bottom: 2px solid var(--mk-danger-ink);\n}\n\n.mk-stat--capability-denied,\n.mk-progress--capability-denied,\n.mk-chart--capability-denied {\n border-bottom: 2px solid var(--mk-warning-ink);\n}\n\n.mk-stat--tier-blocked,\n.mk-progress--tier-blocked,\n.mk-chart--tier-blocked {\n border-bottom: 2px solid var(--mk-info-ink);\n}\n\n.mk-stat--limit,\n.mk-progress--limit,\n.mk-chart--limit {\n border-bottom: 2px solid var(--mk-limit-ink);\n}\n\n/* ---------- layout presets (docs/format.md) ---------- */\n\n/*\n * `render.tsx` only wraps a directive in a `mk-width-*`/`mk-align-*` `<div>`\n * when at least one of these classes actually applies, so the wrapper below\n * IS the element sitting directly in `.doc`'s rhythm flow — never\n * `margin-block` here, that's `.doc > * + *`'s job alone; setting it on the\n * wrapper too would double up spacing. `max-width: min(<size>, 100%)` keeps\n * every preset from ever overflowing the document column, even on a narrow\n * viewport where the size below is wider than the column itself.\n */\n.mk-width-narrow {\n max-width: min(30rem, 100%);\n}\n\n.mk-width-wide {\n max-width: min(64rem, 100%);\n}\n\n/* \"full\" is the full available column width — not a viewport-bleed hack with negative margins. */\n.mk-width-full {\n max-width: 100%;\n}\n\n.mk-align-left {\n margin-inline-end: auto;\n}\n\n.mk-align-center {\n margin-inline: auto;\n}\n\n.mk-align-right {\n margin-inline-start: auto;\n}\n\n/*\n * The five `:::center`/`:::right`/`:::wide`/`:::narrow`/`:::full` layout\n * wrappers (docs/format.md, `layout-wrapper.tsx`) reuse the `mk-width-*`/\n * `mk-align-*` classes above and add `mk-layout` on top for the rules below,\n * which only make sense on a container that has its OWN plain-markdown\n * children (a table, an image, a paragraph) rather than on the bare\n * attribute-interception wrapper `render.tsx` emits for `width=`/`align=`.\n *\n * No outer margin on `.mk-layout` itself, same rule as every component\n * (Architecture rule 4) — `.doc > * + *` spaces the wrapper against its\n * siblings. This rule instead restores RHYTHM *inside* the wrapper's own\n * scope, mirroring `.mk-card__body > * + *`: without it, the wrapper's\n * children would have no spacing between them at all, since `.doc > * + *`\n * only ever sees the wrapper `<div>` as a whole, never reaches inside it.\n */\n.mk-layout > * + * {\n margin-block-start: 1rem;\n}\n\n/* `center`/`right` additionally set text alignment for everything in scope — not just the shrink-to-fit block alignment below. */\n.mk-layout.mk-align-center {\n text-align: center;\n}\n\n.mk-layout.mk-align-right {\n text-align: right;\n}\n\n/*\n * `.mk-align-center`/`.mk-align-right` above (shared with the `align=`\n * attribute wrapper) only center/right-align the wrapper `<div>` ITSELF\n * within ITS container — they say nothing about the wrapper's own children.\n * These two rules do that: they shrink-to-fit and align every direct child\n * of the wrapper's scope, which is what actually centers/right-aligns a\n * narrower-than-column table or image sitting inside `:::center`/`:::right`.\n */\n.mk-layout.mk-align-center > * {\n margin-inline: auto;\n}\n\n.mk-layout.mk-align-right > * {\n margin-inline-start: auto;\n margin-inline-end: 0;\n}\n\n/*\n * `.doc table` (above) sets `display: block` for horizontal-scroll\n * overflow, which also makes the table fill its column — defeating\n * shrink-to-fit alignment before it can even apply. These two rules size a\n * table down to its content instead, so the `margin-inline` rules above\n * have a narrower box to actually move. `.doc th`/`.doc td` set\n * `text-align: left` directly on the cells, so this scope's `text-align`\n * never flips table cell text — only the table's own position in the\n * scope, and any non-table text alongside it.\n */\n.mk-layout.mk-align-center > table,\n.mk-layout.mk-align-right > table {\n width: fit-content;\n max-width: 100%;\n}\n\n/* ---------- row ---------- */\n\n.mk-row {\n display: grid;\n gap: 1rem;\n grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));\n}\n\n/*\n * `minmax(0, 1fr)` — a bare `1fr` track can still grow past an equal share\n * to fit a wide intrinsic-content cell (e.g. a `chart` SVG, a long code\n * span); pinning the minimum to `0` is what keeps such a cell, and\n * therefore the whole row, from blowing out past its column.\n */\n.mk-row--cols-2 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.mk-row--cols-3 {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n.mk-row--cols-4 {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n\n/*\n * No separate blockification rule is needed here, unlike `.mk-card__body`'s:\n * a grid container (which `.mk-row` is) already promotes every direct\n * child's outer display to block-level as a grid item, same as a flex\n * container does — an inline-flex/inline-block component (`stat`, `badge`,\n * ...) used directly as a row cell already stacks/sizes as a proper grid\n * cell with no extra rule required.\n */\n\n@media (max-width: 40rem) {\n .mk-row,\n .mk-row--cols-2,\n .mk-row--cols-3,\n .mk-row--cols-4 {\n grid-template-columns: 1fr;\n }\n}\n\n/*\n * `:::row{align=...}` (docs/format.md): `align` is intercepted like on any\n * other block directive (`render.tsx`/`layout.ts`), wrapping `.mk-row` in\n * the same generic `mk-align-*` `<div>` every other directive gets — but\n * `margin-inline: auto` (the rule above, under \"layout presets\") has\n * nothing to move: a `.mk-row` grid already fills the document column, so\n * shrink-to-fit placement of the row itself is meaningless. These three\n * rules give `align` on a row its own, more useful meaning instead: they\n * set `text-align` on the row, which every cell's content INHERITS (plain\n * CSS inheritance — `.mk-cell` sets no `text-align` of its own, so nothing\n * blocks it). Scoped to `> .mk-row` specifically, so a non-row directive's\n * `align` wrapper is completely unaffected.\n *\n * Locality wins for free: a more local `:::center`/`:::left`/`:::right`\n * wrapper written inside one cell (`.mk-layout.mk-align-*` above) sets\n * `text-align` directly on itself, and an element's own declared value\n * always wins over one it only inherited — no specificity fight needed.\n */\n.mk-align-left > .mk-row {\n text-align: left;\n}\n\n.mk-align-center > .mk-row {\n text-align: center;\n}\n\n.mk-align-right > .mk-row {\n text-align: right;\n}\n\n/* ---------- cell ---------- */\n\n/*\n * `:::cell` (`cell.tsx`) is a TRANSPARENT grouping container: it has no\n * border, background, padding, or outer margin of its own — its whole job is\n * making several blocks count as ONE `.mk-row` grid cell, so anything visual\n * here would betray that. `.mk-cell` therefore has no rule at all; only the\n * rhythm-restoring rule below exists, mirroring `.mk-layout > * + *`:\n * `.doc > * + *` sees the cell as a single box and never reaches inside it,\n * so without this its children would sit flush against each other.\n */\n.mk-cell > * + * {\n margin-block-start: 1rem;\n}\n\n/* ---------- empty inline-component marker ---------- */\n\n/*\n * Wraps an `inline: true` component that received no content\n * (`::badge{label=\"x\"}` instead of `:badge[x]`) — see `render.tsx`'s\n * `isRegisteredInline`/`isEmptyContent` and `render.ts`'s HTML-engine\n * mirror. The component underneath renders unchanged; this is a quiet\n * perceptual hook only (a faint dashed underline, matching the treatment\n * `.mk-value--stale` already gives a quiet-but-present state), with the\n * reason carried in the `title` tooltip rather than in the page.\n */\n.mk-inline-empty {\n border-bottom: 1px dashed var(--mk-faint);\n}\n\n/* ---------- unknown directive fallback ---------- */\n\n.mk-unknown {\n border: 1px dashed var(--mk-faint);\n border-radius: 6px;\n color: var(--mk-muted);\n}\n\n.mk-unknown--block {\n padding: 0.75rem 1rem;\n}\n\n.mk-unknown--inline {\n display: inline-flex;\n align-items: baseline;\n gap: 0.4em;\n padding: 0 0.4em;\n vertical-align: baseline;\n}\n\n.mk-unknown__label {\n font-size: 0.85em;\n font-style: italic;\n margin: 0;\n}\n\n.mk-unknown__content > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-unknown__content > * + * {\n margin-block-start: 0.5rem;\n}\n";
|
|
5
|
+
export const DOC_CSS = "/*\n * Document rhythm: components own their insides only, never outer margins.\n * This single rule spaces every block-level child of `.doc` identically —\n * paragraphs, headings, and components alike — so new components always\n * sit correctly in the flow with zero per-component tuning.\n */\n.doc > * + * {\n margin-block-start: 1rem;\n}\n\n/*\n * ============================================================================\n * TIER 1 TOKENS — the theming contract\n * ============================================================================\n * Every host that wants a themed (in particular, a dark) `.doc` remaps ONLY\n * these custom properties, deliberately scoped to `.doc` and not `:root`:\n * `doc.css` is a library stylesheet embedded into other people's pages\n * (Obsidian injects it globally), so it must stay polite and never claim\n * `:root`. A host theme layer loaded after this one, at the same\n * specificity, simply redeclares the ones below on `.doc` (or a more\n * specific selector) to win the cascade — see `apps/vscode/src/webview/\n * theme.css` and `apps/obsidian/src/obsidian-theme.css`.\n *\n * Nine neutrals + five semantic hues. This list is deliberately small: a\n * host maps ~14 tokens instead of ~60 individual selectors, and a\n * derivation layer below (Tier 2) builds every finer shade FROM these, so\n * remapping these 14 makes every callout/badge/chart/etc. variant\n * theme-correct for free.\n */\n.doc {\n /* ---------- neutrals ---------- */\n --mk-bg: #fff; /* page ground */\n --mk-raised: #fff; /* raised surface for cards — distinct from ground; dark themes need real separation here where light themes get it from the border alone */\n --mk-fg: #1a1a1a; /* body text */\n --mk-surface: #f4f4f5; /* subtle fill one step off the ground: pre, kbd, script marker, details, default callout, zebra rows */\n --mk-surface-strong: #f0f0f2; /* second step off the ground: inline code, th, progress track, default badge background */\n --mk-border: #e4e4e7; /* hairlines */\n --mk-muted: #52525b; /* secondary text: captions, labels, script summary */\n --mk-faint: #94a3b8; /* tertiary/empty: missing values, empty-chart text, unfilled stars */\n --mk-accent: #3b82f6; /* the single interactive/brand color: active tab, progress bar, chart stroke/fill */\n --mk-on-accent: #fff; /* ink that sits ON a solid --mk-accent fill; a host must supply this because it cannot be derived: it depends on the accent's own lightness, not on the page palette */\n\n /* ---------- semantic hues ---------- */\n --mk-info: #3b82f6;\n --mk-success: #15803d;\n --mk-warning: #d97706;\n --mk-danger: #dc2626;\n --mk-limit: #7c3aed; /* the purple, used for the \"hit a resource limit\" failure kind */\n}\n\n/*\n * ============================================================================\n * TIER 2 DERIVATION FORMULAS\n * ============================================================================\n * Every finer shade `doc.css` needs (a callout's tinted background, a\n * badge's tinted text) is a MIX of a Tier 1 hue against `--mk-bg`/`--mk-fg`,\n * never a literal of its own. Because the mix targets those two tokens\n * specifically, a dark host palette flips the derived shade's polarity\n * correctly with no extra work on the host's part — the whole point of\n * this refactor.\n *\n * Exactly three named percentages are used anywhere in this file. A future\n * component reuses one of these three; it does not invent a fourth.\n *\n * --mk-mix-variant-fill: color-mix(in srgb, <hue> 14%, var(--mk-bg))\n * A quiet tinted background — callout body fill.\n * --mk-mix-strong-fill: color-mix(in srgb, <hue> 18%, var(--mk-bg))\n * A slightly stronger tinted background — badge background.\n * --mk-mix-ink: color-mix(in srgb, <hue> 85%, var(--mk-fg))\n * A hue nudged toward body text — usable as ink (badge text, star\n * color) or as a border (callout border).\n *\n * `color-mix()` is unsupported in most email clients, and a custom property\n * whose value fails to parse does not fall back — the declaration goes\n * invalid-at-computed-value and effectively vanishes. `doc.css` is embedded\n * verbatim into `@markii/html`'s `exportHtmlDocument`, whose documented\n * targets include email and archive output, so every derived token below is\n * defined TWICE: first as a literal hex (today's existing light-mode\n * value), then, guarded by `@supports (color: color-mix(in srgb, red,\n * red))`, redefined via the real mix. A modern browser or either Electron\n * host gets live derivation that tracks a remapped Tier 1 palette; an old\n * email client silently keeps exactly today's light palette.\n */\n.doc {\n /* ---- literal light-mode fallback (used verbatim where color-mix is unsupported) ---- */\n --mk-info-fill: #eff6ff;\n --mk-info-strong-fill: #dbeafe;\n --mk-info-ink: #3b82f6;\n --mk-success-strong-fill: #dcfce7;\n --mk-success-ink: #15803d;\n --mk-warning-fill: #fffbeb;\n --mk-warning-strong-fill: #fef3c7;\n --mk-warning-ink: #d97706;\n --mk-danger-fill: #fef2f2;\n --mk-danger-strong-fill: #fee2e2;\n --mk-danger-ink: #dc2626;\n --mk-limit-ink: #7c3aed;\n /*\n * The keycap's inset depth line. Not a themed hue, but not theme-neutral\n * either: a low-alpha BLACK line is invisible on a dark surface, so the\n * literal below is only the no-`color-mix` fallback (where the palette is\n * the light one anyway). The derivation in the `@supports` block below\n * expresses it against `--mk-fg` instead, so it flips to a light line\n * when a host supplies a dark palette, which is the correct depth cue\n * there. Kept in this block, not the Tier 1 block above, so its literal\n * stays inside a block the no-raw-color-literal test allows.\n */\n --mk-shadow-sm: rgba(0, 0, 0, 0.05);\n}\n\n@supports (color: color-mix(in srgb, red, red)) {\n .doc {\n --mk-shadow-sm: color-mix(in srgb, var(--mk-fg) 8%, transparent);\n\n --mk-info-fill: color-mix(in srgb, var(--mk-info) 14%, var(--mk-bg));\n --mk-info-strong-fill: color-mix(in srgb, var(--mk-info) 18%, var(--mk-bg));\n --mk-info-ink: color-mix(in srgb, var(--mk-info) 85%, var(--mk-fg));\n\n --mk-success-strong-fill: color-mix(\n in srgb,\n var(--mk-success) 18%,\n var(--mk-bg)\n );\n --mk-success-ink: color-mix(in srgb, var(--mk-success) 85%, var(--mk-fg));\n\n --mk-warning-fill: color-mix(in srgb, var(--mk-warning) 14%, var(--mk-bg));\n --mk-warning-strong-fill: color-mix(\n in srgb,\n var(--mk-warning) 18%,\n var(--mk-bg)\n );\n --mk-warning-ink: color-mix(in srgb, var(--mk-warning) 85%, var(--mk-fg));\n\n --mk-danger-fill: color-mix(in srgb, var(--mk-danger) 14%, var(--mk-bg));\n --mk-danger-strong-fill: color-mix(\n in srgb,\n var(--mk-danger) 18%,\n var(--mk-bg)\n );\n --mk-danger-ink: color-mix(in srgb, var(--mk-danger) 85%, var(--mk-fg));\n\n --mk-limit-ink: color-mix(in srgb, var(--mk-limit) 85%, var(--mk-fg));\n }\n}\n\n.doc {\n color: var(--mk-fg);\n font-family:\n system-ui,\n -apple-system,\n 'Segoe UI',\n sans-serif;\n line-height: 1.6;\n}\n\n.doc pre {\n overflow-x: auto;\n background: var(--mk-surface);\n padding: 0.75rem 1rem;\n border-radius: 6px;\n}\n\n.doc code {\n background: var(--mk-surface-strong);\n border-radius: 3px;\n padding: 0.1em 0.35em;\n font-size: 0.9em;\n}\n\n.doc pre code {\n background: none;\n padding: 0;\n}\n\n/* ---------- GFM table ---------- */\n\n/*\n * `display: block` on the table itself (rather than wrapping it in an\n * extra element the renderer doesn't otherwise inject) is what makes a wide\n * table scroll horizontally instead of overflowing the page or the doc\n * column — the table box becomes independently scrollable content, the\n * same trick used by GitHub's own Markdown rendering.\n */\n.doc table {\n display: block;\n overflow-x: auto;\n border-collapse: collapse;\n font-size: 0.95em;\n}\n\n.doc th,\n.doc td {\n border: 1px solid var(--mk-border);\n padding: 0.4rem 0.75rem;\n text-align: left;\n}\n\n.doc th {\n font-weight: 600;\n background: var(--mk-surface-strong);\n}\n\n.doc tr:nth-child(even) {\n background: var(--mk-surface);\n}\n\n/* ---------- GFM task list ---------- */\n\n/*\n * `li:has(> input[type=\"checkbox\"])` scopes bullet removal + checkbox\n * alignment to task-list items only — an ordinary `<ul>`/`<ol>` item keeps\n * its normal bullet/number, since GFM only adds a leading `<input>` to\n * items that used `- [ ]`/`- [x]` syntax.\n */\n.doc li:has(> input[type='checkbox']) {\n list-style: none;\n margin-inline-start: -1.5em;\n}\n\n.doc li > input[type='checkbox'] {\n margin-inline-end: 0.5em;\n vertical-align: middle;\n}\n\n/* ---------- callout ---------- */\n\n.mk-callout {\n border: 1px solid var(--mk-callout-border, var(--mk-border));\n border-left-width: 4px;\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-callout-bg, var(--mk-surface));\n}\n\n.mk-callout--info {\n --mk-callout-border: var(--mk-info-ink);\n --mk-callout-bg: var(--mk-info-fill);\n}\n\n.mk-callout--warning {\n --mk-callout-border: var(--mk-warning-ink);\n --mk-callout-bg: var(--mk-warning-fill);\n}\n\n.mk-callout--danger {\n --mk-callout-border: var(--mk-danger-ink);\n --mk-callout-bg: var(--mk-danger-fill);\n}\n\n.mk-callout__header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-weight: 600;\n}\n\n.mk-callout__icon {\n line-height: 1;\n}\n\n/*\n * `display: flex; flex-direction: column` is load-bearing here, not just\n * `> * + *` margin: several block-holding components (`stat`, `badge`)\n * declare their OWN outer display as `inline-flex`/`inline-block` (correct\n * for sitting inline in a sentence), so without a flex/grid parent they\n * flow side-by-side on the same line instead of stacking — a\n * `margin-block-start` on a same-line inline-level sibling creates no\n * visible gap. Making the body a column flex container forces every\n * child's *used* display to blockify (CSS Flexbox: a flex item's outer\n * display is always block-level), so they stack regardless of the\n * component's own declared display, and the margin rule below then has\n * something to actually separate.\n */\n.mk-callout__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-callout__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-callout__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- kbd ---------- */\n\n.mk-kbd {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.4em;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n border: 1px solid var(--mk-border);\n border-bottom-width: 2px;\n border-radius: 4px;\n background: var(--mk-surface);\n box-shadow: inset 0 -1px 0 var(--mk-shadow-sm);\n}\n\n/* ---------- rating ---------- */\n\n.mk-rating {\n display: inline-flex;\n gap: 0.15em;\n font-size: 1.1em;\n color: var(--mk-faint);\n}\n\n.mk-rating__star--filled {\n color: var(--mk-warning);\n}\n\n/* ---------- divider ---------- */\n\n/*\n * The `::before`/`::after` pseudo-elements are the flanking hairlines; the\n * label sits between them as ordinary flex children. The label carries its\n * own inline margin rather than a flex `gap` because an unlabeled divider\n * has no label element to create a gap around — with `gap` the rule would\n * split into two disconnected segments instead of staying one unbroken\n * line.\n */\n\n.mk-divider {\n display: flex;\n align-items: center;\n color: var(--mk-faint);\n}\n\n.mk-divider::before,\n.mk-divider::after {\n content: '';\n flex: 1 1 0;\n border-block-start: 1px solid var(--mk-border);\n}\n\n.mk-divider--dots::before,\n.mk-divider--dots::after {\n border-block-start-style: dotted;\n}\n\n.mk-divider--ornament::before,\n.mk-divider--ornament::after {\n border-block-start-color: transparent;\n}\n\n.mk-divider--label-left::before {\n flex: 0 0 1.5rem;\n}\n\n.mk-divider--label-right::after {\n flex: 0 0 1.5rem;\n}\n\n.mk-divider__label {\n margin-inline: 0.75em;\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-divider__ornament {\n margin-inline: 0.35em;\n font-size: 0.9em;\n}\n\n/* ---------- value interpolation ---------- */\n\n.mk-value {\n display: inline;\n vertical-align: baseline;\n}\n\n.mk-value--stale {\n color: var(--mk-warning-ink);\n border-bottom: 1px dashed var(--mk-warning-ink);\n}\n\n.mk-value--missing {\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.9em;\n color: var(--mk-faint);\n font-style: italic;\n}\n\n/*\n * Failure-kind modifiers (`@markii/runtime`'s `FailureKind`, docs/scripting.md) —\n * layered on top of `.mk-value--missing`, only ever added when the\n * resolution's root entry carried a `failureKind` on a genuine error (see\n * `ValueDirective`). Each just tints the dashed-underline treatment a\n * distinct hue so a reader can tell \"script bug\" apart from \"needs\n * permission\" / \"needs a manual run\" / \"hit a resource limit\" at a glance,\n * with the full message still available via the `title` tooltip.\n *\n * Failure kinds map onto the Tier 1 semantic hues: script-error -> danger,\n * capability-denied -> warning, tier-blocked -> info, limit -> limit.\n */\n.mk-value--script-error {\n border-bottom: 1px dashed var(--mk-danger-ink);\n}\n\n.mk-value--capability-denied {\n border-bottom: 1px dashed var(--mk-warning-ink);\n}\n\n.mk-value--tier-blocked {\n border-bottom: 1px dashed var(--mk-info-ink);\n}\n\n.mk-value--limit {\n border-bottom: 1px dashed var(--mk-limit-ink);\n}\n\n/* ---------- script marker ---------- */\n\n.mk-script {\n border: 1px solid var(--mk-border);\n border-radius: 6px;\n background: var(--mk-surface);\n}\n\n.mk-script__summary {\n cursor: pointer;\n padding: 0.5rem 0.75rem;\n font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace;\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-script[open] > .mk-script__summary {\n border-bottom: 1px solid var(--mk-border);\n}\n\n.mk-script__code {\n margin: 0.75rem;\n margin-block-start: 0;\n}\n\n.mk-script__empty {\n margin: 0.75rem;\n margin-block-start: 0;\n font-size: 0.85em;\n font-style: italic;\n color: var(--mk-faint);\n}\n\n/* ---------- details ---------- */\n\n.mk-details {\n border: 1px solid var(--mk-border);\n border-radius: 6px;\n padding: 0.75rem 1rem;\n background: var(--mk-surface);\n}\n\n.mk-details__summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n.mk-details[open] > .mk-details__summary {\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-details__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-details__body > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-details__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- card ---------- */\n\n.mk-card {\n border: 1px solid var(--mk-border);\n border-radius: 8px;\n padding: 1rem;\n background: var(--mk-raised);\n}\n\n.mk-card__title {\n font-weight: 600;\n margin-block-end: 0.5rem;\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-card__body {\n display: flex;\n flex-direction: column;\n}\n\n.mk-card__body > :first-child {\n margin-block-start: 0;\n}\n\n.mk-card__body > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- badge ---------- */\n\n.mk-badge {\n display: inline-block;\n vertical-align: baseline;\n line-height: 1.4;\n height: 1.4em;\n padding: 0 0.6em;\n font-size: 0.8em;\n font-weight: 600;\n border-radius: 999px;\n color: var(--mk-badge-fg, var(--mk-fg));\n background: var(--mk-badge-bg, var(--mk-surface-strong));\n}\n\n.mk-badge--info {\n --mk-badge-fg: var(--mk-info-ink);\n --mk-badge-bg: var(--mk-info-strong-fill);\n}\n\n.mk-badge--success {\n --mk-badge-fg: var(--mk-success-ink);\n --mk-badge-bg: var(--mk-success-strong-fill);\n}\n\n.mk-badge--warning {\n --mk-badge-fg: var(--mk-warning-ink);\n --mk-badge-bg: var(--mk-warning-strong-fill);\n}\n\n.mk-badge--danger {\n --mk-badge-fg: var(--mk-danger-ink);\n --mk-badge-bg: var(--mk-danger-strong-fill);\n}\n\n/* ---------- figure ---------- */\n\n.mk-figure {\n margin: 0;\n}\n\n.mk-figure__img {\n display: block;\n max-width: 100%;\n height: auto;\n border-radius: 6px;\n}\n\n.mk-figure__caption {\n margin-block-start: 0.5rem;\n font-size: 0.9em;\n color: var(--mk-muted);\n}\n\n.mk-figure__caption > :first-child {\n margin-block-start: 0;\n}\n\n.mk-figure__caption > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- tabs ---------- */\n\n.mk-tabs__list {\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--mk-border);\n}\n\n.mk-tabs__button {\n cursor: pointer;\n border: none;\n background: none;\n padding: 0.5rem 0.9rem;\n font: inherit;\n font-weight: 600;\n color: var(--mk-muted);\n border-bottom: 2px solid transparent;\n margin-block-end: -1px;\n}\n\n.mk-tabs__button--active {\n color: var(--mk-accent);\n border-bottom-color: var(--mk-accent);\n}\n\n/* Blockifies inline-flex/inline-block children (`stat`, `badge`, ...) so they stack — see `.mk-callout__body`'s comment above for why this is load-bearing. */\n.mk-tab {\n display: flex;\n flex-direction: column;\n padding-block-start: 0.75rem;\n}\n\n.mk-tab > :first-child {\n margin-block-start: 0;\n}\n\n.mk-tab > * + * {\n margin-block-start: 0.5rem;\n}\n\n/* ---------- stat ---------- */\n\n.mk-stat {\n display: inline-flex;\n flex-direction: column;\n gap: 0.15rem;\n}\n\n.mk-stat__value {\n font-size: 1.8em;\n font-weight: 700;\n line-height: 1.2;\n}\n\n.mk-stat__label {\n font-size: 0.85em;\n color: var(--mk-muted);\n}\n\n.mk-stat__delta {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--mk-muted);\n}\n\n.mk-stat__delta--up {\n color: var(--mk-success-ink);\n}\n\n.mk-stat__delta--down {\n color: var(--mk-danger-ink);\n}\n\n.mk-stat__delta--flat {\n color: var(--mk-muted);\n}\n\n/* ---------- progress ---------- */\n\n.mk-progress {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n}\n\n.mk-progress__label {\n font-size: 0.85em;\n color: var(--mk-muted);\n flex: 0 0 auto;\n}\n\n.mk-progress__track {\n flex: 1 1 auto;\n height: 0.6rem;\n border-radius: 999px;\n background: var(--mk-border);\n overflow: hidden;\n}\n\n.mk-progress__bar {\n height: 100%;\n background: var(--mk-accent);\n border-radius: inherit;\n}\n\n.mk-progress__percent {\n flex: 0 0 auto;\n font-size: 0.85em;\n font-variant-numeric: tabular-nums;\n color: var(--mk-muted);\n}\n\n/* ---------- chart ---------- */\n\n.mk-chart {\n display: block;\n max-width: 100%;\n}\n\n.mk-chart__line {\n fill: none;\n stroke: var(--mk-accent);\n stroke-width: 2;\n stroke-linejoin: round;\n stroke-linecap: round;\n}\n\n.mk-chart__bar {\n fill: var(--mk-accent);\n}\n\n.mk-chart--empty {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.8em;\n font-style: italic;\n color: var(--mk-faint);\n border: 1px dashed var(--mk-border);\n border-radius: 6px;\n}\n\n/* ---------- data-binding state (docs/scripting.md) ---------- */\n\n/*\n * The block twins of the `.mk-value--*` markers above: a data-bound\n * component (`stat`, `progress`, `chart`) whose `data=` binding is stale or\n * failed keeps its ordinary QUIET body — `—`, a `0%` bar, `no data` — and\n * says so only through these hooks plus its `title` tooltip. No rule here\n * may add body text (`content:` is deliberately absent) or an outer margin;\n * the failure taxonomy's wording lives in one place only, `components/\n * failure-presentation.ts`.\n *\n * The hues match `.mk-value--*` exactly, so the same failing name reads the\n * same whether it surfaced inline via `:value[...]` or as a component.\n */\n.mk-stat--stale,\n.mk-progress--stale,\n.mk-chart--stale {\n opacity: 0.8;\n}\n\n.mk-stat--script-error,\n.mk-progress--script-error,\n.mk-chart--script-error {\n border-bottom: 2px solid var(--mk-danger-ink);\n}\n\n.mk-stat--capability-denied,\n.mk-progress--capability-denied,\n.mk-chart--capability-denied {\n border-bottom: 2px solid var(--mk-warning-ink);\n}\n\n.mk-stat--tier-blocked,\n.mk-progress--tier-blocked,\n.mk-chart--tier-blocked {\n border-bottom: 2px solid var(--mk-info-ink);\n}\n\n.mk-stat--limit,\n.mk-progress--limit,\n.mk-chart--limit {\n border-bottom: 2px solid var(--mk-limit-ink);\n}\n\n/* ---------- layout presets (docs/format.md) ---------- */\n\n/*\n * `render.tsx` only wraps a directive in a `mk-width-*`/`mk-align-*` `<div>`\n * when at least one of these classes actually applies, so the wrapper below\n * IS the element sitting directly in `.doc`'s rhythm flow — never\n * `margin-block` here, that's `.doc > * + *`'s job alone; setting it on the\n * wrapper too would double up spacing. `max-width: min(<size>, 100%)` keeps\n * every preset from ever overflowing the document column, even on a narrow\n * viewport where the size below is wider than the column itself.\n */\n.mk-width-narrow {\n max-width: min(30rem, 100%);\n}\n\n.mk-width-wide {\n max-width: min(64rem, 100%);\n}\n\n/* \"full\" is the full available column width — not a viewport-bleed hack with negative margins. */\n.mk-width-full {\n max-width: 100%;\n}\n\n/*\n * \"fit\" is the one preset that sets `width` rather than only capping it: it\n * shrinks the block to its own content instead of filling the column, and\n * `max-width` keeps that from overflowing when the content is wider than\n * the column. Because the box is now narrower than its container, the\n * `mk-align-*` auto margins below finally have room to work, which is what\n * makes `{width=fit align=right}` hug the content AND sit right.\n */\n.mk-width-fit {\n width: fit-content;\n max-width: 100%;\n}\n\n.mk-align-left {\n margin-inline-end: auto;\n}\n\n.mk-align-center {\n margin-inline: auto;\n}\n\n.mk-align-right {\n margin-inline-start: auto;\n}\n\n/*\n * The `:::center`/`:::left`/`:::right`/`:::wide`/`:::narrow`/`:::full`/`:::fit`\n * layout wrappers (docs/format.md, `layout-wrapper.tsx`) reuse the `mk-width-*`/\n * `mk-align-*` classes above and add `mk-layout` on top for the rules below,\n * which only make sense on a container that has its OWN plain-markdown\n * children (a table, an image, a paragraph) rather than on the bare\n * attribute-interception wrapper `render.tsx` emits for `width=`/`align=`.\n *\n * No outer margin on `.mk-layout` itself, same rule as every component\n * (Architecture rule 4) — `.doc > * + *` spaces the wrapper against its\n * siblings. This rule instead restores RHYTHM *inside* the wrapper's own\n * scope, mirroring `.mk-card__body > * + *`: without it, the wrapper's\n * children would have no spacing between them at all, since `.doc > * + *`\n * only ever sees the wrapper `<div>` as a whole, never reaches inside it.\n */\n.mk-layout > * + * {\n margin-block-start: 1rem;\n}\n\n/*\n * An alignment wrapper additionally sets text alignment for everything in\n * scope, not just the shrink-to-fit block alignment below. `left` carries\n * its own rule for a reason: it is the one wrapper written specifically to\n * opt a scope back OUT of an alignment it inherited (a cell of a\n * `:::row{text=center}`), and only a DECLARED value beats an inherited one.\n * Without this rule `:::left` would silently keep the centered text it was\n * written to undo.\n */\n.mk-layout.mk-align-left {\n text-align: left;\n}\n\n.mk-layout.mk-align-center {\n text-align: center;\n}\n\n.mk-layout.mk-align-right {\n text-align: right;\n}\n\n/*\n * `.mk-align-center`/`.mk-align-right` above (shared with the `align=`\n * attribute wrapper) only center/right-align the wrapper `<div>` ITSELF\n * within ITS container — they say nothing about the wrapper's own children.\n * These two rules do that: they shrink-to-fit and align every direct child\n * of the wrapper's scope, which is what actually centers/right-aligns a\n * narrower-than-column table or image sitting inside `:::center`/`:::right`.\n */\n.mk-layout.mk-align-center > * {\n margin-inline: auto;\n}\n\n.mk-layout.mk-align-right > * {\n margin-inline-start: auto;\n margin-inline-end: 0;\n}\n\n/*\n * `.doc table` (above) sets `display: block` for horizontal-scroll\n * overflow, which also makes the table fill its column — defeating\n * shrink-to-fit alignment before it can even apply. These two rules size a\n * table down to its content instead, so the `margin-inline` rules above\n * have a narrower box to actually move. `.doc th`/`.doc td` set\n * `text-align: left` directly on the cells, so this scope's `text-align`\n * never flips table cell text — only the table's own position in the\n * scope, and any non-table text alongside it.\n */\n.mk-layout.mk-align-center > table,\n.mk-layout.mk-align-right > table {\n width: fit-content;\n max-width: 100%;\n}\n\n/* ---------- text alignment inside a component (docs/format.md) ---------- */\n\n/*\n * The `text` attribute of `row`, `cell`, `card`, and `callout`. Deliberately\n * separate from the `mk-align-*` classes above: those place a block's BOX\n * within the column and never touch its contents, while these align the\n * content inside one component and never move its box. Two different jobs,\n * two different class names, so neither can be mistaken for the other.\n *\n * One rule per value, defined once and honored by all four components, which\n * is what lets `:::row{text=center}` reach its cells through ordinary CSS\n * inheritance: `.mk-cell` declares no `text-align` of its own, so the row's\n * value flows in, and a cell that declares its own (or an alignment wrapper\n * written inside it) wins simply by being declared.\n */\n.mk-text-left {\n text-align: left;\n}\n\n.mk-text-center {\n text-align: center;\n}\n\n.mk-text-right {\n text-align: right;\n}\n\n/* ---------- row ---------- */\n\n.mk-row {\n display: grid;\n gap: 1rem;\n grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));\n}\n\n/*\n * `minmax(0, 1fr)` — a bare `1fr` track can still grow past an equal share\n * to fit a wide intrinsic-content cell (e.g. a `chart` SVG, a long code\n * span); pinning the minimum to `0` is what keeps such a cell, and\n * therefore the whole row, from blowing out past its column.\n */\n.mk-row--cols-2 {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.mk-row--cols-3 {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n.mk-row--cols-4 {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n\n/*\n * No separate blockification rule is needed here, unlike `.mk-card__body`'s:\n * a grid container (which `.mk-row` is) already promotes every direct\n * child's outer display to block-level as a grid item, same as a flex\n * container does — an inline-flex/inline-block component (`stat`, `badge`,\n * ...) used directly as a row cell already stacks/sizes as a proper grid\n * cell with no extra rule required.\n */\n\n@media (max-width: 40rem) {\n .mk-row,\n .mk-row--cols-2,\n .mk-row--cols-3,\n .mk-row--cols-4 {\n grid-template-columns: 1fr;\n }\n}\n\n/* ---------- cell ---------- */\n\n/*\n * `:::cell` (`cell.tsx`) is a TRANSPARENT grouping container: it has no\n * border, background, padding, or outer margin of its own — its whole job is\n * making several blocks count as ONE `.mk-row` grid cell, so anything visual\n * here would betray that. `.mk-cell` therefore has no rule at all; only the\n * rhythm-restoring rule below exists, mirroring `.mk-layout > * + *`:\n * `.doc > * + *` sees the cell as a single box and never reaches inside it,\n * so without this its children would sit flush against each other.\n */\n.mk-cell > * + * {\n margin-block-start: 1rem;\n}\n\n/* ---------- empty inline-component marker ---------- */\n\n/*\n * Wraps an `inline: true` component that received no content\n * (`::badge{label=\"x\"}` instead of `:badge[x]`) — see `render.tsx`'s\n * `isRegisteredInline`/`isEmptyContent` and `render.ts`'s HTML-engine\n * mirror. The component underneath renders unchanged; this is a quiet\n * perceptual hook only (a faint dashed underline, matching the treatment\n * `.mk-value--stale` already gives a quiet-but-present state), with the\n * reason carried in the `title` tooltip rather than in the page.\n */\n.mk-inline-empty {\n border-bottom: 1px dashed var(--mk-faint);\n}\n\n/* ---------- unknown directive fallback ---------- */\n\n.mk-unknown {\n border: 1px dashed var(--mk-faint);\n border-radius: 6px;\n color: var(--mk-muted);\n}\n\n.mk-unknown--block {\n padding: 0.75rem 1rem;\n}\n\n.mk-unknown--inline {\n display: inline-flex;\n align-items: baseline;\n gap: 0.4em;\n padding: 0 0.4em;\n vertical-align: baseline;\n}\n\n.mk-unknown__label {\n font-size: 0.85em;\n font-style: italic;\n margin: 0;\n}\n\n.mk-unknown__content > :first-child {\n margin-block-start: 0.5rem;\n}\n\n.mk-unknown__content > * + * {\n margin-block-start: 0.5rem;\n}\n";
|
package/dist/layout.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LAYOUT_ATTRIBUTE_KEYS } from '@markii/stdlib';
|
|
2
|
+
import type { LayoutAxis } from '@markii/stdlib';
|
|
2
3
|
import type { DirectiveAttributes } from './registry.js';
|
|
3
4
|
/**
|
|
4
5
|
* The closed set of layout-preset attributes (docs/format.md): a small,
|
|
@@ -12,6 +13,21 @@ import type { DirectiveAttributes } from './registry.js';
|
|
|
12
13
|
* hand-copied literal in either.
|
|
13
14
|
*/
|
|
14
15
|
export { LAYOUT_ATTRIBUTE_KEYS };
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a `text` attribute value to its class, or `undefined` for
|
|
18
|
+
* anything outside the closed `left | center | right` vocabulary (including
|
|
19
|
+
* a bare/empty/hostile value): the same defensive shape as `alignClassFor`.
|
|
20
|
+
* The class name is always one of the fixed literals in `TEXT_CLASSES`; an
|
|
21
|
+
* author-supplied value is never interpolated into markup. Never throws.
|
|
22
|
+
*/
|
|
23
|
+
export declare function textClassFor(value: string | null | undefined): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Joins a component's own base class with the `text` class its `text`
|
|
26
|
+
* attribute resolves to, if any. The one helper the four `text`-accepting
|
|
27
|
+
* components share, matching `@markii/react`'s `withTextClass` so the two
|
|
28
|
+
* engines emit the same class string.
|
|
29
|
+
*/
|
|
30
|
+
export declare function withTextClass(baseClassName: string, value: string | null | undefined): string;
|
|
15
31
|
export interface ResolvedLayoutAttributes {
|
|
16
32
|
/** `attributes` with every reserved layout key (present, valid or not) removed. */
|
|
17
33
|
attributes: DirectiveAttributes;
|
|
@@ -31,5 +47,13 @@ export interface ResolvedLayoutAttributes {
|
|
|
31
47
|
* a real key via the prototype chain. An invalid or hostile value never
|
|
32
48
|
* produces a class: it is dropped silently, exactly like an absent attribute.
|
|
33
49
|
* Never throws.
|
|
50
|
+
*
|
|
51
|
+
* `ownedAxis` names an axis the DIRECTIVE'S NAME already decided, which only
|
|
52
|
+
* a layout wrapper has (`:::center` owns `align`, `:::fit` owns `width`; see
|
|
53
|
+
* `@markii/stdlib`'s `layoutWrapperAxis`). That axis's attribute is still
|
|
54
|
+
* stripped, exactly like any reserved key, but produces no class: the name
|
|
55
|
+
* wins, so `:::center{align=right}` is simply centered. The other axis
|
|
56
|
+
* resolves normally, which is what lets a wrapper carry it
|
|
57
|
+
* (`:::center{width=fit}`). Mirrors `@markii/react`'s signature.
|
|
34
58
|
*/
|
|
35
|
-
export declare function resolveLayoutAttributes(attributes: DirectiveAttributes): ResolvedLayoutAttributes;
|
|
59
|
+
export declare function resolveLayoutAttributes(attributes: DirectiveAttributes, ownedAxis?: LayoutAxis): ResolvedLayoutAttributes;
|
package/dist/layout.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ALIGN_PRESETS, LAYOUT_ATTRIBUTE_KEYS, WIDTH_PRESETS, } from '@markii/stdlib';
|
|
1
|
+
import { ALIGN_PRESETS, LAYOUT_ATTRIBUTE_KEYS, TEXT_ALIGN_PRESETS, WIDTH_PRESETS, } from '@markii/stdlib';
|
|
2
2
|
/**
|
|
3
3
|
* The closed set of layout-preset attributes (docs/format.md): a small,
|
|
4
4
|
* non-freeform-CSS vocabulary any block directive can carry regardless of
|
|
@@ -20,8 +20,8 @@ const NORMAL_WIDTH = 'normal';
|
|
|
20
20
|
* hostile value like `__proto__` or `constructor` cannot resolve through
|
|
21
21
|
* the prototype chain to an inherited `Object.prototype` member; it simply
|
|
22
22
|
* misses the lookup, same as any other unrecognized value. `doc.css`
|
|
23
|
-
* (shared with `@markii/react`) defines `.mk-width-
|
|
24
|
-
* `.mk-width-wide`/`.mk-width-full` to match.
|
|
23
|
+
* (shared with `@markii/react`) defines `.mk-width-fit`/
|
|
24
|
+
* `.mk-width-narrow`/`.mk-width-wide`/`.mk-width-full` to match.
|
|
25
25
|
*/
|
|
26
26
|
const WIDTH_CLASSES = Object.assign(Object.create(null), Object.fromEntries(WIDTH_PRESETS.filter((preset) => preset !== NORMAL_WIDTH).map((preset) => [
|
|
27
27
|
preset,
|
|
@@ -34,6 +34,41 @@ const WIDTH_CLASSES = Object.assign(Object.create(null), Object.fromEntries(WIDT
|
|
|
34
34
|
* `.mk-align-right` to match.
|
|
35
35
|
*/
|
|
36
36
|
const ALIGN_CLASSES = Object.assign(Object.create(null), Object.fromEntries(ALIGN_PRESETS.map((preset) => [preset, `mk-align-${preset}`])));
|
|
37
|
+
/**
|
|
38
|
+
* `text` value -> class, derived mechanically from `@markii/stdlib`'s
|
|
39
|
+
* `TEXT_ALIGN_PRESETS` as `mk-text-<preset>`. Same null-prototype defense as
|
|
40
|
+
* the two maps above, and the same class names `@markii/react` emits, so
|
|
41
|
+
* `doc.css` covers both engines.
|
|
42
|
+
*
|
|
43
|
+
* `text` is NOT a reserved layout attribute: it is an ordinary
|
|
44
|
+
* per-component attribute of `row`/`cell`/`card`/`callout` (docs/spec.md
|
|
45
|
+
* §3), stripped by nothing and read by those four components themselves.
|
|
46
|
+
* Its class map lives here only because this module is already this
|
|
47
|
+
* engine's one home of "preset value -> class".
|
|
48
|
+
*/
|
|
49
|
+
const TEXT_CLASSES = Object.assign(Object.create(null), Object.fromEntries(TEXT_ALIGN_PRESETS.map((preset) => [preset, `mk-text-${preset}`])));
|
|
50
|
+
/**
|
|
51
|
+
* Resolves a `text` attribute value to its class, or `undefined` for
|
|
52
|
+
* anything outside the closed `left | center | right` vocabulary (including
|
|
53
|
+
* a bare/empty/hostile value): the same defensive shape as `alignClassFor`.
|
|
54
|
+
* The class name is always one of the fixed literals in `TEXT_CLASSES`; an
|
|
55
|
+
* author-supplied value is never interpolated into markup. Never throws.
|
|
56
|
+
*/
|
|
57
|
+
export function textClassFor(value) {
|
|
58
|
+
if (value === null || value === undefined || value === '')
|
|
59
|
+
return undefined;
|
|
60
|
+
return TEXT_CLASSES[value];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Joins a component's own base class with the `text` class its `text`
|
|
64
|
+
* attribute resolves to, if any. The one helper the four `text`-accepting
|
|
65
|
+
* components share, matching `@markii/react`'s `withTextClass` so the two
|
|
66
|
+
* engines emit the same class string.
|
|
67
|
+
*/
|
|
68
|
+
export function withTextClass(baseClassName, value) {
|
|
69
|
+
const textClass = textClassFor(value);
|
|
70
|
+
return textClass ? `${baseClassName} ${textClass}` : baseClassName;
|
|
71
|
+
}
|
|
37
72
|
function widthClassFor(value) {
|
|
38
73
|
if (value === null || value === undefined || value === '')
|
|
39
74
|
return undefined;
|
|
@@ -59,21 +94,29 @@ function alignClassFor(value) {
|
|
|
59
94
|
* a real key via the prototype chain. An invalid or hostile value never
|
|
60
95
|
* produces a class: it is dropped silently, exactly like an absent attribute.
|
|
61
96
|
* Never throws.
|
|
97
|
+
*
|
|
98
|
+
* `ownedAxis` names an axis the DIRECTIVE'S NAME already decided, which only
|
|
99
|
+
* a layout wrapper has (`:::center` owns `align`, `:::fit` owns `width`; see
|
|
100
|
+
* `@markii/stdlib`'s `layoutWrapperAxis`). That axis's attribute is still
|
|
101
|
+
* stripped, exactly like any reserved key, but produces no class: the name
|
|
102
|
+
* wins, so `:::center{align=right}` is simply centered. The other axis
|
|
103
|
+
* resolves normally, which is what lets a wrapper carry it
|
|
104
|
+
* (`:::center{width=fit}`). Mirrors `@markii/react`'s signature.
|
|
62
105
|
*/
|
|
63
|
-
export function resolveLayoutAttributes(attributes) {
|
|
106
|
+
export function resolveLayoutAttributes(attributes, ownedAxis) {
|
|
64
107
|
let rest = attributes;
|
|
65
108
|
const classes = [];
|
|
66
109
|
if (Object.hasOwn(rest, 'width')) {
|
|
67
110
|
const { width, ...remainder } = rest;
|
|
68
111
|
rest = remainder;
|
|
69
|
-
const widthClass = widthClassFor(width);
|
|
112
|
+
const widthClass = ownedAxis === 'width' ? undefined : widthClassFor(width);
|
|
70
113
|
if (widthClass)
|
|
71
114
|
classes.push(widthClass);
|
|
72
115
|
}
|
|
73
116
|
if (Object.hasOwn(rest, 'align')) {
|
|
74
117
|
const { align, ...remainder } = rest;
|
|
75
118
|
rest = remainder;
|
|
76
|
-
const alignClass = alignClassFor(align);
|
|
119
|
+
const alignClass = ownedAxis === 'align' ? undefined : alignClassFor(align);
|
|
77
120
|
if (alignClass)
|
|
78
121
|
classes.push(alignClass);
|
|
79
122
|
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* the React renderer's, so a note resolves the same way in both.
|
|
8
8
|
*/
|
|
9
9
|
import type { FailureKind, ValueStatus } from '@markii/runtime';
|
|
10
|
+
import type { LayoutAxis } from '@markii/stdlib';
|
|
10
11
|
/**
|
|
11
12
|
* Attributes parsed off a directive, e.g. `{type=warning title="Careful"}`. A
|
|
12
13
|
* bare attribute (present but valueless, e.g. `{collapsed}`) arrives as
|
|
@@ -62,6 +63,17 @@ export interface HtmlRenderContext {
|
|
|
62
63
|
dataStatus?: ValueStatus;
|
|
63
64
|
dataError?: string;
|
|
64
65
|
dataFailureKind?: FailureKind;
|
|
66
|
+
/**
|
|
67
|
+
* The layout class the directive's reserved `width`/`align` attributes
|
|
68
|
+
* resolved to, handed to the component instead of being applied to a
|
|
69
|
+
* wrapper `<div>` around it. Present ONLY for an entry registered with
|
|
70
|
+
* `layout` (a scope component, such as the standard `:::center`), and only
|
|
71
|
+
* for the axis that entry does not already own. Mirrors
|
|
72
|
+
* `@markii/react`'s `layoutClassName` prop, carried on `ctx` for the same
|
|
73
|
+
* reason the data-binding fields are: `HtmlComponent` takes three
|
|
74
|
+
* arguments and has no room for a fourth.
|
|
75
|
+
*/
|
|
76
|
+
layoutClassName?: string;
|
|
65
77
|
}
|
|
66
78
|
/**
|
|
67
79
|
* One registry component: receives the directive's raw string attributes
|
|
@@ -80,6 +92,17 @@ export type HtmlComponent = (attributes: DirectiveAttributes, childrenHtml: stri
|
|
|
80
92
|
export interface HtmlRegistryEntry {
|
|
81
93
|
component: HtmlComponent;
|
|
82
94
|
inline?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Declares this component a LAYOUT SCOPE that already sets one of the two
|
|
97
|
+
* layout axes by its own name, the way the standard `:::center` (align)
|
|
98
|
+
* and `:::fit` (width) wrappers do. The reserved attribute for that axis
|
|
99
|
+
* is dropped without effect; the other axis resolves and arrives as
|
|
100
|
+
* `ctx.layoutClassName` instead of on a wrapper `<div>`, so the scope
|
|
101
|
+
* emits one element carrying both classes. Either way a component never
|
|
102
|
+
* receives `width` or `align` among its attributes (docs/spec.md §2).
|
|
103
|
+
* Mirrors `@markii/react`'s `RegistryEntry.layout`.
|
|
104
|
+
*/
|
|
105
|
+
layout?: LayoutAxis;
|
|
83
106
|
}
|
|
84
107
|
/** One alias: a second name for an existing component, optionally carrying preset attributes. */
|
|
85
108
|
export interface RegistryAlias {
|
|
@@ -123,6 +146,20 @@ export declare function mergeHtmlRegistries(...registries: HtmlRegistry[]): Html
|
|
|
123
146
|
* renderer (docs/spec.md requirement 4).
|
|
124
147
|
*/
|
|
125
148
|
export declare function readRegistryComponent(entry: HtmlRegistryEntry | undefined): HtmlComponent | undefined;
|
|
149
|
+
/**
|
|
150
|
+
* The layout axis the component registered under `name` owns, or
|
|
151
|
+
* `undefined` when there is no such entry, it has no usable component, or it
|
|
152
|
+
* is not a layout scope. Reads `entry.layout` behind the same try/catch
|
|
153
|
+
* `readRegistryComponent` uses, so a hostile throwing getter degrades to
|
|
154
|
+
* "not a layout scope"; a value that is not one of the two axis names is
|
|
155
|
+
* ignored the same way an invalid `width=` is.
|
|
156
|
+
*
|
|
157
|
+
* A broken entry is deliberately NOT a layout scope: it renders the
|
|
158
|
+
* unknown-directive fallback, which has no root element to hand a class to,
|
|
159
|
+
* so the directive keeps the ordinary wrapper `<div>` and its `width`/
|
|
160
|
+
* `align` still show. Mirrors `@markii/react`'s `registryLayoutAxis`.
|
|
161
|
+
*/
|
|
162
|
+
export declare function registryLayoutAxis(registry: HtmlRegistry, name: string): LayoutAxis | undefined;
|
|
126
163
|
/** A directive name and attributes after alias resolution. */
|
|
127
164
|
export interface ResolvedDirective {
|
|
128
165
|
name: string;
|
package/dist/registry.js
CHANGED
|
@@ -77,6 +77,31 @@ export function readRegistryComponent(entry) {
|
|
|
77
77
|
return undefined;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The layout axis the component registered under `name` owns, or
|
|
82
|
+
* `undefined` when there is no such entry, it has no usable component, or it
|
|
83
|
+
* is not a layout scope. Reads `entry.layout` behind the same try/catch
|
|
84
|
+
* `readRegistryComponent` uses, so a hostile throwing getter degrades to
|
|
85
|
+
* "not a layout scope"; a value that is not one of the two axis names is
|
|
86
|
+
* ignored the same way an invalid `width=` is.
|
|
87
|
+
*
|
|
88
|
+
* A broken entry is deliberately NOT a layout scope: it renders the
|
|
89
|
+
* unknown-directive fallback, which has no root element to hand a class to,
|
|
90
|
+
* so the directive keeps the ordinary wrapper `<div>` and its `width`/
|
|
91
|
+
* `align` still show. Mirrors `@markii/react`'s `registryLayoutAxis`.
|
|
92
|
+
*/
|
|
93
|
+
export function registryLayoutAxis(registry, name) {
|
|
94
|
+
const entry = Object.hasOwn(registry, name) ? registry[name] : undefined;
|
|
95
|
+
if (readRegistryComponent(entry) == null)
|
|
96
|
+
return undefined;
|
|
97
|
+
try {
|
|
98
|
+
const axis = entry?.layout;
|
|
99
|
+
return axis === 'width' || axis === 'align' ? axis : undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
80
105
|
/** Whether `registry` has a real, usable component under `name` (own property, non-nullish, non-throwing). */
|
|
81
106
|
function hasComponent(registry, name) {
|
|
82
107
|
return (Object.hasOwn(registry, name) &&
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { toHast, nodeToHast, parseMetaAttributes, isValidScriptName, isBareAttribute, } from '@markii/core';
|
|
2
2
|
import { toHtml } from 'hast-util-to-html';
|
|
3
|
-
import { readRegistryComponent, resolveDirectiveAlias } from './registry.js';
|
|
3
|
+
import { readRegistryComponent, registryLayoutAxis, resolveDirectiveAlias, } from './registry.js';
|
|
4
4
|
import { resolveLayoutAttributes } from './layout.js';
|
|
5
5
|
import { escapeHtml } from './escape.js';
|
|
6
6
|
import { resolveScopedPath } from './resolve.js';
|
|
@@ -82,6 +82,18 @@ function createBaseContext(scope) {
|
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* `ctx` with one layout scope's resolved open-axis class attached, for the
|
|
87
|
+
* single component invocation it belongs to. `undefined` (every ordinary
|
|
88
|
+
* directive) returns `ctx` untouched, so a component that is not a layout
|
|
89
|
+
* scope can never observe the field at all, matching how `@markii/react`
|
|
90
|
+
* only spreads the `layoutClassName` prop when it has one.
|
|
91
|
+
*/
|
|
92
|
+
function withLayoutClass(ctx, layoutClassName) {
|
|
93
|
+
if (layoutClassName === undefined)
|
|
94
|
+
return ctx;
|
|
95
|
+
return { ...ctx, layoutClassName };
|
|
96
|
+
}
|
|
85
97
|
/** `ctx` with one directive's resolved `data=` binding attached, for the single component invocation that binding belongs to. */
|
|
86
98
|
function withDataBinding(ctx, binding) {
|
|
87
99
|
if (!('data' in binding))
|
|
@@ -309,7 +321,7 @@ function renderScriptMarker(node) {
|
|
|
309
321
|
}
|
|
310
322
|
}
|
|
311
323
|
/** Resolves one directive (registry component, `:value[...]`, or the fallback) given its layout-stripped attributes. Never throws. */
|
|
312
|
-
function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope) {
|
|
324
|
+
function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope, layoutClassName) {
|
|
313
325
|
if (name === VALUE_DIRECTIVE_NAME)
|
|
314
326
|
return ctx.valueMarker(plainLabel);
|
|
315
327
|
const inline = kind === TEXT_DIRECTIVE_KIND;
|
|
@@ -324,7 +336,7 @@ function renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel
|
|
|
324
336
|
const binding = resolveDataAttribute(attributes, scope);
|
|
325
337
|
let rendered;
|
|
326
338
|
try {
|
|
327
|
-
rendered = component(binding.attributes, childrenHtml, withDataBinding(ctx, binding));
|
|
339
|
+
rendered = component(binding.attributes, childrenHtml, withLayoutClass(withDataBinding(ctx, binding), layoutClassName));
|
|
328
340
|
}
|
|
329
341
|
catch {
|
|
330
342
|
return componentError(name || '(unnamed)', inline, childrenHtml);
|
|
@@ -350,10 +362,18 @@ function renderDirective(element, registry, ctx, scope) {
|
|
|
350
362
|
const { name, attributes: aliased } = written === VALUE_DIRECTIVE_NAME
|
|
351
363
|
? { name: written, attributes: rawAttributes }
|
|
352
364
|
: resolveDirectiveAlias(registry, written, rawAttributes);
|
|
365
|
+
// A LAYOUT SCOPE (an entry registered with `layout`, such as the standard
|
|
366
|
+
// `:::center`) already sets one axis by its own name: that axis's
|
|
367
|
+
// attribute is dropped without effect, and the other axis's class goes to
|
|
368
|
+
// the component through `ctx` rather than onto a wrapper `<div>`, so
|
|
369
|
+
// `:::center{width=fit}` comes out as one element carrying both classes.
|
|
370
|
+
// Mirrors `@markii/react`'s `createDirectiveElement` exactly.
|
|
353
371
|
const isBlock = kind !== TEXT_DIRECTIVE_KIND;
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
|
|
372
|
+
const ownedAxis = registryLayoutAxis(registry, name);
|
|
373
|
+
const { attributes, className } = resolveLayoutAttributes(aliased, ownedAxis);
|
|
374
|
+
const isLayoutScope = ownedAxis !== undefined && isBlock;
|
|
375
|
+
const content = renderDirectiveContent(name, kind, attributes, childrenHtml, plainLabel, registry, ctx, scope, isLayoutScope ? className : undefined);
|
|
376
|
+
return isBlock && className && !isLayoutScope
|
|
357
377
|
? `<div class="${escapeHtml(className)}">${content}</div>`
|
|
358
378
|
: content;
|
|
359
379
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markii/html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "A framework-free static HTML renderer for Markii (.mk.md): a registry-driven hast-to-HTML string engine. Zero React; for stopped-changing documents (publish, CI, email, archive).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"hast-util-to-html": "^9.0.0",
|
|
57
|
-
"@markii/core": "0.
|
|
58
|
-
"@markii/runtime": "0.
|
|
59
|
-
"@markii/stdlib": "0.
|
|
57
|
+
"@markii/core": "0.10.0",
|
|
58
|
+
"@markii/runtime": "0.10.0",
|
|
59
|
+
"@markii/stdlib": "0.10.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/hast": "^3.0.4"
|