reactolith 1.0.19

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/index.mjs ADDED
@@ -0,0 +1,599 @@
1
+ import { createRoot } from 'react-dom/client';
2
+ import React, { createContext, useState, useRef, useEffect, useContext, useCallback } from 'react';
3
+ import { jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ const RouterContext = createContext(undefined);
6
+ function RouterProvider({ children }) {
7
+ const { router } = useApp();
8
+ const [loading, setLoading] = useState(false);
9
+ const [lastError, setLastError] = useState(null);
10
+ const errorId = useRef(0);
11
+ useEffect(() => {
12
+ const start = () => setLoading(true);
13
+ const end = () => setLoading(false);
14
+ // The router emits: "render:failed", input, init, pushState, response, html, finalUrl
15
+ const onRenderFailed = (input, init, pushState, response, html, finalUrl) => {
16
+ errorId.current += 1;
17
+ setLastError({
18
+ id: errorId.current,
19
+ timestamp: Date.now(),
20
+ input,
21
+ init,
22
+ pushState,
23
+ response,
24
+ html,
25
+ finalUrl,
26
+ });
27
+ };
28
+ router.on("nav:started", start);
29
+ router.on("nav:ended", end);
30
+ router.on("render:failed", onRenderFailed);
31
+ return () => {
32
+ router.off("nav:started", start);
33
+ router.off("nav:ended", end);
34
+ router.off("render:failed", onRenderFailed);
35
+ };
36
+ }, [router]);
37
+ const clearError = () => setLastError(null);
38
+ return (jsx(RouterContext.Provider, { value: { router, loading, lastError, clearError }, children: children }));
39
+ }
40
+ function useRouter() {
41
+ const ctx = useContext(RouterContext);
42
+ if (!ctx) {
43
+ throw new Error("useRouter must be used inside <RouterProvider>");
44
+ }
45
+ return ctx;
46
+ }
47
+
48
+ const AppContext = createContext(undefined);
49
+ function useApp() {
50
+ const ctx = useContext(AppContext);
51
+ if (!ctx) {
52
+ throw new Error("useApp must be used inside <AppProvider>");
53
+ }
54
+ return ctx;
55
+ }
56
+ const AppProvider = ({ app, children, }) => {
57
+ useEffect(() => {
58
+ app.element.classList.remove("hidden");
59
+ }, []);
60
+ return (jsx(AppContext.Provider, { value: app, children: jsx(RouterProvider, { children: children }) }));
61
+ };
62
+
63
+ const isRelativeHref = (href) => {
64
+ if (!href)
65
+ return false;
66
+ if (href.startsWith("#"))
67
+ return false;
68
+ if (href.startsWith("//"))
69
+ return false;
70
+ return !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href);
71
+ };
72
+ const hasNavBypassModifiers = (e) => e.defaultPrevented ||
73
+ e.button !== 0 ||
74
+ e.metaKey ||
75
+ e.ctrlKey ||
76
+ e.shiftKey ||
77
+ e.altKey;
78
+ class Router {
79
+ constructor(app, doc = document, fetchImpl = fetch) {
80
+ this.listeners = {};
81
+ this.app = app;
82
+ this.fetch = (input, init) => fetchImpl(input, init);
83
+ if (doc.defaultView) {
84
+ doc.defaultView.addEventListener("popstate", async () => {
85
+ await this.visit(location.pathname + location.search, { method: "GET" }, false);
86
+ });
87
+ }
88
+ doc.addEventListener("click", (e) => this.onClick(e));
89
+ doc.addEventListener("submit", (e) => this.onSubmit(e));
90
+ }
91
+ ensureSet(type) {
92
+ const existing = this.listeners[type];
93
+ if (existing)
94
+ return existing;
95
+ const created = new Set();
96
+ // Upcast to the union that the field allows; no `any`.
97
+ this.listeners[type] = created;
98
+ return created;
99
+ }
100
+ emit(type, ...args) {
101
+ this.listeners[type]?.forEach((h) => h(...args));
102
+ }
103
+ on(type, handler) {
104
+ const set = this.ensureSet(type);
105
+ set.add(handler);
106
+ return () => this.off(type, handler);
107
+ }
108
+ off(type, handler) {
109
+ this.listeners[type]?.delete(handler);
110
+ }
111
+ async visit(input, init = { method: "GET" }, pushState = true) {
112
+ this.emit("nav:started", input, init, pushState);
113
+ const response = await this.fetch(input, init);
114
+ const html = await response.text();
115
+ const original = typeof input === "string" ? input : input.toString();
116
+ const finalUrl = response.redirected ? response.url : original;
117
+ const result = this.app.render(html);
118
+ if (result && pushState) {
119
+ history.pushState({}, "", finalUrl);
120
+ }
121
+ const event = result ? "render:success" : "render:failed";
122
+ this.emit(event, input, init, pushState, response, html, finalUrl);
123
+ this.emit("nav:ended", input, init, pushState, response, html, finalUrl);
124
+ return { result, response, html, finalUrl };
125
+ }
126
+ async onClick(event) {
127
+ // Ignore modified clicks, right/middle clicks, already-handled events
128
+ if (hasNavBypassModifiers(event))
129
+ return;
130
+ const link = event.target?.closest("a");
131
+ if (!link)
132
+ return;
133
+ const hrefAttr = link.getAttribute("href");
134
+ if (!isRelativeHref(hrefAttr))
135
+ return;
136
+ // Respect targets like _blank or any non-_self
137
+ if (link.target && link.target.toLowerCase() !== "_self")
138
+ return;
139
+ // Respect downloads and explicit external hints
140
+ if (link.hasAttribute("download"))
141
+ return;
142
+ const rel = link.getAttribute("rel") || "";
143
+ if (/\bexternal\b/i.test(rel))
144
+ return;
145
+ event.preventDefault();
146
+ event.stopPropagation();
147
+ await this.visit(hrefAttr);
148
+ }
149
+ async onSubmit(event) {
150
+ const form = event.target;
151
+ if (!form)
152
+ return;
153
+ const actionAttr = form.getAttribute("action");
154
+ const isRelativeAction = actionAttr === null || isRelativeHref(actionAttr);
155
+ if (form.target && form.target.toLowerCase() !== "_self")
156
+ return;
157
+ if (!isRelativeAction)
158
+ return;
159
+ event.preventDefault();
160
+ event.stopPropagation();
161
+ const formData = new FormData(form);
162
+ if (event.submitter instanceof HTMLButtonElement && event.submitter.name) {
163
+ formData.append(event.submitter.name, event.submitter.value || "");
164
+ }
165
+ const method = (form.method || "GET").toUpperCase();
166
+ let body = null;
167
+ let url = actionAttr ?? "";
168
+ if (method === "GET") {
169
+ const params = new URLSearchParams();
170
+ formData.forEach((value, key) => {
171
+ if (typeof value === "string")
172
+ params.append(key, value);
173
+ });
174
+ const q = params.toString();
175
+ const sep = url.includes("?") ? (q ? "&" : "") : q ? "?" : "";
176
+ url = `${url}${sep}${q}`;
177
+ }
178
+ else {
179
+ body = formData;
180
+ }
181
+ await this.visit(url || location.pathname + location.search, {
182
+ method,
183
+ body,
184
+ });
185
+ }
186
+ async navigate(path) {
187
+ await this.visit(path);
188
+ }
189
+ }
190
+
191
+ const toPascalCase = (str) => {
192
+ return str.replace(/(^\w|-\w)/g, (match) => match.replace(/-/, "").toUpperCase());
193
+ };
194
+ const normalizePropName = (name) => {
195
+ if (name.startsWith("json-")) {
196
+ name = name.substring(5);
197
+ }
198
+ name = toPascalCase(name);
199
+ return name.substring(0, 1).toLowerCase() + name.substring(1);
200
+ };
201
+ function getKey(element) {
202
+ return element.attributes.getNamedItem("key")?.value;
203
+ }
204
+ function getProps(element, component, isReactComponent = true) {
205
+ const props = {};
206
+ Array.from(element.attributes).forEach((attr) => {
207
+ if (attr.name !== "key" && !attr.name.startsWith("#")) {
208
+ let value = attr.value;
209
+ if (typeof value === "string" &&
210
+ attr.value.startsWith("{") &&
211
+ attr.value.endsWith("}")) {
212
+ value = React.createElement(component, {
213
+ is: value.substring(1, value.length - 1),
214
+ });
215
+ }
216
+ if (attr.name.startsWith("json-")) {
217
+ props[normalizePropName(attr.name)] = JSON.parse(attr.value);
218
+ }
219
+ else if (!isReactComponent && attr.name.startsWith("data-")) {
220
+ props[attr.name] = attr.value;
221
+ }
222
+ else {
223
+ // Special case: Empty value will be transformed to bool true value
224
+ if (typeof value === "string" && value.length === 0) {
225
+ value = true;
226
+ }
227
+ props[attr.name === "class" ? "className" : normalizePropName(attr.name)] = value;
228
+ }
229
+ }
230
+ });
231
+ return props;
232
+ }
233
+ function getSlots(element, component) {
234
+ const slots = {};
235
+ Array.from(element.childNodes).forEach((child) => {
236
+ if (child instanceof HTMLElement) {
237
+ Array.from(child.attributes).forEach((attr) => {
238
+ if (attr.name === "slot") {
239
+ slots[attr.value] = getChildren(child instanceof HTMLTemplateElement ? child.content : child, component);
240
+ }
241
+ });
242
+ }
243
+ });
244
+ return slots;
245
+ }
246
+ function getChildren(element, component) {
247
+ return Array.from(element.childNodes)
248
+ .map((child, index) => {
249
+ if (child instanceof HTMLElement && child.hasAttribute("slot")) {
250
+ return null;
251
+ }
252
+ if (child instanceof Text) {
253
+ return child.textContent;
254
+ }
255
+ else if (child instanceof Element) {
256
+ const key = child.hasAttribute("key")
257
+ ? child.getAttribute("key")
258
+ : index;
259
+ return (jsx(ReactolithComponent, { element: child, component: component }, key));
260
+ }
261
+ return null;
262
+ })
263
+ .filter(Boolean);
264
+ }
265
+ const ReactolithComponent = React.forwardRef(({ element, component: Component, ...props }, forwardedRef) => {
266
+ if (!element)
267
+ return null;
268
+ const tagName = element.tagName.toLowerCase();
269
+ const children = getChildren(element, Component);
270
+ const isReactComponent = tagName.includes("-") ||
271
+ document.createElement(tagName).constructor === HTMLUnknownElement;
272
+ const type = isReactComponent
273
+ ? Component
274
+ : tagName;
275
+ const allProps = {
276
+ ...getProps(element, Component, isReactComponent),
277
+ ...getSlots(element, Component),
278
+ key: getKey(element),
279
+ ...(isReactComponent ? { is: tagName } : {}),
280
+ ...props,
281
+ ref: forwardedRef,
282
+ };
283
+ return React.createElement(type, allProps, ...children);
284
+ });
285
+ ReactolithComponent.displayName = "ReactolithComponent";
286
+
287
+ class App {
288
+ constructor(component, appProvider = AppProvider, selector = "#reactolith-app", root, doc = document, fetchImp = fetch) {
289
+ this.router = new Router(this, doc, fetchImp);
290
+ this.component = component;
291
+ this.appProvider = appProvider;
292
+ this.doc = doc;
293
+ if (typeof selector === "string") {
294
+ const selStr = selector;
295
+ selector = (doc) => doc.querySelector(selStr);
296
+ }
297
+ this.selector = selector;
298
+ const element = this.selector(doc);
299
+ if (!element) {
300
+ throw new Error("Could not find root element in document. Please check your selector!");
301
+ }
302
+ this.element = element;
303
+ this.root = root || createRoot(this.element);
304
+ // Auto-configure Mercure from data-mercure-hub-url attribute
305
+ const mercureHubUrl = this.element.getAttribute("data-mercure-hub-url");
306
+ if (mercureHubUrl) {
307
+ this.mercureConfig = {
308
+ hubUrl: mercureHubUrl,
309
+ withCredentials: this.element.hasAttribute("data-mercure-with-credentials"),
310
+ };
311
+ }
312
+ this.renderElement(this.element);
313
+ }
314
+ render(document) {
315
+ if (typeof document === "string") {
316
+ const parser = new DOMParser();
317
+ document = parser.parseFromString(document, "text/html");
318
+ }
319
+ // Try to find the root element in the document
320
+ const element = this.selector(document);
321
+ if (!element) {
322
+ return false;
323
+ }
324
+ this.renderElement(element);
325
+ return true;
326
+ }
327
+ renderElement(element) {
328
+ this.root.render(React.createElement(this.appProvider, {
329
+ app: this,
330
+ }, Array.from(element.children)
331
+ .filter((child) => child instanceof HTMLElement)
332
+ .map((element, key) => React.createElement(ReactolithComponent, {
333
+ key,
334
+ element,
335
+ component: this.component,
336
+ }))));
337
+ }
338
+ unmount() {
339
+ this.root.unmount();
340
+ }
341
+ }
342
+
343
+ class Mercure {
344
+ constructor(app) {
345
+ this.eventSource = null;
346
+ this.listeners = {};
347
+ this.currentUrl = null;
348
+ this.options = null;
349
+ this.routerUnsubscribe = null;
350
+ this.app = app;
351
+ }
352
+ ensureSet(type) {
353
+ const existing = this.listeners[type];
354
+ if (existing)
355
+ return existing;
356
+ const created = new Set();
357
+ this.listeners[type] = created;
358
+ return created;
359
+ }
360
+ emit(type, ...args) {
361
+ this.listeners[type]?.forEach((h) => h(...args));
362
+ }
363
+ on(type, handler) {
364
+ const set = this.ensureSet(type);
365
+ set.add(handler);
366
+ return () => this.off(type, handler);
367
+ }
368
+ off(type, handler) {
369
+ this.listeners[type]?.delete(handler);
370
+ }
371
+ /**
372
+ * Subscribe to a Mercure hub for real-time updates.
373
+ * Automatically subscribes to the current pathname and re-subscribes on route changes.
374
+ */
375
+ subscribe(options) {
376
+ // Store options for re-subscription
377
+ this.options = options;
378
+ // Unsubscribe from previous router listener
379
+ if (this.routerUnsubscribe) {
380
+ this.routerUnsubscribe();
381
+ }
382
+ // Listen to router navigation to re-subscribe with new pathname
383
+ this.routerUnsubscribe = this.app.router.on("render:success", () => {
384
+ this.connectToCurrentPath();
385
+ });
386
+ // Connect to current path
387
+ this.connectToCurrentPath();
388
+ }
389
+ /**
390
+ * Connect to EventSource with current pathname as topic
391
+ */
392
+ connectToCurrentPath() {
393
+ if (!this.options)
394
+ return;
395
+ // Close existing connection if any
396
+ if (this.eventSource) {
397
+ this.eventSource.close();
398
+ if (this.currentUrl) {
399
+ this.emit("sse:disconnected", this.currentUrl);
400
+ }
401
+ this.eventSource = null;
402
+ }
403
+ const { hubUrl, lastEventId, withCredentials = false } = this.options;
404
+ // Build the subscription URL with current pathname as topic
405
+ const url = new URL(hubUrl);
406
+ const topic = window.location.pathname;
407
+ url.searchParams.append("topic", topic);
408
+ if (lastEventId) {
409
+ url.searchParams.set("lastEventID", lastEventId);
410
+ }
411
+ this.currentUrl = url.toString();
412
+ // Create EventSource connection
413
+ this.eventSource = new EventSource(this.currentUrl, {
414
+ withCredentials,
415
+ });
416
+ this.eventSource.onopen = () => {
417
+ this.emit("sse:connected", this.currentUrl);
418
+ };
419
+ this.eventSource.onmessage = async (event) => {
420
+ const html = event.data;
421
+ this.emit("sse:message", event, html);
422
+ // If message is empty or only whitespace, refetch the current route
423
+ if (!html || html.trim() === "") {
424
+ this.emit("refetch:started", event);
425
+ try {
426
+ const response = await this.app.router.visit(window.location.pathname + window.location.search, { method: "GET" }, false);
427
+ if (response.result) {
428
+ this.emit("refetch:success", event, response.html);
429
+ }
430
+ else {
431
+ this.emit("refetch:failed", event, new Error("Failed to render refetched content"));
432
+ }
433
+ }
434
+ catch (error) {
435
+ this.emit("refetch:failed", event, error);
436
+ }
437
+ return;
438
+ }
439
+ // Process the HTML through the app's render method
440
+ const result = this.app.render(html);
441
+ if (result) {
442
+ this.emit("render:success", event, html);
443
+ }
444
+ else {
445
+ this.emit("render:failed", event, html);
446
+ }
447
+ };
448
+ this.eventSource.onerror = (error) => {
449
+ this.emit("sse:error", error);
450
+ // If the connection is closed, emit disconnected
451
+ if (this.eventSource?.readyState === EventSource.CLOSED) {
452
+ this.emit("sse:disconnected", this.currentUrl);
453
+ }
454
+ };
455
+ }
456
+ /**
457
+ * Close the SSE connection
458
+ */
459
+ close() {
460
+ // Unsubscribe from router events
461
+ if (this.routerUnsubscribe) {
462
+ this.routerUnsubscribe();
463
+ this.routerUnsubscribe = null;
464
+ }
465
+ if (this.eventSource) {
466
+ this.eventSource.close();
467
+ if (this.currentUrl) {
468
+ this.emit("sse:disconnected", this.currentUrl);
469
+ }
470
+ this.eventSource = null;
471
+ this.currentUrl = null;
472
+ }
473
+ this.options = null;
474
+ }
475
+ /**
476
+ * Check if currently connected
477
+ */
478
+ get connected() {
479
+ return this.eventSource?.readyState === EventSource.OPEN;
480
+ }
481
+ /**
482
+ * Get the current connection URL
483
+ */
484
+ get url() {
485
+ return this.currentUrl;
486
+ }
487
+ /**
488
+ * Get the last event ID (useful for reconnection)
489
+ */
490
+ get lastEventId() {
491
+ // EventSource doesn't expose lastEventId directly,
492
+ // but you can track it via sse:message events
493
+ return undefined;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Generic hook for subscribing to a Mercure topic and receiving raw message data.
499
+ * This is a low-level hook that handles EventSource connection management.
500
+ *
501
+ * @param topic - The Mercure topic to subscribe to
502
+ * @param onMessage - Callback when a message is received
503
+ * @param onError - Optional callback when an error occurs
504
+ *
505
+ * @internal This is a low-level hook. Use useMercureTopic or MercureLive instead.
506
+ */
507
+ function useMercureEventSource(topic, onMessage, onError) {
508
+ const app = useApp();
509
+ useEffect(() => {
510
+ if (!app.mercureConfig) {
511
+ console.warn(`useMercureEventSource: app.mercureConfig is not set. Please configure it before using Mercure features.`);
512
+ return;
513
+ }
514
+ const url = new URL(app.mercureConfig.hubUrl);
515
+ url.searchParams.append("topic", topic);
516
+ const eventSource = new EventSource(url.toString(), {
517
+ withCredentials: app.mercureConfig.withCredentials ?? false,
518
+ });
519
+ eventSource.onmessage = (event) => {
520
+ onMessage(event.data);
521
+ };
522
+ eventSource.onerror = (error) => {
523
+ if (onError) {
524
+ onError(error);
525
+ }
526
+ // EventSource will automatically reconnect
527
+ };
528
+ return () => eventSource.close();
529
+ }, [topic, app, onMessage, onError]);
530
+ }
531
+
532
+ /**
533
+ * Subscribe to a Mercure topic and receive live JSON data updates.
534
+ *
535
+ * @template T - The type of data expected from the topic
536
+ * @param topic - The Mercure topic to subscribe to
537
+ * @param initialValue - The initial value before any updates
538
+ * @returns The current value from the topic
539
+ *
540
+ * @example
541
+ * ```tsx
542
+ * // Simple usage with type inference
543
+ * const count = useMercureTopic('/notifications/count', 0);
544
+ *
545
+ * // With explicit type
546
+ * const status = useMercureTopic<'online' | 'offline'>('/status', 'offline');
547
+ *
548
+ * // With complex type
549
+ * interface Stats { visitors: number; sales: number; }
550
+ * const stats = useMercureTopic<Stats>('/stats', { visitors: 0, sales: 0 });
551
+ * ```
552
+ */
553
+ function useMercureTopic(topic, initialValue) {
554
+ const [value, setValue] = useState(initialValue);
555
+ const handleMessage = useCallback((data) => {
556
+ try {
557
+ const parsed = JSON.parse(data);
558
+ setValue(parsed);
559
+ }
560
+ catch (error) {
561
+ console.error("Failed to parse Mercure message:", error);
562
+ }
563
+ }, []);
564
+ useMercureEventSource(topic, handleMessage);
565
+ return value;
566
+ }
567
+
568
+ function MercureLive({ topic, children }) {
569
+ const app = useApp();
570
+ const [content, setContent] = useState(children);
571
+ // Update content when children prop changes (e.g., during navigation)
572
+ useEffect(() => {
573
+ setContent(children);
574
+ }, [children]);
575
+ const handleMessage = useCallback((data) => {
576
+ try {
577
+ const parser = new DOMParser();
578
+ const doc = parser.parseFromString(data, "text/html");
579
+ const element = doc.body.firstElementChild;
580
+ if (element) {
581
+ setContent(jsx(ReactolithComponent, { element: element, component: app.component }));
582
+ }
583
+ else {
584
+ console.warn("MercureLive: No element found in Mercure message");
585
+ }
586
+ }
587
+ catch (error) {
588
+ console.error("MercureLive: Failed to parse message:", error);
589
+ }
590
+ }, [app.component]);
591
+ const handleError = useCallback((error) => {
592
+ console.error("MercureLive: EventSource error:", error);
593
+ }, []);
594
+ useMercureEventSource(topic, handleMessage, handleError);
595
+ return jsx(Fragment, { children: content });
596
+ }
597
+
598
+ export { App, AppProvider, Mercure, MercureLive, ReactolithComponent, Router, RouterProvider, useApp, useMercureEventSource, useMercureTopic, useRouter };
599
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "reactolith",
3
+ "version": "1.0.19",
4
+ "description": "Use HTML on the server to compose your react application.",
5
+ "license": "MIT",
6
+ "author": "Franz Mayr-Wilding",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/reactolith/reactolith"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/reactolith/reactolith/issues"
13
+ },
14
+ "homepage": "https://github.com/reactolith/reactolith",
15
+ "type": "module",
16
+ "keywords": [
17
+ "react",
18
+ "ssr"
19
+ ],
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.mjs",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.mjs",
27
+ "require": "./dist/index.cjs"
28
+ },
29
+ "./cli/generate-web-types": {
30
+ "require": "./cli/generate-web-types.cjs"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "bin": {
38
+ "generate-web-types": "./dist/cli/generate-web-types.cjs"
39
+ },
40
+ "sideEffects": false,
41
+ "scripts": {
42
+ "clean": "rm -rf dist dist-webpack types",
43
+ "build": "npm run clean && npm run build:rollup",
44
+ "build:rollup": "rollup -c",
45
+ "typecheck": "tsc --noEmit",
46
+ "build:release": "npm install && npm ci --dev && npm run typecheck && npm test -- --run && npm run build --if-present",
47
+ "test": "vitest",
48
+ "test:watch": "vitest --watch",
49
+ "test:coverage": "vitest run --coverage"
50
+ },
51
+ "peerDependencies": {
52
+ "react": "^19",
53
+ "react-dom": "^19"
54
+ },
55
+ "devDependencies": {
56
+ "@eslint/js": "^9.39.2",
57
+ "@react-types/shared": "^3.32.1",
58
+ "@rollup/plugin-commonjs": "^29.0.0",
59
+ "@rollup/plugin-node-resolve": "^16.0.3",
60
+ "@testing-library/dom": "^10.4.1",
61
+ "@testing-library/jest-dom": "^6.9.1",
62
+ "@types/node": "^25.2.0",
63
+ "@types/react": "^19.2.10",
64
+ "@types/react-dom": "^19.2.3",
65
+ "@vitest/coverage-v8": "^4.0.18",
66
+ "eslint": "^9.39.2",
67
+ "eslint-config-prettier": "^10.1.8",
68
+ "eslint-plugin-prettier": "^5.5.5",
69
+ "eslint-plugin-react": "^7.37.5",
70
+ "globals": "^17.3.0",
71
+ "jiti": "^2.6.1",
72
+ "jsdom": "^28.0.0",
73
+ "prettier": "^3.8.1",
74
+ "react": "^19.2.4",
75
+ "react-dom": "^19.2.4",
76
+ "rollup": "^4.57.1",
77
+ "rollup-plugin-dts": "^6.3.0",
78
+ "rollup-plugin-peer-deps-external": "^2.2.4",
79
+ "rollup-plugin-typescript2": "^0.36.0",
80
+ "ts-morph": "^27.0.2",
81
+ "typescript": "^5.9.3",
82
+ "typescript-eslint": "^8.54.0",
83
+ "vitest": "^4.0.18"
84
+ },
85
+ "engines": {
86
+ "node": ">=18"
87
+ }
88
+ }