@getaster/elements-preview 0.0.0-stage → 0.1.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ import { AsterElement, compositeTypes as t, define, each } from "@getaster/elements";
2
+ import { html, nothing } from "lit-html";
3
+ function levelsValid(value) {
4
+ if (!Array.isArray(value))
5
+ return false;
6
+ const view = (v) => v === nothing || !!v && typeof v === "object" && "_$litType$" in v;
7
+ return new Set(value.map(l => l?.id)).size === value.length && value.every(l => l && typeof l.id === "string" && l.id.length > 0 && typeof l.title === "string" && l.title.trim().length > 0
8
+ && (l.summary === undefined || typeof l.summary === "string") && (l.actions === undefined || view(l.actions)) && view(l.content));
9
+ }
10
+ /** Controlled navigation; hosts retain or dispose their own keyed sessions. */
11
+ export class ContextualHierarchy extends AsterElement {
12
+ static tag = "aster-contextual-hierarchy";
13
+ static contract = Object.freeze({
14
+ levels: t.optional(t.custom("context levels with unique IDs and templates", levelsValid, { type: "array", items: { type: "object", required: ["id", "title", "content"], properties: { id: { type: "string" }, title: { type: "string" }, summary: { type: "string" }, content: { asterType: "View" }, actions: { asterType: "View" } } } })),
15
+ navigationVisible: t.optional(t.boolean),
16
+ motion: t.optional(t.custom("none or slide", (v) => v === "none" || v === "slide", { enum: ["none", "slide"] })),
17
+ });
18
+ #levels = this.prop([]);
19
+ #navigationVisible = this.prop(true);
20
+ #motion = this.prop("none");
21
+ #direction = this.prop("forward");
22
+ #id = `aster-hierarchy-${crypto.randomUUID()}`;
23
+ #positions = new Map();
24
+ #opener = null;
25
+ #transition;
26
+ /** Pointer activation does not focus buttons in every browser; pass the actual opener. */
27
+ navigate(levels, opener) {
28
+ this.#opener = opener ?? null;
29
+ try {
30
+ this.levels = levels;
31
+ }
32
+ finally {
33
+ this.#opener = null;
34
+ }
35
+ }
36
+ get levels() { return this.#levels.value; }
37
+ get navigationVisible() { return this.#navigationVisible.value; }
38
+ set navigationVisible(v) { this.#navigationVisible.value = v; }
39
+ get motion() { return this.#motion.value; }
40
+ set motion(v) {
41
+ if (v !== "none" && v !== "slide")
42
+ throw new TypeError("Unknown hierarchy motion");
43
+ this.#motion.value = v;
44
+ }
45
+ set levels(v) {
46
+ if (!levelsValid(v))
47
+ throw new TypeError("Hierarchy levels need unique IDs, titles and content templates");
48
+ const old = this.#levels.peek(), previous = old.at(-1), next = v.at(-1);
49
+ if (previous && previous.id !== next?.id) {
50
+ const pane = this.#pane(previous.id);
51
+ const active = this.#opener ?? document.activeElement;
52
+ this.#positions.set(previous.id, { top: pane?.scrollTop ?? 0, focus: active instanceof HTMLElement && pane?.contains(active) ? active : null });
53
+ }
54
+ if (next && previous && previous.id !== next.id) {
55
+ const back = old.some(l => l.id === next.id);
56
+ this.#direction.value = back ? "back" : "forward";
57
+ this.#transition = { back, id: next.id };
58
+ }
59
+ const ids = new Set(v.map(l => l.id));
60
+ for (const id of this.#positions.keys())
61
+ if (!ids.has(id))
62
+ this.#positions.delete(id);
63
+ this.#levels.value = [...v];
64
+ }
65
+ #pane(id) {
66
+ return [...this.querySelectorAll("[data-hierarchy-level]")].find(el => el.dataset.hierarchyLevel === id && el.closest("aster-contextual-hierarchy") === this);
67
+ }
68
+ render() {
69
+ const levels = this.#levels.value, current = levels.at(-1);
70
+ return html `<div class="aster-hierarchy" data-motion=${this.#motion.value} data-direction=${this.#direction.value}>
71
+ ${this.#navigationVisible.value && levels.length > 1 ? html `<nav aria-label="Context path"><button type="button" @click=${() => this.emit("back", { id: levels.at(-2).id })}>← Back to ${levels.at(-2).title}</button>
72
+ <span>${levels.map(l => l.title).join(" / ")}</span></nav>` : nothing}
73
+ ${each(levels, l => l.id, l => html `<section class="aster-hierarchy-level" data-hierarchy-level=${l.id} ?hidden=${l.id !== current?.id} ?inert=${l.id !== current?.id}
74
+ aria-labelledby=${`${this.#id}-${encodeURIComponent(l.id)}`}>
75
+ <header><div><h3 tabindex="-1" id=${`${this.#id}-${encodeURIComponent(l.id)}`}>${l.title}</h3>${l.summary ? html `<p>${l.summary}</p>` : nothing}</div>${l.actions ?? nothing}</header>
76
+ ${l.content}
77
+ </section>`)}
78
+ </div>`;
79
+ }
80
+ updated() {
81
+ const transition = this.#transition;
82
+ if (!transition)
83
+ return;
84
+ this.#transition = undefined;
85
+ const pane = this.#pane(transition.id), position = this.#positions.get(transition.id);
86
+ if (!pane)
87
+ return;
88
+ pane.scrollTop = transition.back ? position?.top ?? 0 : 0;
89
+ const focus = transition.back && position?.focus?.isConnected && pane.contains(position.focus) ? position.focus : pane.querySelector("h3");
90
+ focus?.focus({ preventScroll: true });
91
+ }
92
+ }
93
+ define("aster-contextual-hierarchy", ContextualHierarchy);
@@ -0,0 +1,3 @@
1
+ export { ConversationSession, type ConversationEntry, type ConversationAdapter, type ConversationWindow, type ConversationTarget, type ConversationSessionOptions, type ConversationOperation } from "./conversation/session.ts";
2
+ export { ContextualHierarchy, type HierarchyLevel } from "./hierarchy/hierarchy.ts";
3
+ export { ConversationReader, type ConversationEntryRenderer, type ConversationContextProvider, type ConversationPassage, type ConversationPassageProvider, type ConversationReaderActions, type ConversationReadingLocation } from "./conversation/view.ts";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { ConversationSession } from "./conversation/session.js";
2
+ export { ContextualHierarchy } from "./hierarchy/hierarchy.js";
3
+ export { ConversationReader } from "./conversation/view.js";
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@getaster/elements-preview",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-preview.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.js",
9
+ "default": "./dist/index.js"
10
+ },
11
+ "./styles/*": "./styles/*",
12
+ "./conversation": {
13
+ "types": "./dist/conversation/session.d.ts",
14
+ "import": "./dist/conversation/session.js"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "@getaster/data": "0.1.0-preview.1",
19
+ "@getaster/elements": "0.1.0-preview.1",
20
+ "@preact/signals-core": "1.14.4",
21
+ "lit-html": "3.3.3"
22
+ },
23
+ "license": "Apache-2.0",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/GetAster/aster-ui.git",
27
+ "directory": "packages/elements-preview"
28
+ },
29
+ "types": "./dist/index.d.ts",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "LICENSE",
36
+ "NOTICE",
37
+ "README.md",
38
+ "styles"
39
+ ],
40
+ "engines": {
41
+ "node": ">=22"
42
+ },
43
+ "sideEffects": [
44
+ "./dist/**/*.js",
45
+ "./styles/*.css"
46
+ ]
47
+ }
@@ -0,0 +1,115 @@
1
+ @layer aster.elements {
2
+ aster-conversation-reader{display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;font:inherit;color:var(--aster-text);background:var(--aster-surface)}
3
+ .ac-reader{display:grid;grid-template-columns:minmax(0,1fr);flex:1;min-height:0;overflow:hidden}
4
+ .ac-reader[data-context]{grid-template-columns:minmax(0,1fr) minmax(300px,37%)}
5
+ aster-conversation-pane{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:var(--aster-surface)}
6
+ .ac-context{display:flex;flex-direction:column;min-width:0;min-height:0;border-left:1px solid var(--aster-rule);background:var(--aster-sunk)}
7
+ .ac-context aster-conversation-pane{flex:1;background:transparent}
8
+ .ac-return{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px 16px;border-bottom:1px solid var(--aster-rule)}
9
+ .ac-return button{font-size:11px;color:var(--aster-accent);padding:5px 0}
10
+ .ac-return button:last-child{font-size:24px;line-height:1;width:30px;height:30px;display:grid;place-items:center}
11
+ .ac-pane-header{padding:20px 24px 14px;border-bottom:1px solid var(--aster-rule);display:flex;align-items:center;justify-content:space-between;gap:16px}
12
+ .ac-focus-launch{color:var(--aster-accent);width:34px;height:34px;display:grid;place-items:center;font-size:20px;flex:none}
13
+ .ac-pane-header h2{margin:0;font-size:18px;line-height:1.3;letter-spacing:-.4px;overflow-wrap:anywhere}
14
+ .ac-kicker{margin:0 0 5px;color:var(--aster-accent);font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.12em}
15
+ .ac-tools{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:7px 20px;color:var(--aster-text-2);font-size:10px;border-bottom:1px solid var(--aster-rule-2)}
16
+ .ac-tools>div{display:flex;align-items:center;gap:8px}
17
+ .ac-tools button{padding:5px;color:var(--aster-accent);min-width:26px;min-height:28px}
18
+ .ac-scroll{flex:1;min-height:0;overflow:auto;overflow-anchor:none;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--aster-rule) transparent;padding:16px 24px 20px;scroll-behavior:auto}
19
+ .ac-entries{list-style:none;margin:0;padding:0}
20
+ .ac-entries>li{padding:0;margin:0 0 19px}
21
+ .ac-entry{position:relative;padding:3px 0;scroll-margin:14px;overflow-wrap:anywhere}
22
+ .ac-entry>header{min-height:32px;padding-inline-end:76px;display:flex;gap:9px;align-items:baseline;flex-wrap:wrap;margin-bottom:7px}
23
+ .ac-entry strong{font-size:12px;font-weight:650}
24
+ .ac-entry time{font-size:10px;color:var(--aster-text-2)}
25
+ .ac-body{white-space:pre-wrap;max-width:76ch;font-size:12px;line-height:1.7}
26
+ .ac-entry[data-kind=note]{border:1px solid var(--aster-rule);border-left:3px solid var(--aster-caution);background:color-mix(in oklch,var(--aster-caution) 6%,transparent);padding:12px;border-radius:6px}
27
+ .ac-entry[data-kind=event]{font-size:11px;color:var(--aster-text-2);padding:8px 12px;border-left:2px solid var(--aster-rule)}
28
+ .ac-entry[data-kind=event] .ac-body{font-size:11px}
29
+ .ac-badge{font-size:9px;border:1px solid var(--aster-rule);padding:2px 6px;border-radius:4px;color:var(--aster-text-2)}
30
+ .ac-entry-actions{display:flex;gap:8px 18px;flex-wrap:wrap;margin-top:6px}
31
+ .ac-entry-actions button{padding:4px 0;color:var(--aster-accent);font-size:10px;min-height:28px}
32
+ .ac-edge{text-align:center;padding:0 0 18px;color:var(--aster-text-2)}
33
+ .ac-edge button{border:1px solid var(--aster-rule);border-radius:6px;padding:7px 12px;background:var(--aster-sunk);font-size:10px}
34
+ .ac-edge button:disabled{border-color:transparent;background:transparent;cursor:default}
35
+ .ac-composer{margin:0 16px 14px;border:1px solid var(--aster-rule);border-radius:8px;padding:11px 13px;flex:none;background:var(--aster-surface)}
36
+ .ac-composer label{display:block;font-size:10px;font-weight:600;color:var(--aster-text-2)}
37
+ .ac-composer textarea{display:block;width:100%;border:0;resize:vertical;min-height:52px;max-height:160px;background:transparent;padding:6px 0;margin:6px 0;font-size:12px;color:var(--aster-text);line-height:1.6;field-sizing:content}
38
+ .ac-composer footer{display:flex;justify-content:space-between;align-items:center;gap:12px}
39
+ .ac-composer footer>span{font-size:9px;color:var(--aster-text-2)}
40
+ .ac-composer button[type=submit]{font-size:11px;white-space:nowrap;background:var(--aster-accent);color:var(--aster-on-accent);padding:7px 12px;border-radius:5px;min-height:32px}
41
+ .ac-composer button:disabled{opacity:.5}
42
+ .ac-feedback{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;padding:3px 18px;min-height:44px;font-size:10px;color:var(--aster-text-2)}
43
+ .ac-feedback button{color:var(--aster-accent);padding:4px 0}
44
+ .ac-error{color:var(--aster-negative);font-size:11px;margin:9px 0 0}
45
+ .ac-selection{display:flex;align-items:center;gap:10px;padding:0;margin-left:auto;font-size:10px;flex-wrap:wrap}
46
+ .ac-selection span{color:var(--aster-text-2)}
47
+ .ac-selection button{color:var(--aster-accent);padding:5px 0}
48
+ .ac-selection button:last-child{margin-left:auto;min-width:24px}
49
+ .ac-reader-feedback{font-size:11px;color:var(--aster-negative);padding-inline:16px}
50
+ .ac-empty{font-size:12px;color:var(--aster-text-2);padding:20px}
51
+ .ac-sr{position:absolute;width:1px;height:1px;clip-path:inset(50%);overflow:hidden;white-space:nowrap}
52
+ .ac-email{border:1px solid var(--aster-rule);border-radius:7px;padding:15px;max-width:78ch;background:var(--aster-surface);font-size:12px;line-height:1.75}
53
+ .ac-email p{white-space:pre-wrap;margin:0}
54
+ .ac-email details{border-top:1px solid var(--aster-rule);margin-top:12px;padding-top:9px;color:var(--aster-text-2);font-size:11px}
55
+ .ac-focus-dialog{position:fixed;inset:0;width:100vw;max-width:none;height:100dvh;max-height:none;margin:0;padding:0;border:0;border-radius:0;background:var(--aster-surface);color:var(--aster-text)}
56
+ .ac-focus-dialog[open]{display:flex;flex-direction:column}
57
+ .ac-focus-dialog::backdrop{background:var(--aster-scrim)}
58
+ .ac-focus-toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:20px;padding:18px 28px;border-bottom:1px solid var(--aster-rule);flex:none}
59
+ .ac-focus-toolbar h2{font-size:20px;margin:0;overflow-wrap:anywhere}
60
+ .ac-focus-toolbar>div:first-child{min-width:0}
61
+ .ac-focus-actions{display:contents}
62
+ .ac-focus-actions aster-command-bar{grid-column:2;grid-row:1;min-width:0}
63
+ .ac-reader-close{grid-column:3;grid-row:1;width:38px;height:38px;display:grid;place-items:center;font-size:26px;color:var(--aster-text-2)}
64
+ .ac-focus-body{flex:1;min-height:0;display:flex;flex-direction:column;max-width:100ch;width:100%;margin-inline:auto}
65
+ .ac-reader-pane{flex:1}
66
+ .ac-reader-pane>.ac-pane-header{display:none}
67
+ .ac-reader-pane .ac-body{font-size:16px;line-height:1.8}
68
+ .ac-reader-pane .ac-entry strong{font-size:13px}
69
+ .ac-reader-pane .ac-entry time{font-size:11px}
70
+ .ac-reader-pane .ac-email{font-size:16px}
71
+ .ac-focus-footer{padding:10px 24px;border-top:1px solid var(--aster-rule);font-size:11px;color:var(--aster-text-2);flex:none;text-align:center}
72
+ @media(max-width:760px){
73
+ .ac-reader[data-context]{grid-template-columns:minmax(0,1fr)}
74
+ .ac-reader[data-context]>.ac-main{display:none}
75
+ .ac-context{border-left:0}
76
+ .ac-scroll{padding-inline:16px}
77
+ .ac-pane-header{padding:16px}
78
+ .ac-focus-toolbar{padding:12px;gap:10px;grid-template-columns:minmax(0,1fr) auto}
79
+ .ac-focus-toolbar h2{font-size:17px}
80
+ .ac-focus-actions aster-command-bar{grid-column:1 / -1;grid-row:2}
81
+ .ac-reader-close{grid-column:2}
82
+ .ac-reader-close,.ac-focus-launch{width:44px;height:44px}
83
+ .ac-composer{margin-inline:10px}
84
+ .ac-composer textarea{font-size:16px;min-height:48px}
85
+ .ac-composer footer>span{max-width:130px}
86
+ .ac-tools button,.ac-entry-actions button,.ac-return button:first-child,.ac-edge button,.ac-feedback button,.ac-selection button{min-height:44px}
87
+ }
88
+ @media(prefers-reduced-motion:reduce){aster-conversation-reader *{scroll-behavior:auto!important;animation:none!important;transition:none!important}}
89
+ @media(forced-colors:active){.ac-entry[data-kind=note],.ac-badge,.ac-composer,.ac-selection{border:1px solid CanvasText}.ac-composer button{border:1px solid ButtonText}}
90
+ }
91
+
92
+ @layer aster.elements {
93
+ /* Match InfoModel's floating row controls without changing reading geometry on hover. */
94
+ .ac-message-controls { position: absolute; inset-block-start: 0; inset-inline-end: 0; z-index: 1; display: flex; gap: 2px; padding: 2px; border-radius: var(--aster-r-ctl); background: var(--aster-surface); box-shadow: var(--aster-e2); }
95
+ .ac-entry[data-kind=note]>.ac-message-controls { inset-block-start: 8px; inset-inline-end: 8px; }
96
+ .ac-message-controls button { display: grid; place-items: center; width: var(--aster-ctl-tight); height: calc(var(--aster-ctl-tight) - 2px); padding: 0; border-radius: var(--aster-r-ctl); color: var(--aster-text-2); }
97
+ .ac-message-controls button:hover { background: var(--aster-accent-soft); color: var(--aster-text); }
98
+ .ac-message-controls button:active { background: oklch(from var(--aster-accent) l c h / .18); }
99
+ .ac-message-controls button:focus-visible { outline: 2px solid var(--aster-focus); outline-offset: -2px; }
100
+ .ac-message-controls svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
101
+ @media (hover: hover) and (pointer: fine) {
102
+ .ac-message-controls { opacity: 0; pointer-events: none; transition: opacity var(--aster-t-micro); }
103
+ .ac-entry:is(:hover, :focus-within)>.ac-message-controls { opacity: 1; pointer-events: auto; }
104
+ }
105
+ @media (hover: none), (pointer: coarse) {
106
+ .ac-entry>header { min-height: 48px; padding-inline-end: 100px; }
107
+ .ac-message-controls button { width: 44px; height: 44px; }
108
+ }
109
+ }
110
+ @media (prefers-reduced-motion: reduce) {
111
+ .ac-message-controls { transition: none !important; }
112
+ }
113
+ @media (forced-colors: active) {
114
+ .ac-message-controls { border: 1px solid ButtonText; }
115
+ }
@@ -0,0 +1,19 @@
1
+ @layer aster.preview {
2
+ aster-contextual-hierarchy { display:block; min-width:0; min-height:0; }
3
+ .aster-hierarchy { display:flex; flex-direction:column; min-height:0; height:100%; }
4
+ .aster-hierarchy > nav { display:flex; flex-wrap:wrap; align-items:center; gap:var(--aster-gap); padding:8px var(--aster-pad); border-block-end:1px solid var(--aster-rule); }
5
+ .aster-hierarchy > nav button { color:var(--aster-accent); min-height:var(--aster-ctl); }
6
+ .aster-hierarchy > nav span { color:var(--aster-text-2); font-size:12px; overflow-wrap:anywhere; }
7
+ .aster-hierarchy-level { min-height:0; overflow:auto; padding:var(--aster-pad); }
8
+ .aster-hierarchy-level[hidden] { display:none; }
9
+ .aster-hierarchy[data-motion=slide] { overflow:hidden; --hierarchy-entry:16px; }
10
+ .aster-hierarchy[data-motion=slide][data-direction=back] { --hierarchy-entry:-16px; }
11
+ .aster-hierarchy:dir(rtl) { --hierarchy-sign:-1; }
12
+ .aster-hierarchy[data-motion=slide] > .aster-hierarchy-level:not([hidden]) { animation:aster-hierarchy-enter 180ms var(--aster-ease); }
13
+ @keyframes aster-hierarchy-enter { from { opacity:.6; transform:translateX(calc(var(--hierarchy-entry) * var(--hierarchy-sign,1))); } to { opacity:1; transform:translateX(0); } }
14
+ @media(prefers-reduced-motion:reduce) { .aster-hierarchy[data-motion=slide] > .aster-hierarchy-level:not([hidden]) { animation:none; } }
15
+ .aster-hierarchy-level > header { display:flex; align-items:start; justify-content:space-between; gap:var(--aster-gap); margin-block-end:var(--aster-gap); }
16
+ .aster-hierarchy-level h3 { margin:0; font-size:18px; }
17
+ .aster-hierarchy-level header p { margin:6px 0 0; color:var(--aster-text-2); }
18
+ @media(pointer:coarse) { .aster-hierarchy > nav button { min-height:44px; } }
19
+ }