tachyon-dom 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/dist/adapters/lambda.d.ts +65 -0
  4. package/dist/adapters/lambda.js +211 -0
  5. package/dist/adapters/node.d.ts +25 -0
  6. package/dist/adapters/node.js +360 -0
  7. package/dist/adapters/workers.d.ts +149 -0
  8. package/dist/adapters/workers.js +272 -0
  9. package/dist/adapters.d.ts +3 -0
  10. package/dist/adapters.js +3 -0
  11. package/dist/app.d.ts +98 -0
  12. package/dist/app.js +306 -0
  13. package/dist/cli.d.ts +55 -0
  14. package/dist/cli.js +711 -0
  15. package/dist/compiler/expression.d.ts +55 -0
  16. package/dist/compiler/expression.js +858 -0
  17. package/dist/compiler/index.d.ts +8 -0
  18. package/dist/compiler/index.js +50 -0
  19. package/dist/compiler/ir.d.ts +4 -0
  20. package/dist/compiler/ir.js +255 -0
  21. package/dist/compiler/parser.d.ts +3 -0
  22. package/dist/compiler/parser.js +236 -0
  23. package/dist/compiler/sfc.d.ts +33 -0
  24. package/dist/compiler/sfc.js +439 -0
  25. package/dist/compiler/targets/client.d.ts +3 -0
  26. package/dist/compiler/targets/client.js +568 -0
  27. package/dist/compiler/targets/server.d.ts +7 -0
  28. package/dist/compiler/targets/server.js +396 -0
  29. package/dist/compiler/targets/stream.d.ts +2 -0
  30. package/dist/compiler/targets/stream.js +204 -0
  31. package/dist/compiler/types.d.ts +185 -0
  32. package/dist/compiler/types.js +1 -0
  33. package/dist/compiler/utils.d.ts +40 -0
  34. package/dist/compiler/utils.js +185 -0
  35. package/dist/compiler/whitespace.d.ts +2 -0
  36. package/dist/compiler/whitespace.js +66 -0
  37. package/dist/compiler.d.ts +1 -0
  38. package/dist/compiler.js +1 -0
  39. package/dist/constant-time.d.ts +1 -0
  40. package/dist/constant-time.js +26 -0
  41. package/dist/cookies.d.ts +44 -0
  42. package/dist/cookies.js +199 -0
  43. package/dist/diagnostics.d.ts +22 -0
  44. package/dist/diagnostics.js +45 -0
  45. package/dist/env.d.ts +27 -0
  46. package/dist/env.js +37 -0
  47. package/dist/html-escape.d.ts +2 -0
  48. package/dist/html-escape.js +20 -0
  49. package/dist/html-whitespace.d.ts +6 -0
  50. package/dist/html-whitespace.js +83 -0
  51. package/dist/i18n.d.ts +19 -0
  52. package/dist/i18n.js +93 -0
  53. package/dist/index.d.ts +20 -0
  54. package/dist/index.js +18 -0
  55. package/dist/language-server.d.ts +17 -0
  56. package/dist/language-server.js +79 -0
  57. package/dist/package-integrity.d.ts +9 -0
  58. package/dist/package-integrity.js +64 -0
  59. package/dist/result.d.ts +13 -0
  60. package/dist/result.js +4 -0
  61. package/dist/router-node.d.ts +2 -0
  62. package/dist/router-node.js +12 -0
  63. package/dist/router.d.ts +324 -0
  64. package/dist/router.js +1024 -0
  65. package/dist/runtime/attr.d.ts +3 -0
  66. package/dist/runtime/attr.js +66 -0
  67. package/dist/runtime/class.d.ts +2 -0
  68. package/dist/runtime/class.js +10 -0
  69. package/dist/runtime/conditional.d.ts +78 -0
  70. package/dist/runtime/conditional.js +140 -0
  71. package/dist/runtime/enhancement.d.ts +11 -0
  72. package/dist/runtime/enhancement.js +70 -0
  73. package/dist/runtime/error-boundary.d.ts +8 -0
  74. package/dist/runtime/error-boundary.js +27 -0
  75. package/dist/runtime/event.d.ts +1 -0
  76. package/dist/runtime/event.js +11 -0
  77. package/dist/runtime/form.d.ts +41 -0
  78. package/dist/runtime/form.js +167 -0
  79. package/dist/runtime/fragment.d.ts +6 -0
  80. package/dist/runtime/fragment.js +18 -0
  81. package/dist/runtime/html.d.ts +8 -0
  82. package/dist/runtime/html.js +6 -0
  83. package/dist/runtime/hydrate.d.ts +68 -0
  84. package/dist/runtime/hydrate.js +205 -0
  85. package/dist/runtime/keyed-rows.d.ts +61 -0
  86. package/dist/runtime/keyed-rows.js +192 -0
  87. package/dist/runtime/list.d.ts +84 -0
  88. package/dist/runtime/list.js +424 -0
  89. package/dist/runtime/portal.d.ts +6 -0
  90. package/dist/runtime/portal.js +12 -0
  91. package/dist/runtime/router.d.ts +102 -0
  92. package/dist/runtime/router.js +649 -0
  93. package/dist/runtime/signal.d.ts +30 -0
  94. package/dist/runtime/signal.js +275 -0
  95. package/dist/runtime/store.d.ts +1 -0
  96. package/dist/runtime/store.js +1 -0
  97. package/dist/runtime/stream-client.d.ts +8 -0
  98. package/dist/runtime/stream-client.js +33 -0
  99. package/dist/runtime/text.d.ts +2 -0
  100. package/dist/runtime/text.js +10 -0
  101. package/dist/runtime/virtual-list.d.ts +15 -0
  102. package/dist/runtime/virtual-list.js +89 -0
  103. package/dist/security.d.ts +27 -0
  104. package/dist/security.js +173 -0
  105. package/dist/server/form-action.d.ts +52 -0
  106. package/dist/server/form-action.js +102 -0
  107. package/dist/server/html.d.ts +19 -0
  108. package/dist/server/html.js +101 -0
  109. package/dist/server/stream.d.ts +4 -0
  110. package/dist/server/stream.js +72 -0
  111. package/dist/source-map.d.ts +17 -0
  112. package/dist/source-map.js +18 -0
  113. package/dist/tachyon-html.d.ts +110 -0
  114. package/dist/testing.d.ts +14 -0
  115. package/dist/testing.js +36 -0
  116. package/dist/typed.d.ts +9 -0
  117. package/dist/typed.js +4 -0
  118. package/dist/vite.d.ts +84 -0
  119. package/dist/vite.js +426 -0
  120. package/package.json +604 -0
