@webjsdev/core 0.7.1
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/README.md +55 -0
- package/index.d.ts +28 -0
- package/index.js +39 -0
- package/package.json +46 -0
- package/src/component.d.ts +120 -0
- package/src/component.js +1061 -0
- package/src/context.js +327 -0
- package/src/css.js +54 -0
- package/src/directives.js +454 -0
- package/src/escape.js +15 -0
- package/src/expose.js +90 -0
- package/src/html.js +30 -0
- package/src/lazy-loader.js +107 -0
- package/src/nav.js +45 -0
- package/src/registry.js +190 -0
- package/src/render-client.js +1717 -0
- package/src/render-server.js +1128 -0
- package/src/repeat.js +37 -0
- package/src/rich-fetch.js +70 -0
- package/src/router-client.js +1434 -0
- package/src/serialize.js +452 -0
- package/src/signal.js +684 -0
- package/src/slot.js +860 -0
- package/src/suspense.js +46 -0
- package/src/task.js +386 -0
- package/src/testing.js +157 -0
- package/src/webjs-frame.js +65 -0
- package/src/websocket-client.js +111 -0
package/src/repeat.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `repeat()`: keyed list directive.
|
|
3
|
+
*
|
|
4
|
+
* Usage in a template:
|
|
5
|
+
* ```js
|
|
6
|
+
* import { html, repeat } from '@webjsdev/core';
|
|
7
|
+
* html`<ul>${repeat(items, (it) => it.id, (it) => html`<li>${it.title}</li>`)}</ul>`;
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Without `repeat()`, an array re-render rebuilds every child (losing focus,
|
|
11
|
+
* selection, and scroll). With `repeat()`, the client renderer maintains a
|
|
12
|
+
* `Map<key, TemplateInstance>`: matched items update in place, new items
|
|
13
|
+
* mount fresh, removed items unmount, and the surviving DOM nodes are moved
|
|
14
|
+
* (not recreated) when the order changes.
|
|
15
|
+
*
|
|
16
|
+
* Keys must be stable and unique within the call. Bad keys (array indices)
|
|
17
|
+
* defeat the purpose; prefer a stable id from the data.
|
|
18
|
+
*
|
|
19
|
+
* On the server this marker is just iterated: order matters, keys don't.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const REPEAT = Symbol.for('webjs.repeat');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @template T
|
|
26
|
+
* @param {Iterable<T>} items
|
|
27
|
+
* @param {(item: T, i: number) => string | number} keyFn
|
|
28
|
+
* @param {(item: T, i: number) => unknown} templateFn
|
|
29
|
+
*/
|
|
30
|
+
export function repeat(items, keyFn, templateFn) {
|
|
31
|
+
return { [REPEAT]: true, items: [...items], keyFn, templateFn };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @param {unknown} x */
|
|
35
|
+
export function isRepeat(x) {
|
|
36
|
+
return !!x && typeof x === 'object' && /** @type any */ (x)[REPEAT] === true;
|
|
37
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { stringify as wjStringify, parse as wjParse } from './serialize.js';
|
|
2
|
+
|
|
3
|
+
const RPC_CONTENT_TYPE = 'application/vnd.webjs+json';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `richFetch(url, init?)`: drop-in `fetch` for calling your own API routes
|
|
7
|
+
* when you want rich types to round-trip (`Date`, `Map`, `Set`, `BigInt`,
|
|
8
|
+
* `TypedArray`, `Blob`, `File`, `FormData`, cycles).
|
|
9
|
+
*
|
|
10
|
+
* - Adds `Accept: application/vnd.webjs+json` to the request so a
|
|
11
|
+
* server-side `json(data)` helper encodes with the webjs serializer.
|
|
12
|
+
* - If the response content-type matches, decodes with the webjs
|
|
13
|
+
* serializer and returns the rich value.
|
|
14
|
+
* - Otherwise decodes with plain `.json()` (so any route you haven't
|
|
15
|
+
* opted in on still works).
|
|
16
|
+
* - Request bodies: if `init.body` is a plain object (not a string,
|
|
17
|
+
* FormData/Blob/ArrayBuffer/ReadableStream/URLSearchParams), it is
|
|
18
|
+
* encoded with the webjs serializer and the request content-type is
|
|
19
|
+
* set to our vendor type.
|
|
20
|
+
*
|
|
21
|
+
* ```js
|
|
22
|
+
* import { richFetch } from '@webjsdev/core';
|
|
23
|
+
* const posts = await richFetch('/api/posts'); // Post[]; createdAt is Date
|
|
24
|
+
* await richFetch('/api/posts', { // rich body
|
|
25
|
+
* method: 'POST',
|
|
26
|
+
* body: { publishAt: new Date(2026, 0, 1) },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @template T
|
|
31
|
+
* @param {string | URL} url
|
|
32
|
+
* @param {RequestInit & { body?: any }} [init]
|
|
33
|
+
* @returns {Promise<T>}
|
|
34
|
+
*/
|
|
35
|
+
export async function richFetch(url, init = {}) {
|
|
36
|
+
const headers = new Headers(init.headers || {});
|
|
37
|
+
if (!headers.has('accept')) headers.set('accept', RPC_CONTENT_TYPE);
|
|
38
|
+
|
|
39
|
+
let body = init.body;
|
|
40
|
+
if (
|
|
41
|
+
body != null &&
|
|
42
|
+
typeof body === 'object' &&
|
|
43
|
+
!(body instanceof FormData) &&
|
|
44
|
+
!(body instanceof Blob) &&
|
|
45
|
+
!(body instanceof ArrayBuffer) &&
|
|
46
|
+
!(body instanceof URLSearchParams) &&
|
|
47
|
+
!(typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) &&
|
|
48
|
+
!ArrayBuffer.isView(body)
|
|
49
|
+
) {
|
|
50
|
+
body = await wjStringify(body);
|
|
51
|
+
if (!headers.has('content-type')) headers.set('content-type', RPC_CONTENT_TYPE);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const res = await fetch(url, { ...init, headers, body });
|
|
55
|
+
const ct = res.headers.get('content-type') || '';
|
|
56
|
+
const text = await res.text();
|
|
57
|
+
const parsed = ct.includes(RPC_CONTENT_TYPE)
|
|
58
|
+
? (text ? wjParse(text) : null)
|
|
59
|
+
: ct.includes('application/json')
|
|
60
|
+
? (text ? JSON.parse(text) : null)
|
|
61
|
+
: text;
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const msg = (parsed && parsed.error) || `richFetch ${url} -> ${res.status}`;
|
|
64
|
+
const err = /** @type any */ (new Error(msg));
|
|
65
|
+
err.status = res.status;
|
|
66
|
+
err.body = parsed;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|