@crestapps/ai-chat-ui 2.0.0-preview.203 → 2.0.0-preview.223
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/ai-chat-widget.js +762 -964
- package/dist/ai-chat-widget.js.map +1 -1
- package/dist/ai-chat-widget.min.js +1 -1
- package/dist/ai-chat-widget.min.js.map +1 -1
- package/dist/ai-chat.css +0 -2
- package/dist/ai-chat.js +2380 -3695
- package/dist/ai-chat.js.map +1 -1
- package/dist/ai-chat.min.css +1 -1
- package/dist/ai-chat.min.js +78 -1
- package/dist/ai-chat.min.js.map +1 -1
- package/dist/chat-image-carousel.css +275 -0
- package/dist/chat-image-carousel.js +309 -0
- package/dist/chat-image-carousel.js.map +1 -0
- package/dist/chat-image-carousel.min.css +1 -0
- package/dist/chat-image-carousel.min.js +2 -0
- package/dist/chat-image-carousel.min.js.map +1 -0
- package/dist/chat-interaction-settings.js +221 -293
- package/dist/chat-interaction-settings.js.map +1 -1
- package/dist/chat-interaction-settings.min.js +1 -1
- package/dist/chat-interaction-settings.min.js.map +1 -1
- package/dist/chat-interaction.js +2330 -3554
- package/dist/chat-interaction.js.map +1 -1
- package/dist/chat-interaction.min.js +62 -1
- package/dist/chat-interaction.min.js.map +1 -1
- package/dist/chat-markers.js +193 -436
- package/dist/chat-markers.js.map +1 -1
- package/dist/chat-markers.min.js +2 -1
- package/dist/chat-markers.min.js.map +1 -1
- package/dist/chat-widget.css +0 -2
- package/dist/chat-widget.min.css +1 -1
- package/dist/document-drop-zone.css +0 -2
- package/dist/document-drop-zone.js +127 -153
- package/dist/document-drop-zone.js.map +1 -1
- package/dist/document-drop-zone.min.css +1 -1
- package/dist/document-drop-zone.min.js +1 -1
- package/dist/document-drop-zone.min.js.map +1 -1
- package/dist/realtime-audio.js +1639 -2504
- package/dist/realtime-audio.js.map +1 -1
- package/dist/realtime-audio.min.js +3 -1
- package/dist/realtime-audio.min.js.map +1 -1
- package/dist/technical-name-generator.js +58 -80
- package/dist/technical-name-generator.js.map +1 -1
- package/dist/technical-name-generator.min.js +1 -1
- package/dist/technical-name-generator.min.js.map +1 -1
- package/package.json +9 -5
- package/dist/ai-chat.css.map +0 -1
- package/dist/chat-widget.css.map +0 -1
- package/dist/document-drop-zone.css.map +0 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/* ── Stacked image carousel ── */
|
|
2
|
+
/* The pictures of one run are laid over each other in a single grid cell, so the stage is as tall as the
|
|
3
|
+
tallest of them and does not jump as the reader moves between pictures of different heights. The current
|
|
4
|
+
picture sits in front; its neighbours are pushed out to either side and shrunk, far enough that their
|
|
5
|
+
edges show and can be clicked. Cards further out are stacked out of sight behind the last visible one.
|
|
6
|
+
|
|
7
|
+
The selectors that size and place a card are deliberately specific: the chat surfaces already style
|
|
8
|
+
.generated-image-container -- the widget through .ai-chat-widget-messages, one host through a <style> of
|
|
9
|
+
its own -- and a card has to win over all of them wherever this file happens to be loaded. */
|
|
10
|
+
.ai-image-carousel {
|
|
11
|
+
--ai-image-carousel-card-width: 70%;
|
|
12
|
+
display: block;
|
|
13
|
+
width: 100%;
|
|
14
|
+
max-width: 520px;
|
|
15
|
+
margin: 0.5rem 0;
|
|
16
|
+
border-radius: 0.5rem;
|
|
17
|
+
transition: max-width 0.25s ease;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The reader's choice from the size picker; medium is the default. */
|
|
21
|
+
.ai-image-carousel[data-carousel-size="small"] {
|
|
22
|
+
max-width: 360px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ai-image-carousel[data-carousel-size="large"] {
|
|
26
|
+
max-width: 760px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ai-image-carousel:focus {
|
|
30
|
+
outline: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ai-image-carousel:focus-visible {
|
|
34
|
+
outline: 2px solid var(--bs-primary, #0d6efd);
|
|
35
|
+
outline-offset: 4px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* The widget's panel is narrow, so the card takes more of it and the fan stays inside it. */
|
|
39
|
+
.ai-chat-widget-messages .ai-image-carousel {
|
|
40
|
+
--ai-image-carousel-card-width: 78%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ai-image-carousel-stage {
|
|
44
|
+
display: grid;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-items: center;
|
|
47
|
+
padding: 0.25rem 0;
|
|
48
|
+
/* The cards are layered with z-index, and without a stacking context of their own those values are compared
|
|
49
|
+
with everything else on the page -- including the zoomed picture and its overlay, which carry none, so the
|
|
50
|
+
cards behind were drawn over the picture being looked at. Isolated, they only order among themselves. */
|
|
51
|
+
isolation: isolate;
|
|
52
|
+
/* A horizontal swipe is the carousel's; a vertical one still scrolls the conversation. */
|
|
53
|
+
touch-action: pan-y;
|
|
54
|
+
user-select: none;
|
|
55
|
+
-webkit-user-select: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.ai-image-carousel-stage > .generated-image-container.ai-image-carousel-item {
|
|
59
|
+
grid-area: 1 / 1;
|
|
60
|
+
display: block;
|
|
61
|
+
width: var(--ai-image-carousel-card-width);
|
|
62
|
+
margin: 0;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
transform-origin: center;
|
|
65
|
+
transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1), opacity 0.35s ease, filter 0.35s ease;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ai-image-carousel-stage > .generated-image-container.ai-image-carousel-item img {
|
|
69
|
+
display: block;
|
|
70
|
+
width: 100%;
|
|
71
|
+
max-width: 100% !important;
|
|
72
|
+
-webkit-user-drag: none;
|
|
73
|
+
box-shadow: 0 0.375rem 1.25rem rgba(0, 0, 0, 0.14);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="0"] {
|
|
77
|
+
z-index: 4;
|
|
78
|
+
cursor: default;
|
|
79
|
+
/* Set on the stage while the reader drags, so the card follows the pointer. */
|
|
80
|
+
transform: translateX(var(--ai-image-carousel-drag, 0px));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="-1"],
|
|
84
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="1"] {
|
|
85
|
+
z-index: 3;
|
|
86
|
+
opacity: 0.85;
|
|
87
|
+
filter: brightness(0.94);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="-1"] {
|
|
91
|
+
transform: translateX(-16%) scale(0.88);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="1"] {
|
|
95
|
+
transform: translateX(16%) scale(0.88);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="-2"],
|
|
99
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="2"] {
|
|
100
|
+
z-index: 2;
|
|
101
|
+
opacity: 0.55;
|
|
102
|
+
filter: brightness(0.9);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="-2"] {
|
|
106
|
+
transform: translateX(-26%) scale(0.76);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="2"] {
|
|
110
|
+
transform: translateX(26%) scale(0.76);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="-3"],
|
|
114
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="3"] {
|
|
115
|
+
z-index: 1;
|
|
116
|
+
opacity: 0;
|
|
117
|
+
pointer-events: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="-3"] {
|
|
121
|
+
transform: translateX(-26%) scale(0.7);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.ai-image-carousel-stage > .ai-image-carousel-item[data-stack-offset="3"] {
|
|
125
|
+
transform: translateX(26%) scale(0.7);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Pointing at a card behind says it can be brought forward. */
|
|
129
|
+
.ai-image-carousel-stage > .ai-image-carousel-item:not(.is-active):hover {
|
|
130
|
+
opacity: 1;
|
|
131
|
+
filter: none;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.ai-image-carousel-stage > .generated-image-container.ai-image-carousel-item:not(.is-active) img {
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Only the picture in front is downloaded; a click on one behind brings it forward instead. */
|
|
139
|
+
.ai-image-carousel-stage > .ai-image-carousel-item:not(.is-active) .ai-download-image {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* While dragging, the card is moved by the pointer, not eased toward it. */
|
|
144
|
+
.ai-image-carousel-stage.is-dragging > .ai-image-carousel-item {
|
|
145
|
+
transition: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.ai-image-carousel-controls {
|
|
149
|
+
display: flex;
|
|
150
|
+
align-items: center;
|
|
151
|
+
gap: 0.75rem;
|
|
152
|
+
margin-top: 0.5rem;
|
|
153
|
+
font-size: 0.8125rem;
|
|
154
|
+
line-height: 1.25;
|
|
155
|
+
color: var(--bs-secondary-color, #6c757d);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.ai-image-carousel-caption {
|
|
159
|
+
flex: 1 1 auto;
|
|
160
|
+
min-width: 0;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
text-overflow: ellipsis;
|
|
163
|
+
white-space: nowrap;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.ai-image-carousel-navigation {
|
|
167
|
+
display: inline-flex;
|
|
168
|
+
flex: none;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 0.375rem;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ai-image-carousel-button {
|
|
174
|
+
display: inline-flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
width: 1.75rem;
|
|
178
|
+
height: 1.75rem;
|
|
179
|
+
padding: 0;
|
|
180
|
+
border: 1px solid var(--bs-border-color, #dee2e6);
|
|
181
|
+
border-radius: 50%;
|
|
182
|
+
color: var(--bs-body-color, #212529);
|
|
183
|
+
background-color: var(--bs-body-bg, #ffffff);
|
|
184
|
+
transition: background-color 0.15s ease, opacity 0.15s ease;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.ai-image-carousel-button:hover:not(:disabled) {
|
|
188
|
+
background-color: var(--bs-tertiary-bg, #f8f9fa);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.ai-image-carousel-button:disabled {
|
|
192
|
+
opacity: 0.4;
|
|
193
|
+
cursor: default;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ai-image-carousel-dots {
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 0.375rem;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ai-image-carousel-dot {
|
|
203
|
+
position: relative;
|
|
204
|
+
width: 0.5rem;
|
|
205
|
+
height: 0.5rem;
|
|
206
|
+
padding: 0;
|
|
207
|
+
border: 0;
|
|
208
|
+
border-radius: 999px;
|
|
209
|
+
background-color: var(--bs-border-color, #dee2e6);
|
|
210
|
+
transition: width 0.2s ease, background-color 0.2s ease;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* The dot is small to look at but not to hit. */
|
|
214
|
+
.ai-image-carousel-dot::after {
|
|
215
|
+
content: "";
|
|
216
|
+
position: absolute;
|
|
217
|
+
inset: -0.375rem -0.25rem;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.ai-image-carousel-dot.is-active {
|
|
221
|
+
width: 1.25rem;
|
|
222
|
+
background-color: var(--bs-primary, #0d6efd);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.ai-image-carousel-sizes {
|
|
226
|
+
display: inline-flex;
|
|
227
|
+
flex: none;
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
border: 1px solid var(--bs-border-color, #dee2e6);
|
|
230
|
+
border-radius: 0.375rem;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.ai-image-carousel-size {
|
|
234
|
+
min-width: 1.5rem;
|
|
235
|
+
height: 1.5rem;
|
|
236
|
+
padding: 0 0.375rem;
|
|
237
|
+
border: 0;
|
|
238
|
+
font-size: 0.6875rem;
|
|
239
|
+
font-weight: 600;
|
|
240
|
+
line-height: 1;
|
|
241
|
+
color: var(--bs-secondary-color, #6c757d);
|
|
242
|
+
background-color: var(--bs-body-bg, #ffffff);
|
|
243
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.ai-image-carousel-size + .ai-image-carousel-size {
|
|
247
|
+
border-left: 1px solid var(--bs-border-color, #dee2e6);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.ai-image-carousel-size:hover {
|
|
251
|
+
color: var(--bs-body-color, #212529);
|
|
252
|
+
background-color: var(--bs-tertiary-bg, #f8f9fa);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.ai-image-carousel-size[aria-pressed="true"] {
|
|
256
|
+
color: #ffffff;
|
|
257
|
+
background-color: var(--bs-primary, #0d6efd);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* The widget's panel is narrower than even the small preset, so there is nothing for the picker to change. */
|
|
261
|
+
.ai-chat-widget-messages .ai-image-carousel-sizes {
|
|
262
|
+
display: none;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* With one picture left -- the others could not be loaded -- there is nothing to move between. */
|
|
266
|
+
.ai-image-carousel[data-image-count="1"] .ai-image-carousel-navigation {
|
|
267
|
+
display: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@media (prefers-reduced-motion: reduce) {
|
|
271
|
+
.ai-image-carousel-stage > .generated-image-container.ai-image-carousel-item,
|
|
272
|
+
.ai-image-carousel-dot {
|
|
273
|
+
transition: none;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** NOTE: This file is generated by the assets build (npm run build) and should not be edited directly!
|
|
3
|
+
** Edit its sources under Assets/ and rebuild; see Assets.json for the files it is built from.
|
|
4
|
+
*/
|
|
5
|
+
(function() {
|
|
6
|
+
//#region src/Resources/CrestApps.AI.Resources/Assets/js/chat-image-carousel.js
|
|
7
|
+
window.CoreAIChatImageCarousel = window.CoreAIChatImageCarousel || (function() {
|
|
8
|
+
"use strict";
|
|
9
|
+
const imageContainerClass = "generated-image-container";
|
|
10
|
+
const swipeThreshold = 40;
|
|
11
|
+
const dragStartThreshold = 8;
|
|
12
|
+
const contentBetweenSelector = "img, svg, canvas, video, audio, iframe, table, hr, input";
|
|
13
|
+
const previousIcon = "<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 3 5 8l5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>";
|
|
14
|
+
const nextIcon = "<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m6 3 5 5-5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>";
|
|
15
|
+
const sizes = [
|
|
16
|
+
{
|
|
17
|
+
name: "small",
|
|
18
|
+
text: "S",
|
|
19
|
+
label: "Small previews"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "medium",
|
|
23
|
+
text: "M",
|
|
24
|
+
label: "Medium previews"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "large",
|
|
28
|
+
text: "L",
|
|
29
|
+
label: "Large previews"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
const defaultSize = "medium";
|
|
33
|
+
const sizeStorageKey = "coreai-image-carousel-size";
|
|
34
|
+
const activeIndexByCarouselKey = /* @__PURE__ */ new Map();
|
|
35
|
+
let drag = null;
|
|
36
|
+
let suppressNextClick = false;
|
|
37
|
+
function stackAdjacentImages(html) {
|
|
38
|
+
if (typeof document === "undefined" || !html) return html;
|
|
39
|
+
const firstContainerIndex = html.indexOf(imageContainerClass);
|
|
40
|
+
if (firstContainerIndex < 0 || html.indexOf(imageContainerClass, firstContainerIndex + 25) < 0) return html;
|
|
41
|
+
const template = document.createElement("template");
|
|
42
|
+
template.innerHTML = html;
|
|
43
|
+
const runs = findImageRuns(template.content);
|
|
44
|
+
if (runs.length === 0) return html;
|
|
45
|
+
for (const run of runs) buildCarousel(run);
|
|
46
|
+
return template.innerHTML;
|
|
47
|
+
}
|
|
48
|
+
function findImageRuns(root) {
|
|
49
|
+
const runs = [];
|
|
50
|
+
let run = [];
|
|
51
|
+
for (const container of root.querySelectorAll("." + imageContainerClass)) {
|
|
52
|
+
if (!container.parentElement || container.parentElement.tagName !== "P") {
|
|
53
|
+
runs.push(run);
|
|
54
|
+
run = [];
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (run.length > 0 && !isOnlyWhitespaceBetween(run[run.length - 1], container)) {
|
|
58
|
+
runs.push(run);
|
|
59
|
+
run = [];
|
|
60
|
+
}
|
|
61
|
+
run.push(container);
|
|
62
|
+
}
|
|
63
|
+
runs.push(run);
|
|
64
|
+
return runs.filter((candidate) => candidate.length > 1);
|
|
65
|
+
}
|
|
66
|
+
function isOnlyWhitespaceBetween(previous, next) {
|
|
67
|
+
const range = previous.ownerDocument.createRange();
|
|
68
|
+
range.setStartAfter(previous);
|
|
69
|
+
range.setEndBefore(next);
|
|
70
|
+
const between = range.cloneContents();
|
|
71
|
+
return between.textContent.trim() === "" && !between.querySelector(contentBetweenSelector);
|
|
72
|
+
}
|
|
73
|
+
function buildCarousel(containers) {
|
|
74
|
+
const ownerDocument = containers[0].ownerDocument;
|
|
75
|
+
const firstParagraph = containers[0].parentElement;
|
|
76
|
+
const vacatedParagraphs = /* @__PURE__ */ new Set();
|
|
77
|
+
const carousel = createElement(ownerDocument, "span", "ai-image-carousel");
|
|
78
|
+
carousel.tabIndex = 0;
|
|
79
|
+
carousel.setAttribute("role", "region");
|
|
80
|
+
carousel.setAttribute("aria-roledescription", "carousel");
|
|
81
|
+
carousel.setAttribute("aria-label", `${containers.length} images`);
|
|
82
|
+
carousel.dataset.carouselKey = getCarouselKey(containers);
|
|
83
|
+
carousel.dataset.carouselSize = getPreferredSize();
|
|
84
|
+
const stage = createElement(ownerDocument, "span", "ai-image-carousel-stage");
|
|
85
|
+
const controls = createElement(ownerDocument, "span", "ai-image-carousel-controls");
|
|
86
|
+
const caption = createElement(ownerDocument, "span", "ai-image-carousel-caption");
|
|
87
|
+
const navigation = createElement(ownerDocument, "span", "ai-image-carousel-navigation");
|
|
88
|
+
const dots = createElement(ownerDocument, "span", "ai-image-carousel-dots");
|
|
89
|
+
caption.setAttribute("aria-live", "polite");
|
|
90
|
+
containers[0].before(carousel);
|
|
91
|
+
containers.forEach((container, index) => {
|
|
92
|
+
removeSeparatorsBefore(container);
|
|
93
|
+
if (container.parentElement !== firstParagraph) vacatedParagraphs.add(container.parentElement);
|
|
94
|
+
container.classList.add("ai-image-carousel-item");
|
|
95
|
+
const image = container.querySelector("img");
|
|
96
|
+
if (image) image.setAttribute("draggable", "false");
|
|
97
|
+
const dot = createElement(ownerDocument, "button", "ai-image-carousel-dot");
|
|
98
|
+
dot.type = "button";
|
|
99
|
+
dot.dataset.carouselTarget = index;
|
|
100
|
+
dot.setAttribute("aria-label", `Show image ${index + 1}`);
|
|
101
|
+
dots.appendChild(dot);
|
|
102
|
+
stage.appendChild(container);
|
|
103
|
+
});
|
|
104
|
+
navigation.append(createStepButton(ownerDocument, -1, "Previous image", previousIcon), dots, createStepButton(ownerDocument, 1, "Next image", nextIcon));
|
|
105
|
+
controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);
|
|
106
|
+
carousel.append(stage, controls);
|
|
107
|
+
for (const paragraph of vacatedParagraphs) if (paragraph.textContent.trim() === "" && !paragraph.querySelector(contentBetweenSelector)) paragraph.remove();
|
|
108
|
+
showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);
|
|
109
|
+
}
|
|
110
|
+
function removeSeparatorsBefore(container) {
|
|
111
|
+
let sibling = container.previousSibling;
|
|
112
|
+
while (sibling && (sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === "" || sibling.nodeName === "BR")) {
|
|
113
|
+
const previousSibling = sibling.previousSibling;
|
|
114
|
+
sibling.remove();
|
|
115
|
+
sibling = previousSibling;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function getCarouselKey(containers) {
|
|
119
|
+
const firstImage = containers[0].querySelector("img");
|
|
120
|
+
const firstSource = firstImage ? firstImage.getAttribute("src") || "" : "";
|
|
121
|
+
return `${containers.length}:${firstSource.slice(-120)}`;
|
|
122
|
+
}
|
|
123
|
+
function createElement(ownerDocument, tagName, className) {
|
|
124
|
+
const element = ownerDocument.createElement(tagName);
|
|
125
|
+
element.className = className;
|
|
126
|
+
return element;
|
|
127
|
+
}
|
|
128
|
+
function createStepButton(ownerDocument, step, label, icon) {
|
|
129
|
+
const button = createElement(ownerDocument, "button", "ai-image-carousel-button");
|
|
130
|
+
button.type = "button";
|
|
131
|
+
button.dataset.carouselStep = step;
|
|
132
|
+
button.setAttribute("aria-label", label);
|
|
133
|
+
button.innerHTML = icon;
|
|
134
|
+
return button;
|
|
135
|
+
}
|
|
136
|
+
function createSizePicker(ownerDocument, selectedSize) {
|
|
137
|
+
const picker = createElement(ownerDocument, "span", "ai-image-carousel-sizes");
|
|
138
|
+
picker.setAttribute("role", "group");
|
|
139
|
+
picker.setAttribute("aria-label", "Preview size");
|
|
140
|
+
for (const size of sizes) {
|
|
141
|
+
const button = createElement(ownerDocument, "button", "ai-image-carousel-size");
|
|
142
|
+
button.type = "button";
|
|
143
|
+
button.title = size.label;
|
|
144
|
+
button.textContent = size.text;
|
|
145
|
+
button.dataset.carouselSizeOption = size.name;
|
|
146
|
+
button.setAttribute("aria-label", size.label);
|
|
147
|
+
button.setAttribute("aria-pressed", size.name === selectedSize ? "true" : "false");
|
|
148
|
+
picker.appendChild(button);
|
|
149
|
+
}
|
|
150
|
+
return picker;
|
|
151
|
+
}
|
|
152
|
+
function getPreferredSize() {
|
|
153
|
+
try {
|
|
154
|
+
const storedSize = localStorage.getItem(sizeStorageKey);
|
|
155
|
+
return sizes.some((size) => size.name === storedSize) ? storedSize : defaultSize;
|
|
156
|
+
} catch (_) {
|
|
157
|
+
return defaultSize;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function setPreferredSize(selectedSize) {
|
|
161
|
+
try {
|
|
162
|
+
localStorage.setItem(sizeStorageKey, selectedSize);
|
|
163
|
+
} catch (_) {}
|
|
164
|
+
for (const carousel of document.querySelectorAll(".ai-image-carousel")) carousel.dataset.carouselSize = selectedSize;
|
|
165
|
+
for (const button of document.querySelectorAll("[data-carousel-size-option]")) button.setAttribute("aria-pressed", button.dataset.carouselSizeOption === selectedSize ? "true" : "false");
|
|
166
|
+
}
|
|
167
|
+
function getItems(carousel) {
|
|
168
|
+
return carousel.querySelectorAll(".ai-image-carousel-stage > .ai-image-carousel-item");
|
|
169
|
+
}
|
|
170
|
+
function getActiveIndex(carousel) {
|
|
171
|
+
return Number(carousel.dataset.activeIndex) || 0;
|
|
172
|
+
}
|
|
173
|
+
function showImage(carousel, requestedIndex) {
|
|
174
|
+
const items = getItems(carousel);
|
|
175
|
+
if (items.length === 0) {
|
|
176
|
+
carousel.remove();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const activeIndex = Math.max(0, Math.min(items.length - 1, requestedIndex));
|
|
180
|
+
const dots = carousel.querySelector(".ai-image-carousel-dots");
|
|
181
|
+
while (dots.children.length > items.length) dots.lastElementChild.remove();
|
|
182
|
+
items.forEach((item, index) => {
|
|
183
|
+
const offset = index - activeIndex;
|
|
184
|
+
const isActive = offset === 0;
|
|
185
|
+
const downloadLink = item.querySelector("a");
|
|
186
|
+
item.dataset.stackOffset = Math.max(-3, Math.min(3, offset));
|
|
187
|
+
item.classList.toggle("is-active", isActive);
|
|
188
|
+
item.setAttribute("aria-hidden", isActive ? "false" : "true");
|
|
189
|
+
if (downloadLink) downloadLink.tabIndex = isActive ? 0 : -1;
|
|
190
|
+
dots.children[index].classList.toggle("is-active", isActive);
|
|
191
|
+
dots.children[index].setAttribute("aria-current", isActive ? "true" : "false");
|
|
192
|
+
});
|
|
193
|
+
const activeImage = items[activeIndex].querySelector("img");
|
|
194
|
+
const altText = activeImage ? activeImage.getAttribute("alt") : "";
|
|
195
|
+
carousel.dataset.activeIndex = activeIndex;
|
|
196
|
+
carousel.dataset.imageCount = items.length;
|
|
197
|
+
carousel.querySelector("[data-carousel-step=\"-1\"]").disabled = activeIndex === 0;
|
|
198
|
+
carousel.querySelector("[data-carousel-step=\"1\"]").disabled = activeIndex === items.length - 1;
|
|
199
|
+
carousel.querySelector(".ai-image-carousel-caption").textContent = altText ? `${activeIndex + 1} / ${items.length} · ${altText}` : `${activeIndex + 1} / ${items.length}`;
|
|
200
|
+
activeIndexByCarouselKey.set(carousel.dataset.carouselKey, activeIndex);
|
|
201
|
+
}
|
|
202
|
+
function stopEvent(event) {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
event.stopPropagation();
|
|
205
|
+
}
|
|
206
|
+
function onClick(event) {
|
|
207
|
+
const carousel = event.target.closest && event.target.closest(".ai-image-carousel");
|
|
208
|
+
if (!carousel) return;
|
|
209
|
+
if (suppressNextClick && event.detail > 0) {
|
|
210
|
+
suppressNextClick = false;
|
|
211
|
+
stopEvent(event);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const stepButton = event.target.closest("[data-carousel-step]");
|
|
215
|
+
if (stepButton) {
|
|
216
|
+
stopEvent(event);
|
|
217
|
+
showImage(carousel, getActiveIndex(carousel) + Number(stepButton.dataset.carouselStep));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const sizeButton = event.target.closest("[data-carousel-size-option]");
|
|
221
|
+
if (sizeButton) {
|
|
222
|
+
stopEvent(event);
|
|
223
|
+
setPreferredSize(sizeButton.dataset.carouselSizeOption);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const dot = event.target.closest("[data-carousel-target]");
|
|
227
|
+
if (dot) {
|
|
228
|
+
stopEvent(event);
|
|
229
|
+
showImage(carousel, Number(dot.dataset.carouselTarget));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const item = event.target.closest(".ai-image-carousel-item");
|
|
233
|
+
if (item && !item.classList.contains("is-active")) {
|
|
234
|
+
stopEvent(event);
|
|
235
|
+
showImage(carousel, Array.prototype.indexOf.call(getItems(carousel), item));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function onKeyDown(event) {
|
|
239
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
240
|
+
const carousel = event.target.closest && event.target.closest(".ai-image-carousel");
|
|
241
|
+
if (!carousel) return;
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
showImage(carousel, getActiveIndex(carousel) + (event.key === "ArrowRight" ? 1 : -1));
|
|
244
|
+
}
|
|
245
|
+
function onPointerDown(event) {
|
|
246
|
+
suppressNextClick = false;
|
|
247
|
+
const stage = event.button === 0 && event.target.closest && event.target.closest(".ai-image-carousel-stage");
|
|
248
|
+
if (!stage) return;
|
|
249
|
+
drag = {
|
|
250
|
+
stage,
|
|
251
|
+
pointerId: event.pointerId,
|
|
252
|
+
startX: event.clientX,
|
|
253
|
+
startY: event.clientY,
|
|
254
|
+
distance: 0,
|
|
255
|
+
isSwiping: false
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function onPointerMove(event) {
|
|
259
|
+
if (!drag || event.pointerId !== drag.pointerId) return;
|
|
260
|
+
const distance = event.clientX - drag.startX;
|
|
261
|
+
if (!drag.isSwiping) {
|
|
262
|
+
if (Math.abs(distance) < dragStartThreshold || Math.abs(distance) < Math.abs(event.clientY - drag.startY)) return;
|
|
263
|
+
drag.isSwiping = true;
|
|
264
|
+
drag.stage.classList.add("is-dragging");
|
|
265
|
+
}
|
|
266
|
+
drag.distance = distance;
|
|
267
|
+
drag.stage.style.setProperty("--ai-image-carousel-drag", `${distance}px`);
|
|
268
|
+
}
|
|
269
|
+
function onPointerUp(event) {
|
|
270
|
+
if (!drag || event.pointerId !== drag.pointerId) return;
|
|
271
|
+
const { stage, distance, isSwiping } = drag;
|
|
272
|
+
endDrag();
|
|
273
|
+
if (!isSwiping) return;
|
|
274
|
+
suppressNextClick = true;
|
|
275
|
+
if (Math.abs(distance) >= swipeThreshold) {
|
|
276
|
+
const carousel = stage.closest(".ai-image-carousel");
|
|
277
|
+
showImage(carousel, getActiveIndex(carousel) + (distance < 0 ? 1 : -1));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function endDrag() {
|
|
281
|
+
if (!drag) return;
|
|
282
|
+
drag.stage.classList.remove("is-dragging");
|
|
283
|
+
drag.stage.style.removeProperty("--ai-image-carousel-drag");
|
|
284
|
+
drag = null;
|
|
285
|
+
}
|
|
286
|
+
function onImageError(event) {
|
|
287
|
+
const item = event.target.tagName === "IMG" && event.target.closest(".ai-image-carousel-item");
|
|
288
|
+
if (!item) return;
|
|
289
|
+
const carousel = item.closest(".ai-image-carousel");
|
|
290
|
+
item.classList.remove("ai-image-carousel-item", "is-active");
|
|
291
|
+
item.removeAttribute("data-stack-offset");
|
|
292
|
+
item.removeAttribute("aria-hidden");
|
|
293
|
+
carousel.after(item);
|
|
294
|
+
showImage(carousel, getActiveIndex(carousel));
|
|
295
|
+
}
|
|
296
|
+
if (typeof document !== "undefined") {
|
|
297
|
+
document.addEventListener("click", onClick, true);
|
|
298
|
+
document.addEventListener("keydown", onKeyDown);
|
|
299
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
300
|
+
document.addEventListener("pointermove", onPointerMove);
|
|
301
|
+
document.addEventListener("pointerup", onPointerUp);
|
|
302
|
+
document.addEventListener("pointercancel", endDrag);
|
|
303
|
+
document.addEventListener("error", onImageError, true);
|
|
304
|
+
}
|
|
305
|
+
return { stackAdjacentImages };
|
|
306
|
+
})();
|
|
307
|
+
//#endregion
|
|
308
|
+
})();
|
|
309
|
+
//# sourceMappingURL=chat-image-carousel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-image-carousel.js","sources":["../../Assets/js/chat-image-carousel.js"],"sourcesContent":["/*\n * Stacks the pictures a single answer shows one after another into one carousel.\n *\n * A tool that previews a document -- slides, sheets, pages -- hands back one figure marker per picture, all on a\n * line of their own, and each one renders at full thumbnail size: eight slides is eight thumbnails running down\n * the conversation. Here a run of two or more pictures with nothing but whitespace between them becomes a stack\n * of cards instead: the current picture in front, its neighbours fanned out behind it, moved through by swiping,\n * clicking a neighbour, the arrows, the dots or the arrow keys. A picture written mid-sentence keeps its place.\n *\n * It works on the sanitized HTML every chat surface already produces, so the surfaces share one implementation\n * without sharing a renderer, and the carousel is written in the same pass as the message -- there is no frame\n * of full-size pictures before they are gathered up. HTML with no such run is handed back exactly as it came.\n *\n * Loaded on its own, like chat-markers.js, and optional: a surface without it renders the pictures as before.\n */\nwindow.CoreAIChatImageCarousel = window.CoreAIChatImageCarousel || (function () {\n 'use strict';\n\n const imageContainerClass = 'generated-image-container';\n\n // How far a drag has to travel before it counts as a swipe rather than a click.\n const swipeThreshold = 40;\n\n // How far a drag has to travel before the card starts following the pointer.\n const dragStartThreshold = 8;\n\n // Cards further than this from the current one are stacked out of sight behind the last visible one.\n const furthestVisibleOffset = 2;\n\n // Anything that is content in its own right, so pictures either side of it are not one run.\n const contentBetweenSelector = 'img, svg, canvas, video, audio, iframe, table, hr, input';\n\n const previousIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M10 3 5 8l5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n const nextIcon = '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m6 3 5 5-5 5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>';\n\n // How large the reader wants previews drawn. It is the reader's taste rather than the answer's, so one choice\n // applies to every carousel on the page and is kept in the browser for the next visit.\n const sizes = [\n { name: 'small', text: 'S', label: 'Small previews' },\n { name: 'medium', text: 'M', label: 'Medium previews' },\n { name: 'large', text: 'L', label: 'Large previews' },\n ];\n const defaultSize = 'medium';\n const sizeStorageKey = 'coreai-image-carousel-size';\n\n // A streamed answer is re-rendered on every chunk, and every render builds the carousel afresh. The picture\n // the reader moved to is remembered here so the next render opens on it rather than back on the first.\n const activeIndexByCarouselKey = new Map();\n\n let drag = null;\n let suppressNextClick = false;\n\n\n function stackAdjacentImages(html) {\n if (typeof document === 'undefined' || !html) {\n return html;\n }\n\n const firstContainerIndex = html.indexOf(imageContainerClass);\n\n if (firstContainerIndex < 0 || html.indexOf(imageContainerClass, firstContainerIndex + imageContainerClass.length) < 0) {\n return html;\n }\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n const runs = findImageRuns(template.content);\n\n if (runs.length === 0) {\n return html;\n }\n\n for (const run of runs) {\n buildCarousel(run);\n }\n\n return template.innerHTML;\n }\n\n\n function findImageRuns(root) {\n const runs = [];\n let run = [];\n\n for (const container of root.querySelectorAll('.' + imageContainerClass)) {\n // Only a picture standing in a paragraph is gathered. One in a list, a table cell or a link belongs\n // to that structure, and lifting it out would change what the answer says.\n if (!container.parentElement || container.parentElement.tagName !== 'P') {\n runs.push(run);\n run = [];\n continue;\n }\n\n if (run.length > 0 && !isOnlyWhitespaceBetween(run[run.length - 1], container)) {\n runs.push(run);\n run = [];\n }\n\n run.push(container);\n }\n\n runs.push(run);\n\n return runs.filter(candidate => candidate.length > 1);\n }\n\n\n // Two pictures belong to one run when nothing a reader would read sits between them: spaces and line breaks\n // within a paragraph, or the boundary between two paragraphs that hold nothing else.\n function isOnlyWhitespaceBetween(previous, next) {\n const range = previous.ownerDocument.createRange();\n range.setStartAfter(previous);\n range.setEndBefore(next);\n\n const between = range.cloneContents();\n\n return between.textContent.trim() === '' && !between.querySelector(contentBetweenSelector);\n }\n\n\n function buildCarousel(containers) {\n const ownerDocument = containers[0].ownerDocument;\n const firstParagraph = containers[0].parentElement;\n const vacatedParagraphs = new Set();\n\n // Spans throughout, for the same reason the picture's own wrapper is one: the carousel sits inside the\n // paragraph the first picture was in, and a block element there would close that paragraph.\n const carousel = createElement(ownerDocument, 'span', 'ai-image-carousel');\n carousel.tabIndex = 0;\n carousel.setAttribute('role', 'region');\n carousel.setAttribute('aria-roledescription', 'carousel');\n carousel.setAttribute('aria-label', `${containers.length} images`);\n carousel.dataset.carouselKey = getCarouselKey(containers);\n carousel.dataset.carouselSize = getPreferredSize();\n\n const stage = createElement(ownerDocument, 'span', 'ai-image-carousel-stage');\n const controls = createElement(ownerDocument, 'span', 'ai-image-carousel-controls');\n const caption = createElement(ownerDocument, 'span', 'ai-image-carousel-caption');\n const navigation = createElement(ownerDocument, 'span', 'ai-image-carousel-navigation');\n const dots = createElement(ownerDocument, 'span', 'ai-image-carousel-dots');\n\n caption.setAttribute('aria-live', 'polite');\n containers[0].before(carousel);\n\n containers.forEach((container, index) => {\n removeSeparatorsBefore(container);\n\n if (container.parentElement !== firstParagraph) {\n vacatedParagraphs.add(container.parentElement);\n }\n\n container.classList.add('ai-image-carousel-item');\n\n const image = container.querySelector('img');\n\n // The browser's own image drag would otherwise take over the pointer the moment a swipe starts.\n if (image) {\n image.setAttribute('draggable', 'false');\n }\n\n const dot = createElement(ownerDocument, 'button', 'ai-image-carousel-dot');\n dot.type = 'button';\n dot.dataset.carouselTarget = index;\n dot.setAttribute('aria-label', `Show image ${index + 1}`);\n dots.appendChild(dot);\n\n stage.appendChild(container);\n });\n\n navigation.append(\n createStepButton(ownerDocument, -1, 'Previous image', previousIcon),\n dots,\n createStepButton(ownerDocument, 1, 'Next image', nextIcon));\n\n controls.append(caption, createSizePicker(ownerDocument, carousel.dataset.carouselSize), navigation);\n carousel.append(stage, controls);\n\n for (const paragraph of vacatedParagraphs) {\n if (paragraph.textContent.trim() === '' && !paragraph.querySelector(contentBetweenSelector)) {\n paragraph.remove();\n }\n }\n\n showImage(carousel, activeIndexByCarouselKey.get(carousel.dataset.carouselKey) || 0);\n }\n\n\n // Whatever separated this picture from the one before it -- a space, a line break -- is all that is left once\n // the pictures have moved into the carousel, and it would otherwise trail the carousel as a blank line.\n function removeSeparatorsBefore(container) {\n let sibling = container.previousSibling;\n\n while (sibling && ((sibling.nodeType === Node.TEXT_NODE && sibling.textContent.trim() === '') || sibling.nodeName === 'BR')) {\n const previousSibling = sibling.previousSibling;\n sibling.remove();\n sibling = previousSibling;\n }\n }\n\n\n // Cheap rather than exact: a generated image can be a data URI hundreds of kilobytes long, so only the tail\n // of the first address -- where a served document's identifier sits -- and the number of pictures are used.\n function getCarouselKey(containers) {\n const firstImage = containers[0].querySelector('img');\n const firstSource = firstImage ? firstImage.getAttribute('src') || '' : '';\n\n return `${containers.length}:${firstSource.slice(-120)}`;\n }\n\n\n function createElement(ownerDocument, tagName, className) {\n const element = ownerDocument.createElement(tagName);\n element.className = className;\n\n return element;\n }\n\n\n function createStepButton(ownerDocument, step, label, icon) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-button');\n button.type = 'button';\n button.dataset.carouselStep = step;\n button.setAttribute('aria-label', label);\n button.innerHTML = icon;\n\n return button;\n }\n\n\n // A segmented S / M / L control.\n function createSizePicker(ownerDocument, selectedSize) {\n const picker = createElement(ownerDocument, 'span', 'ai-image-carousel-sizes');\n picker.setAttribute('role', 'group');\n picker.setAttribute('aria-label', 'Preview size');\n\n for (const size of sizes) {\n const button = createElement(ownerDocument, 'button', 'ai-image-carousel-size');\n button.type = 'button';\n button.title = size.label;\n button.textContent = size.text;\n button.dataset.carouselSizeOption = size.name;\n button.setAttribute('aria-label', size.label);\n button.setAttribute('aria-pressed', size.name === selectedSize ? 'true' : 'false');\n picker.appendChild(button);\n }\n\n return picker;\n }\n\n\n // Storage can be missing or refuse access -- a private window, blocked site data -- and the default is a fine\n // answer then.\n function getPreferredSize() {\n try {\n const storedSize = localStorage.getItem(sizeStorageKey);\n\n return sizes.some(size => size.name === storedSize) ? storedSize : defaultSize;\n } catch (_) {\n return defaultSize;\n }\n }\n\n\n function setPreferredSize(selectedSize) {\n try {\n localStorage.setItem(sizeStorageKey, selectedSize);\n } catch (_) {\n // Still applied to the page; only remembering it for the next visit is lost.\n }\n\n for (const carousel of document.querySelectorAll('.ai-image-carousel')) {\n carousel.dataset.carouselSize = selectedSize;\n }\n\n for (const button of document.querySelectorAll('[data-carousel-size-option]')) {\n button.setAttribute('aria-pressed', button.dataset.carouselSizeOption === selectedSize ? 'true' : 'false');\n }\n }\n\n\n function getItems(carousel) {\n return carousel.querySelectorAll('.ai-image-carousel-stage > .ai-image-carousel-item');\n }\n\n\n function getActiveIndex(carousel) {\n return Number(carousel.dataset.activeIndex) || 0;\n }\n\n\n function showImage(carousel, requestedIndex) {\n const items = getItems(carousel);\n\n if (items.length === 0) {\n carousel.remove();\n\n return;\n }\n\n const activeIndex = Math.max(0, Math.min(items.length - 1, requestedIndex));\n const dots = carousel.querySelector('.ai-image-carousel-dots');\n\n while (dots.children.length > items.length) {\n dots.lastElementChild.remove();\n }\n\n items.forEach((item, index) => {\n const offset = index - activeIndex;\n const isActive = offset === 0;\n const downloadLink = item.querySelector('a');\n\n item.dataset.stackOffset = Math.max(-furthestVisibleOffset - 1, Math.min(furthestVisibleOffset + 1, offset));\n item.classList.toggle('is-active', isActive);\n item.setAttribute('aria-hidden', isActive ? 'false' : 'true');\n\n // A card behind the current one is hidden from assistive technology, so nothing in it may take focus.\n if (downloadLink) {\n downloadLink.tabIndex = isActive ? 0 : -1;\n }\n\n dots.children[index].classList.toggle('is-active', isActive);\n dots.children[index].setAttribute('aria-current', isActive ? 'true' : 'false');\n });\n\n const activeImage = items[activeIndex].querySelector('img');\n const altText = activeImage ? activeImage.getAttribute('alt') : '';\n\n carousel.dataset.activeIndex = activeIndex;\n carousel.dataset.imageCount = items.length;\n carousel.querySelector('[data-carousel-step=\"-1\"]').disabled = activeIndex === 0;\n carousel.querySelector('[data-carousel-step=\"1\"]').disabled = activeIndex === items.length - 1;\n carousel.querySelector('.ai-image-carousel-caption').textContent = altText\n ? `${activeIndex + 1} / ${items.length} · ${altText}`\n : `${activeIndex + 1} / ${items.length}`;\n\n activeIndexByCarouselKey.set(carousel.dataset.carouselKey, activeIndex);\n }\n\n\n function stopEvent(event) {\n event.preventDefault();\n event.stopPropagation();\n }\n\n\n // Captured, so it runs before anything bound to the picture itself: a card behind the current one is brought\n // to the front, and is not zoomed or downloaded on the way.\n function onClick(event) {\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n // A click raised from the keyboard has no pointer behind it, so a swipe that ended without one -- a touch\n // swipe, or a drag released outside the carousel -- must not swallow it.\n if (suppressNextClick && event.detail > 0) {\n suppressNextClick = false;\n stopEvent(event);\n\n return;\n }\n\n const stepButton = event.target.closest('[data-carousel-step]');\n\n if (stepButton) {\n stopEvent(event);\n showImage(carousel, getActiveIndex(carousel) + Number(stepButton.dataset.carouselStep));\n\n return;\n }\n\n const sizeButton = event.target.closest('[data-carousel-size-option]');\n\n if (sizeButton) {\n stopEvent(event);\n setPreferredSize(sizeButton.dataset.carouselSizeOption);\n\n return;\n }\n\n const dot = event.target.closest('[data-carousel-target]');\n\n if (dot) {\n stopEvent(event);\n showImage(carousel, Number(dot.dataset.carouselTarget));\n\n return;\n }\n\n const item = event.target.closest('.ai-image-carousel-item');\n\n if (item && !item.classList.contains('is-active')) {\n stopEvent(event);\n showImage(carousel, Array.prototype.indexOf.call(getItems(carousel), item));\n }\n }\n\n\n // Only while the carousel has focus, so the arrow keys still move the caret in the prompt.\n function onKeyDown(event) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {\n return;\n }\n\n const carousel = event.target.closest && event.target.closest('.ai-image-carousel');\n\n if (!carousel) {\n return;\n }\n\n event.preventDefault();\n showImage(carousel, getActiveIndex(carousel) + (event.key === 'ArrowRight' ? 1 : -1));\n }\n\n\n function onPointerDown(event) {\n suppressNextClick = false;\n\n const stage = event.button === 0 && event.target.closest && event.target.closest('.ai-image-carousel-stage');\n\n if (!stage) {\n return;\n }\n\n drag = {\n stage: stage,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n distance: 0,\n isSwiping: false,\n };\n }\n\n\n // The pointer is not captured: a captured pointer retargets the click that follows to the stage, and the click\n // is what brings a card forward or opens the zoom. Listening on the document follows the pointer anyway.\n function onPointerMove(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const distance = event.clientX - drag.startX;\n\n if (!drag.isSwiping) {\n if (Math.abs(distance) < dragStartThreshold || Math.abs(distance) < Math.abs(event.clientY - drag.startY)) {\n return;\n }\n\n drag.isSwiping = true;\n drag.stage.classList.add('is-dragging');\n }\n\n drag.distance = distance;\n drag.stage.style.setProperty('--ai-image-carousel-drag', `${distance}px`);\n }\n\n\n function onPointerUp(event) {\n if (!drag || event.pointerId !== drag.pointerId) {\n return;\n }\n\n const { stage, distance, isSwiping } = drag;\n\n endDrag();\n\n if (!isSwiping) {\n return;\n }\n\n // The click the browser fires at the end of a drag is not a click on whatever the pointer ended over.\n suppressNextClick = true;\n\n if (Math.abs(distance) >= swipeThreshold) {\n const carousel = stage.closest('.ai-image-carousel');\n showImage(carousel, getActiveIndex(carousel) + (distance < 0 ? 1 : -1));\n }\n }\n\n\n function endDrag() {\n if (!drag) {\n return;\n }\n\n drag.stage.classList.remove('is-dragging');\n drag.stage.style.removeProperty('--ai-image-carousel-drag');\n drag = null;\n }\n\n\n // A picture the server will not serve is taken out of the stack and put back just after it, where the\n // surface's own handler hides it and says it could not be loaded, exactly as it does for a lone picture.\n // Captured on the document because an image's error event does not bubble.\n function onImageError(event) {\n const item = event.target.tagName === 'IMG' && event.target.closest('.ai-image-carousel-item');\n\n if (!item) {\n return;\n }\n\n const carousel = item.closest('.ai-image-carousel');\n\n item.classList.remove('ai-image-carousel-item', 'is-active');\n item.removeAttribute('data-stack-offset');\n item.removeAttribute('aria-hidden');\n carousel.after(item);\n showImage(carousel, getActiveIndex(carousel));\n }\n\n\n if (typeof document !== 'undefined') {\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('pointerdown', onPointerDown);\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', endDrag);\n document.addEventListener('error', onImageError, true);\n }\n\n return {\n stackAdjacentImages: stackAdjacentImages,\n };\n})();\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ai-image-carousel{--ai-image-carousel-card-width:70%;border-radius:.5rem;width:100%;max-width:520px;margin:.5rem 0;transition:max-width .25s;display:block}.ai-image-carousel[data-carousel-size=small]{max-width:360px}.ai-image-carousel[data-carousel-size=large]{max-width:760px}.ai-image-carousel:focus{outline:none}.ai-image-carousel:focus-visible{outline:2px solid var(--bs-primary,#0d6efd);outline-offset:4px}.ai-chat-widget-messages .ai-image-carousel{--ai-image-carousel-card-width:78%}.ai-image-carousel-stage{isolation:isolate;touch-action:pan-y;-webkit-user-select:none;user-select:none;place-items:center;padding:.25rem 0;display:grid}.ai-image-carousel-stage>.generated-image-container.ai-image-carousel-item{width:var(--ai-image-carousel-card-width);cursor:pointer;transform-origin:50%;grid-area:1/1;margin:0;transition:transform .35s cubic-bezier(.2,.7,.2,1),opacity .35s,filter .35s;display:block}.ai-image-carousel-stage>.generated-image-container.ai-image-carousel-item img{-webkit-user-drag:none;width:100%;display:block;box-shadow:0 .375rem 1.25rem #00000024;max-width:100%!important}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="0"]{z-index:4;cursor:default;transform:translateX(var(--ai-image-carousel-drag,0px))}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="-1"],.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="1"]{z-index:3;opacity:.85;filter:brightness(.94)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="-1"]{transform:translate(-16%)scale(.88)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="1"]{transform:translate(16%)scale(.88)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="-2"],.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="2"]{z-index:2;opacity:.55;filter:brightness(.9)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="-2"]{transform:translate(-26%)scale(.76)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="2"]{transform:translate(26%)scale(.76)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="-3"],.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="3"]{z-index:1;opacity:0;pointer-events:none}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="-3"]{transform:translate(-26%)scale(.7)}.ai-image-carousel-stage>.ai-image-carousel-item[data-stack-offset="3"]{transform:translate(26%)scale(.7)}.ai-image-carousel-stage>.ai-image-carousel-item:not(.is-active):hover{opacity:1;filter:none}.ai-image-carousel-stage>.generated-image-container.ai-image-carousel-item:not(.is-active) img{cursor:pointer}.ai-image-carousel-stage>.ai-image-carousel-item:not(.is-active) .ai-download-image{display:none}.ai-image-carousel-stage.is-dragging>.ai-image-carousel-item{transition:none}.ai-image-carousel-controls{color:var(--bs-secondary-color,#6c757d);align-items:center;gap:.75rem;margin-top:.5rem;font-size:.8125rem;line-height:1.25;display:flex}.ai-image-carousel-caption{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.ai-image-carousel-navigation{flex:none;align-items:center;gap:.375rem;display:inline-flex}.ai-image-carousel-button{border:1px solid var(--bs-border-color,#dee2e6);width:1.75rem;height:1.75rem;color:var(--bs-body-color,#212529);background-color:var(--bs-body-bg,#fff);border-radius:50%;justify-content:center;align-items:center;padding:0;transition:background-color .15s,opacity .15s;display:inline-flex}.ai-image-carousel-button:hover:not(:disabled){background-color:var(--bs-tertiary-bg,#f8f9fa)}.ai-image-carousel-button:disabled{opacity:.4;cursor:default}.ai-image-carousel-dots{align-items:center;gap:.375rem;display:inline-flex}.ai-image-carousel-dot{background-color:var(--bs-border-color,#dee2e6);border:0;border-radius:999px;width:.5rem;height:.5rem;padding:0;transition:width .2s,background-color .2s;position:relative}.ai-image-carousel-dot:after{content:"";position:absolute;inset:-.375rem -.25rem}.ai-image-carousel-dot.is-active{background-color:var(--bs-primary,#0d6efd);width:1.25rem}.ai-image-carousel-sizes{border:1px solid var(--bs-border-color,#dee2e6);border-radius:.375rem;flex:none;display:inline-flex;overflow:hidden}.ai-image-carousel-size{min-width:1.5rem;height:1.5rem;color:var(--bs-secondary-color,#6c757d);background-color:var(--bs-body-bg,#fff);border:0;padding:0 .375rem;font-size:.6875rem;font-weight:600;line-height:1;transition:background-color .15s,color .15s}.ai-image-carousel-size+.ai-image-carousel-size{border-left:1px solid var(--bs-border-color,#dee2e6)}.ai-image-carousel-size:hover{color:var(--bs-body-color,#212529);background-color:var(--bs-tertiary-bg,#f8f9fa)}.ai-image-carousel-size[aria-pressed=true]{color:#fff;background-color:var(--bs-primary,#0d6efd)}.ai-chat-widget-messages .ai-image-carousel-sizes,.ai-image-carousel[data-image-count="1"] .ai-image-carousel-navigation{display:none}@media (prefers-reduced-motion:reduce){.ai-image-carousel-stage>.generated-image-container.ai-image-carousel-item,.ai-image-carousel-dot{transition:none}}
|