@design.estate/dees-catalog 13.0.0 → 13.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist_bundle/bundle.js +823 -170
  2. package/dist_bundle/bundle.js.map +1 -1
  3. package/dist_ts_web/00_commitinfo_data.js +1 -1
  4. package/dist_ts_web/elements/00group-input/dees-input-reference/dees-input-reference.js +4 -4
  5. package/dist_ts_web/elements/00group-kanban/dees-kanban/dees-kanban.d.ts +3 -1
  6. package/dist_ts_web/elements/00group-kanban/dees-kanban/dees-kanban.js +75 -46
  7. package/dist_ts_web/elements/00group-kanban/dees-kanban-card/dees-kanban-card.d.ts +4 -0
  8. package/dist_ts_web/elements/00group-kanban/dees-kanban-card/dees-kanban-card.js +72 -27
  9. package/dist_ts_web/elements/00group-kanban/dees-kanban-column/dees-kanban-column.d.ts +7 -2
  10. package/dist_ts_web/elements/00group-kanban/dees-kanban-column/dees-kanban-column.js +87 -45
  11. package/dist_ts_web/elements/00group-kanban/dees-kanban-discussion/dees-kanban-discussion.js +26 -15
  12. package/dist_ts_web/elements/00group-kanban/dees-kanban-ticket-detail/dees-kanban-ticket-detail.d.ts +2 -0
  13. package/dist_ts_web/elements/00group-kanban/dees-kanban-ticket-detail/dees-kanban-ticket-detail.js +73 -40
  14. package/dist_ts_web/elements/00group-kanban/kanban.dragcontroller.d.ts +67 -0
  15. package/dist_ts_web/elements/00group-kanban/kanban.dragcontroller.js +548 -0
  16. package/dist_ts_web/elements/00group-kanban/kanban.helpers.d.ts +3 -1
  17. package/dist_ts_web/elements/00group-kanban/kanban.helpers.js +3 -1
  18. package/dist_ts_web/elements/00group-kanban/kanban.styles.js +11 -3
  19. package/package.json +1 -1
  20. package/readme.md +19 -4
  21. package/scripts/check-packed-consumer.mjs +1 -1
  22. package/ts_web/00_commitinfo_data.ts +1 -1
  23. package/ts_web/elements/00group-input/dees-input-reference/dees-input-reference.ts +3 -3
  24. package/ts_web/elements/00group-kanban/dees-kanban/dees-kanban.ts +61 -34
  25. package/ts_web/elements/00group-kanban/dees-kanban-card/dees-kanban-card.ts +47 -24
  26. package/ts_web/elements/00group-kanban/dees-kanban-column/dees-kanban-column.ts +47 -27
  27. package/ts_web/elements/00group-kanban/dees-kanban-discussion/dees-kanban-discussion.ts +25 -14
  28. package/ts_web/elements/00group-kanban/dees-kanban-ticket-detail/dees-kanban-ticket-detail.ts +64 -38
  29. package/ts_web/elements/00group-kanban/kanban.dragcontroller.ts +481 -0
  30. package/ts_web/elements/00group-kanban/kanban.helpers.ts +5 -1
  31. package/ts_web/elements/00group-kanban/kanban.styles.ts +10 -2
@@ -1,7 +1,7 @@
1
1
  import { DeesElement, customElement, property, state, html, css } from '@design.estate/dees-element';
2
2
  import type { IKanbanComment, IKanbanProvider } from '../interfaces.js';
3
3
  import { kanbanStyles } from '../kanban.styles.js';
4
- import { kanbanError, kanbanEvent } from '../kanban.helpers.js';
4
+ import { kanbanError, kanbanEvent, kanbanInitials } from '../kanban.helpers.js';
5
5
  import { referenceText, type TReferenceContent, type TReferenceSearch } from '../../00group-input/dees-input-reference/index.js';
6
6
  import '../../00group-button/dees-button/dees-button.js';
7
7
 
