@cosmicdrift/kumiko-headless 0.155.1 → 0.156.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/package.json +2 -2
- package/src/apex/index.ts +18 -18
- package/src/format/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.156.0",
|
|
4
4
|
"description": "Headless UI logic for Kumiko — Dispatcher contract, Form-Controller, View-Model, Nav-Resolver. Plattform- und React-frei; jeder Renderer (renderer, renderer-web, renderer-native, …) komponiert darauf.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-framework": "0.156.0",
|
|
40
40
|
"temporal-polyfill": "^0.3.2",
|
|
41
41
|
"zod": "^4.4.3"
|
|
42
42
|
},
|
package/src/apex/index.ts
CHANGED
|
@@ -253,7 +253,7 @@ function renderHero(s: ApexHeroSection): string {
|
|
|
253
253
|
s.logo !== undefined
|
|
254
254
|
? `<img class="hero-pony" src="${escapeHtml(s.logo.src)}" alt="${escapeHtml(s.logo.alt)}"${dim(s.logo)} />`
|
|
255
255
|
: "";
|
|
256
|
-
const
|
|
256
|
+
const ctasHtml = (s.ctas ?? []).map(renderCta).join("\n ");
|
|
257
257
|
const meta = s.metaHtml !== undefined ? `<p class="hero-meta">${s.metaHtml}</p>` : "";
|
|
258
258
|
const visual =
|
|
259
259
|
s.screenshot !== undefined
|
|
@@ -264,7 +264,7 @@ function renderHero(s: ApexHeroSection): string {
|
|
|
264
264
|
<div class="hero-copy">
|
|
265
265
|
${logo}<h1>${escapeHtml(s.title)}</h1>
|
|
266
266
|
<p class="tagline">${escapeHtml(s.tagline)}</p>
|
|
267
|
-
${
|
|
267
|
+
${ctasHtml !== "" ? `<div class="hero-cta">${ctasHtml}</div>` : ""}
|
|
268
268
|
${meta}
|
|
269
269
|
</div>
|
|
270
270
|
${visual}
|
|
@@ -273,7 +273,7 @@ function renderHero(s: ApexHeroSection): string {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
function renderFeatureGrid(s: ApexFeatureGridSection): string {
|
|
276
|
-
const
|
|
276
|
+
const cardsHtml = s.items
|
|
277
277
|
.map(
|
|
278
278
|
(f) => `<article class="feature">
|
|
279
279
|
${f.icon !== undefined ? `<div class="feature-icon">${svgIcon(f.icon)}</div>` : ""}<h3>${escapeHtml(f.title)}</h3>
|
|
@@ -285,7 +285,7 @@ function renderFeatureGrid(s: ApexFeatureGridSection): string {
|
|
|
285
285
|
<div class="container">
|
|
286
286
|
${renderSectionHead(s)}
|
|
287
287
|
<div class="feature-grid">
|
|
288
|
-
${
|
|
288
|
+
${cardsHtml}
|
|
289
289
|
</div>
|
|
290
290
|
</div>
|
|
291
291
|
</section>`;
|
|
@@ -298,7 +298,7 @@ function renderPricingCard(t: ApexPricingTier): string {
|
|
|
298
298
|
const cap =
|
|
299
299
|
t.capLine !== undefined ? [`<li class="price-cap">${escapeHtml(t.capLine)}</li>`] : [];
|
|
300
300
|
const benefits = t.benefits.map((b) => `<li>${escapeHtml(b)}</li>`);
|
|
301
|
-
const
|
|
301
|
+
const itemsHtml = [...cap, ...benefits].join("\n ");
|
|
302
302
|
const per = t.priceSuffix !== undefined ? `<span>${escapeHtml(t.priceSuffix)}</span>` : "";
|
|
303
303
|
const tagline =
|
|
304
304
|
t.tagline !== undefined ? `<p class="price-tagline">${escapeHtml(t.tagline)}</p>` : "";
|
|
@@ -314,27 +314,27 @@ function renderPricingCard(t: ApexPricingTier): string {
|
|
|
314
314
|
${badge}<h3>${escapeHtml(t.name)}</h3>
|
|
315
315
|
${tagline}<div class="price-amount">${escapeHtml(t.amount)}${per}</div>
|
|
316
316
|
<ul class="price-list">
|
|
317
|
-
${
|
|
317
|
+
${itemsHtml}
|
|
318
318
|
</ul>
|
|
319
319
|
${cta}
|
|
320
320
|
</article>`;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
function renderPricingGrid(s: ApexPricingGridSection): string {
|
|
324
|
-
const
|
|
324
|
+
const cardsHtml = s.tiers.map(renderPricingCard).join("\n ");
|
|
325
325
|
const idAttr = s.id !== undefined ? ` id="${escapeHtml(s.id)}"` : "";
|
|
326
326
|
return `<section${idAttr}>
|
|
327
327
|
<div class="container">
|
|
328
328
|
${renderSectionHead(s)}
|
|
329
329
|
<div class="price-grid">
|
|
330
|
-
${
|
|
330
|
+
${cardsHtml}
|
|
331
331
|
</div>
|
|
332
332
|
</div>
|
|
333
333
|
</section>`;
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
function renderInfoGrid(s: ApexInfoGridSection): string {
|
|
337
|
-
const
|
|
337
|
+
const itemsHtml = s.items
|
|
338
338
|
.map(
|
|
339
339
|
(i) => `<div class="trust-item">
|
|
340
340
|
<h3>${escapeHtml(i.title)}</h3>
|
|
@@ -346,7 +346,7 @@ function renderInfoGrid(s: ApexInfoGridSection): string {
|
|
|
346
346
|
<div class="container">
|
|
347
347
|
${renderSectionHead(s)}
|
|
348
348
|
<div class="trust-grid">
|
|
349
|
-
${
|
|
349
|
+
${itemsHtml}
|
|
350
350
|
</div>
|
|
351
351
|
</div>
|
|
352
352
|
</section>`;
|
|
@@ -384,7 +384,7 @@ function renderSection(s: ApexSection): string {
|
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
function renderNavMenu(m: ApexNavMenu): string {
|
|
387
|
-
const
|
|
387
|
+
const itemsHtml = m.items
|
|
388
388
|
.map(
|
|
389
389
|
(it) =>
|
|
390
390
|
`<a class="nav-menu__item" href="${escapeHtml(it.href)}">${
|
|
@@ -398,7 +398,7 @@ function renderNavMenu(m: ApexNavMenu): string {
|
|
|
398
398
|
m.footer !== undefined
|
|
399
399
|
? `<div class="nav-menu__sep"></div><a class="nav-menu__more" href="${escapeHtml(m.footer.href)}">${escapeHtml(m.footer.label)}</a>`
|
|
400
400
|
: "";
|
|
401
|
-
return `<div class="nav-menu"><button type="button" class="nav-menu__trigger" aria-haspopup="true">${escapeHtml(m.label)}<span class="nav-menu__chev">${svgIcon('<path d="m6 9 6 6 6-6"/>')}</span></button><div class="nav-menu__panel">${
|
|
401
|
+
return `<div class="nav-menu"><button type="button" class="nav-menu__trigger" aria-haspopup="true">${escapeHtml(m.label)}<span class="nav-menu__chev">${svgIcon('<path d="m6 9 6 6 6-6"/>')}</span></button><div class="nav-menu__panel">${itemsHtml}${footer}</div></div>`;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
function renderNavEntry(entry: ApexNavEntry): string {
|
|
@@ -413,13 +413,13 @@ function renderNavEntry(entry: ApexNavEntry): string {
|
|
|
413
413
|
export function renderApexHeader(h: ApexHeader): string {
|
|
414
414
|
const logo =
|
|
415
415
|
h.brand.logoSrc !== undefined ? `<img src="${escapeHtml(h.brand.logoSrc)}" alt="" /> ` : "";
|
|
416
|
-
const
|
|
417
|
-
const
|
|
416
|
+
const navLinksHtml = (h.navLinks ?? []).map(renderNavEntry).join("\n ");
|
|
417
|
+
const actionsHtml = (h.actions ?? []).map(renderCta).join("\n ");
|
|
418
418
|
return `<header>
|
|
419
419
|
<div class="container nav">
|
|
420
420
|
<div class="brand"><a href="${escapeHtml(h.brand.href)}">${logo}${escapeHtml(h.brand.label)}</a></div>
|
|
421
|
-
${
|
|
422
|
-
${
|
|
421
|
+
${navLinksHtml !== "" ? `<nav class="nav-links">${navLinksHtml}</nav>` : ""}
|
|
422
|
+
${actionsHtml !== "" ? `<div class="nav-actions">${actionsHtml}</div>` : ""}
|
|
423
423
|
</div>
|
|
424
424
|
</header>`;
|
|
425
425
|
}
|
|
@@ -527,7 +527,7 @@ export function renderApexPage(page: ApexPage): string {
|
|
|
527
527
|
const theme = page.theme ?? "light";
|
|
528
528
|
// Brand-CSS ist app-authored (Trust-Boundary siehe Datei-Header), kein Tenant-Input.
|
|
529
529
|
const cssHtml = (brand.fontFaceCss ?? "") + brand.tokensCss + APEX_STRUCTURAL_CSS;
|
|
530
|
-
const
|
|
530
|
+
const sectionsHtml = page.sections.map(renderSection).join("\n\n ");
|
|
531
531
|
return `<!doctype html>
|
|
532
532
|
<html lang="${escapeHtml(head.lang)}">
|
|
533
533
|
<head>
|
|
@@ -539,7 +539,7 @@ export function renderApexPage(page: ApexPage): string {
|
|
|
539
539
|
<body${theme === "dark" ? ` class="apex-dark"` : ""}>
|
|
540
540
|
${renderApexHeader(page.header)}
|
|
541
541
|
|
|
542
|
-
${
|
|
542
|
+
${sectionsHtml}
|
|
543
543
|
|
|
544
544
|
${renderFooter(page.footer)}
|
|
545
545
|
|
package/src/format/index.ts
CHANGED
|
@@ -27,6 +27,9 @@ function formatDateCell(
|
|
|
27
27
|
const locale = opts?.locale;
|
|
28
28
|
if (opts?.dateStyle || opts?.timeStyle) {
|
|
29
29
|
if (type === "date") {
|
|
30
|
+
// ponytail: timeStyle is ignored here on purpose — a PlainDate has no
|
|
31
|
+
// time component to format, so a timeStyle-only "date" field falls
|
|
32
|
+
// back to PlainDate's default dateStyle instead of rendering a time.
|
|
30
33
|
return toPlainDate(raw).toLocaleString(locale, {
|
|
31
34
|
dateStyle: opts.dateStyle,
|
|
32
35
|
});
|