@rathnasgala/theme 0.0.15 → 0.0.17

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rathnasgala/theme",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Managed Gala static-site theme payload",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "themePackage": {
4
4
  "name": "@rathnasgala/theme",
5
- "version": "0.0.15",
5
+ "version": "0.0.17",
6
6
  "availableDesignThemes": [
7
7
  "editorial"
8
8
  ],
@@ -14,7 +14,7 @@
14
14
  "eleventy.config.js": "efe587447e3dc374a220c7fa037fdf2fffc02d09ba43a4d2b96d6c6aae0e3325",
15
15
  "lib/build-manifest.js": "88949f0112d683ca809bb67ca969e15634792423559a67bc79a534cd807e5535",
16
16
  "lib/engagement-snapshot.js": "298c11b1ab135a3a98a4e31b6320ab60dfd7fda6a9d9053df37d86282a5a6687",
17
- "lib/performance-budget.js": "124c43494c092a956accec46d1463ea063ed73363d0db01f24aaea987459d683",
17
+ "lib/performance-budget.js": "c279622dfde237cd6e3c64775c91d770a0e1f54f5f94257ccc95d9a44de2c4c9",
18
18
  "lib/provider-fixtures/embeds.v1.json": "5f26909fb433135321ddf7fdd14e45aeb702805c88f5676941166c5735143ee2",
19
19
  "lib/provider-fixtures/share-intents.v1.json": "66a8eedfcff711253ee45ceb94b53f7d5a6be3ddbbb3d1f0d4e31a114326cc0a",
20
20
  "lib/publication-state.js": "1b59c29a18bbb1193bddea898dac3a4fb832c7bc4751263be086f3d64392dea0",
@@ -30,18 +30,25 @@
30
30
  "src/_data/feedLinks.js": "06c93352ebd888acb431a1c9a8be6ecff757895468e9f2d336761038751c2d5e",
31
31
  "src/_data/languages.js": "1358748f7fa2f74406d3295a030827d6229a53e94da72cf712e719d7a356aeac",
32
32
  "src/_data/site.js": "d9c001f4cc12a34027dd92303530b11b5402d47613459bb997e22c4bcbea326d",
33
- "src/_includes/components/ui.njk": "b95eca21eb78182989c114f8d03342769553470672e7402e1330e9070c293ff5",
34
- "src/_includes/layouts/base.njk": "d132b0a920e3dd9b60543394da4498b4d25b3821bdaaea0b3a5da55e9200b663",
33
+ "src/_includes/components/ui.njk": "23eb647d8658868449ec6bb809e965a85302124a97639f94d5d28af72df56e4c",
34
+ "src/_includes/layouts/base.njk": "f504b5a80020b90eb47d59db5e626a4554e2f2d4ef64b3b25c98869830581ebe",
35
35
  "src/_includes/layouts/post.njk": "1accd2832064ad19ee7bf18daf5fa1e60569f32c9b7a547934a6361f747d7eb7",
36
36
  "src/assets/embed-codepen.svg": "e9def697f38c466fb2b6652535c4c814e8cbbbefc6e052a69f335aeb27e8356b",
37
37
  "src/assets/embed-gist.svg": "fcd9bc2bf29a5c118492b853c0a01390b7539c38ddb985da3c2a6e2f246a39f7",
38
38
  "src/assets/embed-x.svg": "0ae74edc6790ec7e933067fe5392bce756e93fa7ad71caacf1c1ca99fa4ec9b6",
39
39
  "src/assets/embed-youtube.svg": "10d192f2b14b67664fea88c8cf246321d20190def51d59fad5762e552219b067",
40
- "src/assets/interactions.js": "fafa38854663f840fb4eb206d3ca06a836abf36bedb035a4e8ac211805709f09",
40
+ "src/assets/engagement-comments.js": "b20d9268c9d908b4453a42188eaca25349f25534fb04a9293c9e688a982cd8f8",
41
+ "src/assets/engagement-transport.js": "4f9ca0cd669bf8abc25f2909104e3e4b231657ef97d36a17d06e53454d711786",
42
+ "src/assets/interactions.js": "dd4e343505e528d89596ca67db679b52c989e022f93399193e4377db01fc8a08",
41
43
  "src/assets/preferences.js": "789127bee013ed6a1bbf8cefd7b9109291c16220e7a6e09314e3c93c54731d1d",
42
44
  "src/assets/search.js": "df425f0239277792ad39f2138bc89cb66cc4ecda5ed59042d46abc2f05372088",
43
45
  "src/assets/theme-mode.js": "3797c84dbaa090b5b21427a31928b5dd97f13d04d99833f5e1e74c6b50e01dcc",
44
- "src/assets/theme.css": "2c835fb79531d7b4a71bc2d41a6deb6e6b3689bf8603d903a3ba93add66c0d60",
46
+ "src/assets/theme.css": "94839833f93997415f570041a691da0c96c27992f897e116948077a8b25e8dfa",
47
+ "src/assets/vendor/hooks.js": "ae6ec58592683ca64fa4d65b20d40b103df62a673bcc4712515cf165950b07f3",
48
+ "src/assets/vendor/htm.js": "e4dac9bc40a6f2ec590413d787ae30bbcfdfcde4f28971ee65d9578271b7aad4",
49
+ "src/assets/vendor/preact.js": "712517dd2a42c3c22b5ab0264ad7e9a2b1e8be6cf97fa00dbedd98203617e616",
50
+ "src/assets/vendor/signals-core.js": "1e10560fec5118c9e35eb312c869fe51f4870424766ef2ec0f7cd069bd898c8b",
51
+ "src/assets/vendor/signals.js": "717a88151bacf4f8b79b528a96d71be20a133fb3345230c1146929903cecfd19",
45
52
  "src/cname.11ty.js": "206fc224df91c569e18312158e7f69803061f2952513f501ada54bd06c4f8006",
46
53
  "src/contact.njk": "f4d74d038781e15af0acef571901c91e7753a2e21b6bef617429585698839a34",
47
54
  "src/feed.11ty.js": "009bd2384e08e02a9405683bab1653bab3aeaec16accb0fd01ba69b24c777da9",
@@ -24,7 +24,16 @@ function assertWithin(actual, maximum, label) {
24
24
  export async function enforcePerformanceBudgets({ outputDirectory, budgets }) {
25
25
  const output = path.resolve(outputDirectory);
26
26
  const files = await filesBelow(output);
27
- const managedAssets = files.filter((file) => path.dirname(file) === path.join(output, 'assets'));
27
+ /*
28
+ * Everything under `assets`, nested included. This used to compare `path.dirname` against the
29
+ * assets directory exactly, so anything in a subdirectory was invisible to the budget — the
30
+ * moment the reader runtime moved into `assets/vendor` it would have stopped being counted, and
31
+ * a budget that quietly ignores two thirds of the JavaScript it is meant to guard is worse than
32
+ * having none.
33
+ */
34
+ const assets = path.join(output, 'assets');
35
+ const managedAssets = files.filter(
36
+ (file) => file === assets || file.startsWith(assets + path.sep));
28
37
  const javascriptBytes = await totalBytes(managedAssets.filter((file) => file.endsWith('.js')));
29
38
  const cssBytes = await totalBytes(managedAssets.filter((file) => file.endsWith('.css')));
30
39
  assertWithin(javascriptBytes, budgets.managedJavaScriptBytes, 'Managed JavaScript');
@@ -33,13 +33,12 @@
33
33
  ] %}<button type="button" data-reaction="{{ reaction.value }}" aria-pressed="false">{{ reaction.label }}</button>{% endfor %}
34
34
  </div>
35
35
  <button type="button" data-follow-article aria-pressed="false">Follow article</button>
36
- <form data-comment-create>
37
- <label>Comment<textarea name="body" maxlength="5000" required></textarea></label>
38
- <p data-comment-reply-context hidden></p>
39
- <button type="submit">Post comment</button>
40
- <button type="button" data-cancel-reply hidden>Cancel reply</button>
41
- </form>
42
36
  </section>
37
+ {# The conversation is an island: it owns its own state, because a thread five deep with a page
38
+ cursor, an open reply form and an edit in progress is state, and hand-written DOM kept losing
39
+ track of it. Nothing here is needed by a search engine — comments have always arrived from the
40
+ API after load — so rendering them on the client costs nothing the article cares about. #}
41
+ <div data-gala-comments></div>
43
42
  <output data-engagement-status aria-live="polite"></output>
44
43
  </section>
45
44
  {% endmacro %}
@@ -27,29 +27,30 @@
27
27
  <script src="{{ '/assets/theme-mode.js' | url }}" defer></script>
28
28
  <script src="{{ '/assets/preferences.js' | url }}" defer></script>
29
29
  <script src="{{ '/assets/interactions.js' | url }}" defer></script>
30
+ <script type="module" src="{{ '/assets/engagement-comments.js' | url }}"></script>
30
31
  <script src="{{ '/assets/search.js' | url }}" defer></script>
31
32
  </head>
32
33
  <body>
33
34
  <header class="gala-site-header">
34
35
  <a class="gala-icon-control" href="{{ '/' | url }}" aria-label="Home" title="Home">
35
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M3 11.5 12 4l9 7.5v8a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/></svg>
36
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10.2 12 3.5l9 6.7"/><path d="M5.4 8.9V19a1.4 1.4 0 0 0 1.4 1.4h3.1v-5.1h4.2v5.1h3.1A1.4 1.4 0 0 0 18.6 19V8.9"/></svg>
36
37
  </a>
37
38
  <nav class="gala-site-header__actions" aria-label="Site controls">
38
39
  {% if site.contact.enabled %}<a class="gala-icon-control" href="{{ '/contact/' | url }}" aria-label="Contact author" title="Contact">
39
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M3 5h18v14H3V5Zm2 2v.4l7 5 7-5V7H5Zm14 10V9.8l-7 5-7-5V17h14Z"/></svg>
40
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5.4" width="18" height="13.2" rx="2.2"/><path d="m3.9 7.3 7.2 5.1a1.6 1.6 0 0 0 1.8 0l7.2-5.1"/></svg>
40
41
  </a>{% endif %}
41
42
  <button class="gala-icon-control" type="button" data-theme-mode-toggle aria-label="Color mode: system. Activate for light." title="Appearance">
42
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3a9 9 0 1 0 0 18V3Zm0 2v14a7 7 0 0 1 0-14Z"/></svg>
43
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20.1 13.6A8.2 8.2 0 1 1 10.4 3.9a6.4 6.4 0 0 0 9.7 9.7Z"/></svg>
43
44
  <span class="gala-visually-hidden" data-theme-mode-label>Theme: system</span>
44
45
  </button>
45
46
  <button class="gala-icon-control" type="button" data-open-dialog="gala-settings-dialog" aria-label="Settings" title="Settings">
46
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M10.6 2h2.8l.7 2.2 1.5.9 2.3-.5 1.4 2.4-1.6 1.7v1.7l1.6 1.7-1.4 2.4-2.3-.5-1.5.9-.7 2.2h-2.8l-.7-2.2-1.5-.9-2.3.5-1.4-2.4 1.6-1.7V8.7L4.7 7l1.4-2.4 2.3.5 1.5-.9.7-2.2Zm1.4 6.5a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg>
47
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="2.9"/><path d="M19.3 14.6a1.5 1.5 0 0 0 .3 1.7l.1.1a1.8 1.8 0 1 1-2.6 2.6l-.1-.1a1.5 1.5 0 0 0-2.5 1.1v.3a1.8 1.8 0 0 1-3.6 0v-.2a1.5 1.5 0 0 0-2.6-1.1l-.1.1a1.8 1.8 0 1 1-2.6-2.6l.1-.1a1.5 1.5 0 0 0-1.1-2.5h-.3a1.8 1.8 0 0 1 0-3.6h.2a1.5 1.5 0 0 0 1.1-2.6l-.1-.1a1.8 1.8 0 1 1 2.6-2.6l.1.1a1.5 1.5 0 0 0 1.7.3h.1a1.5 1.5 0 0 0 .9-1.4v-.3a1.8 1.8 0 0 1 3.6 0v.2a1.5 1.5 0 0 0 2.5 1.1l.1-.1a1.8 1.8 0 1 1 2.6 2.6l-.1.1a1.5 1.5 0 0 0-.3 1.7v.1a1.5 1.5 0 0 0 1.4.9h.3a1.8 1.8 0 0 1 0 3.6h-.2a1.5 1.5 0 0 0-1.4.9Z"/></svg>
47
48
  </button>
48
49
  <button class="gala-icon-control" type="button" data-open-dialog="gala-search-dialog" aria-label="Search" title="Search">
49
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M10.5 3a7.5 7.5 0 1 0 4.7 13.3l4.8 4.8 1.4-1.4-4.8-4.8A7.5 7.5 0 0 0 10.5 3Zm0 2a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Z"/></svg>
50
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m20.3 20.3-4.4-4.4"/></svg>
50
51
  </button>
51
52
  <button class="gala-icon-control" type="button" data-open-dialog="gala-account-dialog" data-user-control aria-label="Sign in or view account" title="Account">
52
- <svg aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9Zm0 11c-5 0-8 2.5-8 5.5V21h16v-1.5C20 16.5 17 14 12 14Z"/></svg>
53
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 20.4v-1.7a3.9 3.9 0 0 0-3.9-3.9H8.9A3.9 3.9 0 0 0 5 18.7v1.7"/><circle cx="12" cy="7.6" r="3.9"/></svg>
53
54
  </button>
54
55
  </nav>
55
56
  </header>
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The conversation under an article.
3
+ *
4
+ * Built as an island rather than by hand-writing DOM, because the state here is real: a thread
5
+ * five deep, a page of roots with more behind a cursor, a reply form belonging to one comment, an
6
+ * edit in progress, and a reader who may sign in halfway through. The imperative version rebuilt
7
+ * the whole region from a fresh fetch after every write, which is how a posted comment could take
8
+ * a browser cache's word for it and not appear at all — and a comment that is not rendered has no
9
+ * Reply button, so the thread died with it.
10
+ *
11
+ * The article itself is never touched. Eleventy renders the post and everything search engines
12
+ * read; this mounts only into the comments container, which was always filled from the API and so
13
+ * has nothing to lose by being rendered here.
14
+ */
15
+ import { h, render } from './vendor/preact.js';
16
+ import { useCallback, useEffect, useMemo, useState } from './vendor/hooks.js';
17
+ import htm from './vendor/htm.js';
18
+ // Importing the signals integration is what lets a component re-render when it reads `.value`.
19
+ import './vendor/signals.js';
20
+ import {
21
+ engagementErrorMessage, requestSignIn, sendEngagementWrite, sessionUser,
22
+ } from './engagement-transport.js';
23
+
24
+ const html = htm.bind(h);
25
+
26
+ /** The server's own limit. A reply deeper than this is refused, so it is not offered. */
27
+ const MAXIMUM_DEPTH = 5;
28
+
29
+ const isoTime = (value) => {
30
+ const at = Date.parse(value);
31
+ return Number.isFinite(at) ? new Date(at).toLocaleDateString() : '';
32
+ };
33
+
34
+ /** Roots in order, each followed by its own descendants, from one flat API page. */
35
+ function toThreads(items) {
36
+ const byParent = new Map();
37
+ for (const item of items) {
38
+ const key = item.parentCommentId ?? '';
39
+ if (!byParent.has(key)) byParent.set(key, []);
40
+ byParent.get(key).push(item);
41
+ }
42
+ const build = (parentId, depth) => (byParent.get(parentId) ?? []).map((comment) => ({
43
+ ...comment,
44
+ depth,
45
+ replies: build(comment.commentId, depth + 1),
46
+ }));
47
+ return build('', 0);
48
+ }
49
+
50
+ function Comment({ comment, reader, onReply, onEdit, onDelete, busy }) {
51
+ const [replying, setReplying] = useState(false);
52
+ const [editing, setEditing] = useState(false);
53
+ const [draft, setDraft] = useState('');
54
+ const mine = !comment.deleted && reader && comment.author?.userId === reader.id;
55
+ const canReply = !comment.deleted && comment.depth < MAXIMUM_DEPTH;
56
+
57
+ const submitReply = async (event) => {
58
+ event.preventDefault();
59
+ if (!draft.trim()) return;
60
+ const posted = await onReply(comment.commentId, draft.trim());
61
+ if (posted) { setDraft(''); setReplying(false); }
62
+ };
63
+
64
+ const submitEdit = async (event) => {
65
+ event.preventDefault();
66
+ if (!draft.trim()) return;
67
+ const saved = await onEdit(comment.commentId, draft.trim());
68
+ if (saved) setEditing(false);
69
+ };
70
+
71
+ return html`
72
+ <li class="gala-comment" data-comment-id=${comment.commentId} data-depth=${comment.depth}>
73
+ <p class="gala-comment__meta">
74
+ <strong>${comment.author?.displayName ?? '[deleted]'}</strong>
75
+ ${comment.createdAt && html`<time datetime=${comment.createdAt}>${isoTime(comment.createdAt)}</time>`}
76
+ ${comment.editedAt && html`<span class="gala-comment__edited">edited</span>`}
77
+ ${comment.pending && html`<span class="gala-comment__pending">sending…</span>`}
78
+ </p>
79
+
80
+ ${editing
81
+ ? html`<form class="gala-comment__form" onSubmit=${submitEdit}>
82
+ <label class="gala-visually-hidden" for=${`edit-${comment.commentId}`}>Edit your comment</label>
83
+ <textarea id=${`edit-${comment.commentId}`} rows="3" value=${draft}
84
+ onInput=${(e) => setDraft(e.target.value)}></textarea>
85
+ <div class="gala-comment__controls">
86
+ <button type="submit" disabled=${busy}>Save</button>
87
+ <button type="button" onClick=${() => setEditing(false)}>Cancel</button>
88
+ </div>
89
+ </form>`
90
+ : html`<p class="gala-comment__body">${comment.deleted ? '[deleted]' : comment.body}</p>`}
91
+
92
+ ${!comment.deleted && html`
93
+ <div class="gala-comment-actions">
94
+ ${canReply && html`<button type="button" data-reply-comment=${comment.commentId}
95
+ onClick=${() => { setReplying((open) => !open); setDraft(''); }}>Reply</button>`}
96
+ ${mine && html`<button type="button" data-edit-comment=${comment.commentId}
97
+ onClick=${() => { setEditing(true); setDraft(comment.body ?? ''); }}>Edit</button>`}
98
+ ${mine && html`<button type="button" data-delete-comment=${comment.commentId}
99
+ onClick=${() => onDelete(comment.commentId)} disabled=${busy}>Delete</button>`}
100
+ </div>`}
101
+
102
+ ${replying && html`
103
+ <form class="gala-comment__form" onSubmit=${submitReply}>
104
+ <label class="gala-visually-hidden" for=${`reply-${comment.commentId}`}>
105
+ Reply to ${comment.author?.displayName ?? 'this comment'}
106
+ </label>
107
+ <textarea id=${`reply-${comment.commentId}`} rows="3" value=${draft} autofocus
108
+ placeholder="Write a reply" onInput=${(e) => setDraft(e.target.value)}></textarea>
109
+ <div class="gala-comment__controls">
110
+ <button type="submit" disabled=${busy || !draft.trim()}>Post reply</button>
111
+ <button type="button" onClick=${() => setReplying(false)}>Cancel</button>
112
+ </div>
113
+ </form>`}
114
+
115
+ ${comment.replies.length > 0 && html`
116
+ <ol class="gala-comment-replies">
117
+ ${comment.replies.map((reply) => html`
118
+ <${Comment} key=${reply.commentId} comment=${reply} reader=${reader} busy=${busy}
119
+ onReply=${onReply} onEdit=${onEdit} onDelete=${onDelete} />`)}
120
+ </ol>`}
121
+ </li>`;
122
+ }
123
+
124
+ function Comments({ endpoint }) {
125
+ // Reading `.value` during render subscribes this component to the signal, so a sign-in
126
+ // anywhere on the page re-renders the conversation without anything having to tell it.
127
+ const reader = sessionUser.value;
128
+ const [items, setItems] = useState([]);
129
+ const [cursor, setCursor] = useState(null);
130
+ const [total, setTotal] = useState(0);
131
+ const [status, setStatus] = useState('');
132
+ const [busy, setBusy] = useState(false);
133
+ const [draft, setDraft] = useState('');
134
+ const [loading, setLoading] = useState(true);
135
+
136
+ /* `fresh` skips the browser cache. The endpoint is `max-age=60, public`, which is right for a
137
+ cold visit and wrong straight after a write: the browser would answer from its own cache with
138
+ the state from before the reader wrote anything. */
139
+ const load = useCallback(async (nextCursor = '', { append = false, fresh = false } = {}) => {
140
+ const url = new URL(endpoint);
141
+ if (nextCursor) url.searchParams.set('commentsCursor', nextCursor);
142
+ const response = await fetch(url, {
143
+ headers: { Accept: 'application/json' },
144
+ credentials: 'omit',
145
+ cache: fresh ? 'no-store' : 'default',
146
+ });
147
+ if (!response.ok) throw new Error(`Comments returned HTTP ${response.status}`);
148
+ const payload = await response.json();
149
+ const page = payload?.data?.comments;
150
+ if (!page) throw new TypeError('Comment page is invalid');
151
+ setItems((current) => (append ? [...current, ...page.items] : page.items));
152
+ setCursor(page.nextCursor ?? null);
153
+ if (Number.isSafeInteger(page.totalCount)) setTotal(page.totalCount);
154
+ return page;
155
+ }, [endpoint]);
156
+
157
+ useEffect(() => {
158
+ let live = true;
159
+ load().catch(() => { if (live) setStatus('Comments are temporarily unavailable.'); })
160
+ .finally(() => { if (live) setLoading(false); });
161
+ return () => { live = false; };
162
+ }, [load]);
163
+
164
+ // Signing in changes what the reader may do, not what the article says, so only re-read when
165
+ // the reader actually changes.
166
+ useEffect(() => {
167
+ if (!reader) return undefined;
168
+ load('', { fresh: true }).catch(() => {});
169
+ return undefined;
170
+ }, [reader?.id]);
171
+
172
+ const write = useCallback(async (operation, payload, optimistic) => {
173
+ if (!reader) { requestSignIn({ kind: 'comment' }); return false; }
174
+ setBusy(true);
175
+ setStatus('');
176
+ if (optimistic) setItems((current) => [optimistic, ...current]);
177
+ try {
178
+ await sendEngagementWrite(operation, payload);
179
+ /* Re-read past the cache so what is on screen is what the server actually holds — the
180
+ optimistic row above is a promise to the reader, not a source of truth. */
181
+ await load('', { fresh: true });
182
+ return true;
183
+ } catch (error) {
184
+ if (optimistic) {
185
+ setItems((current) => current.filter((item) => item.commentId !== optimistic.commentId));
186
+ }
187
+ setStatus(engagementErrorMessage(error.message));
188
+ return false;
189
+ } finally {
190
+ setBusy(false);
191
+ }
192
+ }, [reader?.id, load]);
193
+
194
+ const articleId = useMemo(() => {
195
+ const match = /\/v1\/articles\/([^/]+)\/engagement/.exec(endpoint);
196
+ return match ? match[1] : '';
197
+ }, [endpoint]);
198
+
199
+ const post = async (event) => {
200
+ event.preventDefault();
201
+ const body = draft.trim();
202
+ if (!body) return;
203
+ const posted = await write('comment.create', { articleId, body }, {
204
+ commentId: `pending-${crypto.randomUUID()}`,
205
+ parentCommentId: null,
206
+ body,
207
+ depth: 0,
208
+ createdAt: new Date().toISOString(),
209
+ author: { userId: reader?.id, displayName: reader?.displayName },
210
+ pending: true,
211
+ });
212
+ if (posted) setDraft('');
213
+ };
214
+
215
+ const reply = (parentCommentId, body) =>
216
+ write('comment.create', { articleId, parentCommentId, body });
217
+ const edit = (commentId, body) => write('comment.edit', { articleId, commentId, body });
218
+ const remove = (commentId) => write('comment.delete', { articleId, commentId });
219
+
220
+ const threads = useMemo(() => toThreads(items), [items]);
221
+
222
+ return html`
223
+ <section class="gala-comments-island" aria-label="Comments">
224
+ <h2 class="gala-comments__heading">${total === 1 ? '1 comment' : `${total} comments`}</h2>
225
+
226
+ ${reader
227
+ ? html`<form class="gala-comment__form" onSubmit=${post}>
228
+ <label class="gala-visually-hidden" for="gala-new-comment">Add a comment</label>
229
+ <textarea id="gala-new-comment" rows="3" value=${draft} placeholder="Add a comment"
230
+ onInput=${(e) => setDraft(e.target.value)}></textarea>
231
+ <button type="submit" disabled=${busy || !draft.trim()}>Post comment</button>
232
+ </form>`
233
+ : html`<p class="gala-comments__prompt">
234
+ <button type="button" onClick=${() => requestSignIn({ kind: 'comment' })}>
235
+ Sign in to join the conversation
236
+ </button>
237
+ </p>`}
238
+
239
+ ${status && html`<p class="gala-comments__status" role="status">${status}</p>`}
240
+
241
+ ${loading
242
+ ? html`<p class="gala-comments__status" role="status">Loading comments…</p>`
243
+ : threads.length === 0
244
+ ? html`<p class="gala-comments__empty">No comments yet.</p>`
245
+ : html`<ol class="gala-comments">
246
+ ${threads.map((comment) => html`
247
+ <${Comment} key=${comment.commentId} comment=${comment} reader=${reader}
248
+ busy=${busy} onReply=${reply} onEdit=${edit} onDelete=${remove} />`)}
249
+ </ol>`}
250
+
251
+ ${cursor && html`
252
+ <button type="button" class="gala-comments__more" disabled=${busy}
253
+ onClick=${() => { setBusy(true); load(cursor, { append: true })
254
+ .catch(() => setStatus('Could not load more comments.'))
255
+ .finally(() => setBusy(false)); }}>
256
+ Show more comments
257
+ </button>`}
258
+ </section>`;
259
+ }
260
+
261
+ for (const mount of document.querySelectorAll('[data-gala-comments]')) {
262
+ const endpoint = mount.closest('[data-engagement-url]')?.dataset.engagementUrl;
263
+ if (endpoint) render(html`<${Comments} endpoint=${endpoint} />`, mount);
264
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The reader's identity, and the only way to write on their behalf.
3
+ *
4
+ * Both belong to the account frame, which is served by the API and runs on its origin. The
5
+ * reader's token lives there and nowhere else — this page never sees it, and never should: the
6
+ * page is the writer's own site, and a reader's credentials are not the writer's to hold. So a
7
+ * write is a request posted into the frame, and the frame answers with a result.
8
+ *
9
+ * `sessionUser` is a signal rather than a variable because more than one island depends on it and
10
+ * they must not each keep their own copy going stale in its own way.
11
+ */
12
+ import { signal } from './vendor/signals.js';
13
+
14
+ export const sessionUser = signal(null);
15
+
16
+ const sessionFrame = document.querySelector('[data-gala-session-frame]');
17
+ const frameOrigin = sessionFrame ? new URL(sessionFrame.src).origin : null;
18
+ const pending = new Map();
19
+
20
+ /** Readable text for the codes the API actually returns. */
21
+ export function engagementErrorMessage(code) {
22
+ if (code === 'AUTHENTICATION_REQUIRED' || code === 'INVALID_BEARER_TOKEN'
23
+ || code === 'REAUTHENTICATION_REQUIRED') return 'Sign in again to continue.';
24
+ if (code === 'ENGAGEMENT_RATE_LIMITED') return 'You are posting too quickly; try again shortly.';
25
+ if (code === 'CONTACT_RATE_LIMITED') return 'You are sending too quickly; try again later.';
26
+ if (code === 'RESOURCE_NOT_FOUND') return 'That item is no longer available.';
27
+ if (code === 'INVALID_ENGAGEMENT_WRITE' || code === 'INVALID_CONTACT_SUBMISSION'
28
+ || code === 'INVALID_REQUEST') return 'Check your entry and try again.';
29
+ if (code === 'ACCESS_DENIED') return 'That action is not available for this account.';
30
+ if (code === 'IDEMPOTENCY_CONFLICT' || code === 'ENGAGEMENT_STATE_CONFLICT') {
31
+ return 'The item changed; reload and try again.';
32
+ }
33
+ return 'The action could not be completed. Try again.';
34
+ }
35
+
36
+ export function sendEngagementWrite(operation, payload) {
37
+ if (!sessionFrame || !sessionUser.value) {
38
+ return Promise.reject(new Error('AUTHENTICATION_REQUIRED'));
39
+ }
40
+ const requestId = crypto.randomUUID();
41
+ return new Promise((resolve, reject) => {
42
+ pending.set(requestId, { resolve, reject });
43
+ sessionFrame.contentWindow.postMessage(
44
+ { type: 'gala-engagement-write', requestId, operation, payload }, frameOrigin);
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Asks the page to start a sign-in and to put the reader back where they were afterwards.
50
+ *
51
+ * Dispatched as an event rather than called directly: opening the window and replaying the
52
+ * interrupted intent belongs to the page, and an island should not have to know how that works.
53
+ */
54
+ export function requestSignIn(intent) {
55
+ document.dispatchEvent(new CustomEvent('gala-request-sign-in', { detail: intent }));
56
+ }
57
+
58
+ if (sessionFrame) {
59
+ window.addEventListener('message', (event) => {
60
+ if (event.origin !== frameOrigin || event.source !== sessionFrame.contentWindow) return;
61
+ if (event.data?.type === 'gala-engagement-result') {
62
+ const waiting = pending.get(event.data.requestId);
63
+ if (!waiting) return;
64
+ pending.delete(event.data.requestId);
65
+ if (event.data.ok === true) waiting.resolve(event.data.result);
66
+ else waiting.reject(new Error(event.data.error?.code || 'ENGAGEMENT_WRITE_FAILED'));
67
+ return;
68
+ }
69
+ if (event.data?.type !== 'gala-session') return;
70
+ const user = event.data.user;
71
+ sessionUser.value = user && typeof user.id === 'string' ? user : null;
72
+ });
73
+ }
@@ -70,61 +70,6 @@ document.addEventListener('click', async (event) => {
70
70
  return;
71
71
  }
72
72
 
73
- const reply = event.target.closest('[data-reply-comment]');
74
- if (reply) {
75
- const region = reply.closest('[data-engagement-url]');
76
- const form = region?.querySelector('[data-comment-create]');
77
- if (!form) return;
78
- form.dataset.parentCommentId = reply.dataset.replyComment;
79
- const context = form.querySelector('[data-comment-reply-context]');
80
- if (context) {
81
- context.hidden = false;
82
- context.textContent = `Replying to ${reply.dataset.replyAuthor || 'comment'}.`;
83
- }
84
- const cancel = form.querySelector('[data-cancel-reply]');
85
- if (cancel) cancel.hidden = false;
86
- form.querySelector('textarea')?.focus();
87
- return;
88
- }
89
-
90
- const cancelReply = event.target.closest('[data-cancel-reply]');
91
- if (cancelReply) {
92
- clearReply(cancelReply.closest('[data-comment-create]'));
93
- return;
94
- }
95
-
96
- const edit = event.target.closest('[data-edit-comment]');
97
- if (edit) {
98
- showCommentEditor(edit);
99
- return;
100
- }
101
-
102
- const cancelEdit = event.target.closest('[data-cancel-comment-edit]');
103
- if (cancelEdit) {
104
- cancelEdit.closest('[data-comment-editor]')?.remove();
105
- return;
106
- }
107
-
108
- const remove = event.target.closest('[data-delete-comment]');
109
- if (remove) {
110
- const region = remove.closest('[data-engagement-url]');
111
- if (!region) return;
112
- await mutateEngagement(region, 'comment.delete', {
113
- articleId: region.dataset.articleId,
114
- commentId: remove.dataset.deleteComment
115
- });
116
- return;
117
- }
118
-
119
- const moreComments = event.target.closest('[data-comments-cursor]');
120
- if (moreComments) {
121
- const region = moreComments.closest('[data-engagement-url]');
122
- if (!region) return;
123
- moreComments.disabled = true;
124
- await refreshEngagement(region, moreComments.dataset.commentsCursor, true);
125
- return;
126
- }
127
-
128
73
  const share = event.target.closest('[data-copy-url]');
129
74
  if (share) {
130
75
  const value = share.dataset.copyUrl;
@@ -187,57 +132,6 @@ function engagementCounts(data) {
187
132
  ];
188
133
  }
189
134
 
190
- function commentList(items, currentUser) {
191
- const roots = document.createElement('ol');
192
- roots.className = 'gala-comments';
193
- const nodes = new Map();
194
- for (const comment of items) {
195
- const item = document.createElement('li');
196
- item.dataset.commentId = comment.commentId;
197
- const author = comment.author?.displayName ?? '[deleted]';
198
- item.append(textElement('strong', author));
199
- const body = textElement('p', comment.deleted ? '[deleted]' : comment.body);
200
- body.dataset.commentBody = '';
201
- item.append(body);
202
- if (!comment.deleted && currentUser) {
203
- const actions = document.createElement('div');
204
- actions.className = 'gala-comment-actions';
205
- const reply = textElement('button', 'Reply');
206
- reply.type = 'button';
207
- reply.dataset.replyComment = comment.commentId;
208
- reply.dataset.replyAuthor = author;
209
- actions.append(reply);
210
- if (comment.author?.userId === currentUser.id) {
211
- const edit = textElement('button', 'Edit');
212
- edit.type = 'button';
213
- edit.dataset.editComment = comment.commentId;
214
- const remove = textElement('button', 'Delete');
215
- remove.type = 'button';
216
- remove.dataset.deleteComment = comment.commentId;
217
- actions.append(edit, remove);
218
- }
219
- item.append(actions);
220
- }
221
- nodes.set(comment.commentId, item);
222
- }
223
- for (const comment of items) {
224
- const item = nodes.get(comment.commentId);
225
- const parent = comment.parentCommentId ? nodes.get(comment.parentCommentId) : null;
226
- if (!parent) {
227
- roots.append(item);
228
- continue;
229
- }
230
- let replies = parent.querySelector(':scope > ol');
231
- if (!replies) {
232
- replies = document.createElement('ol');
233
- replies.className = 'gala-comment-replies';
234
- parent.append(replies);
235
- }
236
- replies.append(item);
237
- }
238
- return roots;
239
- }
240
-
241
135
  function renderEngagement(region, payload, appendComments = false) {
242
136
  const data = payload?.data;
243
137
  if (!data || typeof data !== 'object') throw new TypeError('Engagement data is invalid');
@@ -267,60 +161,19 @@ function renderEngagement(region, payload, appendComments = false) {
267
161
  live.append(summary);
268
162
  }
269
163
 
270
- if (Array.isArray(data.comments?.items) && data.comments.items.length > 0) {
271
- live.append(commentList(data.comments.items, sessionUser));
272
- }
273
- live.querySelector('[data-comments-cursor]')?.remove();
274
- if (data.comments?.nextCursor) {
275
- const more = textElement('button', 'Load more comments');
276
- more.type = 'button';
277
- more.dataset.commentsCursor = data.comments.nextCursor;
278
- live.append(more);
279
- }
280
164
  region.querySelector('[data-engagement-snapshot]')?.remove();
281
165
  region.querySelector('.gala-engagement__placeholder')?.remove();
282
166
  }
283
167
 
284
- function clearReply(form) {
285
- if (!form) return;
286
- delete form.dataset.parentCommentId;
287
- const context = form.querySelector('[data-comment-reply-context]');
288
- if (context) {
289
- context.hidden = true;
290
- context.textContent = '';
291
- }
292
- const cancel = form.querySelector('[data-cancel-reply]');
293
- if (cancel) cancel.hidden = true;
294
- }
295
-
296
- function showCommentEditor(control) {
297
- const item = control.closest('[data-comment-id]');
298
- if (!item || item.querySelector('[data-comment-editor]')) return;
299
- const form = document.createElement('form');
300
- form.dataset.commentEditor = control.dataset.editComment;
301
- const textarea = document.createElement('textarea');
302
- textarea.name = 'body';
303
- textarea.maxLength = 5000;
304
- textarea.required = true;
305
- textarea.value = item.querySelector('[data-comment-body]')?.textContent ?? '';
306
- const save = textElement('button', 'Save');
307
- save.type = 'submit';
308
- const cancel = textElement('button', 'Cancel');
309
- cancel.type = 'button';
310
- cancel.dataset.cancelCommentEdit = '';
311
- form.append(textarea, save, cancel);
312
- item.append(form);
313
- textarea.focus();
314
- }
315
-
316
- async function refreshEngagement(region, commentsCursor = '', appendComments = false) {
168
+ async function refreshEngagement(region, commentsCursor = '', appendComments = false, fresh = false) {
317
169
  const status = region.querySelector('[data-engagement-status]');
318
170
  try {
319
171
  const requestUrl = new URL(region.dataset.engagementUrl);
320
172
  if (commentsCursor) requestUrl.searchParams.set('commentsCursor', commentsCursor);
321
173
  const response = await fetch(requestUrl, {
322
174
  headers: { Accept: 'application/json' },
323
- credentials: 'omit'
175
+ credentials: 'omit',
176
+ cache: fresh ? 'no-store' : 'default'
324
177
  });
325
178
  if (!response.ok) throw new Error(`Engagement returned HTTP ${response.status}`);
326
179
  const payload = await response.json();
@@ -358,52 +211,14 @@ function recordView(region) {
358
211
  }).catch(() => {});
359
212
  }
360
213
 
361
- // Asked at the moment of intent: the reader reaches for the comment box, and only then are they
362
- // asked who they are never as a wall in front of something they have not decided to do.
363
- document.addEventListener('focusin', (event) => {
214
+ /*
215
+ * The comments island asks for a sign-in when the reader reaches for something that needs one.
216
+ * Opening the window and replaying the interrupted intent stays here, because that is a property
217
+ * of the page, not of any one island.
218
+ */
219
+ document.addEventListener('gala-request-sign-in', (event) => {
364
220
  if (sessionUser || pendingIntent) return;
365
- const field = event.target.closest('[data-comment-create] textarea, [data-comment-editor] textarea');
366
- if (!field) return;
367
- const region = field.closest('[data-engagement-url]');
368
- const form = field.closest('[data-comment-create], [data-comment-editor]');
369
- if (!region || !form) return;
370
- requestSession({ kind: 'comment', region,
371
- selector: `${form.matches('[data-comment-create]') ? '[data-comment-create]' : '[data-comment-editor]'} textarea`,
372
- field });
373
- });
374
-
375
- document.addEventListener('submit', async (event) => {
376
- const create = event.target.closest('[data-comment-create]');
377
- const editor = event.target.closest('[data-comment-editor]');
378
- if (!create && !editor) return;
379
- event.preventDefault();
380
- const form = create ?? editor;
381
- const region = form.closest('[data-engagement-url]');
382
- const body = form.elements.body?.value;
383
- if (!region || typeof body !== 'string' || body.trim() === '') return;
384
- if (!sessionUser) {
385
- return requestSession({ kind: 'comment', region,
386
- selector: `${create ? '[data-comment-create]' : '[data-comment-editor]'} textarea`,
387
- field: form.querySelector('textarea') });
388
- }
389
- if (create) {
390
- const saved = await mutateEngagement(region, 'comment.create', {
391
- articleId: region.dataset.articleId,
392
- parentCommentId: create.dataset.parentCommentId || null,
393
- body
394
- });
395
- if (!saved) return;
396
- create.reset();
397
- clearReply(create);
398
- } else {
399
- const saved = await mutateEngagement(region, 'comment.edit', {
400
- articleId: region.dataset.articleId,
401
- commentId: editor.dataset.commentEditor,
402
- body
403
- });
404
- if (!saved) return;
405
- editor.remove();
406
- }
221
+ requestSession({ kind: event.detail?.kind ?? 'comment' });
407
222
  });
408
223
 
409
224
  let sessionUser = null;
@@ -476,6 +291,23 @@ window.addEventListener('focus', () => {
476
291
  if (sessionUser && pendingIntent) resumeIntent();
477
292
  });
478
293
 
294
+ /*
295
+ * The sign-in window reports success to this page rather than to the account frame.
296
+ *
297
+ * It cannot reach the frame directly: a frame embedded in a publication has its storage — and its
298
+ * BroadcastChannel — partitioned away from a window running at the top level on the API's origin,
299
+ * so the session it wrote is invisible there. The signal carries no token; it only says a sign-in
300
+ * happened, and this asks the frame to look again. Without it the frame never learns the reader
301
+ * signed in, and every click asks them to sign in once more.
302
+ */
303
+ window.addEventListener('message', (event) => {
304
+ const frame = document.querySelector('[data-gala-session-frame]');
305
+ if (!frame || event.data?.type !== 'gala-session-established') return;
306
+ const apiOrigin = new URL(frame.src).origin;
307
+ if (event.origin !== apiOrigin) return;
308
+ frame.contentWindow?.postMessage({ type: 'gala-session-recheck' }, apiOrigin);
309
+ });
310
+
479
311
  document.querySelectorAll('[data-engagement-url]').forEach((region) => {
480
312
  refreshEngagement(region);
481
313
  recordView(region);
@@ -539,7 +371,8 @@ async function mutateEngagement(region, operation, payload) {
539
371
  if (status) status.textContent = 'Saving…';
540
372
  await sendEngagementWrite(operation, payload);
541
373
  if (status) status.textContent = 'Saved.';
542
- await refreshEngagement(region);
374
+ // The reader's own write must be visible to them at once, so this one bypasses the cache.
375
+ await refreshEngagement(region, '', false, true);
543
376
  return true;
544
377
  } catch (error) {
545
378
  if (status) status.textContent = engagementErrorMessage(error.message);
@@ -87,7 +87,9 @@ body {
87
87
  }
88
88
  .gala-icon-control:hover { background: var(--gala-color-surface); }
89
89
  .gala-icon-control:focus-visible { outline: 0.1875rem solid currentcolor; outline-offset: 0.1875rem; }
90
- .gala-icon-control svg { inline-size: 100%; block-size: 100%; fill: currentcolor; }
90
+ /* Stroked, not filled: the icons are drawn on one 24px grid at a single weight, so they read as
91
+ a set rather than as six unrelated silhouettes. */
92
+ .gala-icon-control svg { inline-size: 100%; block-size: 100%; fill: none; stroke: currentcolor; }
91
93
 
92
94
  .gala-dialog {
93
95
  inline-size: min(42rem, calc(100% - 2rem));
@@ -0,0 +1,2 @@
1
+ import{options as n}from"./preact.js";var t,r,u,i,o=0,f=[],c=n,e=c.__b,a=c.__r,v=c.diffed,l=c.__c,m=c.unmount,p=c.__;function s(n,t){c.__h&&c.__h(r,n,o||t),o=0;var u=r.__H||(r.__H={__:[],__h:[]});return n>=u.__.length&&u.__.push({}),u.__[n]}function d(n){return o=1,y(D,n)}function y(n,u,i){var o=s(t++,2);if(o.t=n,!o.__c&&(o.__=[i?i(u):D(void 0,u),function(n){var t=o.__N?o.__N[0]:o.__[0],r=o.t(t,n);t!==r&&(o.__N=[r,o.__[1]],o.__c.setState({}))}],o.__c=r,!r.__f)){var f=function(n,t,r){if(!o.__c.__H)return!0;var u=!1,i=o.__c.props!==n;if(o.__c.__H.__.some(function(n){if(n.__N){u=!0;var t=n.__[0];n.__=n.__N,n.__N=void 0,t!==n.__[0]&&(i=!0)}}),c){var f=c.call(this,n,t,r);return u?f||i:f}return!u||i};r.__f=!0;var c=r.shouldComponentUpdate,e=r.componentWillUpdate;r.componentWillUpdate=function(n,t,r){if(this.__e){var u=c;c=void 0,f(n,t,r),c=u}e&&e.call(this,n,t,r)},r.shouldComponentUpdate=f}return o.__N||o.__}function h(n,u){var i=s(t++,3);!c.__s&&C(i.__H,u)&&(i.__=n,i.u=u,r.__H.__h.push(i))}function _(n,u){var i=s(t++,4);!c.__s&&C(i.__H,u)&&(i.__=n,i.u=u,r.__h.push(i))}function A(n){return o=5,T(function(){return{current:n}},[])}function F(n,t,r){o=6,_(function(){if("function"==typeof n){var r=n(t());return function(){n(null),r&&"function"==typeof r&&r()}}if(n)return n.current=t(),function(){return n.current=null}},null==r?r:r.concat(n))}function T(n,r){var u=s(t++,7);return C(u.__H,r)&&(u.__=n(),u.__H=r,u.__h=n),u.__}function q(n,t){return o=8,T(function(){return n},t)}function x(n){var u=r.context[n.__c],i=s(t++,9);return i.c=n,u?(null==i.__&&(i.__=!0,u.sub(r)),u.props.value):n.__}function P(n,t){c.useDebugValue&&c.useDebugValue(t?t(n):n)}function b(n){var u=s(t++,10),i=d();return u.__=n,r.componentDidCatch||(r.componentDidCatch=function(n,t){u.__&&u.__(n,t),i[1](n)}),[i[0],function(){i[1](void 0)}]}function g(){var n=s(t++,11);if(!n.__){for(var u=r.__v;null!==u&&!u.__m&&null!==u.__;)u=u.__;var i=u.__m||(u.__m=[0,0]);n.__="P"+i[0]+"-"+i[1]++}return n.__}function j(){for(var n;n=f.shift();){var t=n.__H;if(n.__P&&t)try{t.__h.some(z),t.__h.some(B),t.__h=[]}catch(r){t.__h=[],c.__e(r,n.__v)}}}c.__b=function(n){r=null,e&&e(n)},c.__=function(n,t){n&&t.__k&&t.__k.__m&&(n.__m=t.__k.__m),p&&p(n,t)},c.__r=function(n){a&&a(n),t=0;var i=(r=n.__c).__H;i&&(u===r?(i.__h=[],r.__h=[],i.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(i.__h.some(z),i.__h.some(B),i.__h=[],t=0)),u=r},c.diffed=function(n){v&&v(n);var t=n.__c;t&&t.__H&&(t.__H.__h.length&&(1!==f.push(t)&&i===c.requestAnimationFrame||((i=c.requestAnimationFrame)||w)(j)),t.__H.__.some(function(n){n.u&&(n.__H=n.u,n.u=void 0)})),u=r=null},c.__c=function(n,t){t.some(function(n){try{n.__h.some(z),n.__h=n.__h.filter(function(n){return!n.__||B(n)})}catch(r){t.some(function(n){n.__h&&(n.__h=[])}),t=[],c.__e(r,n.__v)}}),l&&l(n,t)},c.unmount=function(n){m&&m(n);var t,r=n.__c;r&&r.__H&&(r.__H.__.some(function(n){try{z(n)}catch(n){t=n}}),r.__H=void 0,t&&c.__e(t,r.__v))};var k="function"==typeof requestAnimationFrame;function w(n){var t,r=function(){clearTimeout(u),k&&cancelAnimationFrame(t),setTimeout(n)},u=setTimeout(r,35);k&&(t=requestAnimationFrame(r))}function z(n){var t=r,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),r=t}function B(n){var t=r;n.__c=n.__(),r=t}function C(n,t){return!n||n.length!==t.length||t.some(function(t,r){return t!==n[r]})}function D(n,t){return"function"==typeof t?t(n):t}export{q as useCallback,x as useContext,P as useDebugValue,h as useEffect,b as useErrorBoundary,g as useId,F as useImperativeHandle,_ as useLayoutEffect,T as useMemo,y as useReducer,A as useRef,d as useState};
2
+
@@ -0,0 +1,2 @@
1
+ var n=function(t,s,r,e){var u;s[0]=0;for(var h=1;h<s.length;h++){var p=s[h++],a=s[h]?(s[0]|=p?1:2,r[s[h++]]):s[++h];3===p?e[0]=a:4===p?e[1]=Object.assign(e[1]||{},a):5===p?(e[1]=e[1]||{})[s[++h]]=a:6===p?e[1][s[++h]]+=a+"":p?(u=t.apply(a,n(t,a,r,["",null])),e.push(u),a[0]?s[0]|=2:(s[h-2]=0,s[h]=u)):e.push(a)}return e},t=new Map;export default function(s){var r=t.get(this);return r||(r=new Map,t.set(this,r)),(r=n(this,r.get(s)||(r.set(s,r=function(n){for(var t,s,r=1,e="",u="",h=[0],p=function(n){1===r&&(n||(e=e.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?h.push(0,n,e):3===r&&(n||e)?(h.push(3,n,e),r=2):2===r&&"..."===e&&n?h.push(4,n,0):2===r&&e&&!n?h.push(5,0,!0,e):r>=5&&((e||!n&&5===r)&&(h.push(r,0,e,s),r=6),n&&(h.push(r,n,0,s),r=6)),e=""},a=0;a<n.length;a++){a&&(1===r&&p(),p(a));for(var l=0;l<n[a].length;l++)t=n[a][l],1===r?"<"===t?(p(),h=[h],r=3):e+=t:4===r?"--"===e&&">"===t?(r=1,e=""):e=t+e[0]:u?t===u?u="":e+=t:'"'===t||"'"===t?u=t:">"===t?(p(),r=1):r&&("="===t?(r=5,s=e,e=""):"/"===t&&(r<5||">"===n[a][l+1])?(p(),3===r&&(h=h[0]),r=h,(h=h[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(p(),r=2):e+=t),3===r&&"!--"===e&&(r=4,h=h[0])}return p(),h}(s)),r),arguments,[])).length>1?r:r[0]}
2
+
@@ -0,0 +1,2 @@
1
+ var n,l,u,t,i,r,o,e,f,c,a,s,h,p,v,y,d={},w=[],_=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,g=Array.isArray;function m(n,l){for(var u in l)n[u]=l[u];return n}function b(n){n&&n.parentNode&&n.parentNode.removeChild(n)}function k(l,u,t){var i,r,o,e={};for(o in u)"key"==o?i=u[o]:"ref"==o?r=u[o]:e[o]=u[o];if(arguments.length>2&&(e.children=arguments.length>3?n.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps)void 0===e[o]&&(e[o]=l.defaultProps[o]);return x(l,e,i,r,null)}function x(n,t,i,r,o){var e={type:n,props:t,key:i,ref:r,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:null==o?++u:o,__i:-1,__u:0};return null==o&&null!=l.vnode&&l.vnode(e),e}function M(){return{current:null}}function S(n){return n.children}function C(n,l){this.props=n,this.context=l}function $(n,l){if(null==l)return n.__?$(n.__,n.__i+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return"function"==typeof n.type?$(n):null}function I(n){if(n.__P&&n.__d){var u=n.__v,t=u.__e,i=[],r=[],o=m({},u);o.__v=u.__v+1,l.vnode&&l.vnode(o),q(n.__P,o,u,n.__n,n.__P.namespaceURI,32&u.__u?[t]:null,i,null==t?$(u):t,!!(32&u.__u),r),o.__v=u.__v,o.__.__k[o.__i]=o,D(i,o,r),u.__e=u.__=null,o.__e!=t&&P(o)}}function P(n){if(null!=(n=n.__)&&null!=n.__c)return n.__e=n.__c.base=null,n.__k.some(function(l){if(null!=l&&null!=l.__e)return n.__e=n.__c.base=l.__e}),P(n)}function A(n){(!n.__d&&(n.__d=!0)&&i.push(n)&&!H.__r++||r!=l.debounceRendering)&&((r=l.debounceRendering)||o)(H)}function H(){try{for(var n,l=1;i.length;)i.length>l&&i.sort(e),n=i.shift(),l=i.length,I(n)}finally{i.length=H.__r=0}}function L(n,l,u,t,i,r,o,e,f,c,a){var s,h,p,v,y,_,g=t&&t.__k||w,m=l.length;for(f=T(u,l,g,f,m),s=0;s<m;s++)null!=(p=u.__k[s])&&(h=-1!=p.__i&&g[p.__i]||d,p.__i=s,_=q(n,p,h,i,r,o,e,f,c,a),v=p.__e,p.ref&&h.ref!=p.ref&&(h.ref&&J(h.ref,null,p),a.push(p.ref,p.__c||v,p)),null==y&&null!=v&&(y=v),4&p.__u?(f=j(p,f,n),h.__e&&(h.__e=null)):"function"==typeof p.type&&void 0!==_?f=_:v&&(f=v.nextSibling),p.__u&=-7);return u.__e=y,f}function T(n,l,u,t,i){var r,o,e,f,c,a=u.length,s=a,h=0;for(n.__k=new Array(i),r=0;r<i;r++)null!=(o=l[r])&&"boolean"!=typeof o&&"function"!=typeof o?("string"==typeof o||"number"==typeof o||"bigint"==typeof o||o.constructor==String?o=n.__k[r]=x(null,o,null,null,null):g(o)?o=n.__k[r]=x(S,{children:o},null,null,null):void 0===o.constructor&&o.__b>0?o=n.__k[r]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):n.__k[r]=o,f=r+h,o.__=n,o.__b=n.__b+1,e=null,-1!=(c=o.__i=O(o,u,f,s))&&(s--,(e=u[c])&&(e.__u|=2)),null==e||null==e.__v?(-1==c&&(i>a?h--:i<a&&h++),"function"!=typeof o.type&&(o.__u|=4)):c!=f&&(c==f-1?h--:c==f+1?h++:(c>f?h--:h++,o.__u|=4))):n.__k[r]=null;if(s)for(r=0;r<a;r++)null!=(e=u[r])&&0==(2&e.__u)&&(e.__e==t&&(t=$(e)),K(e,e));return t}function j(n,l,u){var t,i;if("function"==typeof n.type){for(t=n.__k,i=0;t&&i<t.length;i++)t[i]&&(t[i].__=n,l=j(t[i],l,u));return l}n.__e!=l&&(l&&n.type&&!l.parentNode&&(l=$(n)),l=u.insertBefore(n.__e,l||null));do{l=l&&l.nextSibling}while(null!=l&&8==l.nodeType);return l}function F(n,l){return l=l||[],null==n||"boolean"==typeof n||(g(n)?n.some(function(n){F(n,l)}):l.push(n)),l}function O(n,l,u,t){var i,r,o,e=n.key,f=n.type,c=l[u],a=null!=c&&0==(2&c.__u);if(null===c&&null==e||a&&e==c.key&&f==c.type)return u;if(t>(a?1:0))for(i=u-1,r=u+1;i>=0||r<l.length;)if(null!=(c=l[o=i>=0?i--:r++])&&0==(2&c.__u)&&e==c.key&&f==c.type)return o;return-1}function z(n,l,u){"-"==l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||_.test(l)?u:u+"px"}function N(n,l,u,t,i){var r,o;n:if("style"==l)if("string"==typeof u)n.style.cssText=u;else{if("string"==typeof t&&(n.style.cssText=t=""),t)for(l in t)u&&l in u||z(n.style,l,"");if(u)for(l in u)t&&u[l]==t[l]||z(n.style,l,u[l])}else if("o"==l[0]&&"n"==l[1])r=l!=(l=l.replace(s,"$1")),o=l.toLowerCase(),l=o in n||"onFocusOut"==l||"onFocusIn"==l?o.slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?t?u[a]=t[a]:(u[a]=h,n.addEventListener(l,r?v:p,r)):n.removeEventListener(l,r?v:p,r);else{if("http://www.w3.org/2000/svg"==i)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!=l&&"height"!=l&&"href"!=l&&"list"!=l&&"form"!=l&&"tabIndex"!=l&&"download"!=l&&"rowSpan"!=l&&"colSpan"!=l&&"role"!=l&&"popover"!=l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||!1===u&&"-"!=l[4]?n.removeAttribute(l):n.setAttribute(l,"popover"==l&&1==u?"":u))}}function V(n){return function(u){if(this.l){var t=this.l[u.type+n];if(null==u[c])u[c]=h++;else if(u[c]<t[a])return;return t(l.event?l.event(u):u)}}}function q(n,u,t,i,r,o,e,f,c,a){var s,h,p,v,y,d,_,k,x,M,I,P,A,H,T,j,F=u.type;if(void 0!==u.constructor)return null;128&t.__u&&(c=!!(32&t.__u),o=[f=u.__e=t.__e]),(s=l.__b)&&s(u);n:if("function"==typeof F){h=e.length;try{if(x=u.props,M=F.prototype&&F.prototype.render,I=(s=F.contextType)&&i[s.__c],P=s?I?I.props.value:s.__:i,t.__c?k=(p=u.__c=t.__c).__=p.__E:(M?u.__c=p=new F(x,P):(u.__c=p=new C(x,P),p.constructor=F,p.render=Q),I&&I.sub(p),p.state||(p.state={}),p.__n=i,v=p.__d=!0,p.__h=[],p._sb=[]),M&&null==p.__s&&(p.__s=p.state),M&&null!=F.getDerivedStateFromProps&&(p.__s==p.state&&(p.__s=m({},p.__s)),m(p.__s,F.getDerivedStateFromProps(x,p.__s))),y=p.props,d=p.state,p.__v=u,v)M&&null==F.getDerivedStateFromProps&&null!=p.componentWillMount&&p.componentWillMount(),M&&null!=p.componentDidMount&&p.__h.push(p.componentDidMount);else{if(M&&null==F.getDerivedStateFromProps&&x!==y&&null!=p.componentWillReceiveProps&&p.componentWillReceiveProps(x,P),u.__v==t.__v||!p.__e&&null!=p.shouldComponentUpdate&&!1===p.shouldComponentUpdate(x,p.__s,P)){u.__v!=t.__v&&(p.props=x,p.state=p.__s,p.__d=!1),u.__e=t.__e,u.__k=t.__k,u.__k.some(function(n){n&&(n.__=u)}),w.push.apply(p.__h,p._sb),p._sb=[],p.__h.length&&e.push(p),f=$(t);break n}null!=p.componentWillUpdate&&p.componentWillUpdate(x,p.__s,P),M&&null!=p.componentDidUpdate&&p.__h.push(function(){p.componentDidUpdate(y,d,_)})}if(p.context=P,p.props=x,p.__P=n,p.__e=!1,A=l.__r,H=0,M)p.state=p.__s,p.__d=!1,A&&A(u),s=p.render(p.props,p.state,p.context),w.push.apply(p.__h,p._sb),p._sb=[];else do{p.__d=!1,A&&A(u),s=p.render(p.props,p.state,p.context),p.state=p.__s}while(p.__d&&++H<25);p.state=p.__s,null!=p.getChildContext&&(i=m(m({},i),p.getChildContext())),M&&!v&&null!=p.getSnapshotBeforeUpdate&&(_=p.getSnapshotBeforeUpdate(y,d)),T=null!=s&&s.type===S&&null==s.key?E(s.props.children):s,f=L(n,g(T)?T:[T],u,t,i,r,o,e,f,c,a),p.base=u.__e,u.__u&=-161,p.__h.length&&e.push(p),k&&(p.__E=p.__=null)}catch(n){if(e.length=h,u.__v=null,c||null!=o){if(n.then){for(u.__u|=c?160:128;f&&8==f.nodeType&&f.nextSibling;)f=f.nextSibling;null!=o&&(o[o.indexOf(f)]=null),u.__e=f}else if(null!=o)for(j=o.length;j--;)b(o[j])}else u.__e=t.__e;null==u.__k&&(u.__k=t.__k||[]),n.then||B(u),l.__e(n,u,t)}}else null==o&&u.__v==t.__v?(u.__k=t.__k,u.__e=t.__e):f=u.__e=G(t.__e,u,t,i,r,o,e,c,a);return(s=l.diffed)&&s(u),128&u.__u?void 0:f}function B(n){n&&(n.__c&&(n.__c.__e=!0),n.__k&&n.__k.some(B))}function D(n,u,t){for(var i=0;i<t.length;i++)J(t[i],t[++i],t[++i]);l.__c&&l.__c(u,n),n.some(function(u){try{n=u.__h,u.__h=[],n.some(function(n){n.call(u)})}catch(n){l.__e(n,u.__v)}})}function E(n){return"object"!=typeof n||null==n||n.__b>0?n:g(n)?n.map(E):void 0!==n.constructor?null:m({},n)}function G(u,t,i,r,o,e,f,c,a){var s,h,p,v,y,w,_,m=i.props||d,k=t.props,x=t.type;if("svg"==x?o="http://www.w3.org/2000/svg":"math"==x?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),null!=e)for(s=0;s<e.length;s++)if((y=e[s])&&"setAttribute"in y==!!x&&(x?y.localName==x:3==y.nodeType)){u=y,e[s]=null;break}if(null==u){if(null==x)return document.createTextNode(k);u=document.createElementNS(o,x,k.is&&k),c&&(l.__m&&l.__m(t,e),c=!1),e=null}if(null==x)m===k||c&&u.data==k||(u.data=k);else{if(e="textarea"==x&&null!=k.defaultValue?null:e&&n.call(u.childNodes),!c&&null!=e)for(m={},s=0;s<u.attributes.length;s++)m[(y=u.attributes[s]).name]=y.value;for(s in m)y=m[s],"dangerouslySetInnerHTML"==s?p=y:"children"==s||s in k||"value"==s&&"defaultValue"in k||"checked"==s&&"defaultChecked"in k||N(u,s,null,y,o);for(s in k)y=k[s],"children"==s?v=y:"dangerouslySetInnerHTML"==s?h=y:"value"==s?w=y:"checked"==s?_=y:c&&"function"!=typeof y||m[s]===y||N(u,s,y,m[s],o);if(h)c||p&&(h.__html==p.__html||h.__html==u.innerHTML)||(u.innerHTML=h.__html),t.__k=[];else if(p&&(u.innerHTML=""),L("template"==t.type?u.content:u,g(v)?v:[v],t,i,r,"foreignObject"==x?"http://www.w3.org/1999/xhtml":o,e,f,e?e[0]:i.__k&&$(i,0),c,a),null!=e)for(s=e.length;s--;)b(e[s]);c&&"textarea"!=x||(s="value","progress"==x&&null==w?u.removeAttribute("value"):null!=w&&(w!==u[s]||"progress"==x&&!w||"option"==x&&w!=m[s])&&N(u,s,w,m[s],o),s="checked",null!=_&&_!=u[s]&&N(u,s,_,m[s],o))}return u}function J(n,u,t){try{if("function"==typeof n){var i="function"==typeof n.__u;i&&n.__u(),i&&null==u||(n.__u=n(u))}else n.current=u}catch(n){l.__e(n,t)}}function K(n,u,t){var i,r;if(l.unmount&&l.unmount(n),(i=n.ref)&&(i.current&&i.current!=n.__e||J(i,null,u)),null!=(i=n.__c)){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(n){l.__e(n,u)}i.base=i.__P=i.__n=null}if(i=n.__k)for(r=0;r<i.length;r++)i[r]&&K(i[r],u,t||"function"!=typeof n.type);t||b(n.__e),n.__c=n.__=n.__e=void 0}function Q(n,l,u){return this.constructor(n,u)}function R(u,t,i){var r,o,e,f;t==document&&(t=document.documentElement),l.__&&l.__(u,t),o=(r="function"==typeof i)?null:i&&i.__k||t.__k,e=[],f=[],q(t,u=(!r&&i||t).__k=k(S,null,[u]),o||d,d,t.namespaceURI,!r&&i?[i]:o?null:t.firstChild?n.call(t.childNodes):null,e,!r&&i?i:o?o.__e:t.firstChild,r,f),D(e,u,f),u.props.children=null}function U(n,l){R(n,l,U)}function W(l,u,t){var i,r,o,e,f=m({},l.props);for(o in l.type&&l.type.defaultProps&&(e=l.type.defaultProps),u)"key"==o?i=u[o]:"ref"==o?r=u[o]:f[o]=void 0===u[o]&&null!=e?e[o]:u[o];return arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):t),x(l.type,f,i||l.key,r||l.ref,null)}function X(n){function l(n){var u,t;return this.getChildContext||(u=new Set,(t={})[l.__c]=this,this.getChildContext=function(){return t},this.componentWillUnmount=function(){u=null},this.shouldComponentUpdate=function(n){this.props.value!=n.value&&u.forEach(function(n){n.__e=!0,A(n)})},this.sub=function(n){u.add(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u&&u.delete(n),l&&l.call(n)}}),n.children}return l.__c="__cC"+y++,l.__=n,l.Provider=l.__l=(l.Consumer=function(n,l){return n.children(l)}).contextType=l,l}n=w.slice,l={__e:function(n,l,u,t){for(var i,r,o;l=l.__;)if((i=l.__c)&&!i.__)try{if((r=i.constructor)&&null!=r.getDerivedStateFromError&&(i.setState(r.getDerivedStateFromError(n)),o=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(n,t||{}),o=i.__d),o)return i.__E=i}catch(l){n=l}throw n}},u=0,t=function(n){return null!=n&&void 0===n.constructor},C.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!=this.state?this.__s:this.__s=m({},this.state),"function"==typeof n&&(n=n(m({},u),this.props)),n&&m(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),A(this))},C.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),A(this))},C.prototype.render=S,i=[],o="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,e=function(n,l){return n.__v.__b-l.__v.__b},H.__r=0,f=Math.random().toString(8),c="__d"+f,a="__a"+f,s=/(PointerCapture)$|Capture$/i,h=0,p=V(!1),v=V(!0),y=0;export{C as Component,S as Fragment,W as cloneElement,X as createContext,k as createElement,M as createRef,k as h,U as hydrate,t as isValidElement,l as options,R as render,F as toChildArray};
2
+
@@ -0,0 +1,2 @@
1
+ const i=Symbol.for("preact-signals");function t(){if(e>1){e--;return}let i,t=!1;!function(){let i=f;f=void 0;while(void 0!==i){const t=i.S;if(t.v===i.v)for(let n=t.t;void 0!==n;n=n.x)if(n.i===i.i)n.i=t.i;i=i.o}}();while(void 0!==h){let n=h;h=void 0;c++;while(void 0!==n){const o=n.u;n.u=void 0;n.f&=-3;if(!(8&n.f)&&w(n))try{n.c()}catch(n){if(!t){i=n;t=!0}}n=o}}c=0;e--;if(t)throw i}function n(i){if(e>0)return i();d=++u;e++;try{return i()}finally{t()}}let o,s,h;function r(i){const t=o,n=s;o=void 0;s=void 0;try{return i()}finally{o=t;s=n}}let f,e=0,c=0,u=0,d=0,v=0;function l(i){if(void 0===o)return;let t=i.n;if(void 0===t||t.t!==o){t={i:0,S:i,p:o.s,n:void 0,t:o,e:void 0,x:void 0,r:t};if(void 0!==o.s)o.s.n=t;o.s=t;i.n=t;if(32&o.f)i.S(t);return t}else if(-1===t.i){t.i=0;if(void 0!==t.n){t.n.p=t.p;if(void 0!==t.p)t.p.n=t.n;t.p=o.s;t.n=void 0;o.s.n=t;o.s=t}return t}}function a(i,t){this.v=i;this.i=0;this.n=void 0;this.t=void 0;this.l=0;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;this.name=null==t?void 0:t.name}a.prototype.brand=i;a.prototype.h=function(){return!0};a.prototype.S=function(i){const t=this.t;if(t!==i&&void 0===i.e){i.x=t;this.t=i;if(void 0!==t)t.e=i;else r(()=>{var i;null==(i=this.W)||i.call(this)})}};a.prototype.U=function(i){if(void 0!==this.t){const t=i.e,n=i.x;if(void 0!==t){t.x=n;i.e=void 0}if(void 0!==n){n.e=t;i.x=void 0}if(i===this.t){this.t=n;if(void 0===n)r(()=>{var i;null==(i=this.Z)||i.call(this)})}}};a.prototype.subscribe=function(i){return j(()=>{const t=this.value;r(()=>i(t))},{name:"sub"})};a.prototype.valueOf=function(){return this.value};a.prototype.toString=function(){return this.value+""};a.prototype.toJSON=function(){return this.value};a.prototype.peek=function(){return r(()=>this.value)};Object.defineProperty(a.prototype,"value",{get(){const i=l(this);if(void 0!==i)i.i=this.i;return this.v},set(i){if(i!==this.v){if(c>100)throw new Error("Cycle detected");!function(i){if(0!==e&&0===c)if(i.l!==d){i.l=d;f={S:i,v:i.v,i:i.i,o:f}}}(this);this.v=i;this.i++;v++;e++;try{for(let i=this.t;void 0!==i;i=i.x)i.t.N()}finally{t()}}}});function y(i,t){return new a(i,t)}function w(i){for(let t=i.s;void 0!==t;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function _(i){for(let t=i.s;void 0!==t;t=t.n){const n=t.S.n;if(void 0!==n)t.r=n;t.S.n=t;t.i=-1;if(void 0===t.n){i.s=t;break}}}function b(i){let t,n=i.s;while(void 0!==n){const i=n.p;if(-1===n.i){n.S.U(n);if(void 0!==i)i.n=n.n;if(void 0!==n.n)n.n.p=i}else t=n;n.S.n=n.r;if(void 0!==n.r)n.r=void 0;n=i}i.s=t}function p(i,t){a.call(this,void 0,t);this.x=i;this.s=void 0;this.g=v-1;this.f=4}p.prototype=new a;p.prototype.h=function(){this.f&=-3;if(1&this.f)return!1;if(32==(36&this.f))return!0;this.f&=-5;if(this.g===v)return!0;this.g=v;this.f|=1;if(this.i>0&&!w(this)){this.f&=-2;return!0}const i=o;try{_(this);o=this;const i=this.x();if(16&this.f||this.v!==i||0===this.i){this.v=i;this.f&=-17;this.i++}}catch(i){this.v=i;this.f|=16;this.i++}o=i;b(this);this.f&=-2;return!0};p.prototype.S=function(i){if(void 0===this.t){this.f|=36;for(let i=this.s;void 0!==i;i=i.n)i.S.S(i)}a.prototype.S.call(this,i)};p.prototype.U=function(i){if(void 0!==this.t){a.prototype.U.call(this,i);if(void 0===this.t){this.f&=-33;for(let i=this.s;void 0!==i;i=i.n)i.S.U(i)}}};p.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let i=this.t;void 0!==i;i=i.x)i.t.N()}};Object.defineProperty(p.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const i=l(this);this.h();if(void 0!==i)i.i=this.i;if(16&this.f)throw this.v;return this.v}});function g(i,t){return new p(i,t)}function S(i){const n=i.m;i.m=void 0;if("function"==typeof n){e++;const s=o;o=void 0;try{n()}catch(t){i.f&=-2;i.f|=8;m(i);throw t}finally{o=s;t()}}}function m(i){for(let t=i.s;void 0!==t;t=t.n)t.S.U(t);i.x=void 0;i.s=void 0;S(i)}function x(i){if(o!==this)throw new Error("Out-of-order effect");b(this);o=i;this.f&=-2;if(8&this.f)m(this);t()}function E(i,t){this.x=i;this.m=void 0;this.s=void 0;this.u=void 0;this.f=32;this.name=null==t?void 0:t.name;if(s)s.push(this)}E.prototype.c=function(){const i=this.S();try{if(8&this.f)return;if(void 0===this.x)return;const t=this.x();if("function"==typeof t)this.m=t}finally{i()}};E.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1;this.f&=-9;S(this);_(this);e++;const i=o;o=this;return x.bind(this,i)};E.prototype.N=function(){if(!(2&this.f)){this.f|=2;this.u=h;h=this}};E.prototype.d=function(){this.f|=8;if(!(1&this.f))m(this)};E.prototype.dispose=function(){this.d()};function j(i,t){const n=new E(i,t);try{n.c()}catch(i){n.d();throw i}const o=n.d.bind(n);o[Symbol.dispose]=o;return o}function C(i){return function(...t){return n(()=>r(()=>i.apply(this,t)))}}const O=i=>{for(const t in i){const n=i[t];if("function"==typeof n)i[t]=C(n);else if("object"==typeof n&&null!==n&&!("brand"in n))O(n)}};function k(i){return function(...t){let n,o;const h=function(){let i=s;s=[];return function(){let t=s;if(s&&i)i=i.concat(s);s=i;return t}}();try{o=i(...t)}catch(i){s=void 0;throw i}finally{n=h()}O(o);o[Symbol.dispose]=C(function(){if(n)for(let i=0;i<n.length;i++)n[i].dispose();n=void 0});return o}}export{p as Computed,E as Effect,a as Signal,C as action,n as batch,g as computed,k as createModel,j as effect,y as signal,r as untracked};
2
+
@@ -0,0 +1,2 @@
1
+ import{Component as t,options as i,isValidElement as n,Fragment as e}from"./preact.js";import{useMemo as o,useRef as f,useEffect as r}from"./hooks.js";import{effect as u,Signal as c,computed as s,signal as l,batch as a}from"./signals-core.js";export{Signal,action,batch,computed,createModel,effect,signal,untracked}from"./signals-core.js";const h="undefined"!=typeof window&&!!window.__PREACT_SIGNALS_DEVTOOLS__;let d,p,m,v=[],_=[];u(function(){d=this.N})();function g(t,n){i[t]=n.bind(null,i[t]||(()=>{}))}function b(t){if(m){const t=m;m=void 0;t()}m=t&&t.S()}function y({data:t}){const i=useSignal(t);i.name="ReactiveDom";i.value=t;const[e,f]=o(()=>{let t=this,e=this.__v;while(e=e.__)if(e.__c){e.__c.__$f|=4;break}const o=s(()=>{let t=i.value.value;return 0===t?0:!0===t?"":t||""}),f=s(()=>!Array.isArray(o.value)&&!n(o.value)),r=u(function(){this.N=F;if(f.value){const i=o.value;if(t.__v&&t.__v.__e&&3===t.__v.__e.nodeType)t.__v.__e.data=i}}),c=this.__$u.d;this.__$u.d=function(){r();c.call(this)};return[f,o]},[]);return e.value?f.peek():f.value}y.displayName="ReactiveTextNode";Object.defineProperties(c.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:y},props:{configurable:!0,get(){const t=this;return{data:{get value(){return t.value}}}}},__b:{configurable:!0,value:1}});g("__b",(t,i)=>{if("string"==typeof i.type){let t,n=i.props;for(let e in n){if("children"===e)continue;let o=n[e];if(o instanceof c){if(!t)i.__np=t={};t[e]=o;n[e]=o.peek()}}}t(i)});g("__r",(t,i)=>{t(i);if(i.type!==e){b();let t,n=i.__c;if(n){n.__$f&=-2;t=n.__$u;if(void 0===t)n.__$u=t=function(t,i){let n;u(function(){n=this},{name:i});n.c=t;return n}(function(t){return function(){var i;if(h)null==(i=this.y)||i.call(this);t.__$f|=1;t.setState({})}}(n),"function"==typeof i.type?i.type.displayName||i.type.name:"")}p=n;b(t)}});g("__e",(t,i,n,e)=>{b();p=void 0;t(i,n,e)});g("diffed",(t,i)=>{b();p=void 0;let n;if("string"==typeof i.type&&(n=i.__e)){let t=i.__np,e=i.props,o=n.U;if(o)for(let i in o){let n=o[i];if(!(void 0===n||t&&i in t)){n.d();o[i]=void 0}}if(t){if(!o){o={};n.U=o}for(let i in t){let f=o[i],r=t[i];if(void 0===f){f=w(n,i,r,e);o[i]=f}else f.o(r,e)}}}t(i)});function w(t,i,n,e){const o=i in t&&void 0===t.ownerSVGElement,f=l(n);return{o:(t,i)=>{f.value=t;e=i},d:u(function(){this.N=F;const n=f.value.value;if(e[i]!==n){e[i]=n;if(o)t[i]=n;else if(null!=n&&(!1!==n||"-"===i[4]))t.setAttribute(i,n);else t.removeAttribute(i)}})}}g("unmount",(t,i)=>{if("string"==typeof i.type){let t=i.__e;if(t){const i=t.U;if(i){t.U=void 0;for(let t in i){let n=i[t];if(n)n.d()}}}let n=i.__np;if(n){let t=i.props;for(let i in n)t[i]=n[i]}i.__np=void 0}else{let t=i.__c;if(t){const i=t.__$u;if(i){t.__$u=void 0;i.d()}}}t(i)});g("__h",(t,i,n,e)=>{if(e<3)i.__$f|=2;t(i,n,e)});t.prototype.shouldComponentUpdate=function(t,i){if(this.__R)return!0;const n=this.__$u,e=n&&void 0!==n.s;for(let t in i)return!0;if(this.__f||"boolean"==typeof this.u&&!0===this.u){const t=2&this.__$f;if(!(e||t||4&this.__$f))return!0;if(1&this.__$f)return!0}else{if(!(e||4&this.__$f))return!0;if(3&this.__$f)return!0}for(let i in t)if("__source"!==i&&t[i]!==this.props[i])return!0;for(let i in this.props)if(!(i in t))return!0;return!1};function useSignal(t,i){return o(()=>l(t,i),[])}function useComputed(t,i){const n=f(t);n.current=t;p.__$f|=4;return o(()=>s(()=>n.current(),i),[])}const k="undefined"==typeof requestAnimationFrame?setTimeout:function(t){const i=()=>{clearTimeout(n);cancelAnimationFrame(e);t()},n=setTimeout(i,35),e=requestAnimationFrame(i)},q=t=>{queueMicrotask(()=>{queueMicrotask(t)})};function A(){a(()=>{let t;while(t=v.shift())d.call(t)})}function T(){if(1===v.push(this))(i.requestAnimationFrame||k)(A)}function x(){a(()=>{let t;while(t=_.shift())d.call(t)})}function F(){if(1===_.push(this))(i.requestAnimationFrame||q)(x)}function useSignalEffect(t,i){const n=f(t);n.current=t;r(()=>u(function(){this.N=T;return n.current()},i),[])}function M(t){const i=o(()=>t(),[]);r(()=>i[Symbol.dispose],[i]);return i}export{useComputed,M as useModel,useSignal,useSignalEffect};
2
+