@f-ewald/components 0.5.0 → 0.6.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/README.md +27 -0
- package/custom-elements.json +3290 -835
- package/dist/chat-message.d.ts +39 -0
- package/dist/chat-message.d.ts.map +1 -0
- package/dist/chat-message.js +155 -0
- package/dist/chat-message.js.map +1 -0
- package/dist/data-table.d.ts +39 -0
- package/dist/data-table.d.ts.map +1 -0
- package/dist/data-table.js +119 -0
- package/dist/data-table.js.map +1 -0
- package/dist/editable-text.d.ts +52 -0
- package/dist/editable-text.d.ts.map +1 -0
- package/dist/editable-text.js +224 -0
- package/dist/editable-text.js.map +1 -0
- package/dist/form-select.d.ts +40 -0
- package/dist/form-select.d.ts.map +1 -0
- package/dist/form-select.js +236 -0
- package/dist/form-select.js.map +1 -0
- package/dist/gallery-item-variant.d.ts +21 -0
- package/dist/gallery-item-variant.d.ts.map +1 -0
- package/dist/gallery-item-variant.js +44 -0
- package/dist/gallery-item-variant.js.map +1 -0
- package/dist/gallery-item.d.ts +28 -0
- package/dist/gallery-item.d.ts.map +1 -0
- package/dist/gallery-item.js +54 -0
- package/dist/gallery-item.js.map +1 -0
- package/dist/icons.d.ts +7 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +7 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/live-timer.d.ts +31 -0
- package/dist/live-timer.d.ts.map +1 -0
- package/dist/live-timer.js +78 -0
- package/dist/live-timer.js.map +1 -0
- package/dist/photo-gallery.d.ts +117 -0
- package/dist/photo-gallery.d.ts.map +1 -0
- package/dist/photo-gallery.js +620 -0
- package/dist/photo-gallery.js.map +1 -0
- package/dist/popover-panel.d.ts +34 -0
- package/dist/popover-panel.d.ts.map +1 -0
- package/dist/popover-panel.js +173 -0
- package/dist/popover-panel.js.map +1 -0
- package/dist/status-pill.d.ts +24 -0
- package/dist/status-pill.d.ts.map +1 -0
- package/dist/status-pill.js +101 -0
- package/dist/status-pill.js.map +1 -0
- package/dist/tile-grid.d.ts +32 -0
- package/dist/tile-grid.d.ts.map +1 -0
- package/dist/tile-grid.js +102 -0
- package/dist/tile-grid.js.map +1 -0
- package/dist/tokens.css +39 -0
- package/dist/tokens.d.ts +28 -3
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +42 -43
- package/dist/tokens.js.map +1 -1
- package/dist/user-avatar.d.ts +5 -2
- package/dist/user-avatar.d.ts.map +1 -1
- package/dist/user-avatar.js +22 -6
- package/dist/user-avatar.js.map +1 -1
- package/dist/utils/duration.d.ts +12 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +27 -0
- package/dist/utils/duration.js.map +1 -0
- package/docs/chat-message.md +59 -0
- package/docs/data-table.md +63 -0
- package/docs/editable-text.md +51 -0
- package/docs/form-select.md +64 -0
- package/docs/gallery-item-variant.md +37 -0
- package/docs/gallery-item.md +47 -0
- package/docs/live-timer.md +39 -0
- package/docs/photo-gallery.md +77 -0
- package/docs/popover-panel.md +66 -0
- package/docs/status-pill.md +43 -0
- package/docs/tile-grid.md +58 -0
- package/docs/user-avatar.md +1 -1
- package/llms.txt +280 -1
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import "./relative-time.js";
|
|
3
|
+
export type ChatMessageRole = "user" | "agent" | "system";
|
|
4
|
+
export type ChatMessageVariant = "normal" | "tool" | "thinking";
|
|
5
|
+
/**
|
|
6
|
+
* One conversation entry in a chat-style activity feed. Tool calls and
|
|
7
|
+
* "thinking" traces are variants of this component rather than separate
|
|
8
|
+
* ones — they share the same header, collapse behavior, and body card as a
|
|
9
|
+
* normal message, just dimmed and collapsible with an always-visible summary.
|
|
10
|
+
*
|
|
11
|
+
* @element chat-message
|
|
12
|
+
* @slot - Message body (consumers slot in already-rendered content, e.g. sanitized markdown HTML).
|
|
13
|
+
* @fires toggle - Fired with `{ collapsed: boolean }` when the header/summary is clicked in collapsible mode.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ChatMessage extends LitElement {
|
|
16
|
+
static styles: import("lit").CSSResult[];
|
|
17
|
+
/** Whose message this is; drives the card background/border. */
|
|
18
|
+
role: ChatMessageRole;
|
|
19
|
+
/** `tool`/`thinking` render dimmed and smaller, with `tool` using monospace for its body. */
|
|
20
|
+
variant: ChatMessageVariant;
|
|
21
|
+
/** Header label, e.g. "Freddy" or "Architect". */
|
|
22
|
+
author: string;
|
|
23
|
+
/** ISO-8601 timestamp, rendered via `relative-time` in the header. */
|
|
24
|
+
timestamp: string | null;
|
|
25
|
+
/** Always-visible one-liner for `tool`/`thinking` variants (e.g. a truncated args preview). */
|
|
26
|
+
summary: string;
|
|
27
|
+
/** Whether clicking the header/summary toggles the body slot. */
|
|
28
|
+
collapsible: boolean;
|
|
29
|
+
/** Current collapse state (reflected as an attribute). */
|
|
30
|
+
collapsed: boolean;
|
|
31
|
+
private _toggle;
|
|
32
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
33
|
+
}
|
|
34
|
+
declare global {
|
|
35
|
+
interface HTMLElementTagNameMap {
|
|
36
|
+
"chat-message": ChatMessage;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=chat-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-message.d.ts","sourceRoot":"","sources":["../src/chat-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,oBAAoB,CAAC;AAG5B,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC1D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAEhE;;;;;;;;;GASG;AACH,qBACa,WAAY,SAAQ,UAAU;IACzC,OAAgB,MAAM,4BAiEpB;IAEF,gEAAgE;IAC1B,IAAI,EAAE,eAAe,CAAW;IACtE,6FAA6F;IAChE,OAAO,EAAE,kBAAkB,CAAY;IACpE,kDAAkD;IACtC,MAAM,SAAM;IACxB,sEAAsE;IAC1D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC5C,+FAA+F;IACnF,OAAO,SAAM;IACzB,iEAAiE;IACpC,WAAW,UAAS;IACjD,0DAA0D;IACd,SAAS,UAAS;IAE9D,OAAO,CAAC,OAAO;IAMN,MAAM,yCAgBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,WAAW,CAAC;KAC7B;CACF"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
8
|
+
import { customElement, property } from "lit/decorators.js";
|
|
9
|
+
import { iconChevronRight } from "./icons.js";
|
|
10
|
+
import "./relative-time.js";
|
|
11
|
+
import { tokens } from "./tokens.js";
|
|
12
|
+
/**
|
|
13
|
+
* One conversation entry in a chat-style activity feed. Tool calls and
|
|
14
|
+
* "thinking" traces are variants of this component rather than separate
|
|
15
|
+
* ones — they share the same header, collapse behavior, and body card as a
|
|
16
|
+
* normal message, just dimmed and collapsible with an always-visible summary.
|
|
17
|
+
*
|
|
18
|
+
* @element chat-message
|
|
19
|
+
* @slot - Message body (consumers slot in already-rendered content, e.g. sanitized markdown HTML).
|
|
20
|
+
* @fires toggle - Fired with `{ collapsed: boolean }` when the header/summary is clicked in collapsible mode.
|
|
21
|
+
*/
|
|
22
|
+
let ChatMessage = class ChatMessage extends LitElement {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
/** Whose message this is; drives the card background/border. */
|
|
26
|
+
this.role = "agent";
|
|
27
|
+
/** `tool`/`thinking` render dimmed and smaller, with `tool` using monospace for its body. */
|
|
28
|
+
this.variant = "normal";
|
|
29
|
+
/** Header label, e.g. "Freddy" or "Architect". */
|
|
30
|
+
this.author = "";
|
|
31
|
+
/** ISO-8601 timestamp, rendered via `relative-time` in the header. */
|
|
32
|
+
this.timestamp = null;
|
|
33
|
+
/** Always-visible one-liner for `tool`/`thinking` variants (e.g. a truncated args preview). */
|
|
34
|
+
this.summary = "";
|
|
35
|
+
/** Whether clicking the header/summary toggles the body slot. */
|
|
36
|
+
this.collapsible = false;
|
|
37
|
+
/** Current collapse state (reflected as an attribute). */
|
|
38
|
+
this.collapsed = false;
|
|
39
|
+
}
|
|
40
|
+
static { this.styles = [
|
|
41
|
+
tokens,
|
|
42
|
+
css `
|
|
43
|
+
:host {
|
|
44
|
+
display: block;
|
|
45
|
+
font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
|
|
46
|
+
font-size: var(--ui-font-size, 0.875rem);
|
|
47
|
+
}
|
|
48
|
+
.header {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 0.4rem;
|
|
52
|
+
font-size: 0.75rem;
|
|
53
|
+
color: var(--ui-text-muted, #64748b);
|
|
54
|
+
margin-bottom: 0.3rem;
|
|
55
|
+
}
|
|
56
|
+
.header.clickable {
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
}
|
|
59
|
+
.chevron {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
transition: transform 0.15s ease;
|
|
62
|
+
}
|
|
63
|
+
.chevron.expanded {
|
|
64
|
+
transform: rotate(90deg);
|
|
65
|
+
}
|
|
66
|
+
.author {
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
color: var(--ui-text, #0f172a);
|
|
69
|
+
}
|
|
70
|
+
.summary {
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
text-overflow: ellipsis;
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
}
|
|
75
|
+
.body-card {
|
|
76
|
+
border-radius: var(--ui-radius, 0.5rem);
|
|
77
|
+
padding: 0.6rem 0.8rem;
|
|
78
|
+
color: var(--ui-text, #0f172a);
|
|
79
|
+
line-height: 1.5;
|
|
80
|
+
}
|
|
81
|
+
.body-card[hidden] {
|
|
82
|
+
display: none;
|
|
83
|
+
}
|
|
84
|
+
:host([role="user"]) .body-card {
|
|
85
|
+
background: color-mix(in srgb, var(--ui-primary, #4f46e5) 8%, var(--ui-surface, #fff));
|
|
86
|
+
}
|
|
87
|
+
:host([role="agent"]) .body-card {
|
|
88
|
+
background: var(--ui-surface, #fff);
|
|
89
|
+
border: 1px solid var(--ui-border, #e2e8f0);
|
|
90
|
+
}
|
|
91
|
+
:host([role="system"]) .body-card {
|
|
92
|
+
padding: 0 0 0 1.1rem;
|
|
93
|
+
font-size: 0.8rem;
|
|
94
|
+
color: var(--ui-text-muted, #64748b);
|
|
95
|
+
}
|
|
96
|
+
:host([variant="tool"]) .body-card,
|
|
97
|
+
:host([variant="thinking"]) .body-card {
|
|
98
|
+
opacity: 0.7;
|
|
99
|
+
font-size: 0.8rem;
|
|
100
|
+
}
|
|
101
|
+
:host([variant="tool"]) .body-card {
|
|
102
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
103
|
+
}
|
|
104
|
+
`,
|
|
105
|
+
]; }
|
|
106
|
+
_toggle() {
|
|
107
|
+
if (!this.collapsible)
|
|
108
|
+
return;
|
|
109
|
+
this.collapsed = !this.collapsed;
|
|
110
|
+
this.dispatchEvent(new CustomEvent("toggle", { detail: { collapsed: this.collapsed } }));
|
|
111
|
+
}
|
|
112
|
+
render() {
|
|
113
|
+
return html `
|
|
114
|
+
<div class="header ${this.collapsible ? "clickable" : ""}" @click=${this._toggle}>
|
|
115
|
+
${this.collapsible
|
|
116
|
+
? html `<span class="chevron ${this.collapsed ? "" : "expanded"}"
|
|
117
|
+
>${iconChevronRight(12)}</span
|
|
118
|
+
>`
|
|
119
|
+
: nothing}
|
|
120
|
+
${this.author ? html `<span class="author">${this.author}</span>` : nothing}
|
|
121
|
+
${this.timestamp ? html `<relative-time datetime=${this.timestamp}></relative-time>` : nothing}
|
|
122
|
+
${this.summary ? html `<span class="summary">${this.summary}</span>` : nothing}
|
|
123
|
+
</div>
|
|
124
|
+
<div class="body-card" ?hidden=${this.collapsible && this.collapsed}>
|
|
125
|
+
<slot></slot>
|
|
126
|
+
</div>
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
__decorate([
|
|
131
|
+
property({ reflect: true })
|
|
132
|
+
], ChatMessage.prototype, "role", void 0);
|
|
133
|
+
__decorate([
|
|
134
|
+
property({ reflect: true })
|
|
135
|
+
], ChatMessage.prototype, "variant", void 0);
|
|
136
|
+
__decorate([
|
|
137
|
+
property()
|
|
138
|
+
], ChatMessage.prototype, "author", void 0);
|
|
139
|
+
__decorate([
|
|
140
|
+
property()
|
|
141
|
+
], ChatMessage.prototype, "timestamp", void 0);
|
|
142
|
+
__decorate([
|
|
143
|
+
property()
|
|
144
|
+
], ChatMessage.prototype, "summary", void 0);
|
|
145
|
+
__decorate([
|
|
146
|
+
property({ type: Boolean })
|
|
147
|
+
], ChatMessage.prototype, "collapsible", void 0);
|
|
148
|
+
__decorate([
|
|
149
|
+
property({ type: Boolean, reflect: true })
|
|
150
|
+
], ChatMessage.prototype, "collapsed", void 0);
|
|
151
|
+
ChatMessage = __decorate([
|
|
152
|
+
customElement("chat-message")
|
|
153
|
+
], ChatMessage);
|
|
154
|
+
export { ChatMessage };
|
|
155
|
+
//# sourceMappingURL=chat-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-message.js","sourceRoot":"","sources":["../src/chat-message.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAKrC;;;;;;;;;GASG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,UAAU;IAApC;;QAoEL,gEAAgE;QAC1B,SAAI,GAAoB,OAAO,CAAC;QACtE,6FAA6F;QAChE,YAAO,GAAuB,QAAQ,CAAC;QACpE,kDAAkD;QACtC,WAAM,GAAG,EAAE,CAAC;QACxB,sEAAsE;QAC1D,cAAS,GAAkB,IAAI,CAAC;QAC5C,+FAA+F;QACnF,YAAO,GAAG,EAAE,CAAC;QACzB,iEAAiE;QACpC,gBAAW,GAAG,KAAK,CAAC;QACjD,0DAA0D;QACd,cAAS,GAAG,KAAK,CAAC;IAyBhE,CAAC;aAzGiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8DF;KACF,AAjEqB,CAiEpB;IAiBM,OAAO;QACb,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9B,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3F,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;2BACY,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,IAAI,CAAC,OAAO;UAC5E,IAAI,CAAC,WAAW;YAChB,CAAC,CAAC,IAAI,CAAA,wBAAwB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU;iBACvD,gBAAgB,CAAC,EAAE,CAAC;cACvB;YACJ,CAAC,CAAC,OAAO;UACT,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA,wBAAwB,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,OAAO;UACxE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA,2BAA2B,IAAI,CAAC,SAAS,mBAAmB,CAAC,CAAC,CAAC,OAAO;UAC3F,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,yBAAyB,IAAI,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,OAAO;;uCAE9C,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS;;;KAGpE,CAAC;IACJ,CAAC;CACF,CAAA;AArCuC;IAArC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;yCAA0C;AAEzC;IAA5B,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;4CAAwC;AAExD;IAAX,QAAQ,EAAE;2CAAa;AAEZ;IAAX,QAAQ,EAAE;8CAAiC;AAEhC;IAAX,QAAQ,EAAE;4CAAc;AAEI;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gDAAqB;AAEL;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;8CAAmB;AAjFnD,WAAW;IADvB,aAAa,CAAC,cAAc,CAAC;GACjB,WAAW,CA0GvB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconChevronRight } from \"./icons.js\";\nimport \"./relative-time.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport type ChatMessageRole = \"user\" | \"agent\" | \"system\";\nexport type ChatMessageVariant = \"normal\" | \"tool\" | \"thinking\";\n\n/**\n * One conversation entry in a chat-style activity feed. Tool calls and\n * \"thinking\" traces are variants of this component rather than separate\n * ones — they share the same header, collapse behavior, and body card as a\n * normal message, just dimmed and collapsible with an always-visible summary.\n *\n * @element chat-message\n * @slot - Message body (consumers slot in already-rendered content, e.g. sanitized markdown HTML).\n * @fires toggle - Fired with `{ collapsed: boolean }` when the header/summary is clicked in collapsible mode.\n */\n@customElement(\"chat-message\")\nexport class ChatMessage extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size, 0.875rem);\n }\n .header {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n font-size: 0.75rem;\n color: var(--ui-text-muted, #64748b);\n margin-bottom: 0.3rem;\n }\n .header.clickable {\n cursor: pointer;\n }\n .chevron {\n display: inline-flex;\n transition: transform 0.15s ease;\n }\n .chevron.expanded {\n transform: rotate(90deg);\n }\n .author {\n font-weight: 600;\n color: var(--ui-text, #0f172a);\n }\n .summary {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .body-card {\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.6rem 0.8rem;\n color: var(--ui-text, #0f172a);\n line-height: 1.5;\n }\n .body-card[hidden] {\n display: none;\n }\n :host([role=\"user\"]) .body-card {\n background: color-mix(in srgb, var(--ui-primary, #4f46e5) 8%, var(--ui-surface, #fff));\n }\n :host([role=\"agent\"]) .body-card {\n background: var(--ui-surface, #fff);\n border: 1px solid var(--ui-border, #e2e8f0);\n }\n :host([role=\"system\"]) .body-card {\n padding: 0 0 0 1.1rem;\n font-size: 0.8rem;\n color: var(--ui-text-muted, #64748b);\n }\n :host([variant=\"tool\"]) .body-card,\n :host([variant=\"thinking\"]) .body-card {\n opacity: 0.7;\n font-size: 0.8rem;\n }\n :host([variant=\"tool\"]) .body-card {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n }\n `,\n ];\n\n /** Whose message this is; drives the card background/border. */\n @property({ reflect: true }) override role: ChatMessageRole = \"agent\";\n /** `tool`/`thinking` render dimmed and smaller, with `tool` using monospace for its body. */\n @property({ reflect: true }) variant: ChatMessageVariant = \"normal\";\n /** Header label, e.g. \"Freddy\" or \"Architect\". */\n @property() author = \"\";\n /** ISO-8601 timestamp, rendered via `relative-time` in the header. */\n @property() timestamp: string | null = null;\n /** Always-visible one-liner for `tool`/`thinking` variants (e.g. a truncated args preview). */\n @property() summary = \"\";\n /** Whether clicking the header/summary toggles the body slot. */\n @property({ type: Boolean }) collapsible = false;\n /** Current collapse state (reflected as an attribute). */\n @property({ type: Boolean, reflect: true }) collapsed = false;\n\n private _toggle() {\n if (!this.collapsible) return;\n this.collapsed = !this.collapsed;\n this.dispatchEvent(new CustomEvent(\"toggle\", { detail: { collapsed: this.collapsed } }));\n }\n\n override render() {\n return html`\n <div class=\"header ${this.collapsible ? \"clickable\" : \"\"}\" @click=${this._toggle}>\n ${this.collapsible\n ? html`<span class=\"chevron ${this.collapsed ? \"\" : \"expanded\"}\"\n >${iconChevronRight(12)}</span\n >`\n : nothing}\n ${this.author ? html`<span class=\"author\">${this.author}</span>` : nothing}\n ${this.timestamp ? html`<relative-time datetime=${this.timestamp}></relative-time>` : nothing}\n ${this.summary ? html`<span class=\"summary\">${this.summary}</span>` : nothing}\n </div>\n <div class=\"body-card\" ?hidden=${this.collapsible && this.collapsed}>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"chat-message\": ChatMessage;\n }\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
/** One column header: `key` is looked up via `renderCell`/`getCellValue`, `label` is the header text. */
|
|
3
|
+
export interface DataTableColumn {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A generic, presentational table shell: renders a `<thead>` from `columns`
|
|
9
|
+
* and one `<tr>` per entry in `rows`, with each cell's content produced by
|
|
10
|
+
* `renderCell` (default: plain property lookup on the row object). Knows
|
|
11
|
+
* nothing about what a "row" means — callers own the data shape entirely.
|
|
12
|
+
*
|
|
13
|
+
* Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),
|
|
14
|
+
* without hijacking clicks on nested interactive elements (links/buttons) a
|
|
15
|
+
* cell's rendered content might contain.
|
|
16
|
+
*
|
|
17
|
+
* @element data-table
|
|
18
|
+
*/
|
|
19
|
+
export declare class DataTable extends LitElement {
|
|
20
|
+
#private;
|
|
21
|
+
static styles: import("lit").CSSResult[];
|
|
22
|
+
/** Column headers, in display order. */
|
|
23
|
+
columns: DataTableColumn[];
|
|
24
|
+
/** Row data; opaque to this component beyond what `renderCell` does with it. */
|
|
25
|
+
rows: unknown[];
|
|
26
|
+
/** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */
|
|
27
|
+
rowKey: (row: unknown, index: number) => string | number;
|
|
28
|
+
/** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */
|
|
29
|
+
renderCell: (row: unknown, key: string) => unknown;
|
|
30
|
+
/** When set, clicking a row (outside any nested link/button) navigates to this hash. */
|
|
31
|
+
rowHref: ((row: unknown) => string | null) | null;
|
|
32
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
33
|
+
}
|
|
34
|
+
declare global {
|
|
35
|
+
interface HTMLElementTagNameMap {
|
|
36
|
+
"data-table": DataTable;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=data-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBACa,SAAU,SAAQ,UAAU;;IACvC,OAAgB,MAAM,4BA+BpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gFAAgF;IAChD,IAAI,EAAE,OAAO,EAAE,CAAM;IACrD,oGAAoG;IACpE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG7E;IACX,sFAAsF;IACtD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAGvC;IAC3C,wFAAwF;IACxD,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IAShF,MAAM,yCAwBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
8
|
+
import { customElement, property } from "lit/decorators.js";
|
|
9
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
10
|
+
import { tokens } from "./tokens.js";
|
|
11
|
+
/**
|
|
12
|
+
* A generic, presentational table shell: renders a `<thead>` from `columns`
|
|
13
|
+
* and one `<tr>` per entry in `rows`, with each cell's content produced by
|
|
14
|
+
* `renderCell` (default: plain property lookup on the row object). Knows
|
|
15
|
+
* nothing about what a "row" means — callers own the data shape entirely.
|
|
16
|
+
*
|
|
17
|
+
* Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),
|
|
18
|
+
* without hijacking clicks on nested interactive elements (links/buttons) a
|
|
19
|
+
* cell's rendered content might contain.
|
|
20
|
+
*
|
|
21
|
+
* @element data-table
|
|
22
|
+
*/
|
|
23
|
+
let DataTable = class DataTable extends LitElement {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
/** Column headers, in display order. */
|
|
27
|
+
this.columns = [];
|
|
28
|
+
/** Row data; opaque to this component beyond what `renderCell` does with it. */
|
|
29
|
+
this.rows = [];
|
|
30
|
+
/** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */
|
|
31
|
+
this.rowKey = (_row, index) => index;
|
|
32
|
+
/** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */
|
|
33
|
+
this.renderCell = (row, key) => row[key];
|
|
34
|
+
/** When set, clicking a row (outside any nested link/button) navigates to this hash. */
|
|
35
|
+
this.rowHref = null;
|
|
36
|
+
}
|
|
37
|
+
static { this.styles = [
|
|
38
|
+
tokens,
|
|
39
|
+
css `
|
|
40
|
+
:host {
|
|
41
|
+
display: block;
|
|
42
|
+
font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
|
|
43
|
+
font-size: var(--ui-font-size-sm, 0.75rem);
|
|
44
|
+
}
|
|
45
|
+
table {
|
|
46
|
+
width: 100%;
|
|
47
|
+
border-collapse: collapse;
|
|
48
|
+
}
|
|
49
|
+
th {
|
|
50
|
+
text-align: left;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
color: var(--ui-text-muted, #64748b);
|
|
53
|
+
padding: 0.5rem 0.75rem;
|
|
54
|
+
border-bottom: 1px solid var(--ui-border, #e2e8f0);
|
|
55
|
+
}
|
|
56
|
+
td {
|
|
57
|
+
padding: 0.6rem 0.75rem;
|
|
58
|
+
border-bottom: 1px solid var(--ui-border, #e2e8f0);
|
|
59
|
+
color: var(--ui-text, #0f172a);
|
|
60
|
+
}
|
|
61
|
+
tbody tr.clickable {
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
}
|
|
64
|
+
tbody tr.clickable:hover {
|
|
65
|
+
background: var(--ui-surface-muted, #f8fafc);
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
]; }
|
|
69
|
+
#onRowClick(row, e) {
|
|
70
|
+
if (!this.rowHref)
|
|
71
|
+
return;
|
|
72
|
+
if (e.target.closest("a, button"))
|
|
73
|
+
return;
|
|
74
|
+
const href = this.rowHref(row);
|
|
75
|
+
if (href)
|
|
76
|
+
location.hash = href;
|
|
77
|
+
}
|
|
78
|
+
render() {
|
|
79
|
+
return html `
|
|
80
|
+
<table>
|
|
81
|
+
<thead>
|
|
82
|
+
<tr>
|
|
83
|
+
${this.columns.map((c) => html `<th>${c.label}</th>`)}
|
|
84
|
+
</tr>
|
|
85
|
+
</thead>
|
|
86
|
+
<tbody>
|
|
87
|
+
${repeat(this.rows, (row, i) => this.rowKey(row, i), (row) => html `
|
|
88
|
+
<tr
|
|
89
|
+
class=${this.rowHref ? "clickable" : ""}
|
|
90
|
+
@click=${(e) => this.#onRowClick(row, e)}
|
|
91
|
+
>
|
|
92
|
+
${this.columns.map((c) => html `<td>${this.renderCell(row, c.key) ?? nothing}</td>`)}
|
|
93
|
+
</tr>
|
|
94
|
+
`)}
|
|
95
|
+
</tbody>
|
|
96
|
+
</table>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
__decorate([
|
|
101
|
+
property({ attribute: false })
|
|
102
|
+
], DataTable.prototype, "columns", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
property({ attribute: false })
|
|
105
|
+
], DataTable.prototype, "rows", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
property({ attribute: false })
|
|
108
|
+
], DataTable.prototype, "rowKey", void 0);
|
|
109
|
+
__decorate([
|
|
110
|
+
property({ attribute: false })
|
|
111
|
+
], DataTable.prototype, "renderCell", void 0);
|
|
112
|
+
__decorate([
|
|
113
|
+
property({ attribute: false })
|
|
114
|
+
], DataTable.prototype, "rowHref", void 0);
|
|
115
|
+
DataTable = __decorate([
|
|
116
|
+
customElement("data-table")
|
|
117
|
+
], DataTable);
|
|
118
|
+
export { DataTable };
|
|
119
|
+
//# sourceMappingURL=data-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;;;;;;;;;GAWG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;IAAlC;;QAkCL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gFAAgF;QAChD,SAAI,GAAc,EAAE,CAAC;QACrD,oGAAoG;QACpE,WAAM,GAAqD,CACzF,IAAI,EACJ,KAAK,EACL,EAAE,CAAC,KAAK,CAAC;QACX,sFAAsF;QACtD,eAAU,GAA2C,CACnF,GAAG,EACH,GAAG,EACH,EAAE,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC;QAC3C,wFAAwF;QACxD,YAAO,GAA6C,IAAI,CAAC;IAkC3F,CAAC;aAlFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4BF;KACF,AA/BqB,CA+BpB;IAmBF,WAAW,CAAC,GAAY,EAAE,CAAa;QACrC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;;cAID,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC;;;;YAIpD,MAAM,CACN,IAAI,CAAC,IAAI,EACT,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,EAC/B,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;wBAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;yBAC9B,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;;kBAElD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,OAAO,OAAO,CAAC;;aAEtF,CACF;;;KAGN,CAAC;IACJ,CAAC;CACF,CAAA;AAhDiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAAiC;AAEhC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;uCAAsB;AAErB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;yCAGpB;AAEqB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAGY;AAEX;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAA0D;AAjD9E,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CAmFrB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** One column header: `key` is looked up via `renderCell`/`getCellValue`, `label` is the header text. */\nexport interface DataTableColumn {\n key: string;\n label: string;\n}\n\n/**\n * A generic, presentational table shell: renders a `<thead>` from `columns`\n * and one `<tr>` per entry in `rows`, with each cell's content produced by\n * `renderCell` (default: plain property lookup on the row object). Knows\n * nothing about what a \"row\" means — callers own the data shape entirely.\n *\n * Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),\n * without hijacking clicks on nested interactive elements (links/buttons) a\n * cell's rendered content might contain.\n *\n * @element data-table\n */\n@customElement(\"data-table\")\nexport class DataTable extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size-sm, 0.75rem);\n }\n table {\n width: 100%;\n border-collapse: collapse;\n }\n th {\n text-align: left;\n font-weight: 600;\n color: var(--ui-text-muted, #64748b);\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n td {\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n tbody tr.clickable {\n cursor: pointer;\n }\n tbody tr.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** Column headers, in display order. */\n @property({ attribute: false }) columns: DataTableColumn[] = [];\n /** Row data; opaque to this component beyond what `renderCell` does with it. */\n @property({ attribute: false }) rows: unknown[] = [];\n /** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */\n @property({ attribute: false }) rowKey: (row: unknown, index: number) => string | number = (\n _row,\n index,\n ) => index;\n /** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */\n @property({ attribute: false }) renderCell: (row: unknown, key: string) => unknown = (\n row,\n key,\n ) => (row as Record<string, unknown>)[key];\n /** When set, clicking a row (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) rowHref: ((row: unknown) => string | null) | null = null;\n\n #onRowClick(row: unknown, e: MouseEvent): void {\n if (!this.rowHref) return;\n if ((e.target as HTMLElement).closest(\"a, button\")) return;\n const href = this.rowHref(row);\n if (href) location.hash = href;\n }\n\n override render() {\n return html`\n <table>\n <thead>\n <tr>\n ${this.columns.map((c) => html`<th>${c.label}</th>`)}\n </tr>\n </thead>\n <tbody>\n ${repeat(\n this.rows,\n (row, i) => this.rowKey(row, i),\n (row) => html`\n <tr\n class=${this.rowHref ? \"clickable\" : \"\"}\n @click=${(e: MouseEvent) => this.#onRowClick(row, e)}\n >\n ${this.columns.map((c) => html`<td>${this.renderCell(row, c.key) ?? nothing}</td>`)}\n </tr>\n `,\n )}\n </tbody>\n </table>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"data-table\": DataTable;\n }\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
/**
|
|
3
|
+
* Jira/GitHub-style click-to-edit text: a display span that turns into an
|
|
4
|
+
* `<input>` (or auto-growing `<textarea>` when `multiline`) on click. The
|
|
5
|
+
* input/textarea inherits the host's font, so a title wrapped in an `<h1>`
|
|
6
|
+
* edits at title size.
|
|
7
|
+
*
|
|
8
|
+
* @element editable-text
|
|
9
|
+
* @fires change - Fired with `{ value: string }` when the committed value differs from the previous one.
|
|
10
|
+
*/
|
|
11
|
+
export declare class EditableText extends LitElement {
|
|
12
|
+
static styles: import("lit").CSSResult[];
|
|
13
|
+
/** Current text. */
|
|
14
|
+
value: string;
|
|
15
|
+
/** `false` renders an `<input>`, `true` an auto-growing `<textarea>`. */
|
|
16
|
+
multiline: boolean;
|
|
17
|
+
/** Muted placeholder text shown when `value` is empty, and as the input's placeholder. */
|
|
18
|
+
placeholder: string;
|
|
19
|
+
/** Disables entering edit mode. */
|
|
20
|
+
readonly: boolean;
|
|
21
|
+
/** `aria-label` applied to the input/textarea. */
|
|
22
|
+
label: string;
|
|
23
|
+
private _editing;
|
|
24
|
+
private _draft;
|
|
25
|
+
private _field?;
|
|
26
|
+
/** Enters edit mode, seeding the draft from the current value. */
|
|
27
|
+
private _startEdit;
|
|
28
|
+
/** Commits the draft (reverting an empty single-line commit) and exits edit mode.
|
|
29
|
+
*
|
|
30
|
+
* Guarded on `_editing`: removing the focused input/textarea from the DOM
|
|
31
|
+
* (which happens as soon as `_editing` flips to false) makes the browser
|
|
32
|
+
* fire a native `blur` on it, re-invoking this handler a second time after
|
|
33
|
+
* a cancel already ran — the guard makes that second call a no-op instead
|
|
34
|
+
* of re-committing the stale draft.
|
|
35
|
+
*/
|
|
36
|
+
private _commit;
|
|
37
|
+
/** Discards the draft and exits edit mode without committing. */
|
|
38
|
+
private _cancel;
|
|
39
|
+
private _onInput;
|
|
40
|
+
private _onSingleLineKeydown;
|
|
41
|
+
private _onMultilineKeydown;
|
|
42
|
+
/** Grows the textarea's height to fit its content. */
|
|
43
|
+
private _resize;
|
|
44
|
+
protected updated(changed: Map<string, unknown>): void;
|
|
45
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
46
|
+
}
|
|
47
|
+
declare global {
|
|
48
|
+
interface HTMLElementTagNameMap {
|
|
49
|
+
"editable-text": EditableText;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=editable-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editable-text.d.ts","sourceRoot":"","sources":["../src/editable-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD;;;;;;;;GAQG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,MAAM,4BAuDpB;IAEF,oBAAoB;IACR,KAAK,SAAM;IACvB,yEAAyE;IAC5C,SAAS,UAAS;IAC/C,0FAA0F;IAC9E,WAAW,SAAM;IAC7B,mCAAmC;IACN,QAAQ,UAAS;IAC9C,kDAAkD;IACtC,KAAK,SAAM;IAEd,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAM;IAEH,OAAO,CAAC,MAAM,CAAC,CAAyC;IAElF,kEAAkE;IAClE,OAAO,CAAC,UAAU;IAMlB;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IAUf,iEAAiE;IACjE,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,mBAAmB;IAU3B,sDAAsD;IACtD,OAAO,CAAC,OAAO;IAOf,UAAmB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QASvD;IAEQ,MAAM,yCA4Bd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;CACF"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
8
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
9
|
+
import { tokens } from "./tokens.js";
|
|
10
|
+
/**
|
|
11
|
+
* Jira/GitHub-style click-to-edit text: a display span that turns into an
|
|
12
|
+
* `<input>` (or auto-growing `<textarea>` when `multiline`) on click. The
|
|
13
|
+
* input/textarea inherits the host's font, so a title wrapped in an `<h1>`
|
|
14
|
+
* edits at title size.
|
|
15
|
+
*
|
|
16
|
+
* @element editable-text
|
|
17
|
+
* @fires change - Fired with `{ value: string }` when the committed value differs from the previous one.
|
|
18
|
+
*/
|
|
19
|
+
let EditableText = class EditableText extends LitElement {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
/** Current text. */
|
|
23
|
+
this.value = "";
|
|
24
|
+
/** `false` renders an `<input>`, `true` an auto-growing `<textarea>`. */
|
|
25
|
+
this.multiline = false;
|
|
26
|
+
/** Muted placeholder text shown when `value` is empty, and as the input's placeholder. */
|
|
27
|
+
this.placeholder = "";
|
|
28
|
+
/** Disables entering edit mode. */
|
|
29
|
+
this.readonly = false;
|
|
30
|
+
/** `aria-label` applied to the input/textarea. */
|
|
31
|
+
this.label = "";
|
|
32
|
+
this._editing = false;
|
|
33
|
+
this._draft = "";
|
|
34
|
+
}
|
|
35
|
+
static { this.styles = [
|
|
36
|
+
tokens,
|
|
37
|
+
css `
|
|
38
|
+
:host {
|
|
39
|
+
display: block;
|
|
40
|
+
font-family: inherit;
|
|
41
|
+
font-size: inherit;
|
|
42
|
+
font-weight: inherit;
|
|
43
|
+
line-height: inherit;
|
|
44
|
+
}
|
|
45
|
+
.display {
|
|
46
|
+
cursor: text;
|
|
47
|
+
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
48
|
+
padding: 0.1rem 0.3rem;
|
|
49
|
+
margin: -0.1rem -0.3rem;
|
|
50
|
+
}
|
|
51
|
+
.display.multiline {
|
|
52
|
+
white-space: pre-wrap;
|
|
53
|
+
}
|
|
54
|
+
.display:hover {
|
|
55
|
+
background: var(--ui-surface-muted, #f8fafc);
|
|
56
|
+
}
|
|
57
|
+
.display.empty {
|
|
58
|
+
color: var(--ui-text-muted, #64748b);
|
|
59
|
+
}
|
|
60
|
+
.display.readonly {
|
|
61
|
+
cursor: inherit;
|
|
62
|
+
}
|
|
63
|
+
.display.readonly:hover {
|
|
64
|
+
background: none;
|
|
65
|
+
}
|
|
66
|
+
input,
|
|
67
|
+
textarea {
|
|
68
|
+
display: block;
|
|
69
|
+
width: 100%;
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
font: inherit;
|
|
72
|
+
color: inherit;
|
|
73
|
+
background: var(--ui-surface, #fff);
|
|
74
|
+
border: 1px solid var(--ui-border, #e2e8f0);
|
|
75
|
+
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
76
|
+
padding: 0.1rem 0.3rem;
|
|
77
|
+
margin: -0.1rem -0.3rem;
|
|
78
|
+
outline: none;
|
|
79
|
+
}
|
|
80
|
+
input:focus,
|
|
81
|
+
textarea:focus {
|
|
82
|
+
border-color: var(--ui-primary, #4f46e5);
|
|
83
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
84
|
+
}
|
|
85
|
+
textarea {
|
|
86
|
+
resize: none;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
`,
|
|
90
|
+
]; }
|
|
91
|
+
/** Enters edit mode, seeding the draft from the current value. */
|
|
92
|
+
_startEdit() {
|
|
93
|
+
if (this.readonly)
|
|
94
|
+
return;
|
|
95
|
+
this._draft = this.value;
|
|
96
|
+
this._editing = true;
|
|
97
|
+
}
|
|
98
|
+
/** Commits the draft (reverting an empty single-line commit) and exits edit mode.
|
|
99
|
+
*
|
|
100
|
+
* Guarded on `_editing`: removing the focused input/textarea from the DOM
|
|
101
|
+
* (which happens as soon as `_editing` flips to false) makes the browser
|
|
102
|
+
* fire a native `blur` on it, re-invoking this handler a second time after
|
|
103
|
+
* a cancel already ran — the guard makes that second call a no-op instead
|
|
104
|
+
* of re-committing the stale draft.
|
|
105
|
+
*/
|
|
106
|
+
_commit() {
|
|
107
|
+
if (!this._editing)
|
|
108
|
+
return;
|
|
109
|
+
const next = this._draft;
|
|
110
|
+
this._editing = false;
|
|
111
|
+
if (!this.multiline && next.trim() === "")
|
|
112
|
+
return;
|
|
113
|
+
if (next === this.value)
|
|
114
|
+
return;
|
|
115
|
+
this.value = next;
|
|
116
|
+
this.dispatchEvent(new CustomEvent("change", { detail: { value: next } }));
|
|
117
|
+
}
|
|
118
|
+
/** Discards the draft and exits edit mode without committing. */
|
|
119
|
+
_cancel() {
|
|
120
|
+
this._editing = false;
|
|
121
|
+
}
|
|
122
|
+
_onInput(e) {
|
|
123
|
+
this._draft = e.target.value;
|
|
124
|
+
if (this.multiline)
|
|
125
|
+
this._resize();
|
|
126
|
+
}
|
|
127
|
+
_onSingleLineKeydown(e) {
|
|
128
|
+
if (e.key === "Enter") {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
this._commit();
|
|
131
|
+
}
|
|
132
|
+
else if (e.key === "Escape") {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
this._cancel();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
_onMultilineKeydown(e) {
|
|
138
|
+
if (e.key === "Escape") {
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
this._cancel();
|
|
141
|
+
}
|
|
142
|
+
else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
this._commit();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Grows the textarea's height to fit its content. */
|
|
148
|
+
_resize() {
|
|
149
|
+
const field = this._field;
|
|
150
|
+
if (!field)
|
|
151
|
+
return;
|
|
152
|
+
field.style.height = "auto";
|
|
153
|
+
field.style.height = `${field.scrollHeight}px`;
|
|
154
|
+
}
|
|
155
|
+
updated(changed) {
|
|
156
|
+
if (changed.has("_editing") && this._editing) {
|
|
157
|
+
const field = this._field;
|
|
158
|
+
if (!field)
|
|
159
|
+
return;
|
|
160
|
+
field.focus();
|
|
161
|
+
const end = field.value.length;
|
|
162
|
+
field.setSelectionRange(end, end);
|
|
163
|
+
if (this.multiline)
|
|
164
|
+
this._resize();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
render() {
|
|
168
|
+
if (this._editing) {
|
|
169
|
+
return this.multiline
|
|
170
|
+
? html `<textarea
|
|
171
|
+
rows="3"
|
|
172
|
+
.value=${this._draft}
|
|
173
|
+
placeholder=${this.placeholder}
|
|
174
|
+
aria-label=${this.label || nothing}
|
|
175
|
+
@input=${this._onInput}
|
|
176
|
+
@keydown=${this._onMultilineKeydown}
|
|
177
|
+
@blur=${this._commit}
|
|
178
|
+
></textarea>`
|
|
179
|
+
: html `<input
|
|
180
|
+
type="text"
|
|
181
|
+
.value=${this._draft}
|
|
182
|
+
placeholder=${this.placeholder}
|
|
183
|
+
aria-label=${this.label || nothing}
|
|
184
|
+
@input=${this._onInput}
|
|
185
|
+
@keydown=${this._onSingleLineKeydown}
|
|
186
|
+
@blur=${this._commit}
|
|
187
|
+
/>`;
|
|
188
|
+
}
|
|
189
|
+
const isEmpty = this.value.trim() === "";
|
|
190
|
+
const classes = `display ${this.multiline ? "multiline" : ""} ${isEmpty ? "empty" : ""} ${this.readonly ? "readonly" : ""}`;
|
|
191
|
+
return html `<span class=${classes} @click=${this._startEdit}
|
|
192
|
+
>${isEmpty ? this.placeholder : this.value}</span
|
|
193
|
+
>`;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
__decorate([
|
|
197
|
+
property()
|
|
198
|
+
], EditableText.prototype, "value", void 0);
|
|
199
|
+
__decorate([
|
|
200
|
+
property({ type: Boolean })
|
|
201
|
+
], EditableText.prototype, "multiline", void 0);
|
|
202
|
+
__decorate([
|
|
203
|
+
property()
|
|
204
|
+
], EditableText.prototype, "placeholder", void 0);
|
|
205
|
+
__decorate([
|
|
206
|
+
property({ type: Boolean })
|
|
207
|
+
], EditableText.prototype, "readonly", void 0);
|
|
208
|
+
__decorate([
|
|
209
|
+
property()
|
|
210
|
+
], EditableText.prototype, "label", void 0);
|
|
211
|
+
__decorate([
|
|
212
|
+
state()
|
|
213
|
+
], EditableText.prototype, "_editing", void 0);
|
|
214
|
+
__decorate([
|
|
215
|
+
state()
|
|
216
|
+
], EditableText.prototype, "_draft", void 0);
|
|
217
|
+
__decorate([
|
|
218
|
+
query("input, textarea")
|
|
219
|
+
], EditableText.prototype, "_field", void 0);
|
|
220
|
+
EditableText = __decorate([
|
|
221
|
+
customElement("editable-text")
|
|
222
|
+
], EditableText);
|
|
223
|
+
export { EditableText };
|
|
224
|
+
//# sourceMappingURL=editable-text.js.map
|