@unofficialbox/box-open-elements 0.11.0 → 0.13.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/dist/components/feedback/alert.d.ts +2 -0
- package/dist/components/feedback/alert.js +41 -8
- package/dist/components/feedback/indicator.d.ts +34 -0
- package/dist/components/feedback/indicator.js +167 -0
- package/dist/components/feedback/nudge.js +0 -1
- package/dist/components/feedback/toast.d.ts +4 -5
- package/dist/components/feedback/toast.js +19 -46
- package/dist/components/feedback/tone.d.ts +18 -0
- package/dist/components/feedback/tone.js +23 -0
- package/dist/components/forms/calendar.d.ts +25 -0
- package/dist/components/forms/calendar.js +94 -2
- package/dist/components/forms/tile-group.d.ts +58 -0
- package/dist/components/forms/tile-group.js +280 -0
- package/dist/components/output/code-block.d.ts +49 -0
- package/dist/components/output/code-block.js +236 -0
- package/dist/components/output/formatted-date.d.ts +38 -0
- package/dist/components/output/formatted-date.js +90 -0
- package/dist/components/output/formatted-file-size.d.ts +31 -0
- package/dist/components/output/formatted-file-size.js +61 -0
- package/dist/components/output/formatted-number.d.ts +37 -0
- package/dist/components/output/formatted-number.js +106 -0
- package/dist/components/output/formatted-value.d.ts +44 -0
- package/dist/components/output/formatted-value.js +100 -0
- package/dist/components/output/index.d.ts +6 -0
- package/dist/components/output/index.js +9 -0
- package/dist/components/output/relative-time.d.ts +41 -0
- package/dist/components/output/relative-time.js +82 -0
- package/dist/element-maps.d.ts +17 -1
- package/dist/entries/code-block.d.ts +1 -0
- package/dist/entries/code-block.js +1 -0
- package/dist/entries/comment-thread.d.ts +1 -0
- package/dist/entries/comment-thread.js +1 -0
- package/dist/entries/formatted-date.d.ts +1 -0
- package/dist/entries/formatted-date.js +1 -0
- package/dist/entries/formatted-file-size.d.ts +1 -0
- package/dist/entries/formatted-file-size.js +1 -0
- package/dist/entries/formatted-number.d.ts +1 -0
- package/dist/entries/formatted-number.js +1 -0
- package/dist/entries/indicator.d.ts +1 -0
- package/dist/entries/indicator.js +1 -0
- package/dist/entries/relative-time.d.ts +1 -0
- package/dist/entries/relative-time.js +1 -0
- package/dist/entries/tile-group.d.ts +1 -0
- package/dist/entries/tile-group.js +1 -0
- package/dist/foundations/format/format.d.ts +82 -0
- package/dist/foundations/format/format.js +122 -0
- package/dist/foundations/format/index.d.ts +1 -0
- package/dist/foundations/format/index.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/patterns/comments/comment-thread.d.ts +79 -0
- package/dist/patterns/comments/comment-thread.js +561 -0
- package/dist/patterns/comments/index.d.ts +2 -0
- package/dist/patterns/comments/index.js +5 -0
- package/dist/patterns/comments/types.d.ts +39 -0
- package/dist/patterns/comments/types.js +8 -0
- package/dist/patterns/preview/annotation-thread.d.ts +67 -48
- package/dist/patterns/preview/annotation-thread.js +96 -440
- package/package.json +1 -1
|
@@ -1,483 +1,139 @@
|
|
|
1
|
-
import { BaseElement } from "../../core/index.js";
|
|
2
|
-
import { boeBrandInteractiveStyles, boeFocusRingShadow, boeNeutralInteractiveStyles, } from "../../foundations/tokens/index.js";
|
|
3
|
-
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
4
1
|
import { boePanel } from "../../foundations/geometry/index.js";
|
|
2
|
+
import { CommentThread } from "../comments/index.js";
|
|
5
3
|
const DEFAULT_TAG_NAME = "box-annotation-thread";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
.replaceAll("<", "<")
|
|
9
|
-
.replaceAll(">", ">")
|
|
10
|
-
.replaceAll('"', """)
|
|
11
|
-
.replaceAll("'", "'");
|
|
12
|
-
const elementStyles = `
|
|
13
|
-
/* Author display rules on parts would otherwise defeat the UA's
|
|
14
|
-
[hidden] rule — state toggling relies on the hidden attribute. */
|
|
15
|
-
[hidden] {
|
|
16
|
-
display: none !important;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
:host {
|
|
20
|
-
display: block;
|
|
21
|
-
color: inherit;
|
|
22
|
-
font: inherit;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
[part="thread"] {
|
|
26
|
-
display: grid;
|
|
27
|
-
gap: ${boePanel.gap};
|
|
28
|
-
padding: ${boePanel.padding};
|
|
29
|
-
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
|
|
30
|
-
border-radius: ${boePanel.radius};
|
|
31
|
-
background: ${boePanel.background};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
[part="header"] {
|
|
35
|
-
display: grid;
|
|
36
|
-
gap: 0.42rem;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
[part="title"] {
|
|
40
|
-
margin: 0;
|
|
41
|
-
font: inherit;
|
|
42
|
-
font-size: 1.1rem;
|
|
43
|
-
font-weight: 700;
|
|
44
|
-
color: var(--boe-token-text-text, #1f1e1b);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
[part="message"] {
|
|
48
|
-
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
49
|
-
line-height: 1.5;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
[part="entries"] {
|
|
53
|
-
display: grid;
|
|
54
|
-
gap: 0.6rem;
|
|
55
|
-
margin: 0;
|
|
56
|
-
padding: 0;
|
|
57
|
-
list-style: none;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
[part="entry-item"] {
|
|
61
|
-
display: block;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
[part="entry"] {
|
|
65
|
-
appearance: none;
|
|
4
|
+
const anchorStyles = `
|
|
5
|
+
[part="anchor"] {
|
|
66
6
|
display: grid;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
align-items: start;
|
|
71
|
-
padding: 0.55rem;
|
|
72
|
-
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 48%, transparent);
|
|
7
|
+
gap: 0.3rem;
|
|
8
|
+
padding: 0.45rem 0.55rem;
|
|
9
|
+
border-inline-start: 2px solid color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 45%, transparent);
|
|
73
10
|
border-radius: ${boePanel.radius};
|
|
74
|
-
background: color-mix(in srgb, var(--boe-token-surface-surface, #ffffff) 78%, transparent);
|
|
75
|
-
color: inherit;
|
|
76
|
-
font: inherit;
|
|
77
|
-
text-align: left;
|
|
78
|
-
cursor: pointer;
|
|
79
|
-
transition:
|
|
80
|
-
transform ${boeMotionDuration.fast} ${boeMotionEasing.standard},
|
|
81
|
-
border-color ${boeMotionDuration.fast} ${boeMotionEasing.standard},
|
|
82
|
-
box-shadow ${boeMotionDuration.fast} ${boeMotionEasing.standard};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
[part="entry-avatar"] {
|
|
86
|
-
display: inline-grid;
|
|
87
|
-
place-items: center;
|
|
88
|
-
inline-size: 2rem;
|
|
89
|
-
block-size: 2rem;
|
|
90
|
-
border-radius: 999px;
|
|
91
|
-
background: var(--boe-token-surface-item-surface-selected, #f2f7fd);
|
|
92
|
-
color: var(--boe-token-surface-surface-brand, #0061d5);
|
|
93
|
-
font-size: 0.72rem;
|
|
94
|
-
font-weight: 700;
|
|
95
|
-
letter-spacing: 0.06em;
|
|
96
|
-
text-transform: uppercase;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
[part="entry-copy"] {
|
|
100
|
-
display: grid;
|
|
101
|
-
gap: 0.24rem;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
[part="entry-topline"] {
|
|
105
|
-
display: flex;
|
|
106
|
-
flex-wrap: wrap;
|
|
107
|
-
align-items: center;
|
|
108
|
-
gap: 0.35rem;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
[part="entry-author"] {
|
|
112
|
-
font-weight: 700;
|
|
113
|
-
color: var(--boe-token-text-text, #1f1e1b);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
[part="entry-tool"],
|
|
117
|
-
[part="entry-status"] {
|
|
118
|
-
display: inline-flex;
|
|
119
|
-
padding: 0.18rem 0.42rem;
|
|
120
|
-
border-radius: 999px;
|
|
121
11
|
background: var(--boe-token-surface-item-surface-selected, #f2f7fd);
|
|
122
|
-
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
123
|
-
font-size: 0.75rem;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
[part="entry-body"] {
|
|
127
|
-
line-height: 1.55;
|
|
128
|
-
color: var(--boe-token-text-text, #1f1e1b);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
[part="entry-time"] {
|
|
132
|
-
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
133
|
-
font-size: 0.78rem;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
[part="actions"] {
|
|
137
|
-
display: flex;
|
|
138
|
-
flex-wrap: wrap;
|
|
139
|
-
gap: 0.5rem;
|
|
140
12
|
}
|
|
141
13
|
|
|
142
|
-
[part="
|
|
143
|
-
appearance: none;
|
|
144
|
-
min-height: 2rem;
|
|
145
|
-
padding: 0.4rem 0.7rem;
|
|
146
|
-
border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 60%, transparent);
|
|
147
|
-
border-radius: 999px;
|
|
148
|
-
background: var(--boe-token-surface-surface, #ffffff);
|
|
149
|
-
color: inherit;
|
|
150
|
-
font: inherit;
|
|
151
|
-
cursor: pointer;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
[part="empty"] {
|
|
155
|
-
padding: ${boePanel.padding};
|
|
156
|
-
border-radius: ${boePanel.radius};
|
|
157
|
-
border: 1px dashed color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
|
|
158
|
-
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
[part="composer"] {
|
|
162
|
-
display: grid;
|
|
163
|
-
gap: 0.5rem;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
[part="composer-label"] {
|
|
14
|
+
[part="anchor-location"] {
|
|
167
15
|
font-size: 0.74rem;
|
|
168
16
|
font-weight: 700;
|
|
169
|
-
letter-spacing: 0.
|
|
17
|
+
letter-spacing: 0.06em;
|
|
170
18
|
text-transform: uppercase;
|
|
171
19
|
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
172
20
|
}
|
|
173
21
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
color: inherit;
|
|
181
|
-
font: inherit;
|
|
22
|
+
/* Quoted document text, so it reads as a citation rather than as one of
|
|
23
|
+
the comments below it. */
|
|
24
|
+
[part="anchor-quote"] {
|
|
25
|
+
margin: 0;
|
|
26
|
+
color: var(--boe-token-text-text, #1f1e1b);
|
|
27
|
+
font-style: italic;
|
|
182
28
|
line-height: 1.5;
|
|
183
|
-
resize: vertical;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
[part="composer-submit"] {
|
|
187
|
-
appearance: none;
|
|
188
|
-
justify-self: start;
|
|
189
|
-
min-height: 2rem;
|
|
190
|
-
padding: 0.4rem 0.7rem;
|
|
191
|
-
border: 1px solid transparent;
|
|
192
|
-
border-radius: 999px;
|
|
193
|
-
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
194
|
-
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
195
|
-
font: inherit;
|
|
196
|
-
cursor: pointer;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
${boeNeutralInteractiveStyles('[part="entry"]')}
|
|
200
|
-
${boeNeutralInteractiveStyles('[part="action"]')}
|
|
201
|
-
${boeBrandInteractiveStyles('[part="action"][data-tone="primary"]')}
|
|
202
|
-
${boeBrandInteractiveStyles('[part="composer-submit"]')}
|
|
203
|
-
|
|
204
|
-
[part="entry"][aria-pressed="true"],
|
|
205
|
-
[part="entry"][aria-pressed="true"]:hover:not(:disabled) {
|
|
206
|
-
border-color: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 34%, transparent);
|
|
207
|
-
box-shadow: ${boeFocusRingShadow};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
[part="action"][data-tone="primary"] {
|
|
211
|
-
border-color: transparent;
|
|
212
|
-
background: var(--boe-token-surface-surface-brand, #0061d5);
|
|
213
|
-
color: var(--boe-token-text-text-on-brand, #ffffff);
|
|
214
29
|
}
|
|
215
30
|
`;
|
|
216
|
-
|
|
31
|
+
/**
|
|
32
|
+
* A comment thread anchored to a place in a document.
|
|
33
|
+
*
|
|
34
|
+
* This is `box-comment-thread` plus the anchor. The distinction is the whole
|
|
35
|
+
* point: comments stand on their own — on a file, a task, a contract clause —
|
|
36
|
+
* and only become annotations when something ties them to a page, a region, or
|
|
37
|
+
* a run of selected text. Hosts that want the former should reach for
|
|
38
|
+
* `box-comment-thread` directly rather than for an annotation with no anchor.
|
|
39
|
+
*
|
|
40
|
+
* Every attribute, part and event of the underlying thread is inherited
|
|
41
|
+
* unchanged, so this remains a drop-in for the previous implementation.
|
|
42
|
+
*/
|
|
43
|
+
export class AnnotationThread extends CommentThread {
|
|
217
44
|
static tagName = DEFAULT_TAG_NAME;
|
|
218
45
|
static get observedAttributes() {
|
|
219
|
-
return [
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
46
|
+
return [...CommentThread.observedAttributes, "anchor"];
|
|
47
|
+
}
|
|
48
|
+
anchorEl = null;
|
|
49
|
+
threadStyles() {
|
|
50
|
+
return `${super.threadStyles()}\n${anchorStyles}`;
|
|
51
|
+
}
|
|
52
|
+
headerExtras() {
|
|
53
|
+
return `
|
|
54
|
+
<div part="anchor" hidden>
|
|
55
|
+
<span part="anchor-location"></span>
|
|
56
|
+
<blockquote part="anchor-quote" hidden></blockquote>
|
|
57
|
+
</div>
|
|
58
|
+
`;
|
|
232
59
|
}
|
|
233
|
-
|
|
234
|
-
|
|
60
|
+
defaultHeading() {
|
|
61
|
+
return "Annotation Thread";
|
|
235
62
|
}
|
|
236
|
-
|
|
237
|
-
return
|
|
63
|
+
emptyText() {
|
|
64
|
+
return "No annotation thread entries available.";
|
|
238
65
|
}
|
|
239
|
-
|
|
240
|
-
|
|
66
|
+
defaultComposerLabel() {
|
|
67
|
+
return "Reply";
|
|
241
68
|
}
|
|
242
|
-
|
|
243
|
-
return
|
|
69
|
+
defaultPlaceholder() {
|
|
70
|
+
return "Add a reply";
|
|
244
71
|
}
|
|
245
|
-
|
|
246
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Annotations name their pill `toolLabel`. Mapping it here keeps that
|
|
74
|
+
* vocabulary for existing hosts while the base model stays generic.
|
|
75
|
+
*/
|
|
76
|
+
entryBadge(entry) {
|
|
77
|
+
return entry.toolLabel ?? entry.badge;
|
|
247
78
|
}
|
|
248
|
-
|
|
249
|
-
|
|
79
|
+
/** Where in the document this thread sits. Null when unanchored. */
|
|
80
|
+
get anchor() {
|
|
81
|
+
return this.parseJsonAttribute("anchor", null);
|
|
250
82
|
}
|
|
251
|
-
set
|
|
83
|
+
set anchor(value) {
|
|
252
84
|
if (!value) {
|
|
253
|
-
this.removeAttribute("
|
|
85
|
+
this.removeAttribute("anchor");
|
|
254
86
|
return;
|
|
255
87
|
}
|
|
256
|
-
this.setAttribute("
|
|
257
|
-
}
|
|
258
|
-
get selectedEntryId() {
|
|
259
|
-
return this.getAttribute("selected-entry-id") ?? "";
|
|
88
|
+
this.setAttribute("anchor", JSON.stringify(value));
|
|
260
89
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.removeAttribute("selected-entry-id");
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
this.setAttribute("selected-entry-id", value);
|
|
267
|
-
}
|
|
268
|
-
get heading() {
|
|
269
|
-
return this.getAttribute("heading") ?? "Annotation Thread";
|
|
90
|
+
get entries() {
|
|
91
|
+
return super.entries;
|
|
270
92
|
}
|
|
271
|
-
set
|
|
272
|
-
|
|
93
|
+
set entries(value) {
|
|
94
|
+
super.entries = value;
|
|
273
95
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The anchor in words.
|
|
98
|
+
*
|
|
99
|
+
* A region is described by its page rather than by its coordinates: the
|
|
100
|
+
* numbers locate the highlight for the renderer, but they tell a reader
|
|
101
|
+
* nothing, and a screen reader least of all.
|
|
102
|
+
*/
|
|
103
|
+
anchorLocation(anchor) {
|
|
104
|
+
if (typeof anchor.page === "number" && Number.isFinite(anchor.page)) {
|
|
105
|
+
return anchor.region ? `Page ${anchor.page} · region` : `Page ${anchor.page}`;
|
|
281
106
|
}
|
|
282
|
-
|
|
283
|
-
return
|
|
107
|
+
if (anchor.region) {
|
|
108
|
+
return "Region";
|
|
284
109
|
}
|
|
110
|
+
return anchor.quote ? "Selected text" : "";
|
|
285
111
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
bubbles: true,
|
|
289
|
-
composed: true,
|
|
290
|
-
detail: {
|
|
291
|
-
action: actionId,
|
|
292
|
-
selectedEntryId: this.selectedEntryId || null,
|
|
293
|
-
},
|
|
294
|
-
}));
|
|
295
|
-
}
|
|
296
|
-
emitEntrySelected(entry) {
|
|
297
|
-
this.selectedEntryId = entry.id;
|
|
298
|
-
this.dispatchEvent(new CustomEvent("entry-selected", {
|
|
299
|
-
bubbles: true,
|
|
300
|
-
composed: true,
|
|
301
|
-
detail: entry,
|
|
302
|
-
}));
|
|
303
|
-
}
|
|
304
|
-
submitComposer() {
|
|
305
|
-
const body = this.composerInputEl.value.trim();
|
|
306
|
-
if (!this.composable || !body) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
this.dispatchEvent(new CustomEvent("entry-submitted", {
|
|
310
|
-
bubbles: true,
|
|
311
|
-
composed: true,
|
|
312
|
-
detail: {
|
|
313
|
-
body,
|
|
314
|
-
inReplyToId: this.selectedEntryId || null,
|
|
315
|
-
},
|
|
316
|
-
}));
|
|
317
|
-
this.composerInputEl.value = "";
|
|
318
|
-
}
|
|
319
|
-
entriesKey() {
|
|
320
|
-
return this.getAttribute("entries") ?? "";
|
|
321
|
-
}
|
|
322
|
-
actionsKey() {
|
|
323
|
-
return JSON.stringify(this.actions.map(action => action.id));
|
|
324
|
-
}
|
|
325
|
-
rebuildEntries() {
|
|
326
|
-
this.entriesEl.innerHTML = this.entries
|
|
327
|
-
.map(entry => {
|
|
328
|
-
const selected = entry.id === this.selectedEntryId;
|
|
329
|
-
return `
|
|
330
|
-
<li part="entry-item" role="listitem">
|
|
331
|
-
<button
|
|
332
|
-
type="button"
|
|
333
|
-
part="entry"
|
|
334
|
-
data-entry-id="${escapeHtml(entry.id)}"
|
|
335
|
-
aria-pressed="${selected ? "true" : "false"}"
|
|
336
|
-
>
|
|
337
|
-
<span part="entry-avatar">${escapeHtml(entry.initials ?? entry.author.slice(0, 2).toUpperCase())}</span>
|
|
338
|
-
<span part="entry-copy">
|
|
339
|
-
<span part="entry-topline">
|
|
340
|
-
<span part="entry-author">${escapeHtml(entry.author)}</span>
|
|
341
|
-
${entry.toolLabel ? `<span part="entry-tool">${escapeHtml(entry.toolLabel)}</span>` : ""}
|
|
342
|
-
${entry.status ? `<span part="entry-status">${escapeHtml(entry.status)}</span>` : ""}
|
|
343
|
-
</span>
|
|
344
|
-
<span part="entry-body">${escapeHtml(entry.body)}</span>
|
|
345
|
-
${entry.createdAt ? `<span part="entry-time">${escapeHtml(entry.createdAt)}</span>` : ""}
|
|
346
|
-
</span>
|
|
347
|
-
</button>
|
|
348
|
-
</li>
|
|
349
|
-
`;
|
|
350
|
-
})
|
|
351
|
-
.join("");
|
|
352
|
-
}
|
|
353
|
-
patchEntrySelection() {
|
|
354
|
-
this.entriesEl.querySelectorAll('[part="entry"]').forEach(button => {
|
|
355
|
-
const selected = button.getAttribute("data-entry-id") === this.selectedEntryId;
|
|
356
|
-
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
rebuildActions() {
|
|
360
|
-
this.actionsEl.innerHTML = this.actions
|
|
361
|
-
.map(action => `
|
|
362
|
-
<button type="button" part="action" data-action-id="${escapeHtml(action.id)}" data-tone="${escapeHtml(action.tone ?? "neutral")}">
|
|
363
|
-
${escapeHtml(action.label)}
|
|
364
|
-
</button>
|
|
365
|
-
`)
|
|
366
|
-
.join("");
|
|
367
|
-
}
|
|
368
|
-
patchActionLabels() {
|
|
369
|
-
// Action IDs are arbitrary strings (quotes, newlines, …) — match on the
|
|
370
|
-
// dataset instead of interpolating them into a CSS selector.
|
|
371
|
-
const buttons = Array.from(this.actionsEl.querySelectorAll('[part="action"]'));
|
|
372
|
-
this.actions.forEach(action => {
|
|
373
|
-
const button = buttons.find(candidate => candidate.dataset.actionId === action.id);
|
|
374
|
-
if (!button) {
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
button.textContent = action.label;
|
|
378
|
-
button.dataset.tone = action.tone ?? "neutral";
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
renderTemplate() {
|
|
112
|
+
update() {
|
|
113
|
+
super.update();
|
|
382
114
|
if (!this.shadowRoot) {
|
|
383
115
|
return;
|
|
384
116
|
}
|
|
385
|
-
this.shadowRoot.
|
|
386
|
-
|
|
387
|
-
<article part="thread">
|
|
388
|
-
<header part="header">
|
|
389
|
-
<h2 part="title"></h2>
|
|
390
|
-
<div part="message" hidden></div>
|
|
391
|
-
</header>
|
|
392
|
-
<ul part="entries" role="list" hidden></ul>
|
|
393
|
-
<div part="empty" hidden>No annotation thread entries available.</div>
|
|
394
|
-
<div part="actions" hidden></div>
|
|
395
|
-
<div part="composer" hidden>
|
|
396
|
-
<label part="composer-label" for="annotation-thread-composer-input">Reply</label>
|
|
397
|
-
<textarea
|
|
398
|
-
part="composer-input"
|
|
399
|
-
id="annotation-thread-composer-input"
|
|
400
|
-
placeholder="Add a reply"
|
|
401
|
-
></textarea>
|
|
402
|
-
<button type="button" part="composer-submit">Reply</button>
|
|
403
|
-
</div>
|
|
404
|
-
</article>
|
|
405
|
-
`;
|
|
406
|
-
this.titleEl = this.shadowRoot.querySelector('[part="title"]');
|
|
407
|
-
this.messageEl = this.shadowRoot.querySelector('[part="message"]');
|
|
408
|
-
this.entriesEl = this.shadowRoot.querySelector('[part="entries"]');
|
|
409
|
-
this.emptyEl = this.shadowRoot.querySelector('[part="empty"]');
|
|
410
|
-
this.actionsEl = this.shadowRoot.querySelector('[part="actions"]');
|
|
411
|
-
this.composerEl = this.shadowRoot.querySelector('[part="composer"]');
|
|
412
|
-
this.composerInputEl = this.shadowRoot.querySelector('[part="composer-input"]');
|
|
413
|
-
}
|
|
414
|
-
setupListeners() {
|
|
415
|
-
this.entriesEl.addEventListener("click", event => {
|
|
416
|
-
const button = event.target.closest('[part="entry"]');
|
|
417
|
-
if (!button || !this.entriesEl.contains(button)) {
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
const entryId = button.getAttribute("data-entry-id");
|
|
421
|
-
const entry = this.entries.find(item => item.id === entryId);
|
|
422
|
-
if (entry) {
|
|
423
|
-
this.emitEntrySelected(entry);
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
this.actionsEl.addEventListener("click", event => {
|
|
427
|
-
const button = event.target.closest('[part="action"]');
|
|
428
|
-
if (!button || !this.actionsEl.contains(button)) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
const actionId = button.getAttribute("data-action-id");
|
|
432
|
-
if (actionId) {
|
|
433
|
-
this.emitAction(actionId);
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
this.composerEl.addEventListener("click", event => {
|
|
437
|
-
const button = event.target.closest('[part="composer-submit"]');
|
|
438
|
-
if (button) {
|
|
439
|
-
this.submitComposer();
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
this.composerInputEl.addEventListener("keydown", event => {
|
|
443
|
-
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
444
|
-
event.preventDefault();
|
|
445
|
-
this.submitComposer();
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
update() {
|
|
450
|
-
if (!this.entriesEl) {
|
|
117
|
+
this.anchorEl ??= this.shadowRoot.querySelector('[part="anchor"]');
|
|
118
|
+
if (!this.anchorEl) {
|
|
451
119
|
return;
|
|
452
120
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
this.
|
|
459
|
-
this.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const actions = this.actions;
|
|
469
|
-
this.actionsEl.hidden = actions.length === 0;
|
|
470
|
-
const nextActions = this.actionsKey();
|
|
471
|
-
if (nextActions !== this.actionsSignature || this.actionsEl.childElementCount === 0) {
|
|
472
|
-
this.actionsSignature = nextActions;
|
|
473
|
-
this.rebuildActions();
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
this.patchActionLabels();
|
|
121
|
+
const anchor = this.anchor;
|
|
122
|
+
const location = anchor ? this.anchorLocation(anchor) : "";
|
|
123
|
+
const quote = anchor?.quote ?? "";
|
|
124
|
+
// Hidden only when the anchor says nothing renderable — an anchor carrying
|
|
125
|
+
// just a region still names its page, and a quote alone still shows.
|
|
126
|
+
this.anchorEl.hidden = !location && !quote;
|
|
127
|
+
const locationEl = this.anchorEl.querySelector('[part="anchor-location"]');
|
|
128
|
+
if (locationEl) {
|
|
129
|
+
locationEl.hidden = !location;
|
|
130
|
+
locationEl.textContent = location;
|
|
131
|
+
}
|
|
132
|
+
const quoteEl = this.anchorEl.querySelector('[part="anchor-quote"]');
|
|
133
|
+
if (quoteEl) {
|
|
134
|
+
quoteEl.hidden = !quote;
|
|
135
|
+
quoteEl.textContent = quote;
|
|
477
136
|
}
|
|
478
|
-
const composable = this.composable;
|
|
479
|
-
this.composerEl.hidden = !composable;
|
|
480
|
-
this.composerInputEl.disabled = !composable;
|
|
481
137
|
}
|
|
482
138
|
}
|
|
483
139
|
AnnotationThread.register();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unofficialbox/box-open-elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Box Open Elements: an open, framework-agnostic design system and web component library for Box-style experiences",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://unofficialbox.github.io/box-open-elements",
|