@astryx-vue/lab 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, getCurrentInstance, guardReactiveProps, h, inject, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onBeforeUnmount, onMounted, onScopeDispose, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toValue, unref, useAttrs, useId, useModel, useSlots, useTemplateRef, watch, withCtx } from "vue";
2
+ import { AlertDialog, BottomSheet, Button, CheckboxIndicator, ComplexSelector, EmptyState, Field, FieldStatus, HStack, Heading, Icon, IconButton, Item, List, Popover, SYNC_TOKENIZE_THRESHOLD, SyntaxTheme, Text, TextInput, Token, Tooltip, VStack, VisuallyHidden, applyHighlightRangesFlat, tokenize, tokenizeAsync, useAnnounce, useAstryxMessages, useGridFocus, useScrollableArea, useTranslator } from "@astryx-vue/core";
3
+ import "../styles/chat-emoji-picker.css";
4
+ import "../styles/chat-reaction-bar.css";
2
5
  import "../styles/circular-progress.css";
3
- import { AlertDialog, BottomSheet, Button, CheckboxIndicator, ComplexSelector, EmptyState, Field, FieldStatus, HStack, Heading, Icon, IconButton, Item, List, Popover, SyntaxTheme, Text, TextInput, Token, Tooltip, VStack, VisuallyHidden, tokenize, useAnnounce, useScrollableArea, useTranslator } from "@astryx-vue/core";
6
+ import "../styles/code-editor.css";
4
7
  import "../styles/diff-viewer.css";
5
8
  import "../styles/info-tip.css";
6
9
  import "../styles/list-input.css";
@@ -11,6 +14,289 @@ import "../styles/svg-icon.css";
11
14
  import "../styles/tour.css";
12
15
  import "../styles/transfer-list.css";
13
16
  import "../styles/transfer-list-selector.css";
