@bytesbrains/weblocks 0.3.0 → 0.5.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/AGENT.md +10 -3
- package/CATALOG.md +40 -3
- package/CHANGELOG.md +48 -0
- package/README.md +28 -3
- package/catalog.json +214 -8
- package/lib/blocks/brandIcons.d.ts +5 -0
- package/lib/blocks/brandIcons.js +18 -0
- package/lib/blocks/copyright.d.ts +2 -0
- package/lib/blocks/copyright.js +48 -0
- package/lib/blocks/gallery.js +2 -1
- package/lib/blocks/legal.d.ts +2 -0
- package/lib/blocks/legal.js +100 -0
- package/lib/blocks/socialLinks.js +71 -21
- package/lib/blocks/videoGallery.d.ts +2 -0
- package/lib/blocks/videoGallery.js +110 -0
- package/lib/blocks/videoUtil.d.ts +14 -0
- package/lib/blocks/videoUtil.js +30 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/islands/carousel.d.ts +1 -0
- package/lib/islands/carousel.js +122 -0
- package/lib/islands/lightbox.d.ts +1 -0
- package/lib/islands/lightbox.js +139 -0
- package/lib/islands/video.d.ts +1 -0
- package/lib/islands/video.js +48 -0
- package/lib/markdown.d.ts +2 -0
- package/lib/markdown.js +90 -0
- package/lib/registry.js +6 -3
- package/lib/render.d.ts +6 -0
- package/lib/render.js +2 -1
- package/package.json +5 -3
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lightbox` island — click-to-zoom viewer for `gallery` images.
|
|
3
|
+
*
|
|
4
|
+
* Shipped from `@bytesbrains/weblocks/islands/lightbox.js`; the host serves it at
|
|
5
|
+
* the island URL the renderer emits (default `/_island/lightbox.js`). Zero
|
|
6
|
+
* dependencies, self-executing on load, idempotent. Enhances only galleries the
|
|
7
|
+
* renderer flagged with `data-wl-lightbox`; a no-op without a DOM (safe to
|
|
8
|
+
* import in Node/SSR).
|
|
9
|
+
*
|
|
10
|
+
* Features: open on click / Enter, prev–next within the gallery, arrow-key nav,
|
|
11
|
+
* Escape + backdrop to close, touch swipe, caption, background scroll lock, and
|
|
12
|
+
* focus returned to the opener.
|
|
13
|
+
*/
|
|
14
|
+
if (typeof document !== 'undefined') {
|
|
15
|
+
const ready = (fn) => document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn) : fn();
|
|
16
|
+
ready(() => {
|
|
17
|
+
const galleries = Array.from(document.querySelectorAll('.blk-gallery[data-wl-lightbox]'));
|
|
18
|
+
if (!galleries.length)
|
|
19
|
+
return;
|
|
20
|
+
if (!document.getElementById('wl-lightbox-css')) {
|
|
21
|
+
const s = document.createElement('style');
|
|
22
|
+
s.id = 'wl-lightbox-css';
|
|
23
|
+
s.textContent = [
|
|
24
|
+
'.wl-lb{position:fixed;inset:0;z-index:2147483000;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.9)}',
|
|
25
|
+
'.wl-lb[data-open]{display:flex}',
|
|
26
|
+
'.wl-lb figure{margin:0;display:flex;flex-direction:column;align-items:center;gap:.7rem}',
|
|
27
|
+
'.wl-lb img{max-width:92vw;max-height:82vh;object-fit:contain;border-radius:6px;user-select:none}',
|
|
28
|
+
'.wl-lb figcaption{color:#eee;font:500 14px system-ui,sans-serif;text-align:center;max-width:60ch;padding:0 1rem}',
|
|
29
|
+
'.wl-lb button{position:absolute;background:rgba(255,255,255,.14);color:#fff;border:0;cursor:pointer;border-radius:999px;width:48px;height:48px;font-size:26px;line-height:1;display:flex;align-items:center;justify-content:center;transition:background .15s}',
|
|
30
|
+
'.wl-lb button:hover,.wl-lb button:focus-visible{background:rgba(255,255,255,.28);outline:2px solid #fff}',
|
|
31
|
+
'.wl-lb .wl-close{top:14px;right:14px}',
|
|
32
|
+
'.wl-lb .wl-prev{left:14px;top:50%;transform:translateY(-50%)}',
|
|
33
|
+
'.wl-lb .wl-next{right:14px;top:50%;transform:translateY(-50%)}',
|
|
34
|
+
'html.wl-lb-lock{overflow:hidden}',
|
|
35
|
+
'@media(max-width:520px){.wl-lb button{width:40px;height:40px;font-size:22px}}',
|
|
36
|
+
].join('');
|
|
37
|
+
document.head.appendChild(s);
|
|
38
|
+
}
|
|
39
|
+
// Built with createElement (no innerHTML) — the overlay chrome is static, but
|
|
40
|
+
// constructing nodes keeps the "no HTML strings into the DOM" rule absolute.
|
|
41
|
+
const mkBtn = (cls, glyph, label) => {
|
|
42
|
+
const b = document.createElement('button');
|
|
43
|
+
b.type = 'button';
|
|
44
|
+
b.className = cls;
|
|
45
|
+
b.textContent = glyph;
|
|
46
|
+
b.setAttribute('aria-label', label);
|
|
47
|
+
return b;
|
|
48
|
+
};
|
|
49
|
+
const ov = document.createElement('div');
|
|
50
|
+
ov.className = 'wl-lb';
|
|
51
|
+
ov.setAttribute('role', 'dialog');
|
|
52
|
+
ov.setAttribute('aria-modal', 'true');
|
|
53
|
+
ov.setAttribute('aria-label', 'Image viewer');
|
|
54
|
+
const btnClose = mkBtn('wl-close', '×', 'Close');
|
|
55
|
+
const btnPrev = mkBtn('wl-prev', '‹', 'Previous image');
|
|
56
|
+
const btnNext = mkBtn('wl-next', '›', 'Next image');
|
|
57
|
+
const fig = document.createElement('figure');
|
|
58
|
+
const bigImg = document.createElement('img');
|
|
59
|
+
bigImg.alt = '';
|
|
60
|
+
const cap = document.createElement('figcaption');
|
|
61
|
+
fig.append(bigImg, cap);
|
|
62
|
+
ov.append(btnClose, btnPrev, btnNext, fig);
|
|
63
|
+
document.body.appendChild(ov);
|
|
64
|
+
let items = [];
|
|
65
|
+
let idx = 0;
|
|
66
|
+
let opener = null;
|
|
67
|
+
const show = () => {
|
|
68
|
+
const img = items[idx];
|
|
69
|
+
if (!img)
|
|
70
|
+
return;
|
|
71
|
+
bigImg.src = img.currentSrc || img.src;
|
|
72
|
+
bigImg.alt = img.alt || '';
|
|
73
|
+
const c = img.closest('figure')?.querySelector('figcaption')?.textContent || '';
|
|
74
|
+
cap.textContent = c;
|
|
75
|
+
cap.style.display = c ? '' : 'none';
|
|
76
|
+
const multi = items.length > 1;
|
|
77
|
+
btnPrev.style.display = multi ? '' : 'none';
|
|
78
|
+
btnNext.style.display = multi ? '' : 'none';
|
|
79
|
+
};
|
|
80
|
+
const open = (list, i, from) => {
|
|
81
|
+
items = list;
|
|
82
|
+
idx = i;
|
|
83
|
+
opener = from;
|
|
84
|
+
show();
|
|
85
|
+
ov.setAttribute('data-open', '');
|
|
86
|
+
document.documentElement.classList.add('wl-lb-lock');
|
|
87
|
+
btnClose.focus();
|
|
88
|
+
};
|
|
89
|
+
const close = () => {
|
|
90
|
+
ov.removeAttribute('data-open');
|
|
91
|
+
document.documentElement.classList.remove('wl-lb-lock');
|
|
92
|
+
bigImg.removeAttribute('src');
|
|
93
|
+
opener?.focus();
|
|
94
|
+
};
|
|
95
|
+
const nav = (d) => { if (items.length) {
|
|
96
|
+
idx = (idx + d + items.length) % items.length;
|
|
97
|
+
show();
|
|
98
|
+
} };
|
|
99
|
+
btnClose.addEventListener('click', close);
|
|
100
|
+
btnPrev.addEventListener('click', () => nav(-1));
|
|
101
|
+
btnNext.addEventListener('click', () => nav(1));
|
|
102
|
+
ov.addEventListener('click', (e) => { if (e.target === ov)
|
|
103
|
+
close(); });
|
|
104
|
+
document.addEventListener('keydown', (e) => {
|
|
105
|
+
if (!ov.hasAttribute('data-open'))
|
|
106
|
+
return;
|
|
107
|
+
if (e.key === 'Escape')
|
|
108
|
+
close();
|
|
109
|
+
else if (e.key === 'ArrowLeft')
|
|
110
|
+
nav(-1);
|
|
111
|
+
else if (e.key === 'ArrowRight')
|
|
112
|
+
nav(1);
|
|
113
|
+
});
|
|
114
|
+
let sx = 0;
|
|
115
|
+
ov.addEventListener('touchstart', (e) => { sx = e.changedTouches[0].clientX; }, { passive: true });
|
|
116
|
+
ov.addEventListener('touchend', (e) => {
|
|
117
|
+
const dx = e.changedTouches[0].clientX - sx;
|
|
118
|
+
if (Math.abs(dx) > 40)
|
|
119
|
+
nav(dx < 0 ? 1 : -1);
|
|
120
|
+
});
|
|
121
|
+
galleries.forEach((g) => {
|
|
122
|
+
const imgs = Array.from(g.querySelectorAll('figure img'));
|
|
123
|
+
imgs.forEach((img, i) => {
|
|
124
|
+
img.style.cursor = 'zoom-in';
|
|
125
|
+
img.setAttribute('role', 'button');
|
|
126
|
+
img.setAttribute('tabindex', '0');
|
|
127
|
+
const go = () => open(imgs, i, img);
|
|
128
|
+
img.addEventListener('click', go);
|
|
129
|
+
img.addEventListener('keydown', (e) => {
|
|
130
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
go();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `video` island — click-to-play for `video-gallery` cards.
|
|
3
|
+
*
|
|
4
|
+
* Shipped from `@bytesbrains/weblocks/islands/video.js`; served at the island URL
|
|
5
|
+
* the renderer emits (default `/_island/video.js`). Zero dependencies,
|
|
6
|
+
* self-executing, idempotent, guarded by `typeof document`. Each card is an
|
|
7
|
+
* `<a>` facade (thumbnail + play button) that links to the video on its platform;
|
|
8
|
+
* this island intercepts the click and swaps the thumbnail for the real,
|
|
9
|
+
* autoplaying player inline. Built with `createElement`/`replaceChildren` — no
|
|
10
|
+
* HTML strings into the DOM.
|
|
11
|
+
*/
|
|
12
|
+
if (typeof document !== 'undefined') {
|
|
13
|
+
const play = (card) => {
|
|
14
|
+
if (card.dataset.wlPlaying)
|
|
15
|
+
return;
|
|
16
|
+
const embed = card.dataset.wlEmbed;
|
|
17
|
+
const media = card.querySelector('.v-media');
|
|
18
|
+
if (!embed || !media)
|
|
19
|
+
return;
|
|
20
|
+
card.dataset.wlPlaying = '1';
|
|
21
|
+
let el;
|
|
22
|
+
if (card.dataset.wlProvider === 'file') {
|
|
23
|
+
const v = document.createElement('video');
|
|
24
|
+
v.src = embed;
|
|
25
|
+
v.controls = true;
|
|
26
|
+
v.autoplay = true;
|
|
27
|
+
v.setAttribute('playsinline', '');
|
|
28
|
+
el = v;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const f = document.createElement('iframe');
|
|
32
|
+
f.src = embed;
|
|
33
|
+
f.title = card.dataset.wlTitle || 'Video';
|
|
34
|
+
f.setAttribute('allow', 'autoplay; encrypted-media; picture-in-picture; fullscreen');
|
|
35
|
+
f.setAttribute('allowfullscreen', '');
|
|
36
|
+
el = f;
|
|
37
|
+
}
|
|
38
|
+
el.className = 'v-frame';
|
|
39
|
+
media.replaceChildren(el);
|
|
40
|
+
};
|
|
41
|
+
const ready = (fn) => document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', fn) : fn();
|
|
42
|
+
ready(() => {
|
|
43
|
+
document.querySelectorAll('.blk-video-gallery [data-wl-video]').forEach((card) => {
|
|
44
|
+
card.addEventListener('click', (e) => { e.preventDefault(); play(card); });
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export {};
|
package/lib/markdown.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny, SAFE Markdown-subset renderer — escape-first, whitelist-only.
|
|
3
|
+
*
|
|
4
|
+
* This is NOT a raw-HTML passthrough (that would break the engine's no-injection
|
|
5
|
+
* invariant, see VISION.md §3). Every character of the source is HTML-escaped
|
|
6
|
+
* BEFORE any formatting is applied, so a literal `<script>` renders as text,
|
|
7
|
+
* never markup. Only a fixed set of safe elements is ever produced, and link
|
|
8
|
+
* hrefs are scheme-sanitized. Total: it never throws.
|
|
9
|
+
*
|
|
10
|
+
* Supported:
|
|
11
|
+
* #, ##, ###… headings (→ h2…h6)
|
|
12
|
+
* - or * unordered list 1. ordered list
|
|
13
|
+
* > blockquote --- / *** / ___ horizontal rule
|
|
14
|
+
* blank line paragraph break
|
|
15
|
+
* **bold** *italic* _italic_ `code` [text](url)
|
|
16
|
+
*/
|
|
17
|
+
import { escapeHtml, sanitizeUrl } from './schema.js';
|
|
18
|
+
/** Inline formatting. Operates on ALREADY-ESCAPED text; only adds safe tags. */
|
|
19
|
+
function inline(escaped) {
|
|
20
|
+
let s = escaped;
|
|
21
|
+
// `code` first, so its contents aren't touched by the emphasis rules.
|
|
22
|
+
s = s.replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`);
|
|
23
|
+
// [text](url) — url is taken from escaped text (already attribute-safe) and
|
|
24
|
+
// scheme-sanitized; not re-escaped (that would double-encode `&`).
|
|
25
|
+
s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, text, url) => `<a href="${sanitizeUrl(url)}" rel="noopener">${text}</a>`);
|
|
26
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, (_m, t) => `<strong>${t}</strong>`);
|
|
27
|
+
s = s.replace(/(^|[^*])\*([^*\s][^*]*?)\*/g, (_m, pre, t) => `${pre}<em>${t}</em>`);
|
|
28
|
+
s = s.replace(/(^|[^_\w])_([^_]+)_/g, (_m, pre, t) => `${pre}<em>${t}</em>`);
|
|
29
|
+
return s;
|
|
30
|
+
}
|
|
31
|
+
const esc = (raw) => inline(escapeHtml(raw));
|
|
32
|
+
/** Render a Markdown-subset string to safe HTML. */
|
|
33
|
+
export function renderMarkdown(src) {
|
|
34
|
+
const lines = String(src ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
35
|
+
const out = [];
|
|
36
|
+
let para = [];
|
|
37
|
+
const flush = () => { if (para.length) {
|
|
38
|
+
out.push(`<p>${esc(para.join(' '))}</p>`);
|
|
39
|
+
para = [];
|
|
40
|
+
} };
|
|
41
|
+
for (let i = 0; i < lines.length;) {
|
|
42
|
+
const t = lines[i].trim();
|
|
43
|
+
if (!t) {
|
|
44
|
+
flush();
|
|
45
|
+
i++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(t)) {
|
|
49
|
+
flush();
|
|
50
|
+
out.push('<hr>');
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const h = /^(#{1,6})\s+(.*)$/.exec(t);
|
|
55
|
+
if (h) {
|
|
56
|
+
flush();
|
|
57
|
+
out.push(`<h${Math.min(h[1].length + 1, 6)}>${esc(h[2].trim())}</h${Math.min(h[1].length + 1, 6)}>`);
|
|
58
|
+
i++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (/^>\s?/.test(t)) {
|
|
62
|
+
flush();
|
|
63
|
+
const q = [];
|
|
64
|
+
for (; i < lines.length && /^>\s?/.test(lines[i].trim()); i++)
|
|
65
|
+
q.push(lines[i].trim().replace(/^>\s?/, ''));
|
|
66
|
+
out.push(`<blockquote>${esc(q.join(' '))}</blockquote>`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (/^[-*]\s+/.test(t)) {
|
|
70
|
+
flush();
|
|
71
|
+
const items = [];
|
|
72
|
+
for (; i < lines.length && /^[-*]\s+/.test(lines[i].trim()); i++)
|
|
73
|
+
items.push(lines[i].trim().replace(/^[-*]\s+/, ''));
|
|
74
|
+
out.push(`<ul>${items.map((it) => `<li>${esc(it)}</li>`).join('')}</ul>`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (/^\d+\.\s+/.test(t)) {
|
|
78
|
+
flush();
|
|
79
|
+
const items = [];
|
|
80
|
+
for (; i < lines.length && /^\d+\.\s+/.test(lines[i].trim()); i++)
|
|
81
|
+
items.push(lines[i].trim().replace(/^\d+\.\s+/, ''));
|
|
82
|
+
out.push(`<ol>${items.map((it) => `<li>${esc(it)}</li>`).join('')}</ol>`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
para.push(t);
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
flush();
|
|
89
|
+
return out.join('\n');
|
|
90
|
+
}
|
package/lib/registry.js
CHANGED
|
@@ -15,6 +15,7 @@ import { team } from './blocks/team.js';
|
|
|
15
15
|
import { gallery } from './blocks/gallery.js';
|
|
16
16
|
import { carousel } from './blocks/carousel.js';
|
|
17
17
|
import { video } from './blocks/video.js';
|
|
18
|
+
import { videoGallery } from './blocks/videoGallery.js';
|
|
18
19
|
import { map } from './blocks/map.js';
|
|
19
20
|
import { timeline } from './blocks/timeline.js';
|
|
20
21
|
import { tabs } from './blocks/tabs.js';
|
|
@@ -32,8 +33,10 @@ import { cta } from './blocks/cta.js';
|
|
|
32
33
|
import { socialLinks } from './blocks/socialLinks.js';
|
|
33
34
|
import { contactDetails } from './blocks/contactDetails.js';
|
|
34
35
|
import { directions } from './blocks/directions.js';
|
|
36
|
+
import { legal } from './blocks/legal.js';
|
|
35
37
|
import { divider } from './blocks/divider.js';
|
|
36
38
|
import { spacer } from './blocks/spacer.js';
|
|
39
|
+
import { copyright } from './blocks/copyright.js';
|
|
37
40
|
import { appShell } from './blocks/appShell.js';
|
|
38
41
|
import { sidebar } from './blocks/sidebar.js';
|
|
39
42
|
import { footer } from './blocks/footer.js';
|
|
@@ -48,7 +51,7 @@ const SPECS = [
|
|
|
48
51
|
features, about, richText, split, steps, stats,
|
|
49
52
|
services, pricing, logos, team,
|
|
50
53
|
// media
|
|
51
|
-
gallery, carousel, video, map,
|
|
54
|
+
gallery, carousel, video, videoGallery, map,
|
|
52
55
|
// structured content
|
|
53
56
|
timeline, tabs, accordion, testimonials, faq,
|
|
54
57
|
// collections
|
|
@@ -56,10 +59,10 @@ const SPECS = [
|
|
|
56
59
|
// dynamic / powered
|
|
57
60
|
contactForm, newsletter, search, auth,
|
|
58
61
|
// conversion / contact
|
|
59
|
-
cta, socialLinks, contactDetails, directions,
|
|
62
|
+
cta, socialLinks, contactDetails, directions, legal,
|
|
60
63
|
// rhythm
|
|
61
64
|
divider, spacer,
|
|
62
|
-
footer,
|
|
65
|
+
copyright, footer,
|
|
63
66
|
];
|
|
64
67
|
export const REGISTRY = new Map(SPECS.map((s) => [s.type, s]));
|
|
65
68
|
export function getSpec(type) {
|
package/lib/render.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ import type { SiteManifest } from './types.js';
|
|
|
3
3
|
export interface RenderOptions {
|
|
4
4
|
/** Host runtime for powered bricks (§6). Defaults to the inert no-op adapter. */
|
|
5
5
|
runtime?: RuntimeAdapter;
|
|
6
|
+
/**
|
|
7
|
+
* URL prefix where the host serves island scripts (from the package's
|
|
8
|
+
* `./islands/*.js` subpath). Defaults to `/_island`, so a `lightbox` island is
|
|
9
|
+
* emitted as `<script src="/_island/lightbox.js">`.
|
|
10
|
+
*/
|
|
11
|
+
islandBase?: string;
|
|
6
12
|
}
|
|
7
13
|
/** The full document. */
|
|
8
14
|
export declare function renderSite(manifest: SiteManifest, options?: RenderOptions): string;
|
package/lib/render.js
CHANGED
|
@@ -57,8 +57,9 @@ export function renderSite(manifest, options = {}) {
|
|
|
57
57
|
if (needsIsland(spec, value))
|
|
58
58
|
islands.add(spec.island);
|
|
59
59
|
}
|
|
60
|
+
const islandBase = (options.islandBase ?? '/_island').replace(/\/+$/, '');
|
|
60
61
|
const islandTags = [...islands]
|
|
61
|
-
.map((name) => `<script type="module" src="
|
|
62
|
+
.map((name) => `<script type="module" src="${escapeAttr(islandBase)}/${escapeAttr(name)}.js" data-island="${escapeAttr(name)}"></script>`)
|
|
62
63
|
.join('\n');
|
|
63
64
|
return `<!doctype html>
|
|
64
65
|
<html lang="${escapeAttr(meta.lang || 'en')}">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytesbrains/weblocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Block engine for AI-composable web apps — the AI composes a SiteManifest from a fixed block catalog; the engine validates and renders it to static HTML. Snap-together \"Lego\" web building blocks, shareable across repos.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"types": "./lib/index.d.ts",
|
|
31
31
|
"import": "./lib/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./islands/*.js": "./lib/islands/*.js",
|
|
33
34
|
"./catalog.json": "./catalog.json"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
@@ -56,9 +57,9 @@
|
|
|
56
57
|
"access": "public"
|
|
57
58
|
},
|
|
58
59
|
"scripts": {
|
|
59
|
-
"build": "tsc -p tsconfig.json",
|
|
60
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.islands.json",
|
|
60
61
|
"emit:catalog": "node scripts/emit-catalog.mjs",
|
|
61
|
-
"test": "
|
|
62
|
+
"test": "npm run build && node --test lib/*.test.js test/*.mjs",
|
|
62
63
|
"example": "tsc -p tsconfig.json && node lib/example.js",
|
|
63
64
|
"ai": "tsc -p tsconfig.json && node scripts/ai-run.mjs",
|
|
64
65
|
"prepare": "npm run build",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"@types/node": "^22.0.0",
|
|
70
|
+
"jsdom": "^29.1.1",
|
|
69
71
|
"typescript": "^5.6.0"
|
|
70
72
|
}
|
|
71
73
|
}
|