@@ -20,31 +20,42 @@ export class DeesKanbanDiscussion extends DeesElement {
20
20
  @state() private accessor pending = new Set<string>();
21
21
  @state() private accessor failures = new Map<string, string>();
22
22
  public static styles = [...kanbanStyles, css`
23
- header { display: flex; align-items: center; margin-bottom: 16px; }
24
- h3 { font-size: var(--dees-font-body-size); font-weight: 600; }
23
+ header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
24
+ h3 { font-size: var(--dees-font-footnote-size); font-weight: 600; }
25
+ .refresh::part(button) { width: 28px; height: 28px; min-height: 28px; }
25
26
  ol { list-style: none; padding: 0; margin: 0; }
26
- li { padding: 0 0 20px; }
27
- .author { font-weight: 550; font-size: var(--dees-font-footnote-size); }
27
+ li { display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: 10px; padding: 10px 0; }
28
+ li > .avatar { width: 28px; height: 28px; margin-top: 1px; }
29
+ .author { font-weight: 600; font-size: var(--dees-font-footnote-size); }
30
+ .meta { gap: 4px 10px; }
28
31
  time { font-size: var(--dees-font-caption1-size); color: var(--dees-color-text-secondary); }
29
- .message { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.6; margin-top: 6px; }
30
- .reference { display: inline; border: 0; border-radius: var(--dees-radius-sm); padding: 1px 4px; color: var(--dees-color-link); background: var(--dees-color-badge-default-bg); font: inherit; cursor: pointer; }
31
- .compose { padding-top: 4px; }
32
- .compose .row { padding-top: 8px; }
32
+ .message { white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--dees-font-subhead-size); line-height: 22px; margin-top: 2px; }
33
+ .reference { display: inline; border: 0; border-radius: var(--dees-radius-sm); padding: 1px 5px; color: var(--dees-color-link); background: var(--dees-color-fill-tertiary); font: inherit; font-weight: 500; cursor: pointer; text-align: left; overflow-wrap: anywhere; }
34
+ .compose { margin: 14px 0 0 38px; padding: 0; border: 1px solid var(--dees-color-control-border); border-radius: var(--dees-radius-xl); corner-shape: var(--dees-corner-shape); background: var(--dees-color-control-bg); }
35
+ .compose:has(dees-input-reference:focus-within) { outline: 2px solid var(--dees-color-focus-ring); outline-offset: 1px; }
36
+ dees-input-reference { --dees-reference-min-height: 64px; --dees-reference-font-size: var(--dees-font-subhead-size); }
37
+ dees-input-reference::part(editor) { border: 0; background: transparent; outline: none; }
38
+ .compose .row { padding: 4px 10px 10px; gap: 8px; }
39
+ .compose .caption { font-size: var(--dees-font-caption1-size); }
40
+ .shortcut { white-space: nowrap; }
41
+ .compose .error { padding: 0 12px 10px; }
42
+ @media (max-width: 600px) { .compose { margin-left: 0; } .compose .caption { flex-basis: 100%; } }
33
43
  `];
34
44
  public render() {
35
45
  const pending = this.pending.has(this.ticketId);
36
46
  const comments = this.comments.filter(comment => comment.ticketId === this.ticketId)
37
47
  .slice().sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
38
- return html`<header><h3>Discussion <span class="caption">${comments.length}</span></h3><span class="spacer"></span>
39
- <dees-button size="sm" type="ghost" icon="lucide:RefreshCw" text="Refresh" .disabled=${this.loading}
48
+ return html`<header><h3>Discussion</h3><span class="count">${comments.length}</span><span class="spacer"></span>
49
+ <dees-button class="refresh" size="icon" type="ghost" icon="lucide:RefreshCw" text="Refresh" .disabled=${this.loading}
40
50
  @clicked=${() => kanbanEvent(this, 'kanban-comments-refresh', { ticketId: this.ticketId })}></dees-button></header>
41
51
  ${this.loading ? html`<p class="caption" role="status">Loading comments…</p>` : ''}
42
52
  ${this.error ? html`<p class="error" role="alert">${this.error}</p>` : ''}
43
53
  <ol aria-label="Comments">${comments.map(comment => html`<li>
44
- <div class="row"><span class="author">${comment.author.name}</span><time datetime=${comment.createdAt}>${new Date(comment.createdAt).toLocaleString()}</time></div>
54
+ <span class="avatar" aria-hidden="true">${kanbanInitials(comment.author.name)}</span><div>
55
+ <div class="row meta"><span class="author">${comment.author.name}</span><time datetime=${comment.createdAt} title=${comment.createdAt}>${new Date(comment.createdAt).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })}</time></div>
45
56
  <div class="message">${comment.body.map(part => part.type === 'text' ? part.text : html`<button class="reference"
46
57
  @click=${() => kanbanEvent(this, 'kanban-ticket-open', { ticketId: part.id })}>${part.label}</button>`)}</div>
47
- </li>`)}</ol>
58
+ </div></li>`)}</ol>
48
59
  ${!comments.length && !this.loading && !this.error ? html`<p class="empty">Start the conversation.</p>` : ''}
49
60
  ${this.addComment && !this.readOnly ? html`<div class="compose">
50
61
  <dees-input-reference label="Comment" placeholder="Write a comment…" .documentKey=${this.ticketId} .value=${this.draft} .disabled=${pending}
@@ -55,7 +66,7 @@ export class DeesKanbanDiscussion extends DeesElement {
55
66
  }}
56
67
  @reference-open=${(event: CustomEvent<{ id: string }>) => kanbanEvent(this, 'kanban-ticket-open', { ticketId: event.detail.id })}
57
68
  @reference-submit=${() => { void this.submit(); }}></dees-input-reference>
58
- <div class="row"><span class="caption">Type #_ to link a ticket · Ctrl/⌘ Enter to send</span><span class="spacer"></span>
69
+ <div class="row"><span class="caption">Type <strong>#_</strong> to link a ticket · <span class="shortcut">Ctrl/⌘ Enter to send</span></span><span class="spacer"></span>
59
70
  <dees-button type="accent" size="sm" text="Comment" .disabled=${pending || !referenceText(this.draft).trim()}
60
71
  .status=${pending ? 'pending' : 'normal'} @clicked=${() => { void this.submit(); }}></dees-button></div>
61
72
  ${this.failures.get(this.ticketId) ? html`<p class="error" role="alert">${this.failures.get(this.ticketId)}</p>` : ''}
@@ -2,7 +2,7 @@ import { DeesElement, customElement, property, state, html, css } from '@design.
2
2
  import type { IKanbanColumn, IKanbanComment, IKanbanPerson, IKanbanProvider, IKanbanTicket, IKanbanTicketDraft, TKanbanPriority } from '../interfaces.js';
3
3
  import type { TReferenceContent, TReferenceSearch } from '../../00group-input/dees-input-reference/interfaces.js';
4
4
  import { kanbanStyles } from '../kanban.styles.js';
5
- import { kanbanEvent, sortKanbanTickets, ticketDraft, ticketLabel } from '../kanban.helpers.js';
5
+ import { kanbanEvent, kanbanInitials, kanbanPriorityIcon, sortKanbanTickets, ticketDraft, ticketLabel } from '../kanban.helpers.js';
6
6
  import type { DeesInputText } from '../../00group-input/dees-input-text/dees-input-text.js';
7
7
  import type { DeesInputTextarea } from '../../00group-input/dees-input-textarea/dees-input-textarea.js';
8
8
  import '../../00group-input/dees-input-text/dees-input-text.js';
@@ -28,22 +28,44 @@ export class DeesKanbanTicketDetail extends DeesElement {
28
28
  @property({ type: String }) accessor error = '';
29
29
  @property({ type: Boolean }) accessor saving = false;
30
30
  @property({ type: Boolean }) accessor canGoBack = false;
31
+ /** Keep the identifier visible unless the containing surface already labels the ticket. */
32
+ @property({ type: Boolean }) accessor showTicketKey = true;
31
33
  @state() private accessor editing = false;
32
34
  public static styles = [...kanbanStyles, css`
33
- .layout { display: grid; grid-template-columns: minmax(0, 1fr) 230px; gap: 28px; }
34
- .main { min-width: 0; }
35
- .toolbar { margin-bottom: 16px; }
36
- h2 { font-size: var(--dees-font-title2-size); line-height: 1.3; font-weight: 600; margin-bottom: 18px; overflow-wrap: anywhere; }
37
- .description { white-space: pre-wrap; line-height: 1.65; overflow-wrap: anywhere; margin-bottom: 24px; }
38
- aside { border-left: 1px solid var(--dees-color-border-subtle); padding-left: 22px; }
39
- .fields { display: grid; gap: 16px; }
40
- .fields label { display: block; margin-bottom: 6px; }
41
- h3 { font-size: var(--dees-font-body-size); margin-bottom: 12px; }
35
+ :host { padding: 24px; }
36
+ .layout { display: grid; grid-template-columns: minmax(0, 1fr) 248px; grid-template-areas: 'lead props' 'body props'; gap: 24px; align-items: start; }
37
+ .lead { grid-area: lead; min-width: 0; }
38
+ .body { grid-area: body; min-width: 0; }
39
+ .toolbar { margin-bottom: 20px; flex-wrap: nowrap; min-width: 0; }
40
+ .toolbar .badge { text-transform: capitalize; flex-shrink: 0; }
41
+ .ticket-key { flex-shrink: 0; font-variant-numeric: tabular-nums; }
42
+ .link.parent { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--dees-font-footnote-size); }
43
+ .toolbar dees-button { flex-shrink: 0; }
44
+ h2 { font-size: var(--dees-font-title2-size); line-height: 1.3; font-weight: 600; margin-bottom: 12px; letter-spacing: -.015em; overflow-wrap: anywhere; }
45
+ .description { white-space: pre-wrap; font-size: var(--dees-font-subhead-size); line-height: 22px; overflow-wrap: anywhere; max-width: 64ch; }
46
+ aside { grid-area: props; min-width: 0; padding: 14px 16px; border-radius: var(--dees-radius-xl); corner-shape: var(--dees-corner-shape); background: var(--dees-color-settings-group); }
47
+ .fields { display: grid; gap: 14px; font-size: var(--dees-font-footnote-size); font-weight: 500; }
48
+ .fields label { display: block; margin-bottom: 5px; font-size: var(--dees-font-caption1-size); font-weight: 400; }
49
+ .value { min-height: var(--dees-control-height-lg); display: flex; align-items: center; }
50
+ .person { display: flex; align-items: center; gap: 7px; }
42
51
  .children { margin-bottom: 24px; }
43
- .child { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--dees-color-border-subtle); }
44
- .discussion { border-top: 1px solid var(--dees-color-border-subtle); padding-top: 22px; }
45
- .edit { display: grid; gap: 14px; margin-bottom: 22px; }
46
- @media (max-width: 700px) { .layout { grid-template-columns: minmax(0, 1fr); gap: 24px; } aside { grid-row: 1; border-left: 0; padding-left: 0; } .fields { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
52
+ .child-list { border-radius: var(--dees-radius-xl); corner-shape: var(--dees-corner-shape); background: var(--dees-color-settings-group); overflow: hidden; }
53
+ .child { display: flex; width: 100%; align-items: center; gap: 10px; min-height: 44px; padding: 10px 12px; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
54
+ .child + .child { border-top: 1px solid var(--dees-color-border-subtle); }
55
+ .child:hover { background: var(--dees-color-row-hover); }
56
+ .child:focus-visible { outline-offset: -3px; }
57
+ .child-key { font-size: var(--dees-font-caption1-size); color: var(--dees-color-text-secondary); font-variant-numeric: tabular-nums; flex-shrink: 0; }
58
+ .child-title { flex: 1; min-width: 0; font-size: var(--dees-font-footnote-size); font-weight: 500; overflow-wrap: anywhere; }
59
+ .child .badge { flex-shrink: 0; }
60
+ .discussion { border-top: 1px solid var(--dees-color-border-subtle); padding-top: 20px; }
61
+ .edit { display: grid; gap: 14px; }
62
+ @media (max-width: 700px) {
63
+ :host { padding: 16px; }
64
+ .layout { grid-template-columns: minmax(0, 1fr); grid-template-areas: 'lead' 'props' 'body'; gap: 20px; }
65
+ .fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
66
+ .toolbar { gap: 6px; margin-bottom: 16px; }
67
+ .child { gap: 7px; }
68
+ }
47
69
  `];
48
70
  public updated(changed: Map<string, unknown>): void {
49
71
  const previous = changed.get('ticket') as IKanbanTicket | undefined;
@@ -65,12 +87,13 @@ export class DeesKanbanTicketDetail extends DeesElement {
65
87
  const parents = this.tickets.filter(item => item.kind === (ticket.kind === 'subtask' ? 'task' : 'epic'));
66
88
  return html`<div class="toolbar row">
67
89
  ${this.canGoBack ? html`<dees-button size="sm" type="ghost" icon="lucide:ArrowLeft" text="Back" @clicked=${() => kanbanEvent(this, 'kanban-detail-back', {})}></dees-button>` : ''}
68
- <span class="badge">${ticket.kind}</span><span class="caption">${ticket.key}</span>
69
- ${parent ? html`<span class="caption">/</span><button class="link caption" @click=${() => kanbanEvent(this, 'kanban-ticket-open', { ticketId: parent.id })}>${parent.title}</button>` : ''}
90
+ <span class="badge">${ticket.kind}</span>
91
+ ${this.showTicketKey ? html`<span class="caption ticket-key">${ticket.key}</span>` : ''}
92
+ ${parent ? html`<span class="caption" aria-hidden="true">/</span><button class="link parent" title=${parent.title} @click=${() => kanbanEvent(this, 'kanban-ticket-open', { ticketId: parent.id })}>${parent.title}</button>` : ''}
70
93
  <span class="spacer"></span>${editable && !this.editing ? html`<dees-button size="sm" type="ghost" text="Edit ticket" icon="lucide:Pencil" @clicked=${() => { this.editing = true; this.changeDraft({}); }}></dees-button>` : ''}
71
94
  </div>
72
95
  ${this.error ? html`<p class="error" role="alert">${this.error}</p>` : ''}
73
- <div class="layout"><div class="main">
96
+ <div class="layout"><section class="lead">
74
97
  ${this.editing && editable ? html`<div class="edit">
75
98
  <dees-input-text label="Title" .value=${draft.title} .disabled=${this.saving}
76
99
  @input=${(event: Event) => this.changeDraft({ title: String((event.currentTarget as DeesInputText).value ?? '') })}></dees-input-text>
@@ -82,33 +105,36 @@ export class DeesKanbanTicketDetail extends DeesElement {
82
105
  this.editing = false; this.draft = undefined; kanbanEvent(this, 'kanban-ticket-draft', { ticketId: ticket.id, draft: undefined });
83
106
  }}></dees-button></div>
84
107
  </div>` : html`<h2>${ticket.title}</h2><div class="description ${ticket.description ? '' : 'muted'}">${ticket.description || 'No description yet.'}</div>`}
85
- ${ticket.kind !== 'subtask' ? html`<section class="children"><div class="row"><h3>${ticket.kind === 'epic' ? 'Tasks' : 'Subtasks'}</h3><span class="spacer"></span>
86
- ${this.provider.createTicket && ticket.permissions?.createChild !== false ? html`<dees-button size="sm" type="ghost" icon="lucide:Plus" text=${ticket.kind === 'epic' ? 'Add task' : 'Add subtask'}
87
- @clicked=${() => kanbanEvent(this, 'kanban-ticket-create', { kind: ticket.kind === 'epic' ? 'task' : 'subtask', parentId: ticket.id, columnId: this.columns[0]?.id ?? ticket.columnId })}></dees-button>` : ''}</div>
88
- ${children.map(child => html`<div class="child"><button class="link" @click=${() => kanbanEvent(this, 'kanban-ticket-open', { ticketId: child.id })}>${ticketLabel(child)}</button>
89
- <span class="badge">${this.columns.find(column => column.id === child.columnId)?.title ?? child.columnId}</span></div>`)}
90
- ${!children.length ? html`<p class="caption">No ${ticket.kind === 'epic' ? 'tasks' : 'subtasks'} yet.</p>` : ''}</section>` : ''}
91
- <dees-kanban-discussion class="discussion" .ticketId=${ticket.id} .comments=${this.comments} .draft=${this.commentDraft}
92
- .loading=${this.loadingComments} .error=${this.commentsError} .readOnly=${ticket.permissions?.comment === false}
93
- .searchTickets=${this.searchTickets} .addComment=${this.provider.addComment ? this.addComment : undefined}></dees-kanban-discussion>
94
- </div><aside><div class="fields">
95
- <dees-input-dropdown label="Status" .options=${this.columns.map(column => ({ key: column.id, option: column.title }))}
108
+ </section><aside><div class="fields">
109
+ <div><label class="caption">Status</label><dees-input-dropdown label="Status" .labelPosition=${'none'} .options=${this.columns.map(column => ({ key: column.id, option: column.title }))}
96
110
  .value=${{ key: ticket.columnId, option: this.columns.find(column => column.id === ticket.columnId)?.title ?? ticket.columnId }}
97
- .disabled=${!movable || this.saving} @selectedOption=${(event: CustomEvent<{ key: string }>) => kanbanEvent(this, 'kanban-ticket-move', { ticketId: ticket.id, columnId: event.detail.key })}></dees-input-dropdown>
111
+ .disabled=${!movable || this.saving} @selectedOption=${(event: CustomEvent<{ key: string }>) => kanbanEvent(this, 'kanban-ticket-move', { ticketId: ticket.id, columnId: event.detail.key })}></dees-input-dropdown></div>
98
112
  ${this.editing && editable ? html`
99
- <dees-input-dropdown label="Priority" .disabled=${this.saving} .value=${{ key: draft.priority, option: draft.priority }}
113
+ <div><label class="caption">Priority</label><dees-input-dropdown label="Priority" .labelPosition=${'none'} .disabled=${this.saving} .value=${{ key: draft.priority, option: draft.priority }}
100
114
  .options=${['none', 'low', 'normal', 'high', 'urgent'].map(key => ({ key, option: key }))}
101
- @selectedOption=${(event: CustomEvent<{ key: TKanbanPriority }>) => this.changeDraft({ priority: event.detail.key })}></dees-input-dropdown>
102
- <dees-input-dropdown label="Assignee" .disabled=${this.saving} .value=${{ key: draft.assigneeId, option: this.people.find(person => person.id === draft.assigneeId)?.name ?? ticket.assignee?.name ?? 'Unassigned' }}
115
+ @selectedOption=${(event: CustomEvent<{ key: TKanbanPriority }>) => this.changeDraft({ priority: event.detail.key })}></dees-input-dropdown></div>
116
+ <div><label class="caption">Assignee</label><dees-input-dropdown label="Assignee" .labelPosition=${'none'} .disabled=${this.saving} .value=${{ key: draft.assigneeId, option: this.people.find(person => person.id === draft.assigneeId)?.name ?? ticket.assignee?.name ?? 'Unassigned' }}
103
117
  .options=${[{ key: '', option: 'Unassigned' }, ...this.people.map(person => ({ key: person.id, option: person.name }))]}
104
- @selectedOption=${(event: CustomEvent<{ key: string }>) => this.changeDraft({ assigneeId: event.detail.key })}></dees-input-dropdown>
105
- ${ticket.kind !== 'epic' ? html`<dees-input-dropdown label=${ticket.kind === 'task' ? 'Epic' : 'Parent task'} .disabled=${this.saving}
118
+ @selectedOption=${(event: CustomEvent<{ key: string }>) => this.changeDraft({ assigneeId: event.detail.key })}></dees-input-dropdown></div>
119
+ ${ticket.kind !== 'epic' ? html`<div><label class="caption">${ticket.kind === 'task' ? 'Epic' : 'Parent task'}</label><dees-input-dropdown label=${ticket.kind === 'task' ? 'Epic' : 'Parent task'} .labelPosition=${'none'} .disabled=${this.saving}
106
120
  .value=${{ key: draft.parentId, option: parents.find(item => item.id === draft.parentId)?.title ?? 'None' }}
107
121
  .options=${[...(ticket.kind === 'task' ? [{ key: '', option: 'None' }] : []), ...parents.map(item => ({ key: item.id, option: ticketLabel(item) }))]}
108
- @selectedOption=${(event: CustomEvent<{ key: string }>) => this.changeDraft({ parentId: event.detail.key })}></dees-input-dropdown>` : ''}
109
- ` : html`<div><label class="caption">Priority</label>${ticket.priority ?? 'none'}</div><div><label class="caption">Assignee</label>${ticket.assignee?.name ?? 'Unassigned'}</div>`}
110
- ${ticket.labels?.length ? html`<div><label class="caption">Labels</label><div class="row">${ticket.labels.map(label => html`<span class="badge">${label}</span>`)}</div></div>` : ''}
111
- </div></aside></div>`;
122
+ @selectedOption=${(event: CustomEvent<{ key: string }>) => this.changeDraft({ parentId: event.detail.key })}></dees-input-dropdown></div>` : ''}
123
+ ` : html`<div><label class="caption">Priority</label><span class="priority value" data-priority=${ticket.priority ?? 'none'}><dees-icon .icon=${kanbanPriorityIcon(ticket.priority)} iconSize="13" aria-hidden="true"></dees-icon>${ticket.priority ?? 'none'}</span></div>
124
+ <div><label class="caption">Assignee</label><span class="person value"><span class="avatar" aria-hidden="true">${ticket.assignee ? kanbanInitials(ticket.assignee.name) : '–'}</span>${ticket.assignee?.name ?? 'Unassigned'}</span></div>`}
125
+ ${ticket.labels?.length ? html`<div><label class="caption">Labels</label><div class="row value">${ticket.labels.map(label => html`<span class="badge">${label}</span>`)}</div></div>` : ''}
126
+ </div></aside><div class="body">
127
+ ${ticket.kind !== 'subtask' ? html`<section class="children"><div class="section-title"><h3>${ticket.kind === 'epic' ? 'Tasks' : 'Subtasks'}</h3><span class="count">${children.length}</span><span class="spacer"></span>
128
+ ${this.provider.createTicket && ticket.permissions?.createChild !== false ? html`<dees-button size="sm" type="ghost" icon="lucide:Plus" text=${ticket.kind === 'epic' ? 'Add task' : 'Add subtask'}
129
+ @clicked=${() => kanbanEvent(this, 'kanban-ticket-create', { kind: ticket.kind === 'epic' ? 'task' : 'subtask', parentId: ticket.id, columnId: this.columns[0]?.id ?? ticket.columnId })}></dees-button>` : ''}</div>
130
+ <div class="child-list">${children.map(child => html`<button class="child" @click=${() => kanbanEvent(this, 'kanban-ticket-open', { ticketId: child.id })}>
131
+ <span class="child-key">${child.key}</span><span class="child-title">${child.title}</span>
132
+ <span class="badge">${this.columns.find(column => column.id === child.columnId)?.title ?? child.columnId}</span></button>`)}</div>
133
+ ${!children.length ? html`<p class="caption">No ${ticket.kind === 'epic' ? 'tasks' : 'subtasks'} yet.</p>` : ''}</section>` : ''}
134
+ <dees-kanban-discussion class="discussion" .ticketId=${ticket.id} .comments=${this.comments} .draft=${this.commentDraft}
135
+ .loading=${this.loadingComments} .error=${this.commentsError} .readOnly=${ticket.permissions?.comment === false}
136
+ .searchTickets=${this.searchTickets} .addComment=${this.provider.addComment ? this.addComment : undefined}></dees-kanban-discussion>
137
+ </div></div>`;
112
138
  }
113
139
  public finishEditing(): void { this.editing = false; }
114
140
  }