17
+ //#region src/Chat/emojis.ts
18
+ /**
19
+ * 两行常用回应。调用方可以用 `emojis` 换成自己的一组。
20
+ * 内容和原版 `DEFAULT_CHAT_EMOJIS` 一致。
21
+ */
22
+ const DEFAULT_CHAT_EMOJIS = [
23
+ {
24
+ emoji: "👍",
25
+ name: "thumbs up"
26
+ },
27
+ {
28
+ emoji: "❤️",
29
+ name: "heart"
30
+ },
31
+ {
32
+ emoji: "😂",
33
+ name: "joy"
34
+ },
35
+ {
36
+ emoji: "🎉",
37
+ name: "tada"
38
+ },
39
+ {
40
+ emoji: "😮",
41
+ name: "wow"
42
+ },
43
+ {
44
+ emoji: "😢",
45
+ name: "cry"
46
+ },
47
+ {
48
+ emoji: "🔥",
49
+ name: "fire"
50
+ },
51
+ {
52
+ emoji: "👀",
53
+ name: "eyes"
54
+ },
55
+ {
56
+ emoji: "✅",
57
+ name: "check"
58
+ },
59
+ {
60
+ emoji: "🙏",
61
+ name: "pray"
62
+ },
63
+ {
64
+ emoji: "💯",
65
+ name: "hundred"
66
+ },
67
+ {
68
+ emoji: "🚀",
69
+ name: "rocket"
70
+ },
71
+ {
72
+ emoji: "😍",
73
+ name: "heart eyes"
74
+ },
75
+ {
76
+ emoji: "🤔",
77
+ name: "thinking"
78
+ },
79
+ {
80
+ emoji: "👋",
81
+ name: "wave"
82
+ },
83
+ {
84
+ emoji: "⭐",
85
+ name: "star"
86
+ }
87
+ ];
88
+ //#endregion
89
+ //#region src/Chat/filterEmojis.ts
90
+ /** 短名包含查询,或者查询本身就是那个表情字符。大小写不敏感,首尾空白不算。 */
91
+ function filterEmojis(emojis, query) {
92
+ const trimmed = query.trim().toLowerCase();
93
+ if (trimmed === "") return [...emojis];
94
+ return emojis.filter((option) => option.name.toLowerCase().includes(trimmed) || option.emoji === trimmed);
95
+ }
96
+ //#endregion
97
+ //#region src/Chat/ChatEmojiPicker.vue
98
+ const _hoisted_1$18 = {
99
+ key: 0,
100
+ class: "astryx-chat-emoji-picker__empty"
101
+ };
102
+ const _hoisted_2$16 = ["aria-label"];
103
+ const _hoisted_3$14 = [
104
+ "data-emoji",
105
+ "aria-label",
106
+ "onClick"
107
+ ];
108
+ const _hoisted_4$13 = { "aria-hidden": "true" };
109
+ const _sfc_main = /*@__PURE__*/ defineComponent({
110
+ name: "AstryxChatEmojiPicker",
111
+ inheritAttrs: false,
112
+ __name: "ChatEmojiPicker",
113
+ props: {
114
+ emojis: {},
115
+ label: {},
116
+ searchLabel: {}
117
+ },
118
+ emits: ["select"],
119
+ setup(__props, { expose: __expose, emit: __emit }) {
120
+ const props = __props;
121
+ const emit = __emit;
122
+ const { t } = useTranslator();
123
+ const messages = useAstryxMessages();
124
+ const query = ref("");
125
+ const isOpen = ref(false);
126
+ const panel = useTemplateRef("panel");
127
+ const { gridRef, handleKeyDown } = useGridFocus({ columns: 8 });
128
+ function setGrid(element) {
129
+ gridRef.value = element instanceof HTMLElement ? element : null;
130
+ }
131
+ const options = computed(() => props.emojis ?? DEFAULT_CHAT_EMOJIS);
132
+ const visible = computed(() => filterEmojis(options.value, query.value));
133
+ const dialogLabel = computed(() => props.label ?? messages.value.emojiPicker.pickLabel);
134
+ const searchText = computed(() => props.searchLabel ?? t("@astryx.emojiPicker.searchLabel"));
135
+ const gridLabel = computed(() => t("@astryx.emojiPicker.label"));
136
+ const emptyText = computed(() => t("@astryx.emojiPicker.noResults", { query: query.value.trim() }));
137
+ function onOpenChange(open) {
138
+ isOpen.value = open;
139
+ if (!open) query.value = "";
140
+ }
141
+ function pick(emoji) {
142
+ emit("select", emoji);
143
+ query.value = "";
144
+ isOpen.value = false;
145
+ }
146
+ __expose({ element: panel });
147
+ return (_ctx, _cache) => {
148
+ return openBlock(), createBlock(unref(Popover), {
149
+ "is-open": isOpen.value,
150
+ label: dialogLabel.value,
151
+ placement: "below",
152
+ alignment: "start",
153
+ padding: 2,
154
+ width: 320,
155
+ onOpenChange
156
+ }, {
157
+ content: withCtx(() => [createElementVNode("div", mergeProps({
158
+ ref_key: "panel",
159
+ ref: panel,
160
+ class: "astryx-chat-emoji-picker"
161
+ }, _ctx.$attrs), [createVNode(unref(TextInput), {
162
+ modelValue: query.value,
163
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => query.value = $event),
164
+ class: "astryx-chat-emoji-picker__search",
165
+ label: searchText.value,
166
+ "is-label-hidden": "",
167
+ size: "sm",
168
+ placeholder: searchText.value,
169
+ "has-clear": "",
170
+ width: "100%"
171
+ }, null, 8, [
172
+ "modelValue",
173
+ "label",
174
+ "placeholder"
175
+ ]), visible.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_1$18, toDisplayString(emptyText.value), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" eslint-disable-next-line vue-a11y/no-static-element-interactions -- 焦点在格子按钮上,方向键冒泡到这层再按网格移动。 "), createElementVNode("div", {
176
+ ref: setGrid,
177
+ class: "astryx-chat-emoji-picker__grid",
178
+ role: "group",
179
+ "aria-label": gridLabel.value,
180
+ onKeydown: _cache[1] || (_cache[1] = (...args) => unref(handleKeyDown) && unref(handleKeyDown)(...args))
181
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(visible.value, (option) => {
182
+ return openBlock(), createElementBlock("button", {
183
+ key: option.emoji,
184
+ type: "button",
185
+ class: "astryx-chat-emoji-picker__cell",
186
+ "data-emoji": option.emoji,
187
+ "aria-label": unref(messages).emojiPicker.reactWith(option.name),
188
+ onClick: ($event) => pick(option.emoji)
189
+ }, [createElementVNode("span", _hoisted_4$13, toDisplayString(option.emoji), 1)], 8, _hoisted_3$14);
190
+ }), 128))], 40, _hoisted_2$16)], 2112))], 16)]),
191
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
192
+ _: 3
193
+ }, 8, ["is-open", "label"]);
194
+ };
195
+ }
196
+ });
197
+ //#endregion
198
+ //#region src/Chat/ChatReactionBar.vue
199
+ const _hoisted_1$17 = ["aria-label"];
200
+ const _hoisted_2$15 = [
201
+ "data-selected",
202
+ "aria-pressed",
203
+ "aria-label",
204
+ "onClick"
205
+ ];
206
+ const _hoisted_3$13 = {
207
+ class: "astryx-chat-reaction-bar__emoji",
208
+ "aria-hidden": "true"
209
+ };
210
+ const _hoisted_4$12 = { class: "astryx-chat-reaction-bar__count" };
211
+ const _hoisted_5$11 = [
212
+ "data-selected",
213
+ "aria-pressed",
214
+ "aria-label",
215
+ "onClick"
216
+ ];
217
+ const _hoisted_6$9 = {
218
+ class: "astryx-chat-reaction-bar__emoji",
219
+ "aria-hidden": "true"
220
+ };
221
+ const _hoisted_7$7 = { class: "astryx-chat-reaction-bar__count" };
222
+ const _hoisted_8$5 = ["aria-label"];
223
+ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
224
+ name: "AstryxChatReactionBar",
225
+ inheritAttrs: false,
226
+ __name: "ChatReactionBar",
227
+ props: {
228
+ reactions: {},
229
+ emojis: {},
230
+ addLabel: {},
231
+ label: {}
232
+ },
233
+ emits: ["toggle", "add"],
234
+ setup(__props, { expose: __expose }) {
235
+ const props = __props;
236
+ const instance = getCurrentInstance();
237
+ const { t } = useTranslator();
238
+ const messages = useAstryxMessages();
239
+ const root = useTemplateRef("root");
240
+ const groupLabel = computed(() => props.label ?? messages.value.comments.reactionsLabel);
241
+ const addLabel = computed(() => props.addLabel ?? t("@astryx.comments.addReaction"));
242
+ const hasAdd = computed(() => {
243
+ const vnodeProps = instance?.vnode.props;
244
+ return vnodeProps?.onAdd != null || vnodeProps?.onAddOnce != null;
245
+ });
246
+ function pillLabel(reaction) {
247
+ return reaction.label ?? messages.value.comments.reactionWith(reaction.count, reaction.emoji);
248
+ }
249
+ __expose({ element: root });
250
+ return (_ctx, _cache) => {
251
+ return openBlock(), createElementBlock("div", mergeProps({
252
+ ref_key: "root",
253
+ ref: root,
254
+ class: "astryx-chat-reaction-bar",
255
+ role: "group",
256
+ "aria-label": groupLabel.value
257
+ }, _ctx.$attrs), [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.reactions, (reaction) => {
258
+ return openBlock(), createElementBlock(Fragment, { key: reaction.emoji }, [reaction.label != null ? (openBlock(), createBlock(unref(Tooltip), {
259
+ key: 0,
260
+ content: reaction.label
261
+ }, {
262
+ default: withCtx(() => [createElementVNode("button", {
263
+ type: "button",
264
+ class: "astryx-chat-reaction-bar__pill",
265
+ "data-selected": reaction.isSelected === true || void 0,
266
+ "aria-pressed": reaction.isSelected === true,
267
+ "aria-label": pillLabel(reaction),
268
+ onClick: ($event) => _ctx.$emit("toggle", reaction.emoji)
269
+ }, [createElementVNode("span", _hoisted_3$13, toDisplayString(reaction.emoji), 1), createElementVNode("span", _hoisted_4$12, toDisplayString(reaction.count), 1)], 8, _hoisted_2$15)]),
270
+ _: 2
271
+ }, 1032, ["content"])) : (openBlock(), createElementBlock("button", {
272
+ key: 1,
273
+ type: "button",
274
+ class: "astryx-chat-reaction-bar__pill",
275
+ "data-selected": reaction.isSelected === true || void 0,
276
+ "aria-pressed": reaction.isSelected === true,
277
+ "aria-label": pillLabel(reaction),
278
+ onClick: ($event) => _ctx.$emit("toggle", reaction.emoji)
279
+ }, [createElementVNode("span", _hoisted_6$9, toDisplayString(reaction.emoji), 1), createElementVNode("span", _hoisted_7$7, toDisplayString(reaction.count), 1)], 8, _hoisted_5$11))], 64);
280
+ }), 128)), hasAdd.value ? (openBlock(), createBlock(_sfc_main, {
281
+ key: 0,
282
+ emojis: __props.emojis,
283
+ label: addLabel.value,
284
+ onSelect: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("add", $event))
285
+ }, {
286
+ default: withCtx(() => [createVNode(unref(Tooltip), { content: addLabel.value }, {
287
+ default: withCtx(() => [createElementVNode("button", {
288
+ type: "button",
289
+ class: "astryx-chat-reaction-bar__add",
290
+ "aria-label": addLabel.value
291
+ }, " + ", 8, _hoisted_8$5)]),
292
+ _: 1
293
+ }, 8, ["content"])]),
294
+ _: 1
295
+ }, 8, ["emojis", "label"])) : createCommentVNode("v-if", true)], 16, _hoisted_1$17);
296
+ };
297
+ }
298
+ });
299
+ //#endregion
14
300
  //#region src/CircularProgress/types.ts
