@hachej/boring-ui-kit 0.1.111 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +255 -239
- package/THIRD_PARTY_NOTICES.md +85 -0
- package/dist/anchor-conformance.d.ts +78 -0
- package/dist/anchor-conformance.js +288 -0
- package/dist/canvas-document.d.ts +25 -0
- package/dist/canvas-document.js +141 -0
- package/dist/canvas-editor-native.d.ts +22 -0
- package/dist/canvas-editor-native.js +112 -0
- package/dist/canvas-editor.d.ts +6 -0
- package/dist/canvas-editor.js +13 -0
- package/dist/canvas-mounted.d.ts +41 -0
- package/dist/canvas-mounted.js +180 -0
- package/dist/canvas-proposals.d.ts +11 -0
- package/dist/canvas-proposals.js +45 -0
- package/dist/canvas-records.d.ts +3 -0
- package/dist/canvas-records.js +39 -0
- package/dist/canvas.d.ts +92 -0
- package/dist/canvas.js +264 -0
- package/dist/contracts.d.ts +125 -0
- package/dist/contracts.js +1 -0
- package/dist/experience-catalog.d.ts +142 -0
- package/dist/experience-catalog.js +25 -0
- package/dist/experience-compose.d.ts +83 -0
- package/dist/experience-compose.js +218 -0
- package/dist/experience-composition-schema.d.ts +29 -0
- package/dist/experience-composition-schema.js +22 -0
- package/dist/experience-document-viewer.d.ts +13 -0
- package/dist/experience-document-viewer.js +54 -0
- package/dist/experience-document.d.ts +64 -0
- package/dist/experience-document.js +234 -0
- package/dist/experience-region-text.d.ts +3 -0
- package/dist/experience-region-text.js +84 -0
- package/dist/experience-region-tree.d.ts +22 -0
- package/dist/experience-region-tree.js +50 -0
- package/dist/experience-regions.d.ts +9 -0
- package/dist/experience-regions.js +122 -0
- package/dist/experience-renderer.d.ts +16 -0
- package/dist/experience-renderer.js +37 -0
- package/dist/experience.d.ts +12 -0
- package/dist/experience.js +23 -0
- package/dist/file-tree-shadcn.d.ts +8 -0
- package/dist/file-tree-shadcn.js +14 -0
- package/dist/file-tree-view.d.ts +64 -0
- package/dist/file-tree-view.js +256 -0
- package/dist/file-tree.d.ts +57 -0
- package/dist/file-tree.js +204 -0
- package/dist/html-preview.d.ts +2 -0
- package/dist/html-preview.js +47 -0
- package/dist/html-viewer.d.ts +13 -0
- package/dist/html-viewer.js +69 -0
- package/dist/html.d.ts +21 -0
- package/dist/html.js +21 -0
- package/dist/index.d.ts +2 -472
- package/dist/index.js +1 -2671
- package/dist/markdown-editor.d.ts +28 -0
- package/dist/markdown-editor.js +230 -0
- package/dist/markdown-extensions.d.ts +16 -0
- package/dist/markdown-extensions.js +96 -0
- package/dist/markdown-icons.d.ts +244 -0
- package/dist/markdown-icons.js +32 -0
- package/dist/markdown-mounted.d.ts +63 -0
- package/dist/markdown-mounted.js +263 -0
- package/dist/markdown-parser.d.ts +4 -0
- package/dist/markdown-parser.js +33 -0
- package/dist/markdown-proposal-diff.d.ts +5 -0
- package/dist/markdown-proposal-diff.js +122 -0
- package/dist/markdown-safety.d.ts +21 -0
- package/dist/markdown-safety.js +135 -0
- package/dist/markdown.d.ts +86 -0
- package/dist/markdown.js +121 -0
- package/dist/native-chat.d.ts +150 -0
- package/dist/native-chat.js +376 -0
- package/dist/pi.d.ts +12 -0
- package/dist/pi.js +1 -0
- package/dist/public-text-buffer.d.ts +5 -0
- package/dist/public-text-buffer.js +1 -0
- package/dist/remote-chat.d.ts +76 -0
- package/dist/remote-chat.js +324 -0
- package/dist/resources.d.ts +34 -0
- package/dist/resources.js +1 -0
- package/dist/text-buffer-state.d.ts +24 -0
- package/dist/text-buffer-state.js +1 -0
- package/dist/text-buffer.d.ts +52 -0
- package/dist/text-buffer.js +315 -0
- package/dist/text-draft-controls.d.ts +9 -0
- package/dist/text-draft-controls.js +33 -0
- package/dist/text-draft-types.d.ts +111 -0
- package/dist/text-draft-types.js +1 -0
- package/dist/text-drafts.d.ts +29 -0
- package/dist/text-drafts.js +378 -0
- package/package.json +226 -58
- package/styles/file-tree.css +46 -0
- package/dist/styles.css +0 -2308
- package/dist/tokens.css +0 -155
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { assertRegionReplacement, experienceRegion } from './experience-region-tree.js';
|
|
2
|
+
function membersOfValidatedJson(text, start) {
|
|
3
|
+
const whitespace = (index) => { while (/\s/.test(text[index] ?? '') && index < text.length)
|
|
4
|
+
index++; return index; };
|
|
5
|
+
const stringEnd = (index) => {
|
|
6
|
+
for (index++; index < text.length; index++) {
|
|
7
|
+
if (text[index] === '\\')
|
|
8
|
+
index++;
|
|
9
|
+
else if (text[index] === '"')
|
|
10
|
+
return index + 1;
|
|
11
|
+
}
|
|
12
|
+
throw new TypeError('Invalid JSON string');
|
|
13
|
+
};
|
|
14
|
+
const valueEnd = (index) => {
|
|
15
|
+
if (text[index] === '"')
|
|
16
|
+
return stringEnd(index);
|
|
17
|
+
if (text[index] !== '{' && text[index] !== '[') {
|
|
18
|
+
while (index < text.length && !/[\s,}\]]/.test(text[index] ?? ''))
|
|
19
|
+
index++;
|
|
20
|
+
return index;
|
|
21
|
+
}
|
|
22
|
+
let depth = 0;
|
|
23
|
+
for (; index < text.length; index++) {
|
|
24
|
+
if (text[index] === '"') {
|
|
25
|
+
index = stringEnd(index) - 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (text[index] === '{' || text[index] === '[')
|
|
29
|
+
depth++;
|
|
30
|
+
if ((text[index] === '}' || text[index] === ']') && --depth === 0)
|
|
31
|
+
return index + 1;
|
|
32
|
+
}
|
|
33
|
+
throw new TypeError('Invalid JSON container');
|
|
34
|
+
};
|
|
35
|
+
start = whitespace(start);
|
|
36
|
+
if (text[start] !== '{')
|
|
37
|
+
throw new TypeError('Expected a JSON object');
|
|
38
|
+
let index = start + 1;
|
|
39
|
+
const entries = [], keys = new Set();
|
|
40
|
+
while (text[whitespace(index)] !== '}') {
|
|
41
|
+
const memberStart = index, keyStart = whitespace(index), keyEnd = stringEnd(keyStart);
|
|
42
|
+
const key = JSON.parse(text.slice(keyStart, keyEnd));
|
|
43
|
+
if (typeof key !== 'string' || keys.has(key))
|
|
44
|
+
throw new TypeError('Ambiguous JSON member');
|
|
45
|
+
keys.add(key);
|
|
46
|
+
const colon = whitespace(keyEnd);
|
|
47
|
+
if (text[colon] !== ':')
|
|
48
|
+
throw new TypeError('Invalid JSON member');
|
|
49
|
+
const begin = whitespace(colon + 1), finish = valueEnd(begin), end = whitespace(finish);
|
|
50
|
+
entries.push({ key, start: memberStart, valueStart: begin, valueEnd: finish, end });
|
|
51
|
+
if (text[end] === '}')
|
|
52
|
+
return { entries, end: end + 1 };
|
|
53
|
+
if (text[end] !== ',')
|
|
54
|
+
throw new TypeError('Invalid JSON object');
|
|
55
|
+
index = end + 1;
|
|
56
|
+
}
|
|
57
|
+
return { entries, end: whitespace(index) + 1 };
|
|
58
|
+
}
|
|
59
|
+
export function replaceRegionText(text, base, next, name) {
|
|
60
|
+
assertRegionReplacement(base, next, name);
|
|
61
|
+
const oldRegion = experienceRegion(base, name), newRegion = experienceRegion(next, name);
|
|
62
|
+
const root = membersOfValidatedJson(text, 0), elementMember = root.entries.find(member => member.key === 'elements');
|
|
63
|
+
if (!elementMember)
|
|
64
|
+
throw new TypeError('Missing experience elements');
|
|
65
|
+
const elements = membersOfValidatedJson(text, elementMember.valueStart), parts = [];
|
|
66
|
+
const last = elements.entries.at(-1);
|
|
67
|
+
for (const member of elements.entries) {
|
|
68
|
+
const end = member === last ? member.valueEnd : member.end;
|
|
69
|
+
if (oldRegion.descendants.has(member.key))
|
|
70
|
+
continue;
|
|
71
|
+
if (member.key !== oldRegion.id) {
|
|
72
|
+
parts.push(text.slice(member.start, end));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const region = membersOfValidatedJson(text, member.valueStart), children = region.entries.find(item => item.key === 'children');
|
|
76
|
+
const value = JSON.stringify(newRegion.node.children);
|
|
77
|
+
parts.push(children
|
|
78
|
+
? text.slice(member.start, children.valueStart) + value + text.slice(children.valueEnd, end)
|
|
79
|
+
: text.slice(member.start, region.end - 1) + `${region.entries.length ? ',' : ''}"children":${value}` + text.slice(region.end - 1, end));
|
|
80
|
+
}
|
|
81
|
+
for (const id of newRegion.descendants)
|
|
82
|
+
parts.push(`${JSON.stringify(id)}:${JSON.stringify(next.elements[id])}`);
|
|
83
|
+
return text.slice(0, elementMember.valueStart + 1) + parts.join(',') + text.slice(last?.valueEnd ?? elements.end - 1);
|
|
84
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExperienceDescriptor } from './experience-compose.js';
|
|
2
|
+
export declare function experienceRegion(descriptor: ExperienceDescriptor, name: string): {
|
|
3
|
+
id: string;
|
|
4
|
+
node: {
|
|
5
|
+
type: "boring/stack" | "boring/row" | "boring/grid" | "boring/cell" | "boring/generated";
|
|
6
|
+
props: Record<string, unknown>;
|
|
7
|
+
children: string[];
|
|
8
|
+
};
|
|
9
|
+
path: string[];
|
|
10
|
+
descendants: Set<string>;
|
|
11
|
+
props: {
|
|
12
|
+
region: string;
|
|
13
|
+
candidates: string[];
|
|
14
|
+
maxElements: number;
|
|
15
|
+
minWidth: number;
|
|
16
|
+
regenerate: ("open" | "phase" | "request")[];
|
|
17
|
+
kinds?: string[] | undefined;
|
|
18
|
+
prompt?: string | undefined;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare function assertRegionReplacement(base: ExperienceDescriptor, next: ExperienceDescriptor, name: string): void;
|
|
22
|
+
//# sourceMappingURL=experience-region-tree.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { layoutProps } from './experience-catalog.js';
|
|
2
|
+
export function experienceRegion(descriptor, name) {
|
|
3
|
+
const path = [];
|
|
4
|
+
const find = (id) => {
|
|
5
|
+
const node = descriptor.elements[id];
|
|
6
|
+
if (!node)
|
|
7
|
+
return false;
|
|
8
|
+
path.push(id);
|
|
9
|
+
if (node.type === 'boring/generated' && node.props.region === name)
|
|
10
|
+
return true;
|
|
11
|
+
for (const child of node.children)
|
|
12
|
+
if (find(child))
|
|
13
|
+
return true;
|
|
14
|
+
path.pop();
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
if (!find(descriptor.root))
|
|
18
|
+
throw new TypeError('Unknown generated region');
|
|
19
|
+
const id = path.at(-1);
|
|
20
|
+
const node = id === undefined ? undefined : descriptor.elements[id];
|
|
21
|
+
if (!id || !node)
|
|
22
|
+
throw new TypeError('Unknown generated region');
|
|
23
|
+
const descendants = new Set();
|
|
24
|
+
const collect = (key) => {
|
|
25
|
+
descendants.add(key);
|
|
26
|
+
for (const child of descriptor.elements[key]?.children ?? [])
|
|
27
|
+
collect(child);
|
|
28
|
+
};
|
|
29
|
+
for (const child of node.children)
|
|
30
|
+
collect(child);
|
|
31
|
+
return { id, node, path, descendants, props: layoutProps['boring/generated'].parse(node.props) };
|
|
32
|
+
}
|
|
33
|
+
export function assertRegionReplacement(base, next, name) {
|
|
34
|
+
const region = experienceRegion(base, name), replacement = experienceRegion(next, name);
|
|
35
|
+
const header = ({ elements: _elements, ...rest }) => rest;
|
|
36
|
+
if (region.id !== replacement.id || JSON.stringify(header(base)) !== JSON.stringify(header(next)))
|
|
37
|
+
throw new TypeError('Region replacement changed the fixed layout');
|
|
38
|
+
for (const [id, node] of Object.entries(base.elements)) {
|
|
39
|
+
if (region.descendants.has(id))
|
|
40
|
+
continue;
|
|
41
|
+
const candidate = next.elements[id];
|
|
42
|
+
const before = id === region.id ? { ...node, children: [] } : node;
|
|
43
|
+
const after = id === region.id && candidate ? { ...candidate, children: [] } : candidate;
|
|
44
|
+
if (JSON.stringify(before) !== JSON.stringify(after))
|
|
45
|
+
throw new TypeError('Region replacement changed a fixed element');
|
|
46
|
+
}
|
|
47
|
+
for (const id of Object.keys(next.elements))
|
|
48
|
+
if (!replacement.descendants.has(id) && !Object.hasOwn(base.elements, id))
|
|
49
|
+
throw new TypeError('Region replacement added a fixed element');
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ExperienceCompositionOptions, ExperienceCompositionSnapshot } from './experience-compose.js';
|
|
2
|
+
export type ExperienceRegionTrigger = 'open' | 'phase' | 'request';
|
|
3
|
+
export interface ExperienceRegionCompositionOptions extends Omit<ExperienceCompositionOptions, 'fallback' | 'layouts'> {
|
|
4
|
+
readonly descriptor: unknown;
|
|
5
|
+
readonly region: string;
|
|
6
|
+
readonly trigger: ExperienceRegionTrigger;
|
|
7
|
+
}
|
|
8
|
+
export declare function composeExperienceRegion(options: ExperienceRegionCompositionOptions): AsyncGenerator<ExperienceCompositionSnapshot>;
|
|
9
|
+
//# sourceMappingURL=experience-regions.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { composeExperience, validateExperience } from './experience-compose.js';
|
|
3
|
+
import { compositionCandidate, compositionDefinition, compositionLimits, compositionId as newCompositionId } from './experience-composition-schema.js';
|
|
4
|
+
import { experienceRegion } from './experience-region-tree.js';
|
|
5
|
+
import { layoutProps } from './experience-catalog.js';
|
|
6
|
+
function prepareRegion(base, options, access) {
|
|
7
|
+
const region = experienceRegion(base, options.region);
|
|
8
|
+
if (!region.props.regenerate.includes(options.trigger))
|
|
9
|
+
throw new TypeError('Region trigger is not enabled');
|
|
10
|
+
const prompt = region.props.prompt;
|
|
11
|
+
if (prompt !== undefined && prompt !== options.intent && prompt !== options.definition.intents[options.intent])
|
|
12
|
+
throw new TypeError('Region prompt is not a registered intent');
|
|
13
|
+
const constraints = region.path.flatMap(id => {
|
|
14
|
+
const node = base.elements[id];
|
|
15
|
+
return node?.type === 'boring/generated' ? [experienceRegion(base, String(node.props.region))] : [];
|
|
16
|
+
});
|
|
17
|
+
const allowedKind = (kind) => Object.hasOwn(base.kinds, kind) && constraints.every(item => item.props.kinds === undefined || item.props.kinds.includes(kind));
|
|
18
|
+
const used = new Map();
|
|
19
|
+
for (const [id, node] of Object.entries(base.elements))
|
|
20
|
+
if (!region.descendants.has(id) && node.type === 'boring/cell') {
|
|
21
|
+
const { ref } = layoutProps['boring/cell'].parse(node.props);
|
|
22
|
+
used.set(ref, (used.get(ref) ?? 0) + 1);
|
|
23
|
+
}
|
|
24
|
+
const cells = access.cells.flatMap(cell => {
|
|
25
|
+
const remaining = (cell.maxUses ?? 1) - (used.get(cell.ref) ?? 0);
|
|
26
|
+
return remaining > 0 && allowedKind('boring/cell') && allowedKind(cell.kind)
|
|
27
|
+
&& constraints.every(item => item.props.candidates.includes(cell.ref) || item.props.candidates.includes(cell.kind))
|
|
28
|
+
? [{ ...cell, maxUses: remaining }] : [];
|
|
29
|
+
});
|
|
30
|
+
const candidates = options.candidates.filter(candidate => cells.some(cell => cell.ref === candidate.ref));
|
|
31
|
+
if (candidates.length === 0)
|
|
32
|
+
throw new TypeError('Region has no eligible candidates');
|
|
33
|
+
const layouts = ['boring/stack', 'boring/row', 'boring/grid'].filter(allowedKind);
|
|
34
|
+
const outside = Object.keys(base.elements).length - region.descendants.size;
|
|
35
|
+
const maxElements = Math.min(options.limits.maxElements, 200 - outside, ...constraints.map(item => item.props.maxElements - (item.descendants.size - region.descendants.size)));
|
|
36
|
+
if (maxElements < 1)
|
|
37
|
+
throw new TypeError('Region has no available element budget');
|
|
38
|
+
const maxDepth = Math.min(options.limits.maxDepth, 25 - region.path.length);
|
|
39
|
+
if (maxDepth < 1)
|
|
40
|
+
throw new TypeError('Region has no available depth');
|
|
41
|
+
const limits = { ...options.limits, maxElements, maxDepth };
|
|
42
|
+
return { region, options: { ...options, cells, candidates, layouts, limits, fallback: null } };
|
|
43
|
+
}
|
|
44
|
+
function mergeRegion(base, prepared, subtree, prefix, access) {
|
|
45
|
+
const elements = new Map(Object.entries(base.elements).filter(([id]) => !prepared.region.descendants.has(id)));
|
|
46
|
+
const names = new Map(Object.keys(subtree.elements).map((id, index) => [id, `${prefix}_${index}`]));
|
|
47
|
+
const renamed = (id) => {
|
|
48
|
+
const name = names.get(id);
|
|
49
|
+
if (!name || Object.hasOwn(base.elements, name))
|
|
50
|
+
throw new TypeError('Invalid region element identity');
|
|
51
|
+
return name;
|
|
52
|
+
};
|
|
53
|
+
for (const [kind, version] of Object.entries(subtree.kinds))
|
|
54
|
+
if (base.kinds[kind] !== version)
|
|
55
|
+
throw new TypeError('Undeclared region kind');
|
|
56
|
+
for (const [id, node] of Object.entries(subtree.elements))
|
|
57
|
+
elements.set(renamed(id), { ...node, children: node.children.map(renamed) });
|
|
58
|
+
elements.set(prepared.region.id, { ...prepared.region.node, children: [renamed(subtree.root)] });
|
|
59
|
+
return validateExperience({ ...base, elements: Object.fromEntries(elements) }, access);
|
|
60
|
+
}
|
|
61
|
+
export async function* composeExperienceRegion(options) {
|
|
62
|
+
const compositionId = newCompositionId(), prefix = `r_${compositionId}`;
|
|
63
|
+
const { canView, evaluate, signal, intent, region, trigger } = options;
|
|
64
|
+
const access = { cells: options.cells.map(cell => ({ ...cell })), canView };
|
|
65
|
+
let base, prepared;
|
|
66
|
+
let sequence = 0;
|
|
67
|
+
const captured = (() => {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = compositionDefinition.parse(options.definition);
|
|
70
|
+
const definition = { name: parsed.name, intents: parsed.intents, kinds: parsed.kinds, ...(parsed.title === undefined ? {} : { title: parsed.title }) };
|
|
71
|
+
const candidates = z.array(compositionCandidate).max(128).parse(options.candidates).map(({ ref, metadata, root, resource }) => ({ ref, metadata, ...(root === undefined ? {} : { root }), ...(resource === undefined ? {} : { resource }) }));
|
|
72
|
+
return { definition, candidates, limits: compositionLimits.parse(options.limits) };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
try {
|
|
79
|
+
base = validateExperience(options.descriptor, access);
|
|
80
|
+
signal.throwIfAborted();
|
|
81
|
+
if (captured)
|
|
82
|
+
prepared = prepareRegion(base, { ...access, ...captured, evaluate, signal, intent, region, trigger, descriptor: base }, access);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
prepared = undefined;
|
|
86
|
+
}
|
|
87
|
+
const fallback = () => {
|
|
88
|
+
try {
|
|
89
|
+
return base === undefined ? null : validateExperience(base, access);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const snapshot = (reason) => Object.freeze({ compositionId, sequence: sequence++, kind: 'default', descriptor: fallback(), reason });
|
|
96
|
+
yield snapshot('pending');
|
|
97
|
+
if (!base || !prepared) {
|
|
98
|
+
yield snapshot(signal.aborted ? 'cancelled' : 'unavailable');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
for await (const event of composeExperience({ ...prepared.options, evaluate: request => {
|
|
103
|
+
if (!fallback())
|
|
104
|
+
throw new TypeError('Region default is no longer available');
|
|
105
|
+
signal.throwIfAborted();
|
|
106
|
+
return evaluate(request);
|
|
107
|
+
} })) {
|
|
108
|
+
signal.throwIfAborted();
|
|
109
|
+
if (event.kind === 'default') {
|
|
110
|
+
if (event.reason !== 'pending')
|
|
111
|
+
yield snapshot(event.reason);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const descriptor = mergeRegion(base, prepared, event.descriptor, prefix, access);
|
|
115
|
+
signal.throwIfAborted();
|
|
116
|
+
yield Object.freeze({ compositionId, sequence: sequence++, kind: event.kind, descriptor });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
yield snapshot(signal.aborted ? 'cancelled' : 'unavailable');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ExperienceAccess, ExperienceCell, ExperienceDescriptor } from './experience-compose.js';
|
|
3
|
+
/** A cell the host can show: its access record plus how to render it. */
|
|
4
|
+
export interface RenderedExperienceCell extends ExperienceCell {
|
|
5
|
+
readonly render: () => ReactNode;
|
|
6
|
+
}
|
|
7
|
+
interface LiveCells {
|
|
8
|
+
readonly cells: readonly RenderedExperienceCell[];
|
|
9
|
+
readonly canView: ExperienceAccess['canView'];
|
|
10
|
+
readonly kinds: ExperienceDescriptor['kinds'];
|
|
11
|
+
}
|
|
12
|
+
export declare function ExperienceRenderer({ descriptor, cells, canView }: Omit<LiveCells, 'kinds'> & {
|
|
13
|
+
readonly descriptor: ExperienceDescriptor;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=experience-renderer.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { defineRegistry, JSONUIProvider, Renderer } from '@json-render/react';
|
|
5
|
+
import { experienceCatalog } from './experience-catalog.js';
|
|
6
|
+
const CellContext = createContext(null);
|
|
7
|
+
const MinimumWidth = createContext(0);
|
|
8
|
+
const gaps = { small: '0.5rem', medium: '1rem', large: '1.5rem' };
|
|
9
|
+
const { registry } = defineRegistry(experienceCatalog, { components: {
|
|
10
|
+
'boring/stack': ({ props, children }) => _jsx("div", { "data-boring": "experience-stack", style: { display: 'flex', flexDirection: 'column', gap: gaps[props.gap] }, children: children }),
|
|
11
|
+
'boring/row': ({ props, children }) => _jsx("div", { "data-boring": "experience-row", style: { display: 'flex', flexWrap: 'wrap', gap: gaps[props.gap] }, children: children }),
|
|
12
|
+
'boring/grid': ({ props, children }) => {
|
|
13
|
+
const minimumWidth = useContext(MinimumWidth);
|
|
14
|
+
return _jsx("div", { "data-boring": "experience-grid", style: { display: 'grid', gridTemplateColumns: `repeat(${props.columns}, minmax(${minimumWidth}px, 1fr))`, gap: gaps[props.gap] }, children: children });
|
|
15
|
+
},
|
|
16
|
+
'boring/cell': ({ props }) => {
|
|
17
|
+
const live = useContext(CellContext);
|
|
18
|
+
const matches = live?.cells.filter(cell => cell.ref === props.ref) ?? [];
|
|
19
|
+
const cell = matches.length === 1 ? matches[0] : undefined;
|
|
20
|
+
let permitted = false;
|
|
21
|
+
try {
|
|
22
|
+
permitted = !!cell && live?.kinds[cell.kind] === cell.version && live.canView(cell.ref) === true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
permitted = false;
|
|
26
|
+
}
|
|
27
|
+
const minimumWidth = useContext(MinimumWidth);
|
|
28
|
+
return _jsx("div", { "data-boring": "experience-cell", style: { minWidth: minimumWidth, flexShrink: 0 }, children: permitted && cell ? _jsx(cell.render, {}) : _jsx("p", { role: "status", children: "Cell unavailable" }) });
|
|
29
|
+
},
|
|
30
|
+
'boring/generated': ({ props, children }) => {
|
|
31
|
+
const minimumWidth = Math.max(useContext(MinimumWidth), props.minWidth);
|
|
32
|
+
return _jsx("div", { "data-boring": "experience-region", "data-region": props.region, style: { overflowX: 'auto', minWidth: 0 }, children: _jsx(MinimumWidth, { value: minimumWidth, children: children }) });
|
|
33
|
+
},
|
|
34
|
+
} });
|
|
35
|
+
export function ExperienceRenderer({ descriptor, cells, canView }) {
|
|
36
|
+
return _jsx(CellContext, { value: { cells, canView, kinds: descriptor.kinds }, children: _jsx(JSONUIProvider, { registry: registry, children: _jsx(Renderer, { spec: descriptor, registry: registry }) }) });
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RenderedExperienceCell } from './experience-renderer.js';
|
|
2
|
+
import type { ExperienceAccess } from './experience-compose.js';
|
|
3
|
+
export { ExperienceRenderer } from './experience-renderer.js';
|
|
4
|
+
export type { RenderedExperienceCell } from './experience-renderer.js';
|
|
5
|
+
export interface ExperienceProps {
|
|
6
|
+
readonly descriptor: unknown;
|
|
7
|
+
readonly cells: readonly RenderedExperienceCell[];
|
|
8
|
+
readonly canView: ExperienceAccess['canView'];
|
|
9
|
+
readonly className?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function Experience(props: ExperienceProps): import("react").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=experience.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { ExperienceRenderer } from './experience-renderer.js';
|
|
5
|
+
import { validateExperience } from './experience-compose.js';
|
|
6
|
+
export { ExperienceRenderer } from './experience-renderer.js';
|
|
7
|
+
function select(props) {
|
|
8
|
+
try {
|
|
9
|
+
return { input: props.descriptor, kind: 'ready', descriptor: validateExperience(props.descriptor, props) };
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return { input: props.descriptor, kind: 'invalid' };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function Experience(props) {
|
|
16
|
+
const [current, setCurrent] = useState(() => select(props));
|
|
17
|
+
const candidate = current.input === props.descriptor && current.kind === 'ready' ? null : select(props);
|
|
18
|
+
const offered = candidate?.kind === 'invalid' && current.kind === 'invalid' ? null : candidate;
|
|
19
|
+
return _jsxs("section", { "data-boring": "experience", className: props.className, "aria-label": current.kind === 'ready' ? current.descriptor.title ?? current.descriptor.name : 'Experience unavailable', children: [offered && (offered.kind === 'ready'
|
|
20
|
+
? _jsx("button", { type: "button", onClick: () => setCurrent(offered), children: "Use proposed layout" })
|
|
21
|
+
: _jsx("p", { role: "alert", children: "Proposed layout unavailable. The current layout has been retained." })), current.kind === 'invalid' ? _jsx("p", { role: "alert", children: "Experience unavailable" })
|
|
22
|
+
: _jsx(ExperienceRenderer, { descriptor: current.descriptor, cells: props.cells, canView: props.canView })] });
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export declare function SidebarMenu({ className, ...props }: ComponentProps<'ul'>): import("react").JSX.Element;
|
|
3
|
+
export declare function SidebarMenuItem({ className, ...props }: ComponentProps<'li'>): import("react").JSX.Element;
|
|
4
|
+
export declare function SidebarMenuButton({ className, isActive, ...props }: ComponentProps<'button'> & {
|
|
5
|
+
readonly isActive?: boolean;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function SidebarMenuSub({ className, ...props }: ComponentProps<'ul'>): import("react").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=file-tree-shadcn.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
export function SidebarMenu({ className = '', ...props }) {
|
|
4
|
+
return _jsx("ul", { "data-slot": "sidebar-menu", "data-sidebar": "menu", className: `flex w-full min-w-0 flex-col gap-1 ${className}`, ...props });
|
|
5
|
+
}
|
|
6
|
+
export function SidebarMenuItem({ className = '', ...props }) {
|
|
7
|
+
return _jsx("li", { "data-slot": "sidebar-menu-item", "data-sidebar": "menu-item", className: `group/menu-item relative ${className}`, ...props });
|
|
8
|
+
}
|
|
9
|
+
export function SidebarMenuButton({ className = '', isActive = false, ...props }) {
|
|
10
|
+
return _jsx("button", { type: "button", "data-slot": "sidebar-menu-button", "data-sidebar": "menu-button", "data-size": "default", "data-active": isActive, className: `peer/menu-button flex h-8 w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 ${className}`, ...props });
|
|
11
|
+
}
|
|
12
|
+
export function SidebarMenuSub({ className = '', ...props }) {
|
|
13
|
+
return _jsx("ul", { "data-slot": "sidebar-menu-sub", "data-sidebar": "menu-sub", className: `mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 ${className}`, ...props });
|
|
14
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RevisionProvider } from '@hachej/boring-files/revision';
|
|
2
|
+
import type { FileTreeController } from './file-tree.js';
|
|
3
|
+
/** Every word of the tree, so a host can translate it. Functions receive what they name. */
|
|
4
|
+
export declare const defaultFileTreeLabels: {
|
|
5
|
+
region: string;
|
|
6
|
+
tree: string;
|
|
7
|
+
search: string;
|
|
8
|
+
searchPlaceholder: string;
|
|
9
|
+
refresh: string;
|
|
10
|
+
upload: string;
|
|
11
|
+
uploadFiles: string;
|
|
12
|
+
uploads: string;
|
|
13
|
+
loading: string;
|
|
14
|
+
retry: string;
|
|
15
|
+
noMatches: string;
|
|
16
|
+
empty: string;
|
|
17
|
+
loadMore: string;
|
|
18
|
+
history: string;
|
|
19
|
+
historyOf: (path: string) => string;
|
|
20
|
+
closeHistory: string;
|
|
21
|
+
loadingHistory: string;
|
|
22
|
+
noHistory: string;
|
|
23
|
+
earlierVersion: string;
|
|
24
|
+
historyUnavailable: string;
|
|
25
|
+
revisionUnavailable: string;
|
|
26
|
+
readOnlyRevision: (bytes: number) => string;
|
|
27
|
+
binaryFile: (mediaType: string) => string;
|
|
28
|
+
uploading: string;
|
|
29
|
+
saved: string;
|
|
30
|
+
unconfirmed: string;
|
|
31
|
+
checkUpload: string;
|
|
32
|
+
uploadStatusUnavailable: string;
|
|
33
|
+
uploadFailed: (name: string) => string;
|
|
34
|
+
};
|
|
35
|
+
export type FileTreeLabels = typeof defaultFileTreeLabels;
|
|
36
|
+
/** The tree's words in French (`locale="fr"`). */
|
|
37
|
+
export declare const frenchFileTreeLabels: FileTreeLabels;
|
|
38
|
+
/** The languages the tree ships with; anything else is set word by word through `labels`. */
|
|
39
|
+
export type FileTreeLocale = 'en' | 'fr';
|
|
40
|
+
export interface FileTreeProps {
|
|
41
|
+
readonly revisionProvider: RevisionProvider;
|
|
42
|
+
readonly onOpen?: (path: string) => void;
|
|
43
|
+
readonly selectedPath?: string;
|
|
44
|
+
readonly className?: string;
|
|
45
|
+
readonly uploadDirectory?: string;
|
|
46
|
+
/** The tree's language. Default `en`. */
|
|
47
|
+
readonly locale?: FileTreeLocale;
|
|
48
|
+
/** Words over the locale's, for a language or a wording the tree does not ship with. */
|
|
49
|
+
readonly labels?: Partial<FileTreeLabels>;
|
|
50
|
+
/** Offer the Upload button. Default true. */
|
|
51
|
+
readonly upload?: boolean;
|
|
52
|
+
/** Offer each file's History button. Default true. */
|
|
53
|
+
readonly history?: boolean;
|
|
54
|
+
/** Reload the open folders and the current search whenever this value changes (for example after the agent saves a file). */
|
|
55
|
+
readonly refreshKey?: unknown;
|
|
56
|
+
}
|
|
57
|
+
/** One borrowed binding powers browsing, search, upload and history. Historical previews never replace an editor's draft. */
|
|
58
|
+
export declare function FileTree(props: FileTreeProps): import("react").JSX.Element;
|
|
59
|
+
export interface FileTreeViewProps extends Omit<FileTreeProps, 'revisionProvider'> {
|
|
60
|
+
readonly controller: FileTreeController;
|
|
61
|
+
}
|
|
62
|
+
/** Renders a borrowed controller, for a workspace whose chat and tree share upload state. */
|
|
63
|
+
export declare function FileTreeView({ controller, onOpen, selectedPath, className, uploadDirectory, locale, labels: given, upload, history, refreshKey }: FileTreeViewProps): import("react").JSX.Element;
|
|
64
|
+
//# sourceMappingURL=file-tree-view.d.ts.map
|