@@ -0,0 +1,424 @@
1
+ import { setClassPresence } from "./class.js";
2
+ import { setAttributeValue, setRef, setStyleValue } from "./attr.js";
3
+ import { setText } from "./text.js";
4
+ import { bindControl, setControlValue } from "./form.js";
5
+ import { mountConditional } from "./conditional.js";
6
+ import { createSignal, effect, untrack } from "./signal.js";
7
+ const listStates = new WeakMap();
8
+ const readPath = (scope, expression) => {
9
+ const parts = expression.split(".");
10
+ let current = scope;
11
+ for (const part of parts) {
12
+ if (current == null || typeof current !== "object") {
13
+ return undefined;
14
+ }
15
+ current = current[part];
16
+ }
17
+ return current;
18
+ };
19
+ const readItemPath = (item, expression, itemName) => {
20
+ if (expression === itemName) {
21
+ return item;
22
+ }
23
+ const prefix = `${itemName}.`;
24
+ if (!expression.startsWith(prefix)) {
25
+ return undefined;
26
+ }
27
+ return readPath(item, expression.slice(prefix.length));
28
+ };
29
+ const writePath = (scope, expression, value) => {
30
+ const parts = expression.split(".");
31
+ const property = parts.pop();
32
+ let current = scope;
33
+ for (const part of parts) {
34
+ current = current && typeof current === "object" ? current[part] : undefined;
35
+ }
36
+ if (property && current && typeof current === "object") {
37
+ current[property] = value;
38
+ }
39
+ };
40
+ const readBinding = (scope, binding) => (binding.read ? binding.read(scope) : readPath(scope, binding.expression));
41
+ const readHandler = (scope, binding) => binding.read ? binding.read(scope) : readPath(scope, binding.handler);
42
+ const scopedItem = (itemName, item, scope) => ({ ...scope, [itemName]: item });
43
+ const nodeAt = (root, path) => {
44
+ let current = root;
45
+ for (const index of path) {
46
+ current = current.childNodes[index];
47
+ }
48
+ return current;
49
+ };
50
+ const nodeAtRecord = (record, path) => {
51
+ if (record.nodes.length <= 1) {
52
+ return nodeAt(record.element, path);
53
+ }
54
+ const [firstIndex, ...rest] = path;
55
+ const root = record.nodes[firstIndex ?? 0] ?? record.element;
56
+ return nodeAt(root, rest);
57
+ };
58
+ const createTemplate = (templateHtml) => {
59
+ const template = document.createElement("template");
60
+ template.innerHTML = templateHtml;
61
+ return template;
62
+ };
63
+ const optionsSignature = (options) => options.signature ??
64
+ JSON.stringify({
65
+ key: options.key,
66
+ itemName: options.itemName,
67
+ templateHtml: options.templateHtml,
68
+ bindings: options.bindings,
69
+ });
70
+ const cleanupListState = (state) => {
71
+ for (const cleanup of state.cleanups) {
72
+ cleanup();
73
+ }
74
+ state.cleanups.length = 0;
75
+ state.records.forEach(cleanupRecord);
76
+ state.records.clear();
77
+ };
78
+ const cleanupNestedListStates = (node) => {
79
+ if (!(node instanceof Element)) {
80
+ return;
81
+ }
82
+ const state = listStates.get(node);
83
+ if (state) {
84
+ cleanupListState(state);
85
+ listStates.delete(node);
86
+ }
87
+ for (const child of Array.from(node.childNodes)) {
88
+ cleanupNestedListStates(child);
89
+ }
90
+ };
91
+ const getListState = (container, options) => {
92
+ const current = listStates.get(container);
93
+ if (current && current.options === options) {
94
+ return current;
95
+ }
96
+ const signature = optionsSignature(options);
97
+ if (current && current.signature === signature) {
98
+ current.options = options;
99
+ return current;
100
+ }
101
+ if (current) {
102
+ cleanupListState(current);
103
+ }
104
+ const template = createTemplate(options.templateHtml);
105
+ const elementIndices = Array.from(template.content.childNodes).flatMap((node, index) => node instanceof Element ? [index] : []);
106
+ const next = {
107
+ signature,
108
+ options,
109
+ templateHtml: options.templateHtml,
110
+ records: new Map(),
111
+ recordsByElement: new WeakMap(),
112
+ template,
113
+ elementIndices,
114
+ cleanups: [],
115
+ };
116
+ listStates.set(container, next);
117
+ return next;
118
+ };
119
+ const cleanupRecord = (record) => {
120
+ for (const cleanup of record.cleanups) {
121
+ cleanup();
122
+ }
123
+ record.cleanups.length = 0;
124
+ for (const node of record.nodes) {
125
+ cleanupNestedListStates(node);
126
+ node.parentNode?.removeChild(node);
127
+ }
128
+ };
129
+ const shouldApplyValue = (record, index, value) => {
130
+ if (Object.is(record.lastValues[index], value)) {
131
+ return false;
132
+ }
133
+ record.lastValues[index] = value;
134
+ return true;
135
+ };
136
+ const applyRowBinding = (record, scope, options, binding, index) => {
137
+ record.revision();
138
+ if (binding.kind === "text") {
139
+ const value = readBinding(scope, binding);
140
+ if (shouldApplyValue(record, index, value)) {
141
+ setText(nodeAtRecord(record, binding.path), value);
142
+ }
143
+ }
144
+ else if (binding.kind === "class") {
145
+ const value = readBinding(scope, binding);
146
+ if (shouldApplyValue(record, index, value)) {
147
+ setClassPresence(nodeAtRecord(record, binding.path), binding.className, value);
148
+ }
149
+ }
150
+ else if (binding.kind === "attr") {
151
+ const value = readBinding(scope, binding);
152
+ if (shouldApplyValue(record, index, value)) {
153
+ setAttributeValue(nodeAtRecord(record, binding.path), binding.name, value);
154
+ }
155
+ }
156
+ else if (binding.kind === "style") {
157
+ const value = readBinding(scope, binding);
158
+ if (shouldApplyValue(record, index, value)) {
159
+ setStyleValue(nodeAtRecord(record, binding.path), binding.name, value);
160
+ }
161
+ }
162
+ else if (binding.kind === "ref") {
163
+ setRef(scope, binding.expression, nodeAtRecord(record, binding.path));
164
+ }
165
+ else if (binding.kind === "model") {
166
+ const value = readBinding(scope, binding);
167
+ if (shouldApplyValue(record, index, value)) {
168
+ setControlValue(nodeAtRecord(record, binding.path), binding.property, value);
169
+ }
170
+ }
171
+ else if (binding.kind === "list") {
172
+ const eachBinding = binding.read ? { expression: binding.each, read: binding.read } : { expression: binding.each };
173
+ const value = readBinding(scope, eachBinding);
174
+ mountKeyedList(record.element, binding.path, value, { ...binding, scope });
175
+ }
176
+ else if (binding.kind === "if") {
177
+ const testBinding = binding.read ? { expression: binding.test, read: binding.read } : { expression: binding.test };
178
+ const value = readBinding(scope, testBinding);
179
+ mountConditional(record.element, binding.path, value, scope, binding);
180
+ }
181
+ };
182
+ const bindRowBindings = (record, options) => {
183
+ const rowBindings = options.bindings
184
+ .map((binding, index) => ({ binding, index }))
185
+ .filter((entry) => entry.binding.kind !== "event");
186
+ if (rowBindings.length === 0) {
187
+ return;
188
+ }
189
+ record.cleanups.push(untrack(() => effect(() => {
190
+ for (const { binding, index } of rowBindings) {
191
+ applyRowBinding(record, record.scope, options, binding, index);
192
+ }
193
+ })));
194
+ };
195
+ const bindRowEvents = (record, options) => {
196
+ const delegateKeys = new Set();
197
+ for (const binding of options.bindings) {
198
+ if (binding.kind !== "event") {
199
+ continue;
200
+ }
201
+ const delegateKey = `${binding.eventName}:${binding.path.join(".")}:${binding.handler}`;
202
+ if (delegateKeys.has(delegateKey)) {
203
+ continue;
204
+ }
205
+ delegateKeys.add(delegateKey);
206
+ const target = nodeAtRecord(record, binding.path);
207
+ if (!(target instanceof Element)) {
208
+ continue;
209
+ }
210
+ const listener = (event) => {
211
+ const handler = readHandler(record.scope, binding);
212
+ if (typeof handler === "function") {
213
+ handler(event);
214
+ }
215
+ };
216
+ target.addEventListener(binding.eventName, listener);
217
+ record.cleanups.push(() => target.removeEventListener(binding.eventName, listener));
218
+ }
219
+ };
220
+ const bindRowControls = (record, options) => {
221
+ for (const binding of options.bindings) {
222
+ if (binding.kind !== "model") {
223
+ continue;
224
+ }
225
+ const element = nodeAtRecord(record, binding.path);
226
+ record.cleanups.push(bindControl(element, binding.property, () => readBinding(record.scope, binding), (value) => {
227
+ if (binding.write) {
228
+ binding.write(record.scope, value);
229
+ return;
230
+ }
231
+ writePath(record.scope, binding.expression, value);
232
+ }));
233
+ }
234
+ };
235
+ const keyFor = (item, options) => {
236
+ const key = options.keyReadItem
237
+ ? options.keyReadItem(item)
238
+ : options.keyRead
239
+ ? options.keyRead(scopedItem(options.itemName, item, options.scope))
240
+ : readItemPath(item, options.key, options.itemName);
241
+ if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
242
+ return key;
243
+ }
244
+ return String(key);
245
+ };
246
+ const isProductionEnvironment = () => typeof process !== "undefined" && process.env.NODE_ENV === "production";
247
+ const warnDuplicateKey = (key, options) => {
248
+ if (isProductionEnvironment() || typeof console.warn !== "function") {
249
+ return;
250
+ }
251
+ const location = options.signature ? ` ${options.signature}` : "";
252
+ console.warn(`Duplicate key ${JSON.stringify(String(key))} in keyed <for> list${location}. Later items with the same key were skipped.`);
253
+ };
254
+ const createRecord = (state, key, item, options, existingElements) => {
255
+ const nodes = Array.from(state.template.content.childNodes).map((node) => node.cloneNode(true));
256
+ if (existingElements) {
257
+ state.elementIndices.forEach((nodeIndex, elementIndex) => {
258
+ const existing = existingElements[elementIndex];
259
+ if (existing)
260
+ nodes[nodeIndex] = existing;
261
+ });
262
+ }
263
+ const element = nodes.find((node) => node instanceof Element);
264
+ if (!element) {
265
+ return undefined;
266
+ }
267
+ const scope = scopedItem(options.itemName, item, options.scope);
268
+ const record = {
269
+ key,
270
+ element,
271
+ nodes,
272
+ scope,
273
+ cleanups: [],
274
+ lastValues: [],
275
+ item,
276
+ revision: createSignal(0),
277
+ };
278
+ for (const node of nodes) {
279
+ if (node instanceof Element) {
280
+ state.recordsByElement.set(node, record);
281
+ }
282
+ }
283
+ bindRowEvents(record, options);
284
+ bindRowBindings(record, options);
285
+ untrack(() => bindRowControls(record, options));
286
+ return record;
287
+ };
288
+ const updateRecord = (record, item, options) => {
289
+ if (options.scope) {
290
+ Object.assign(record.scope, options.scope);
291
+ }
292
+ record.scope[options.itemName] = item;
293
+ record.item = item;
294
+ record.revision.update((value) => value + 1);
295
+ };
296
+ const moveBefore = (container, node, before) => {
297
+ const movableContainer = container;
298
+ if (typeof movableContainer.moveBefore === "function") {
299
+ try {
300
+ movableContainer.moveBefore(node, before);
301
+ return;
302
+ }
303
+ catch (error) {
304
+ if (!(error instanceof DOMException && error.name === "HierarchyRequestError")) {
305
+ throw error;
306
+ }
307
+ }
308
+ }
309
+ container.insertBefore(node, before);
310
+ };
311
+ const longestIncreasingSubsequencePositions = (values) => {
312
+ const predecessors = Array(values.length).fill(-1);
313
+ const tails = [];
314
+ const tailPositions = [];
315
+ for (let index = 0; index < values.length; index++) {
316
+ const value = values[index];
317
+ if (value < 0) {
318
+ continue;
319
+ }
320
+ let low = 0;
321
+ let high = tails.length;
322
+ while (low < high) {
323
+ const middle = (low + high) >> 1;
324
+ if (tails[middle] < value) {
325
+ low = middle + 1;
326
+ }
327
+ else {
328
+ high = middle;
329
+ }
330
+ }
331
+ if (low > 0) {
332
+ predecessors[index] = tailPositions[low - 1];
333
+ }
334
+ tails[low] = value;
335
+ tailPositions[low] = index;
336
+ }
337
+ const positions = new Set();
338
+ let cursor = tailPositions[tails.length - 1] ?? -1;
339
+ while (cursor >= 0) {
340
+ positions.add(cursor);
341
+ cursor = predecessors[cursor];
342
+ }
343
+ return positions;
344
+ };
345
+ const positionRecords = (container, orderedRecords, previousRecords) => {
346
+ const previousOrder = new Map();
347
+ Array.from(previousRecords.keys()).forEach((key, index) => previousOrder.set(key, index));
348
+ const stablePositions = longestIncreasingSubsequencePositions(orderedRecords.map((record) => previousOrder.get(record.key) ?? -1));
349
+ let anchor = null;
350
+ for (let index = orderedRecords.length - 1; index >= 0; index--) {
351
+ const record = orderedRecords[index];
352
+ if (stablePositions.has(index)) {
353
+ anchor = record.nodes[0] ?? anchor;
354
+ continue;
355
+ }
356
+ for (let nodeIndex = record.nodes.length - 1; nodeIndex >= 0; nodeIndex--) {
357
+ const node = record.nodes[nodeIndex];
358
+ if (node.parentNode !== container || node.nextSibling !== anchor) {
359
+ moveBefore(container, node, anchor);
360
+ }
361
+ anchor = node;
362
+ }
363
+ }
364
+ };
365
+ export const mountKeyedList = (root, path, items, options) => {
366
+ const container = nodeAt(root, path);
367
+ if (!(container instanceof Element)) {
368
+ return;
369
+ }
370
+ const state = getListState(container, options);
371
+ if (!items) {
372
+ state.records.forEach((record) => {
373
+ cleanupRecord(record);
374
+ state.recordsByElement.delete(record.element);
375
+ });
376
+ state.records.clear();
377
+ return;
378
+ }
379
+ const nextRecords = new Map();
380
+ const orderedRecords = [];
381
+ const serverElements = Array.from(container.children);
382
+ const canAdoptServerRows = state.records.size === 0
383
+ && state.elementIndices.length > 0
384
+ && serverElements.length >= items.length * state.elementIndices.length;
385
+ const seenKeys = new Set();
386
+ for (const item of items) {
387
+ const key = keyFor(item, options);
388
+ if (seenKeys.has(key)) {
389
+ warnDuplicateKey(key, options);
390
+ continue;
391
+ }
392
+ seenKeys.add(key);
393
+ const existing = state.records.get(key);
394
+ const adoptable = canAdoptServerRows
395
+ ? serverElements.slice(orderedRecords.length * state.elementIndices.length, (orderedRecords.length + 1) * state.elementIndices.length)
396
+ : undefined;
397
+ const record = existing ?? createRecord(state, key, item, options, adoptable);
398
+ if (!record) {
399
+ continue;
400
+ }
401
+ if (existing) {
402
+ updateRecord(record, item, options);
403
+ }
404
+ nextRecords.set(key, record);
405
+ orderedRecords.push(record);
406
+ }
407
+ state.records.forEach((record, key) => {
408
+ if (!nextRecords.has(key)) {
409
+ cleanupRecord(record);
410
+ for (const node of record.nodes) {
411
+ if (node instanceof Element) {
412
+ state.recordsByElement.delete(node);
413
+ }
414
+ }
415
+ }
416
+ });
417
+ if (canAdoptServerRows) {
418
+ container.replaceChildren(...orderedRecords.flatMap((record) => record.nodes));
419
+ }
420
+ else {
421
+ positionRecords(container, orderedRecords, state.records);
422
+ }
423
+ state.records = nextRecords;
424
+ };
@@ -0,0 +1,6 @@
1
+ export type PortalHandle = {
2
+ target: Element;
3
+ nodes: Node[];
4
+ remove: () => void;
5
+ };
6
+ export declare const mountPortal: (target: Element, nodes: readonly Node[]) => PortalHandle;
@@ -0,0 +1,12 @@
1
+ export const mountPortal = (target, nodes) => {
2
+ target.append(...nodes);
3
+ return {
4
+ target,
5
+ nodes: [...nodes],
6
+ remove: () => {
7
+ for (const node of nodes) {
8
+ node.parentNode?.removeChild(node);
9
+ }
10
+ },
11
+ };
12
+ };
@@ -0,0 +1,102 @@
1
+ import { type ClientHtml } from "./html.js";
2
+ export { rawHtml, type ClientHtml } from "./html.js";
3
+ export type ClientRouteParams = Record<string, string>;
4
+ export type ClientRouteContext<Data = unknown> = {
5
+ url: URL;
6
+ params: ClientRouteParams;
7
+ data: Data;
8
+ signal: AbortSignal;
9
+ };
10
+ type ClientRenderValue = string | ClientHtml | Node | readonly Node[] | DocumentFragment;
11
+ export type ClientHeadDescriptor = {
12
+ title?: string;
13
+ metas?: Array<Record<string, string>>;
14
+ links?: Array<Record<string, string>>;
15
+ scripts?: Array<Record<string, string>>;
16
+ };
17
+ export type ClientRouteDefinition<Data = unknown> = {
18
+ id?: string;
19
+ path: string;
20
+ children?: readonly ClientRouteDefinition[];
21
+ target?: string | Element | ((context: {
22
+ root: Element;
23
+ url: URL;
24
+ params: ClientRouteParams;
25
+ data: Data;
26
+ }) => Element | undefined | null);
27
+ load?: (context: Omit<ClientRouteContext<Data>, "data">) => Data | Promise<Data>;
28
+ head?: (context: ClientRouteContext<Data>) => ClientHeadDescriptor | Promise<ClientHeadDescriptor>;
29
+ action?: (context: Omit<ClientRouteContext<Data>, "data"> & {
30
+ request: Request;
31
+ }) => Response | Promise<Response>;
32
+ revalidateOnAction?: "self" | "all" | readonly string[] | ((context: {
33
+ url: URL;
34
+ response: Response;
35
+ }) => readonly string[]);
36
+ render: (context: ClientRouteContext<Data>) => ClientRenderValue | Promise<ClientRenderValue>;
37
+ };
38
+ export type ClientRouterOptions = {
39
+ root: Element;
40
+ routes: readonly ClientRouteDefinition[];
41
+ baseUrl?: string;
42
+ notFound?: (context: {
43
+ url: URL;
44
+ }) => ClientRenderValue;
45
+ error?: (context: {
46
+ url: URL;
47
+ error: unknown;
48
+ }) => ClientRenderValue;
49
+ scrollTo?: (x: number, y: number) => void;
50
+ focusSelector?: string;
51
+ cache?: boolean;
52
+ initialCache?: readonly {
53
+ href: string;
54
+ data: unknown;
55
+ }[];
56
+ eager?: boolean;
57
+ liveRegion?: Element;
58
+ title?: (context: {
59
+ url: URL;
60
+ data: unknown;
61
+ }) => string;
62
+ viewTransition?: boolean | ((context: {
63
+ url: URL;
64
+ params: ClientRouteParams;
65
+ data: unknown;
66
+ }) => boolean);
67
+ };
68
+ export type ClientRouter = {
69
+ start: () => Promise<void>;
70
+ navigate: (href: string, options?: {
71
+ replace?: boolean;
72
+ }) => Promise<void>;
73
+ submit: (href: string, init?: RequestInit) => Promise<Response>;
74
+ prefetch: (href: string) => Promise<void>;
75
+ revalidate: (hrefs?: string | readonly string[]) => Promise<void>;
76
+ invalidate: (href?: string) => void;
77
+ settled: () => Promise<void>;
78
+ dispose: () => void;
79
+ };
80
+ export type RouteHotReloader = {
81
+ accept: (update?: {
82
+ routeIds?: readonly string[];
83
+ href?: string;
84
+ }) => Promise<void>;
85
+ };
86
+ export type RouteHotReloaderOptions = Pick<ClientRouter, "invalidate" | "navigate"> & {
87
+ currentPath?: () => string;
88
+ onUpdate?: (update: {
89
+ routeIds?: readonly string[];
90
+ href: string;
91
+ }) => void | Promise<void>;
92
+ };
93
+ export type RouteHotApi = {
94
+ on: (event: "tachyon-dom:routes-update", callback: (payload: {
95
+ routeIds?: readonly string[];
96
+ href?: string;
97
+ }) => void) => void;
98
+ dispose?: (callback: () => void) => void;
99
+ };
100
+ export declare const createClientRouter: (options: ClientRouterOptions) => ClientRouter;
101
+ export declare const createRouteHotReloader: (options: RouteHotReloaderOptions) => RouteHotReloader;
102
+ export declare const connectRouteHotReloader: (hot: RouteHotApi | undefined, reloader: RouteHotReloader) => (() => void);