15
301
  /** Ring geometry per size: outer diameter and stroke width in px. */
16
302
  const SIZE_CONFIG = {
@@ -33,9 +319,9 @@ function defaultFormatValueLabel(value, max) {
33
319
  }
34
320
  //#endregion
35
321
  //#region src/CircularProgress/CircularProgress.vue
36
- const _hoisted_1$15 = ["data-size", "data-variant"];
37
- const _hoisted_2$13 = { class: "astryx-circular-progress__ring" };
38
- const _hoisted_3$11 = [
322
+ const _hoisted_1$16 = ["data-size", "data-variant"];
323
+ const _hoisted_2$14 = { class: "astryx-circular-progress__ring" };
324
+ const _hoisted_3$12 = [
39
325
  "aria-valuenow",
40
326
  "aria-valuemin",
41
327
  "aria-valuemax",
@@ -44,14 +330,14 @@ const _hoisted_3$11 = [
44
330
  "height",
45
331
  "viewBox"
46
332
  ];
47
- const _hoisted_4$10 = [
333
+ const _hoisted_4$11 = [
48
334
  "data-variant",
49
335
  "cx",
50
336
  "cy",
51
337
  "r",
52
338
  "stroke-width"
53
339
  ];
54
- const _hoisted_5$9 = [
340
+ const _hoisted_5$10 = [
55
341
  "data-variant",
56
342
  "cx",
57
343
  "cy",
@@ -71,7 +357,7 @@ const _hoisted_7$6 = {
71
357
  key: 0,
72
358
  class: "astryx-circular-progress__center"
73
359
  };
74
- const _sfc_main = /*@__PURE__*/ defineComponent({
360
+ const _sfc_main$2 = /*@__PURE__*/ defineComponent({
75
361
  name: "AstryxCircularProgress",
76
362
  inheritAttrs: false,
77
363
  __name: "CircularProgress",
@@ -139,7 +425,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
139
425
  "astryx-circular-progress__visually-hidden": !showLabel.value,
140
426
  "astryx-circular-progress__label--disabled": showLabel.value && __props.isDisabled
141
427
  }])
142
- }, toDisplayString(__props.label), 3), createElementVNode("div", _hoisted_2$13, [(openBlock(), createElementBlock("svg", {
428
+ }, toDisplayString(__props.label), 3), createElementVNode("div", _hoisted_2$14, [(openBlock(), createElementBlock("svg", {
143
429
  class: normalizeClass(["astryx-circular-progress__svg", { "astryx-circular-progress__svg--indeterminate": __props.isIndeterminate }]),
144
430
  role: "progressbar",
145
431
  "aria-labelledby": labelId,
@@ -157,7 +443,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
157
443
  cy: geometry.value.center,
158
444
  r: geometry.value.radius,
159
445
  "stroke-width": geometry.value.strokeWidth
160
- }, null, 8, _hoisted_4$10), __props.isIndeterminate ? (openBlock(), createElementBlock("circle", {
446
+ }, null, 8, _hoisted_4$11), __props.isIndeterminate ? (openBlock(), createElementBlock("circle", {
161
447
  key: 0,
162
448
  class: "astryx-circular-progress__fill astryx-circular-progress__fill--indeterminate",
163
449
  "data-variant": fillVariant.value,
@@ -165,7 +451,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
165
451
  cy: geometry.value.center,
166
452
  r: geometry.value.radius,
167
453
  "stroke-width": geometry.value.strokeWidth
168
- }, null, 8, _hoisted_5$9)) : (openBlock(), createElementBlock("circle", {
454
+ }, null, 8, _hoisted_5$10)) : (openBlock(), createElementBlock("circle", {
169
455
  key: 1,
170
456
  class: "astryx-circular-progress__fill",
171
457
  "data-variant": fillVariant.value,
@@ -175,10 +461,278 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
175
461
  "stroke-width": geometry.value.strokeWidth,
176
462
  "stroke-dasharray": geometry.value.circumference,
177
463
  "stroke-dashoffset": dashoffset.value
178
- }, null, 8, _hoisted_6$8))], 10, _hoisted_3$11)), _ctx.$slots.default || hasCenterContent.value ? (openBlock(), createElementBlock("div", _hoisted_7$6, [renderSlot(_ctx.$slots, "default", {}, () => [hasCenterContent.value ? (openBlock(), createElementBlock("span", {
464
+ }, null, 8, _hoisted_6$8))], 10, _hoisted_3$12)), _ctx.$slots.default || hasCenterContent.value ? (openBlock(), createElementBlock("div", _hoisted_7$6, [renderSlot(_ctx.$slots, "default", {}, () => [hasCenterContent.value ? (openBlock(), createElementBlock("span", {
179
465
  key: 0,
180
466
  class: normalizeClass(["astryx-circular-progress__value", { "astryx-circular-progress__value--disabled": __props.isDisabled }])
181
- }, toDisplayString(valueText.value), 3)) : createCommentVNode("v-if", true)])])) : createCommentVNode("v-if", true)])], 16, _hoisted_1$15);
467
+ }, toDisplayString(valueText.value), 3)) : createCommentVNode("v-if", true)])])) : createCommentVNode("v-if", true)])], 16, _hoisted_1$16);
468
+ };
469
+ }
470
+ });
471
+ //#endregion
472
+ //#region src/CodeEditor/keyboard.ts
473
+ /** 把 caret 放到元素文本的字符偏移处。 */
474
+ function setCaretAtTextOffset(element, offset) {
475
+ const selection = element.ownerDocument.defaultView?.getSelection();
476
+ if (!selection) return;
477
+ const range = element.ownerDocument.createRange();
478
+ const walker = element.ownerDocument.createTreeWalker(element, NodeFilter.SHOW_TEXT);
479
+ let node = walker.nextNode();
480
+ let remaining = Math.max(0, offset);
481
+ while (node) {
482
+ const length = node.textContent?.length ?? 0;
483
+ if (remaining <= length) {
484
+ range.setStart(node, remaining);
485
+ range.collapse(true);
486
+ selection.removeAllRanges();
487
+ selection.addRange(range);
488
+ return;
489
+ }
490
+ remaining -= length;
491
+ node = walker.nextNode();
492
+ }
493
+ range.selectNodeContents(element);
494
+ range.collapse(false);
495
+ selection.removeAllRanges();
496
+ selection.addRange(range);
497
+ }
498
+ /** 当前 caret 在元素纯文本里的偏移。 */
499
+ function caretTextOffset(element) {
500
+ const selection = element.ownerDocument.defaultView?.getSelection();
501
+ if (!selection || selection.rangeCount === 0) return null;
502
+ const range = selection.getRangeAt(0);
503
+ const before = range.cloneRange();
504
+ before.selectNodeContents(element);
505
+ before.setEnd(range.startContainer, range.startOffset);
506
+ return before.toString().length;
507
+ }
508
+ /** 用一段文本替换当前选区,并把 caret 放在新文本后面。 */
509
+ function insertAtCaret(element, text) {
510
+ const selection = element.ownerDocument.defaultView?.getSelection();
511
+ if (!selection || selection.rangeCount === 0) return;
512
+ const range = selection.getRangeAt(0);
513
+ range.deleteContents();
514
+ const node = element.ownerDocument.createTextNode(text);
515
+ range.insertNode(node);
516
+ range.setStartAfter(node);
517
+ range.setEndAfter(node);
518
+ selection.removeAllRanges();
519
+ selection.addRange(range);
520
+ }
521
+ /** 去掉当前行开头最多两个空格,返回新文本和 caret 应到的位置。 */
522
+ function outdentCurrentLine(element) {
523
+ const offset = caretTextOffset(element);
524
+ if (offset == null) return null;
525
+ const value = element.textContent ?? "";
526
+ const lineStart = value.lastIndexOf("\n", offset - 1) + 1;
527
+ const line = value.slice(lineStart);
528
+ const removeCount = line.startsWith(" ") ? 2 : line.startsWith(" ") ? 1 : 0;
529
+ if (removeCount === 0) return null;
530
+ return {
531
+ value: `${value.slice(0, lineStart)}${value.slice(lineStart + removeCount)}`,
532
+ caret: offset - Math.min(removeCount, Math.max(0, offset - lineStart))
533
+ };
534
+ }
535
+ //#endregion
536
+ //#region src/CodeEditor/CodeEditor.vue
537
+ const _hoisted_1$15 = ["data-size", "data-language"];
538
+ const _hoisted_2$13 = {
539
+ key: 0,
540
+ class: "astryx-code-editor__gutter",
541
+ "aria-hidden": "true"
542
+ };
543
+ const _hoisted_3$11 = { class: "astryx-code-editor__body" };
544
+ const _hoisted_4$10 = {
545
+ key: 0,
546
+ class: "astryx-code-editor__placeholder",
547
+ "aria-hidden": "true"
548
+ };
549
+ const _hoisted_5$9 = [
550
+ "contenteditable",
551
+ "aria-label",
552
+ "aria-readonly",
553
+ "aria-describedby"
554
+ ];
555
+ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
556
+ name: "AstryxCodeEditor",
557
+ __name: "CodeEditor",
558
+ props: {
559
+ value: {},
560
+ language: { default: "plaintext" },
561
+ label: {},
562
+ hasLineNumbers: { type: Boolean },
563
+ isReadOnly: { type: Boolean },
564
+ placeholder: {},
565
+ maxHeight: {},
566
+ size: { default: "md" },
567
+ tokenizer: {},
568
+ escapeHint: { default: "Press Escape then Tab to move focus out of the editor." }
569
+ },
570
+ emits: ["change"],
571
+ setup(__props, { emit: __emit }) {
572
+ const props = __props;
573
+ const emit = __emit;
574
+ const editor = useTemplateRef("editor");
575
+ const focused = ref(false);
576
+ const composing = ref(false);
577
+ const tabMovesFocus = ref(false);
578
+ const hintId = `astryx-code-editor-${useId()}-escape`;
579
+ const lines = computed(() => props.value.split("\n"));
580
+ const showPlaceholder = computed(() => props.value === "" && Boolean(props.placeholder));
581
+ const containerStyle = computed(() => props.maxHeight == null ? void 0 : { maxHeight: typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight });
582
+ const AUTO_CLOSE_PAIRS = {
583
+ "(": ")",
584
+ "[": "]",
585
+ "{": "}",
586
+ "\"": "\"",
587
+ "'": "'",
588
+ "`": "`"
589
+ };
590
+ const MODE_NEUTRAL_KEYS = /* @__PURE__ */ new Set([
591
+ "Shift",
592
+ "Control",
593
+ "Alt",
594
+ "Meta",
595
+ "CapsLock"
596
+ ]);
597
+ let highlightCleanup;
598
+ let highlightAbort;
599
+ function hasHighlightAPI() {
600
+ return typeof CSS !== "undefined" && "highlights" in CSS && typeof Highlight !== "undefined";
601
+ }
602
+ function paint(tokens, element) {
603
+ highlightCleanup?.();
604
+ highlightCleanup = tokens.length === 0 ? void 0 : applyHighlightRangesFlat(element, tokens);
605
+ }
606
+ function paintCurrent() {
607
+ const element = editor.value;
608
+ if (!element || !hasHighlightAPI()) return;
609
+ highlightAbort?.abort();
610
+ highlightCleanup?.();
611
+ highlightCleanup = void 0;
612
+ const custom = props.tokenizer;
613
+ if (props.value.length >= SYNC_TOKENIZE_THRESHOLD && custom == null) {
614
+ const controller = new AbortController();
615
+ highlightAbort = controller;
616
+ tokenizeAsync(props.value, props.language, controller.signal).then((tokens) => {
617
+ if (!controller.signal.aborted && editor.value === element) paint(tokens, element);
618
+ });
619
+ return;
620
+ }
621
+ paint((custom ?? tokenize)(props.value, props.language), element);
622
+ }
623
+ watch([
624
+ () => props.value,
625
+ () => props.language,
626
+ () => props.tokenizer
627
+ ], () => {
628
+ nextTick(paintCurrent);
629
+ }, {
630
+ immediate: true,
631
+ flush: "post"
632
+ });
633
+ watch(() => props.value, (value) => {
634
+ const element = editor.value;
635
+ if (element && element.textContent !== value) element.textContent = value;
636
+ }, { flush: "post" });
637
+ onBeforeUnmount(() => {
638
+ highlightAbort?.abort();
639
+ highlightCleanup?.();
640
+ });
641
+ function commit() {
642
+ emit("change", editor.value?.textContent ?? "");
643
+ }
644
+ function onInput() {
645
+ if (!composing.value) commit();
646
+ }
647
+ function onKeydown(event) {
648
+ if (props.isReadOnly) return;
649
+ if (event.key === "Escape") {
650
+ if (!event.defaultPrevented) tabMovesFocus.value = true;
651
+ return;
652
+ }
653
+ const element = editor.value;
654
+ if (!element) return;
655
+ if (event.key === "Tab") {
656
+ if (tabMovesFocus.value) {
657
+ tabMovesFocus.value = false;
658
+ return;
659
+ }
660
+ event.preventDefault();
661
+ if (event.shiftKey) {
662
+ const result = outdentCurrentLine(element);
663
+ if (result == null) return;
664
+ element.textContent = result.value;
665
+ setCaretAtTextOffset(element, result.caret);
666
+ } else insertAtCaret(element, " ");
667
+ commit();
668
+ return;
669
+ }
670
+ if (!MODE_NEUTRAL_KEYS.has(event.key)) tabMovesFocus.value = false;
671
+ if (event.key === "Enter") {
672
+ event.preventDefault();
673
+ const offset = caretTextOffset(element);
674
+ if (offset == null) return;
675
+ const before = (element.textContent ?? "").slice(0, offset);
676
+ insertAtCaret(element, `\n${before.slice(before.lastIndexOf("\n") + 1).match(/^\s*/)?.[0] ?? ""}`);
677
+ commit();
678
+ return;
679
+ }
680
+ const close = AUTO_CLOSE_PAIRS[event.key];
681
+ if (close) {
682
+ const selection = element.ownerDocument.defaultView?.getSelection();
683
+ if (!selection || selection.rangeCount === 0 || !selection.getRangeAt(0).collapsed) return;
684
+ event.preventDefault();
685
+ insertAtCaret(element, `${event.key}${close}`);
686
+ const offset = caretTextOffset(element);
687
+ if (offset != null) setCaretAtTextOffset(element, offset - close.length);
688
+ commit();
689
+ }
690
+ }
691
+ return (_ctx, _cache) => {
692
+ return openBlock(), createElementBlock("div", {
693
+ class: normalizeClass(["astryx-code-editor", { "is-focused": focused.value }]),
694
+ "data-size": __props.size,
695
+ "data-language": __props.language
696
+ }, [createElementVNode("div", {
697
+ class: "astryx-code-editor__container",
698
+ style: normalizeStyle(containerStyle.value)
699
+ }, [__props.hasLineNumbers ? (openBlock(), createElementBlock("div", _hoisted_2$13, [(openBlock(true), createElementBlock(Fragment, null, renderList(lines.value, (_, index) => {
700
+ return openBlock(), createElementBlock("div", { key: index }, toDisplayString(index + 1), 1);
701
+ }), 128))])) : createCommentVNode("v-if", true), createElementVNode("div", _hoisted_3$11, [
702
+ showPlaceholder.value ? (openBlock(), createElementBlock("div", _hoisted_4$10, toDisplayString(__props.placeholder), 1)) : createCommentVNode("v-if", true),
703
+ createElementVNode("code", {
704
+ ref_key: "editor",
705
+ ref: editor,
706
+ class: "astryx-code-editor__input",
707
+ contenteditable: __props.isReadOnly ? "false" : "plaintext-only",
708
+ tabindex: "0",
709
+ role: "textbox",
710
+ "aria-multiline": "true",
711
+ "aria-label": __props.label,
712
+ "aria-readonly": __props.isReadOnly || void 0,
713
+ "aria-describedby": __props.isReadOnly ? void 0 : hintId,
714
+ spellcheck: "false",
715
+ onInput,
716
+ onKeydown,
717
+ onFocus: _cache[0] || (_cache[0] = ($event) => focused.value = true),
718
+ onBlur: _cache[1] || (_cache[1] = ($event) => {
719
+ focused.value = false;
720
+ tabMovesFocus.value = false;
721
+ }),
722
+ onCompositionstart: _cache[2] || (_cache[2] = ($event) => composing.value = true),
723
+ onCompositionend: _cache[3] || (_cache[3] = ($event) => {
724
+ composing.value = false;
725
+ onInput();
726
+ })
727
+ }, toDisplayString(__props.value), 41, _hoisted_5$9),
728
+ !__props.isReadOnly ? (openBlock(), createBlock(unref(VisuallyHidden), {
729
+ key: 1,
730
+ id: hintId
731
+ }, {
732
+ default: withCtx(() => [createTextVNode(toDisplayString(__props.escapeHint), 1)]),
733
+ _: 1
734
+ })) : createCommentVNode("v-if", true)
735
+ ])], 4)], 10, _hoisted_1$15);
182
736
  };
183
737
  }
