@cogenta/theme-kit 0.3.2
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/LICENSE +373 -0
- package/dist/actions.d.ts +17 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +43 -0
- package/dist/actions.js.map +1 -0
- package/dist/archive.d.ts +85 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +21 -0
- package/dist/archive.js.map +1 -0
- package/dist/chrome.d.ts +161 -0
- package/dist/chrome.d.ts.map +1 -0
- package/dist/chrome.js +198 -0
- package/dist/chrome.js.map +1 -0
- package/dist/comments.d.ts +54 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +68 -0
- package/dist/comments.js.map +1 -0
- package/dist/contract.d.ts +97 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +31 -0
- package/dist/contract.js.map +1 -0
- package/dist/entry-header.d.ts +62 -0
- package/dist/entry-header.d.ts.map +1 -0
- package/dist/entry-header.js +65 -0
- package/dist/entry-header.js.map +1 -0
- package/dist/entry.d.ts +21 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +65 -0
- package/dist/entry.js.map +1 -0
- package/dist/heading.d.ts +14 -0
- package/dist/heading.d.ts.map +1 -0
- package/dist/heading.js +34 -0
- package/dist/heading.js.map +1 -0
- package/dist/html.d.ts +42 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +94 -0
- package/dist/html.js.map +1 -0
- package/dist/icons.d.ts +37 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +187 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +45 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +70 -0
- package/dist/media.js.map +1 -0
- package/dist/page.d.ts +104 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +134 -0
- package/dist/page.js.map +1 -0
- package/dist/rich-text.d.ts +5 -0
- package/dist/rich-text.d.ts.map +1 -0
- package/dist/rich-text.js +125 -0
- package/dist/rich-text.js.map +1 -0
- package/dist/strings.d.ts +30 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +124 -0
- package/dist/strings.js.map +1 -0
- package/dist/theme-toggle.d.ts +69 -0
- package/dist/theme-toggle.d.ts.map +1 -0
- package/dist/theme-toggle.js +136 -0
- package/dist/theme-toggle.js.map +1 -0
- package/package.json +43 -0
package/dist/contract.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract D, transcribed once for every theme package to implement against.
|
|
3
|
+
*
|
|
4
|
+
* `ThemeManifest`/`defineTheme`/`SkinTokens` are `@cogenta/render`'s own,
|
|
5
|
+
* already-validated definitions (`theme/manifest.ts`, `skin/tokens.ts`) —
|
|
6
|
+
* re-exported here rather than duplicated, since `@cogenta/render` is also
|
|
7
|
+
* what `loadTheme`/`verifyTheme`/the plugin theme marketplace already check
|
|
8
|
+
* a manifest against. A second, drifting copy of either is exactly the
|
|
9
|
+
* mistake this package exists to stop repeating.
|
|
10
|
+
*
|
|
11
|
+
* `RenderContext`/`ContentEntry`/`ContentClient`/`QueryRequest`/`Page` below
|
|
12
|
+
* are declared fresh here rather than imported from `@cogenta/render`: that
|
|
13
|
+
* package's own `context/types.ts` shapes a *different*, still-unused
|
|
14
|
+
* pipeline (the deferred Astro static build, `astro/integration.ts`) whose
|
|
15
|
+
* `ContentEntry` nests fields under `.values` and whose `MediaReference` is a
|
|
16
|
+
* full asset object — incompatible with the flat, string-keyed contract
|
|
17
|
+
* `cogenta serve`'s real, tested SSR path has used since L3 and every theme
|
|
18
|
+
* (including this repository's own reference theme) is built against.
|
|
19
|
+
* Unifying onto the Astro shape would change live, working behaviour for a
|
|
20
|
+
* pipeline nothing serves through yet — this contract is the one actually in
|
|
21
|
+
* production, transcribed rather than guessed at.
|
|
22
|
+
*
|
|
23
|
+
* `ImageOptions`/`ImageSource` *are* taken from `@cogenta/render` — those two
|
|
24
|
+
* are genuinely the same contract on both pipelines (`describeMedia`, which
|
|
25
|
+
* `cogenta serve` already calls, returns exactly this shape), and reusing
|
|
26
|
+
* them picks up `kind`/`poster` (`theme@1.1`): the fix that lets a theme tell
|
|
27
|
+
* a video apart from a broken image, which the very first theme built against
|
|
28
|
+
* the pre-1.1 copy of this contract could not.
|
|
29
|
+
*/
|
|
30
|
+
export { defineTheme } from '@cogenta/render';
|
|
31
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAWH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ImageSource, RenderContext } from './contract.js';
|
|
2
|
+
import { type HtmlElement } from './html.js';
|
|
3
|
+
import { type PageContent } from './page.js';
|
|
4
|
+
/**
|
|
5
|
+
* The article furniture a stored entry carries beyond its blocks — contract A
|
|
6
|
+
* fields, taxonomy classification, a computed reading time — none of which a
|
|
7
|
+
* `richText`/blocks page had any way to show before contract D `theme@1.4`:
|
|
8
|
+
* `PageContent` used to be a bare title and a block list, so a blog post and
|
|
9
|
+
* a `page` collection's about page rendered identically apart from their
|
|
10
|
+
* body. This is additive and optional (see `PageContent.entry`'s own
|
|
11
|
+
* comment) — a page with no `entry` renders exactly as it did under `1.3`.
|
|
12
|
+
*/
|
|
13
|
+
export interface PageEntryAuthor {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One taxonomy classification, already resolved to a label and a route — the
|
|
18
|
+
* same shape `TermArchiveLink` uses, so a theme's eyebrow and its archive
|
|
19
|
+
* breadcrumb read identically. `href` is `null` for a term this render could
|
|
20
|
+
* not resolve to a live archive page, exactly the "unresolvable, never a
|
|
21
|
+
* dead link" rule `entryHref`/`TermArchiveEntry.href` already follow.
|
|
22
|
+
*/
|
|
23
|
+
export interface PageEntryTerm {
|
|
24
|
+
readonly taxonomy: string;
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly href: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface PageEntryMeta {
|
|
29
|
+
readonly collection: string;
|
|
30
|
+
/** ISO 8601, when the collection declares `publishedAt` and this entry has one. */
|
|
31
|
+
readonly publishedAt?: string;
|
|
32
|
+
/** ISO 8601, the entry's system `updatedAt`. */
|
|
33
|
+
readonly updatedAt?: string;
|
|
34
|
+
/** Already resolved (`entryImage`) — never a raw `MediaReference` a header would have to look up itself. */
|
|
35
|
+
readonly image?: ImageSource;
|
|
36
|
+
readonly excerpt?: string;
|
|
37
|
+
readonly author?: PageEntryAuthor;
|
|
38
|
+
/** Every taxonomy field the collection declares, classified terms only. */
|
|
39
|
+
readonly terms?: readonly PageEntryTerm[];
|
|
40
|
+
/** Computed by the host from the entry's `richText` field (~200 words/minute), rounded up. */
|
|
41
|
+
readonly readingMinutes?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The shared way a theme turns `PageContent.entry` into markup — an eyebrow
|
|
45
|
+
* of taxonomy terms, the title, an optional excerpt, a meta line (date,
|
|
46
|
+
* byline, reading time) and a cover image, in that order.
|
|
47
|
+
*
|
|
48
|
+
* `null` in two cases, both meaning "render nothing extra, `renderPage`'s own
|
|
49
|
+
* bare title still applies": `page.entry` is absent (a `blocks`-only page, or
|
|
50
|
+
* a host that never wired the L25 D2 fields), or the page already draws its
|
|
51
|
+
* own heading (`pageHasOwnHeading` — a `hero` block renders the `h1` itself,
|
|
52
|
+
* and this header must not add a second one).
|
|
53
|
+
*
|
|
54
|
+
* A theme is free to ignore this entirely and build its own header from
|
|
55
|
+
* `page.entry` directly (a magazine-style byline block, say); this is the
|
|
56
|
+
* shared default `theme-canonical` and any theme that wants one without
|
|
57
|
+
* writing it from scratch uses instead.
|
|
58
|
+
*/
|
|
59
|
+
export declare function renderEntryHeader(page: PageContent, ctx: RenderContext, options?: {
|
|
60
|
+
readonly className?: string;
|
|
61
|
+
}): HtmlElement | null;
|
|
62
|
+
//# sourceMappingURL=entry-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-header.d.ts","sourceRoot":"","sources":["../src/entry-header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/D,OAAO,EAAE,KAAK,WAAW,EAAK,MAAM,WAAW,CAAA;AAE/C,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,WAAW,CAAA;AAE/D;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,4GAA4G;IAC5G,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAA;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IACzC,8FAA8F;IAC9F,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,aAAa,EAClB,OAAO,GAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5C,WAAW,GAAG,IAAI,CAyBpB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { h } from './html.js';
|
|
2
|
+
import { renderImageSource } from './media.js';
|
|
3
|
+
import { pageHasOwnHeading } from './page.js';
|
|
4
|
+
/** `Intl.DateTimeFormat`'s `dateStyle: 'long'`, in the page's own locale — the same formatting every other themed date on this page uses. */
|
|
5
|
+
function formatDate(iso, locale) {
|
|
6
|
+
try {
|
|
7
|
+
return new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(new Date(iso));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// An invalid locale tag, or a date `Intl` cannot parse — the raw ISO
|
|
11
|
+
// string is still a true, readable answer, never a thrown error out of a
|
|
12
|
+
// page header.
|
|
13
|
+
return iso;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function renderTerms(terms) {
|
|
17
|
+
if (terms === undefined || terms.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
return h('ul', { class: 'cg-entry-header__terms' }, terms.map((term) => h('li', {}, term.href === null ? term.label : h('a', { href: term.href }, term.label))));
|
|
20
|
+
}
|
|
21
|
+
function renderMeta(entry, ctx) {
|
|
22
|
+
const parts = [];
|
|
23
|
+
if (entry.publishedAt !== undefined) {
|
|
24
|
+
parts.push(h('time', { datetime: entry.publishedAt }, formatDate(entry.publishedAt, ctx.locale)));
|
|
25
|
+
}
|
|
26
|
+
if (entry.author !== undefined) {
|
|
27
|
+
parts.push(h('span', { class: 'cg-entry-header__author' }, entry.author.name));
|
|
28
|
+
}
|
|
29
|
+
if (entry.readingMinutes !== undefined) {
|
|
30
|
+
parts.push(h('span', { class: 'cg-entry-header__reading-time' }, ctx.t('entry.readingTime', { minutes: entry.readingMinutes })));
|
|
31
|
+
}
|
|
32
|
+
if (parts.length === 0)
|
|
33
|
+
return null;
|
|
34
|
+
return h('p', { class: 'cg-entry-header__meta' }, parts);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The shared way a theme turns `PageContent.entry` into markup — an eyebrow
|
|
38
|
+
* of taxonomy terms, the title, an optional excerpt, a meta line (date,
|
|
39
|
+
* byline, reading time) and a cover image, in that order.
|
|
40
|
+
*
|
|
41
|
+
* `null` in two cases, both meaning "render nothing extra, `renderPage`'s own
|
|
42
|
+
* bare title still applies": `page.entry` is absent (a `blocks`-only page, or
|
|
43
|
+
* a host that never wired the L25 D2 fields), or the page already draws its
|
|
44
|
+
* own heading (`pageHasOwnHeading` — a `hero` block renders the `h1` itself,
|
|
45
|
+
* and this header must not add a second one).
|
|
46
|
+
*
|
|
47
|
+
* A theme is free to ignore this entirely and build its own header from
|
|
48
|
+
* `page.entry` directly (a magazine-style byline block, say); this is the
|
|
49
|
+
* shared default `theme-canonical` and any theme that wants one without
|
|
50
|
+
* writing it from scratch uses instead.
|
|
51
|
+
*/
|
|
52
|
+
export function renderEntryHeader(page, ctx, options = {}) {
|
|
53
|
+
const entry = page.entry;
|
|
54
|
+
if (entry === undefined)
|
|
55
|
+
return null;
|
|
56
|
+
if (pageHasOwnHeading(page.blocks))
|
|
57
|
+
return null;
|
|
58
|
+
const cover = entry.image === undefined
|
|
59
|
+
? null
|
|
60
|
+
: h('figure', { class: 'cg-entry-header__cover' }, renderImageSource(entry.image, { loading: 'eager' }));
|
|
61
|
+
return h('header', { class: options.className ?? 'cg-entry-header' }, renderTerms(entry.terms), h('h1', { class: 'cg-entry-header__title' }, page.title), entry.excerpt === undefined
|
|
62
|
+
? null
|
|
63
|
+
: h('p', { class: 'cg-entry-header__excerpt' }, entry.excerpt), renderMeta(entry, ctx), cover);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=entry-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-header.js","sourceRoot":"","sources":["../src/entry-header.ts"],"names":[],"mappings":"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"}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContentEntry, ImageOptions, ImageSource, RenderContext } from './contract.js';
|
|
2
|
+
export declare function entryTitle(entry: ContentEntry, ctx: RenderContext): string;
|
|
3
|
+
export declare function entryExcerpt(entry: ContentEntry): string | undefined;
|
|
4
|
+
/** ISO 8601, for `<time datetime>`. Anything else is not a date this can use. */
|
|
5
|
+
export declare function entryDate(entry: ContentEntry): string | undefined;
|
|
6
|
+
export declare function entryHref(entry: ContentEntry, ctx: RenderContext): string;
|
|
7
|
+
/**
|
|
8
|
+
* An entry's own picture, read from whichever of the usual field names a
|
|
9
|
+
* collection actually declares — a schema is free to call its cover image
|
|
10
|
+
* field anything, but these seven names cover the convention nearly every
|
|
11
|
+
* blueprint and headless CMS import already uses (contract D `theme@1.4`).
|
|
12
|
+
*
|
|
13
|
+
* A value only counts when it is a non-empty string: contract B stores a
|
|
14
|
+
* media reference as the media library's id (`MediaReference`), never a URL
|
|
15
|
+
* or an object, so anything else at that field name is not this entry's
|
|
16
|
+
* picture, whatever it is. `undefined` — never a thrown error — means "this
|
|
17
|
+
* entry has no image a theme can show", the same fallback `entryExcerpt`
|
|
18
|
+
* already gives for a missing summary.
|
|
19
|
+
*/
|
|
20
|
+
export declare function entryImage(entry: ContentEntry, ctx: RenderContext, options?: ImageOptions): ImageSource | undefined;
|
|
21
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAa3F,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAM1E;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAMpE;AAED,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAGjE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAEzE;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,aAAa,EAClB,OAAO,CAAC,EAAE,YAAY,GACrB,WAAW,GAAG,SAAS,CAMzB"}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract A fixes the system fields but not the schema-defined ones: which
|
|
3
|
+
* fields an entry has depends on its collection, and a theme that assumes
|
|
4
|
+
* `title` exists breaks on the first collection that calls it `name`.
|
|
5
|
+
*
|
|
6
|
+
* So every read of an unknown field goes through here, returns a fallback the
|
|
7
|
+
* visitor can live with, and never renders `undefined`.
|
|
8
|
+
*/
|
|
9
|
+
const TITLE_FIELDS = ['title', 'name', 'label'];
|
|
10
|
+
export function entryTitle(entry, ctx) {
|
|
11
|
+
for (const field of TITLE_FIELDS) {
|
|
12
|
+
const value = entry[field];
|
|
13
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return ctx.t('entry.untitled');
|
|
17
|
+
}
|
|
18
|
+
const EXCERPT_FIELDS = ['excerpt', 'summary', 'description'];
|
|
19
|
+
export function entryExcerpt(entry) {
|
|
20
|
+
for (const field of EXCERPT_FIELDS) {
|
|
21
|
+
const value = entry[field];
|
|
22
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/** ISO 8601, for `<time datetime>`. Anything else is not a date this can use. */
|
|
28
|
+
export function entryDate(entry) {
|
|
29
|
+
const value = entry.publishedAt ?? entry.createdAt;
|
|
30
|
+
return typeof value === 'string' && !Number.isNaN(Date.parse(value)) ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function entryHref(entry, ctx) {
|
|
33
|
+
return ctx.link({ collection: entry.collection, id: entry.id });
|
|
34
|
+
}
|
|
35
|
+
const IMAGE_FIELDS = [
|
|
36
|
+
'coverImage',
|
|
37
|
+
'cover',
|
|
38
|
+
'image',
|
|
39
|
+
'featuredImage',
|
|
40
|
+
'photo',
|
|
41
|
+
'thumbnail',
|
|
42
|
+
'seoImage',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* An entry's own picture, read from whichever of the usual field names a
|
|
46
|
+
* collection actually declares — a schema is free to call its cover image
|
|
47
|
+
* field anything, but these seven names cover the convention nearly every
|
|
48
|
+
* blueprint and headless CMS import already uses (contract D `theme@1.4`).
|
|
49
|
+
*
|
|
50
|
+
* A value only counts when it is a non-empty string: contract B stores a
|
|
51
|
+
* media reference as the media library's id (`MediaReference`), never a URL
|
|
52
|
+
* or an object, so anything else at that field name is not this entry's
|
|
53
|
+
* picture, whatever it is. `undefined` — never a thrown error — means "this
|
|
54
|
+
* entry has no image a theme can show", the same fallback `entryExcerpt`
|
|
55
|
+
* already gives for a missing summary.
|
|
56
|
+
*/
|
|
57
|
+
export function entryImage(entry, ctx, options) {
|
|
58
|
+
for (const field of IMAGE_FIELDS) {
|
|
59
|
+
const value = entry[field];
|
|
60
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
61
|
+
return ctx.image(value, options);
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAU,CAAA;AAExD,MAAM,UAAU,UAAU,CAAC,KAAmB,EAAE,GAAkB;IAChE,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAA;IACpE,CAAC;IACD,OAAO,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;AAChC,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,CAAU,CAAA;AAErE,MAAM,UAAU,YAAY,CAAC,KAAmB;IAC9C,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAA;IACpE,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,KAAmB;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,SAAS,CAAA;IAClD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1F,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAmB,EAAE,GAAkB;IAC/D,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;AACjE,CAAC;AAED,MAAM,YAAY,GAAG;IACnB,YAAY;IACZ,OAAO;IACP,OAAO;IACP,eAAe;IACf,OAAO;IACP,WAAW;IACX,UAAU;CACF,CAAA;AAEV;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CACxB,KAAmB,EACnB,GAAkB,EAClB,OAAsB;IAEtB,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACxF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Child, type HtmlElement } from './html.js';
|
|
2
|
+
export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
3
|
+
/** The level a block's own title is rendered at, or `null` if it carries none. */
|
|
4
|
+
export declare function blockHeadingTag(blockName: string): HeadingTag | null;
|
|
5
|
+
/**
|
|
6
|
+
* The level for a title *inside* a block — a feature's name, a listed entry.
|
|
7
|
+
*
|
|
8
|
+
* One below the block's own heading when the block renders one, and at the
|
|
9
|
+
* block's declared level when it does not: a `featureGrid` without a title must
|
|
10
|
+
* not start its items at `h3`, which would skip a level.
|
|
11
|
+
*/
|
|
12
|
+
export declare function nestedHeadingTag(blockName: string, blockRendersHeading: boolean): HeadingTag;
|
|
13
|
+
export declare function heading(tag: HeadingTag, attrs: Readonly<Record<string, string | number | boolean | undefined>>, ...children: readonly Child[]): HtmlElement;
|
|
14
|
+
//# sourceMappingURL=heading.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heading.d.ts","sourceRoot":"","sources":["../src/heading.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,WAAW,EAAK,MAAM,WAAW,CAAA;AAY3D,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEhE,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAIpE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,GAAG,UAAU,CAK5F;AAED,wBAAgB,OAAO,CACrB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC,EACtE,GAAG,QAAQ,EAAE,SAAS,KAAK,EAAE,GAC5B,WAAW,CAEb"}
|
package/dist/heading.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { VOCABULARY } from '@cogenta/blocks';
|
|
2
|
+
import { h } from './html.js';
|
|
3
|
+
/**
|
|
4
|
+
* The heading level of a block is **not** a theme decision: contract B fixes it
|
|
5
|
+
* in each block's `a11y.headingLevel`, and a theme reads it from here rather
|
|
6
|
+
* than restating it. Restating it is how an outline drifts — one block gets
|
|
7
|
+
* promoted in one theme, the page grows a second `h1`, and nothing fails.
|
|
8
|
+
*/
|
|
9
|
+
const DECLARED_LEVELS = new Map(VOCABULARY.map((block) => [block.name, block.a11y.headingLevel]));
|
|
10
|
+
/** The level a block's own title is rendered at, or `null` if it carries none. */
|
|
11
|
+
export function blockHeadingTag(blockName) {
|
|
12
|
+
const level = DECLARED_LEVELS.get(blockName);
|
|
13
|
+
if (level === undefined || level === 'none')
|
|
14
|
+
return null;
|
|
15
|
+
return level;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The level for a title *inside* a block — a feature's name, a listed entry.
|
|
19
|
+
*
|
|
20
|
+
* One below the block's own heading when the block renders one, and at the
|
|
21
|
+
* block's declared level when it does not: a `featureGrid` without a title must
|
|
22
|
+
* not start its items at `h3`, which would skip a level.
|
|
23
|
+
*/
|
|
24
|
+
export function nestedHeadingTag(blockName, blockRendersHeading) {
|
|
25
|
+
const own = blockHeadingTag(blockName) ?? 'h2';
|
|
26
|
+
if (!blockRendersHeading)
|
|
27
|
+
return own;
|
|
28
|
+
const next = Number(own.slice(1)) + 1;
|
|
29
|
+
return `h${Math.min(next, 6)}`;
|
|
30
|
+
}
|
|
31
|
+
export function heading(tag, attrs, ...children) {
|
|
32
|
+
return h(tag, attrs, ...children);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=heading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heading.js","sourceRoot":"","sources":["../src/heading.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAgC,CAAC,EAAE,MAAM,WAAW,CAAA;AAE3D;;;;;GAKG;AACH,MAAM,eAAe,GAAsC,IAAI,GAAG,CAChE,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CACjE,CAAA;AAID,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IACxD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,mBAA4B;IAC9E,MAAM,GAAG,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,IAAI,CAAA;IAC9C,IAAI,CAAC,mBAAmB;QAAE,OAAO,GAAG,CAAA;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACrC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,EAAgB,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,GAAe,EACf,KAAsE,EACtE,GAAG,QAA0B;IAE7B,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAA;AACnC,CAAC"}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, dependency-free HTML tree and serialiser.
|
|
3
|
+
*
|
|
4
|
+
* Why a theme's markup is built here rather than written inline as template
|
|
5
|
+
* strings:
|
|
6
|
+
*
|
|
7
|
+
* 1. It is testable without a bundler — the vocabulary's blocks get real
|
|
8
|
+
* snapshot tests, and rules like "exactly one `h1`" or "every `<img>` has
|
|
9
|
+
* `alt`" are asserted on the actual output rather than on a reading of the
|
|
10
|
+
* source.
|
|
11
|
+
* 2. There is **no way to emit raw HTML**. A block stores semantic data only
|
|
12
|
+
* (rule R3); with no `raw()` escape hatch, a string that arrives in a
|
|
13
|
+
* block field cannot become markup, whatever it contains.
|
|
14
|
+
*/
|
|
15
|
+
export type AttributeValue = string | number | boolean | undefined;
|
|
16
|
+
export type Attributes = Readonly<Record<string, AttributeValue>>;
|
|
17
|
+
export interface HtmlElement {
|
|
18
|
+
readonly kind: 'element';
|
|
19
|
+
readonly tag: string;
|
|
20
|
+
readonly attrs: Attributes;
|
|
21
|
+
readonly children: readonly HtmlNode[];
|
|
22
|
+
}
|
|
23
|
+
export interface HtmlText {
|
|
24
|
+
readonly kind: 'text';
|
|
25
|
+
readonly value: string;
|
|
26
|
+
}
|
|
27
|
+
export type HtmlNode = HtmlElement | HtmlText;
|
|
28
|
+
/** Anything a caller may pass as a child. `null`/`false` drop out, so a
|
|
29
|
+
* conditional child reads as `condition && node` without a wrapper. */
|
|
30
|
+
export type Child = HtmlNode | string | null | false | undefined | readonly Child[];
|
|
31
|
+
export declare function text(value: string): HtmlText;
|
|
32
|
+
export declare function h(tag: string, attrs?: Attributes, ...children: readonly Child[]): HtmlElement;
|
|
33
|
+
/**
|
|
34
|
+
* `&` first, or every following replacement would be escaped twice.
|
|
35
|
+
* `<` and `>` cover text; the quotes are escaped in attributes only, where they
|
|
36
|
+
* are the ones that can break out.
|
|
37
|
+
*/
|
|
38
|
+
export declare function escapeText(value: string): string;
|
|
39
|
+
export declare function escapeAttribute(value: string): string;
|
|
40
|
+
export declare function serialize(node: HtmlNode): string;
|
|
41
|
+
export declare function serializeAll(nodes: readonly HtmlNode[]): string;
|
|
42
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAElE,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;AAEjE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAA;CACvC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAA;AAE7C;uEACuE;AACvE,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,KAAK,EAAE,CAAA;AAsBnF,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAE5C;AAiBD,wBAAgB,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,UAAe,EAAE,GAAG,QAAQ,EAAE,SAAS,KAAK,EAAE,GAAG,WAAW,CAIjG;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAiBD,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAKhD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAE/D"}
|
package/dist/html.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, dependency-free HTML tree and serialiser.
|
|
3
|
+
*
|
|
4
|
+
* Why a theme's markup is built here rather than written inline as template
|
|
5
|
+
* strings:
|
|
6
|
+
*
|
|
7
|
+
* 1. It is testable without a bundler — the vocabulary's blocks get real
|
|
8
|
+
* snapshot tests, and rules like "exactly one `h1`" or "every `<img>` has
|
|
9
|
+
* `alt`" are asserted on the actual output rather than on a reading of the
|
|
10
|
+
* source.
|
|
11
|
+
* 2. There is **no way to emit raw HTML**. A block stores semantic data only
|
|
12
|
+
* (rule R3); with no `raw()` escape hatch, a string that arrives in a
|
|
13
|
+
* block field cannot become markup, whatever it contains.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Elements that carry no children and no closing tag. Serialising `<img>` with
|
|
17
|
+
* one would produce markup no parser agrees on.
|
|
18
|
+
*/
|
|
19
|
+
const VOID_ELEMENTS = new Set([
|
|
20
|
+
'area',
|
|
21
|
+
'base',
|
|
22
|
+
'br',
|
|
23
|
+
'col',
|
|
24
|
+
'embed',
|
|
25
|
+
'hr',
|
|
26
|
+
'img',
|
|
27
|
+
'input',
|
|
28
|
+
'link',
|
|
29
|
+
'meta',
|
|
30
|
+
'source',
|
|
31
|
+
'track',
|
|
32
|
+
'wbr',
|
|
33
|
+
]);
|
|
34
|
+
export function text(value) {
|
|
35
|
+
return { kind: 'text', value };
|
|
36
|
+
}
|
|
37
|
+
function flatten(children, into) {
|
|
38
|
+
for (const child of children) {
|
|
39
|
+
if (child === null || child === false || child === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
if (typeof child === 'string') {
|
|
42
|
+
into.push(text(child));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(child)) {
|
|
46
|
+
flatten(child, into);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
into.push(child);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function h(tag, attrs = {}, ...children) {
|
|
53
|
+
const collected = [];
|
|
54
|
+
flatten(children, collected);
|
|
55
|
+
return { kind: 'element', tag, attrs, children: collected };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `&` first, or every following replacement would be escaped twice.
|
|
59
|
+
* `<` and `>` cover text; the quotes are escaped in attributes only, where they
|
|
60
|
+
* are the ones that can break out.
|
|
61
|
+
*/
|
|
62
|
+
export function escapeText(value) {
|
|
63
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
64
|
+
}
|
|
65
|
+
export function escapeAttribute(value) {
|
|
66
|
+
return escapeText(value).replace(/"/g, '"').replace(/'/g, ''');
|
|
67
|
+
}
|
|
68
|
+
function serializeAttributes(attrs) {
|
|
69
|
+
let out = '';
|
|
70
|
+
// Insertion order, not sorted: it is deterministic already, and an author
|
|
71
|
+
// reading a snapshot expects the order they wrote.
|
|
72
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
73
|
+
if (value === undefined || value === false)
|
|
74
|
+
continue;
|
|
75
|
+
if (value === true) {
|
|
76
|
+
out += ` ${name}`;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
out += ` ${name}="${escapeAttribute(String(value))}"`;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
export function serialize(node) {
|
|
84
|
+
if (node.kind === 'text')
|
|
85
|
+
return escapeText(node.value);
|
|
86
|
+
const open = `<${node.tag}${serializeAttributes(node.attrs)}>`;
|
|
87
|
+
if (VOID_ELEMENTS.has(node.tag))
|
|
88
|
+
return open;
|
|
89
|
+
return `${open}${node.children.map(serialize).join('')}</${node.tag}>`;
|
|
90
|
+
}
|
|
91
|
+
export function serializeAll(nodes) {
|
|
92
|
+
return nodes.map(serialize).join('');
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=html.js.map
|
package/dist/html.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"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"}
|
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type HtmlElement } from './html.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inline icons for the block vocabulary's `icon: string` fields (contract B —
|
|
4
|
+
* `featureGrid` items, and any future block that names one the same way).
|
|
5
|
+
*
|
|
6
|
+
* Contract B stores a *symbol name*, never markup (R3) — a block cannot carry
|
|
7
|
+
* its own `<svg>`, so until this module existed every theme rendered
|
|
8
|
+
* `<span data-icon="…">` with nothing inside it: the name reached the page,
|
|
9
|
+
* but nothing visible ever did. This is the shared, zero-dependency mapping
|
|
10
|
+
* from that name to a real icon, written once here rather than five times
|
|
11
|
+
* (one per theme package) the way `renderSocialLinks`/`renderBrandMark`
|
|
12
|
+
* already are for their own corner of a page's chrome.
|
|
13
|
+
*
|
|
14
|
+
* Every path is authored directly for this file — outline style, a single
|
|
15
|
+
* `stroke-width`, no copied icon-library data, no external font or sprite
|
|
16
|
+
* sheet (R9/R10). `renderIcon` returns `null` for a name outside the closed
|
|
17
|
+
* list below, which is a *theme's* signal to fall back to whatever it
|
|
18
|
+
* rendered before this module existed — never a placeholder glyph that would
|
|
19
|
+
* itself need explaining.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ICON_NAMES: readonly ['check', 'star', 'bolt', 'shield', 'chart', 'users', 'user', 'globe', 'clock', 'heart', 'leaf', 'mail', 'phone', 'map-pin', 'calendar', 'book', 'code', 'cloud', 'lock', 'search', 'settings', 'sparkles', 'truck', 'credit-card', 'gift', 'coffee', 'utensils', 'wine', 'camera', 'pen', 'layers', 'rocket', 'trending-up', 'award', 'smile', 'sun', 'moon', 'arrow-right', 'arrow-up-right', 'external-link', 'download', 'play', 'quote', 'tag', 'briefcase', 'home', 'message', 'bell', 'refresh', 'image', 'zap'];
|
|
22
|
+
export type IconName = (typeof ICON_NAMES)[number];
|
|
23
|
+
/**
|
|
24
|
+
* One `<svg viewBox="0 0 24 24">` per known name, `null` for anything else —
|
|
25
|
+
* a theme's own fallback (usually the pre-icon `<span data-icon>`, kept for
|
|
26
|
+
* the page builder to key off) applies exactly as it did before this module
|
|
27
|
+
* existed.
|
|
28
|
+
*
|
|
29
|
+
* `aria-hidden`/`focusable="false"`: the icon is always beside a text label
|
|
30
|
+
* (a feature's own title) that already carries the accessible name — an icon
|
|
31
|
+
* that duplicated it would be announced twice.
|
|
32
|
+
*/
|
|
33
|
+
export declare function renderIcon(name: string, options?: {
|
|
34
|
+
readonly className?: string;
|
|
35
|
+
readonly size?: number;
|
|
36
|
+
}): HtmlElement | null;
|
|
37
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/icons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAK,MAAM,WAAW,CAAA;AAE/C;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,UAAU,YACrB,OAAO,EACP,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,EACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,MAAM,EACN,MAAM,EACN,OAAO,EACP,SAAS,EACT,UAAU,EACV,MAAM,EACN,MAAM,EACN,OAAO,EACP,MAAM,EACN,QAAQ,EACR,UAAU,EACV,UAAU,EACV,OAAO,EACP,aAAa,EACb,MAAM,EACN,QAAQ,EACR,UAAU,EACV,MAAM,EACN,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,EACP,KAAK,EACL,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,MAAM,EACN,OAAO,EACP,KAAK,EACL,WAAW,EACX,MAAM,EACN,SAAS,EACT,MAAM,EACN,SAAS,EACT,OAAO,EACP,KAAK,CACG,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAyFlD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACpE,WAAW,GAAG,IAAI,CAqBpB"}
|