184
738
  });
@@ -531,7 +1085,7 @@ const _hoisted_47 = {
531
1085
  key: 0,
532
1086
  class: "astryx-diff-viewer__annotation-content"
533
1087
  };
534
- const _sfc_main$16 = /*@__PURE__*/ defineComponent({
1088
+ const _sfc_main$19 = /*@__PURE__*/ defineComponent({
535
1089
  name: "AstryxDiffViewerTable",
536
1090
  __name: "DiffViewerTable",
537
1091
  props: {
@@ -1262,7 +1816,7 @@ const _hoisted_11$1 = {
1262
1816
  class: "astryx-diff-viewer__message"
1263
1817
  };
1264
1818
  const _hoisted_12 = ["aria-label"];
1265
- const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1819
+ const _sfc_main$4 = /*@__PURE__*/ defineComponent({
1266
1820
  name: "AstryxDiffViewer",
1267
1821
  inheritAttrs: false,
1268
1822
  __name: "DiffViewer",
@@ -1451,7 +2005,7 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1451
2005
  }), [createElementVNode("div", {
1452
2006
  ref: unref(scroll).content,
1453
2007
  class: "astryx-diff-viewer__content"
1454
- }, [createVNode(_sfc_main$16, {
2008
+ }, [createVNode(_sfc_main$19, {
1455
2009
  "view-mode": __props.viewMode,
1456
2010
  "unified-rows": unifiedRows.value,
1457
2011
  "split-rows": splitRows.value,
@@ -1773,7 +2327,7 @@ function parseUnifiedDiff(patch) {
1773
2327
  //#endregion
1774
2328
  //#region src/InfoTip/InfoTip.vue
1775
2329
  const _hoisted_1$12 = ["aria-label"];
1776
- const _sfc_main$2 = /*@__PURE__*/ defineComponent({
2330
+ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1777
2331
  name: "AstryxInfoTip",
1778
2332
  __name: "InfoTip",
1779
2333
  props: {
@@ -1900,7 +2454,7 @@ const _hoisted_7$3 = {
1900
2454
  key: 0,
1901
2455
  class: "astryx-list-input__control-cell astryx-list-input__control-cell--reorder"
1902
2456
  };
1903
- const _sfc_main$15 = /*@__PURE__*/ defineComponent({
2457
+ const _sfc_main$18 = /*@__PURE__*/ defineComponent({
1904
2458
  name: "AstryxListInputRow",
1905
2459
  __name: "ListInputRow",
1906
2460
  props: {
@@ -2346,7 +2900,7 @@ const _hoisted_5$5 = {
2346
2900
  class: "astryx-list-input__action-content"
2347
2901
  };
2348
2902
  const focusableFieldSelector = "[data-list-input-cell] input, [data-list-input-cell] select, [data-list-input-cell] textarea, [data-list-input-cell] button, [data-list-input-cell] [tabindex]:not([tabindex=\"-1\"])";
2349
- const _sfc_main$3 = /*@__PURE__*/ defineComponent({
2903
+ const _sfc_main$6 = /*@__PURE__*/ defineComponent({
2350
2904
  name: "AstryxListInput",
2351
2905
  inheritAttrs: false,
2352
2906
  __name: "ListInput",
@@ -2624,7 +3178,7 @@ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
2624
3178
  description: unref(t)("@astryx.listInput.emptyDescription", { itemName: __props.itemName }),
2625
3179
  "is-compact": ""
2626
3180
  }, null, 8, ["title", "description"])])) : createCommentVNode("v-if", true), (openBlock(true), createElementBlock(Fragment, null, renderList(unref(reorder).displayValue.value, (item, index) => {
2627
- return openBlock(), createBlock(_sfc_main$15, {
3181
+ return openBlock(), createBlock(_sfc_main$18, {
2628
3182
  key: itemKeyOf(item),
2629
3183
  item,
2630
3184
  index,
@@ -2739,7 +3293,7 @@ const _hoisted_11 = {
2739
3293
  key: 0,
2740
3294
  class: "astryx-log-stream__detail"
2741
3295
  };
2742
- const _sfc_main$14 = /*@__PURE__*/ defineComponent({
3296
+ const _sfc_main$17 = /*@__PURE__*/ defineComponent({
2743
3297
  name: "AstryxLogStreamRow",
2744
3298
  __name: "LogStreamRow",
2745
3299
  props: {
@@ -2843,7 +3397,7 @@ function useLogFollow(options) {
2843
3397
  //#region src/LogStream/LogStream.vue
2844
3398
  const _hoisted_1$8 = ["data-variant"];
2845
3399
  const _hoisted_2$7 = ["aria-label", "aria-live"];
2846
- const _sfc_main$4 = /*@__PURE__*/ defineComponent({
3400
+ const _sfc_main$7 = /*@__PURE__*/ defineComponent({
2847
3401
  name: "AstryxLogStream",
2848
3402
  inheritAttrs: false,
2849
3403
  __name: "LogStream",
@@ -2909,7 +3463,7 @@ const _sfc_main$4 = /*@__PURE__*/ defineComponent({
2909
3463
  style: normalizeStyle(scrollerStyle.value),
2910
3464
  onScrollPassive: _cache[0] || (_cache[0] = (...args) => unref(handleScroll) && unref(handleScroll)(...args))
2911
3465
  }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.entries, (entry) => {
2912
- return renderSlot(_ctx.$slots, "entry", { entry }, () => [createVNode(_sfc_main$14, {
3466
+ return renderSlot(_ctx.$slots, "entry", { entry }, () => [createVNode(_sfc_main$17, {
2913
3467
  entry,
2914
3468
  "has-timestamps": __props.hasTimestamps,
2915
3469
  "has-source": hasSource.value,
@@ -3075,7 +3629,7 @@ const _hoisted_10$1 = {
3075
3629
  class: "astryx-mobile-tokenizer__footer"
3076
3630
  };
3077
3631
  const CREATABLE_ID_PREFIX = "__xds_create__";
3078
- const _sfc_main$5 = /*@__PURE__*/ defineComponent({
3632
+ const _sfc_main$8 = /*@__PURE__*/ defineComponent({
3079
3633
  name: "AstryxMobileTokenizer",
3080
3634
  inheritAttrs: false,
3081
3635
  __name: "MobileTokenizer",
@@ -3398,7 +3952,7 @@ const _hoisted_10 = {
3398
3952
  key: 1,
3399
3953
  class: "astryx-stat__media"
3400
3954
  };
3401
- const _sfc_main$7 = /*@__PURE__*/ defineComponent({
3955
+ const _sfc_main$10 = /*@__PURE__*/ defineComponent({
3402
3956
  name: "AstryxStat",
3403
3957
  inheritAttrs: false,
3404
3958
  __name: "Stat",
@@ -4255,7 +4809,7 @@ const _hoisted_6$1 = {
4255
4809
  key: 1,
4256
4810
  class: "astryx-svg-icon__layer astryx-svg-icon__layer--secondary-stroke"
4257
4811
  };
4258
- const _sfc_main$6 = /*@__PURE__*/ defineComponent({
4812
+ const _sfc_main$9 = /*@__PURE__*/ defineComponent({
4259
4813
  name: "AstryxSvgIcon",
4260
4814
  inheritAttrs: false,
4261
4815
  __name: "SVGIcon",
@@ -4491,7 +5045,7 @@ const xifExamples = [
4491
5045
  const TOUR_CONTEXT_KEY = Symbol("AstryxTour");
4492
5046
  //#endregion
4493
5047
  //#region src/Tour/Tour.vue
4494
- const _sfc_main$8 = /*@__PURE__*/ defineComponent({
5048
+ const _sfc_main$11 = /*@__PURE__*/ defineComponent({
4495
5049
  name: "AstryxTour",
4496
5050
  __name: "Tour",
4497
5051
  props: {
@@ -4555,7 +5109,7 @@ const _sfc_main$8 = /*@__PURE__*/ defineComponent({
4555
5109
  //#endregion
4556
5110
  //#region src/Tour/TourHighlight.vue
4557
5111
  const _hoisted_1$4 = ["data-testid"];
4558
- const _sfc_main$13 = /*@__PURE__*/ defineComponent({
5112
+ const _sfc_main$16 = /*@__PURE__*/ defineComponent({
4559
5113
  __name: "TourHighlight",
4560
5114
  props: {
4561
5115
  rect: {},
@@ -4599,7 +5153,7 @@ const _sfc_main$13 = /*@__PURE__*/ defineComponent({
4599
5153
  //#region src/Tour/TourStep.vue
4600
5154
  const _hoisted_1$3 = ["data-testid"];
4601
5155
  const _hoisted_2$3 = { key: 1 };
4602
- const _sfc_main$9 = /*@__PURE__*/ defineComponent({
5156
+ const _sfc_main$12 = /*@__PURE__*/ defineComponent({
4603
5157
  name: "AstryxTourStep",
4604
5158
  __name: "TourStep",
4605
5159
  props: {
@@ -4666,7 +5220,7 @@ const _sfc_main$9 = /*@__PURE__*/ defineComponent({
4666
5220
  if (!open) tour?.onDismiss("close");
4667
5221
  }
4668
5222
  return (_ctx, _cache) => {
4669
- return unref(tour) && isActiveStep.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(_sfc_main$13, {
5223
+ return unref(tour) && isActiveStep.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(_sfc_main$16, {
4670
5224
  rect: rect.value,
4671
5225
  "has-backdrop": unref(tour).hasBackdrop.value,
4672
5226
  onBackdropClick: _cache[0] || (_cache[0] = ($event) => unref(tour).onDismiss("backdrop"))
@@ -4833,7 +5387,7 @@ const _hoisted_5 = {
4833
5387
  class: "astryx-transfer-list-panel__group-heading"
4834
5388
  };
4835
5389
  const _hoisted_6 = ["data-transfer-list-empty"];
4836
- const _sfc_main$12 = /*@__PURE__*/ defineComponent({
5390
+ const _sfc_main$15 = /*@__PURE__*/ defineComponent({
4837
5391
  name: "AstryxTransferListPanel",
4838
5392
  __name: "TransferListPanel",
4839
5393
  props: {
@@ -5305,7 +5859,7 @@ const _hoisted_3$1 = {
5305
5859
  class: "astryx-transfer-list__controls"
5306
5860
  };
5307
5861
  const _hoisted_4 = { class: "astryx-transfer-list-collection" };
5308
- const _sfc_main$10 = /*@__PURE__*/ defineComponent({
5862
+ const _sfc_main$13 = /*@__PURE__*/ defineComponent({
5309
5863
  name: "AstryxTransferList",
5310
5864
  inheritAttrs: false,
5311
5865
  __name: "TransferList",
@@ -5579,7 +6133,7 @@ const _sfc_main$10 = /*@__PURE__*/ defineComponent({
5579
6133
  default: withCtx(() => [createTextVNode(toDisplayString(unref(t)("@astryx.transferList.reorderInstructions")), 1)]),
5580
6134
  _: 1
5581
6135
  }),
5582
- createElementVNode("div", _hoisted_4, [createVNode(_sfc_main$12, {
6136
+ createElementVNode("div", _hoisted_4, [createVNode(_sfc_main$15, {
5583
6137
  side: "selected",
5584
6138
  "heading-id": selectedHeadingId,
5585
6139
  label: selectedLabel.value,
@@ -5615,7 +6169,7 @@ const _sfc_main$10 = /*@__PURE__*/ defineComponent({
5615
6169
  "dragging-value",
5616
6170
  "grabbed-value",
5617
6171
  "drop-target"
5618
- ]), createVNode(_sfc_main$12, {
6172
+ ]), createVNode(_sfc_main$15, {
5619
6173
  side: "available",
5620
6174
  "heading-id": availableHeadingId,
5621
6175
  label: availableLabel.value,
@@ -5670,7 +6224,7 @@ const _hoisted_3 = {
5670
6224
  class: "astryx-transfer-list-selector__footer"
5671
6225
  };
5672
6226
  const DEFAULT_SELECTOR_WIDTH = "min(41rem, calc(100vw - 32px))";
5673
- const _sfc_main$11 = /*@__PURE__*/ defineComponent({
6227
+ const _sfc_main$14 = /*@__PURE__*/ defineComponent({
5674
6228
  name: "AstryxTransferListSelector",
5675
6229
  __name: "TransferListSelector",
5676
6230
  props: {
@@ -5807,7 +6361,7 @@ const _sfc_main$11 = /*@__PURE__*/ defineComponent({
5807
6361
  }, [createElementVNode("fieldset", {
5808
6362
  class: "astryx-transfer-list-selector__fieldset",
5809
6363
  disabled: __props.isDisabled
5810
- }, [createVNode(_sfc_main$10, {
6364
+ }, [createVNode(_sfc_main$13, {
5811
6365
  label: __props.label,
5812
6366
  "is-label-hidden": "",
5813
6367
  options: __props.options,
@@ -5886,4 +6440,4 @@ const _sfc_main$11 = /*@__PURE__*/ defineComponent({
5886
6440
  }
5887
6441
  });
5888
6442
  //#endregion
5889
- export { _sfc_main as CircularProgress, _sfc_main$1 as DiffViewer, _sfc_main$2 as InfoTip, _sfc_main$3 as ListInput, _sfc_main$4 as LogStream, _sfc_main$5 as MobileTokenizer, _sfc_main$6 as SVGIcon, _sfc_main$7 as Stat, _sfc_main$8 as Tour, _sfc_main$9 as TourStep, _sfc_main$10 as TransferList, _sfc_main$11 as TransferListSelector, addCurvature, adjustTension, applyPersonality, bellIcon, calendarIcon, checkIcon, createDiffFile, eyeIcon, folderIcon, heartIcon, homeIcon, lockIcon, mailIcon, menuIcon, parseUnifiedDiff, roundCorners, searchIcon, settingsIcon, shieldIcon, starIcon, starterIcons, useTour, xIcon, xifBell, xifBellOverride, xifCheck, xifExamples, xifFile, xifHome, xifShield, xifStar };
6443
+ export { _sfc_main as ChatEmojiPicker, _sfc_main$1 as ChatReactionBar, _sfc_main$2 as CircularProgress, _sfc_main$3 as CodeEditor, DEFAULT_CHAT_EMOJIS, _sfc_main$4 as DiffViewer, _sfc_main$5 as InfoTip, _sfc_main$6 as ListInput, _sfc_main$7 as LogStream, _sfc_main$8 as MobileTokenizer, _sfc_main$9 as SVGIcon, _sfc_main$10 as Stat, _sfc_main$11 as Tour, _sfc_main$12 as TourStep, _sfc_main$13 as TransferList, _sfc_main$14 as TransferListSelector, addCurvature, adjustTension, applyPersonality, bellIcon, calendarIcon, checkIcon, createDiffFile, eyeIcon, folderIcon, heartIcon, homeIcon, lockIcon, mailIcon, menuIcon, parseUnifiedDiff, roundCorners, searchIcon, settingsIcon, shieldIcon, starIcon, starterIcons, useTour, xIcon, xifBell, xifBellOverride, xifCheck, xifExamples, xifFile, xifHome, xifShield, xifStar };