@fadenstack/ui-web 0.2.0-rc.2 → 0.3.0-rc.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/dom.d.ts CHANGED
@@ -13,11 +13,31 @@ export declare function dismissOnOutside(anchor: HTMLElement, close: () => void)
13
13
  export declare function fitToContent(box: HTMLTextAreaElement): void;
14
14
  /** Focuses a text box with the caret after its text (after "Edit", or the host attached something). */
15
15
  export declare function focusAtEnd(box: HTMLTextAreaElement): void;
16
+ /** What a key in the composer does (`composerKey`). */
17
+ export type ComposerAction = "send" | "stop" | "pick" | "next" | "previous" | "close";
16
18
  /**
17
19
  * What a key in the composer does: Enter sends (Shift+Enter is a new line, and an input method that is still
18
- * composing keeps its Enter), Escape stops the running answer.
20
+ * composing keeps its Enter), Escape stops the running answer. While the skill picker is open (`pickerOpen`), the
21
+ * arrows move in it, Enter and Tab pick, and Escape closes it.
19
22
  */
20
- export declare function composerKey(e: Pick<KeyboardEvent, "key" | "shiftKey" | "isComposing">, busy: boolean): "send" | "stop" | undefined;
23
+ export declare function composerKey(e: Pick<KeyboardEvent, "key" | "shiftKey" | "isComposing">, busy: boolean, pickerOpen?: boolean): ComposerAction | undefined;
24
+ /** The pictures among pasted or dropped files. */
25
+ export declare function picturesIn(transfer: DataTransfer | null | undefined): File[];
26
+ /** A picture made ready for a message: a `data:` URL, and its size in pixels. */
27
+ export interface ReadPicture {
28
+ dataUrl: string;
29
+ width: number;
30
+ height: number;
31
+ }
32
+ /**
33
+ * A picture as a message takes it: at most `maxSide` pixels on its longer side (models scale larger ones down
34
+ * anyway, and the transcript keeps it), as JPEG on white (transparent parts of a screenshot stay readable).
35
+ * Throws when the browser cannot read it as a picture.
36
+ */
37
+ export declare function readPicture(picture: Blob, options?: {
38
+ maxSide?: number;
39
+ quality?: number;
40
+ }): Promise<ReadPicture>;
21
41
  /** How long a copy button shows its tick. */
22
42
  export declare const COPIED_MS = 1500;
23
43
  //# sourceMappingURL=dom.d.ts.map
package/dist/dom.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,SAAK,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,IAAI,IAAI,CAAA;CAAE,CAoBzG;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAanF;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAI3D;AAED,uGAAuG;AACvG,wBAAgB,UAAU,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAIzD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,UAAU,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAIlI;AAED,6CAA6C;AAC7C,eAAO,MAAM,SAAS,OAAO,CAAC"}
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAEnG;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,SAAK,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,IAAI,IAAI,CAAA;CAAE,CAoBzG;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAanF;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAI3D;AAED,uGAAuG;AACvG,wBAAgB,UAAU,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAIzD;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;AAEtF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,UAAU,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,UAAQ,GAAG,cAAc,GAAG,SAAS,CAUrJ;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,EAAE,CAE5E;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAmB3H;AAED,6CAA6C;AAC7C,eAAO,MAAM,SAAS,OAAO,CAAC"}
package/dist/dom.js CHANGED
@@ -57,15 +57,57 @@ export function focusAtEnd(box) {
57
57
  }
58
58
  /**
59
59
  * What a key in the composer does: Enter sends (Shift+Enter is a new line, and an input method that is still
60
- * composing keeps its Enter), Escape stops the running answer.
60
+ * composing keeps its Enter), Escape stops the running answer. While the skill picker is open (`pickerOpen`), the
61
+ * arrows move in it, Enter and Tab pick, and Escape closes it.
61
62
  */
62
- export function composerKey(e, busy) {
63
+ export function composerKey(e, busy, pickerOpen = false) {
64
+ if (pickerOpen && !e.isComposing) {
65
+ if (e.key === "ArrowDown")
66
+ return "next";
67
+ if (e.key === "ArrowUp")
68
+ return "previous";
69
+ if ((e.key === "Enter" && !e.shiftKey) || e.key === "Tab")
70
+ return "pick";
71
+ if (e.key === "Escape")
72
+ return "close";
73
+ }
63
74
  if (e.key === "Enter" && !e.shiftKey && !e.isComposing)
64
75
  return "send";
65
76
  if (e.key === "Escape" && busy)
66
77
  return "stop";
67
78
  return undefined;
68
79
  }
80
+ /** The pictures among pasted or dropped files. */
81
+ export function picturesIn(transfer) {
82
+ return Array.from(transfer?.files ?? []).filter((f) => f.type.startsWith("image/"));
83
+ }
84
+ /**
85
+ * A picture as a message takes it: at most `maxSide` pixels on its longer side (models scale larger ones down
86
+ * anyway, and the transcript keeps it), as JPEG on white (transparent parts of a screenshot stay readable).
87
+ * Throws when the browser cannot read it as a picture.
88
+ */
89
+ export async function readPicture(picture, options = {}) {
90
+ const maxSide = options.maxSide ?? 1600;
91
+ const bitmap = await createImageBitmap(picture);
92
+ try {
93
+ const scale = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height, 1));
94
+ const width = Math.max(1, Math.round(bitmap.width * scale));
95
+ const height = Math.max(1, Math.round(bitmap.height * scale));
96
+ const canvas = document.createElement("canvas");
97
+ canvas.width = width;
98
+ canvas.height = height;
99
+ const context = canvas.getContext("2d");
100
+ if (!context)
101
+ throw new Error("No canvas to draw the picture on.");
102
+ context.fillStyle = "#fff";
103
+ context.fillRect(0, 0, width, height);
104
+ context.drawImage(bitmap, 0, 0, width, height);
105
+ return { dataUrl: canvas.toDataURL("image/jpeg", options.quality ?? 0.85), width, height };
106
+ }
107
+ finally {
108
+ bitmap.close();
109
+ }
110
+ }
69
111
  /** How long a copy button shows its tick. */
70
112
  export const COPIED_MS = 1500;
71
113
  //# sourceMappingURL=dom.js.map
package/dist/dom.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dom.js","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"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"}
package/dist/icons.d.ts CHANGED
@@ -6,6 +6,9 @@
6
6
  */
7
7
  export declare const iconPaths: {
8
8
  readonly send: "M3.4 20.4 21 12 3.4 3.6l-.01 6.53L15 12 3.39 13.87z";
9
+ readonly image: "M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5z";
10
+ readonly camera: "M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4zM9 2 7.17 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-3.17L15 2z";
11
+ readonly translate: "m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2zm-2.62 7 1.62-4.33L19.12 17z";
9
12
  readonly stop: "M7 7h10v10H7z";
10
13
  readonly plus: "M11 5h2v6h6v2h-6v6h-2v-6H5v-2h6z";
11
14
  readonly history: "M13 3a9 9 0 0 0-9 9H1l3.9 3.9.1.1L9 12H6a7 7 0 1 1 2.05 4.95l-1.42 1.42A9 9 0 1 0 13 3zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z";
@@ -17,6 +20,8 @@ export declare const iconPaths: {
17
20
  readonly warning: "M1 21h22L12 2zm12-3h-2v-2h2zm0-4h-2v-4h2z";
18
21
  readonly shield: "M12 1 3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11z";
19
22
  readonly info: "M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z";
23
+ /** After a link that opens in a new tab. */
24
+ readonly external: "M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z";
20
25
  readonly edit: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75z";
21
26
  readonly retry: "M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z";
22
27
  readonly trash: "M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6zM19 4h-3.5l-1-1h-5l-1 1H5v2h14z";
@@ -1 +1 @@
1
- {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/icons.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;CAwBZ,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,SAAS,CAAC"}
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../src/icons.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;IAgBpB,4CAA4C;;;;;;;;;;;;;CAapC,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,SAAS,CAAC"}
package/dist/icons.js CHANGED
@@ -6,6 +6,9 @@
6
6
  */
7
7
  export const iconPaths = {
8
8
  send: "M3.4 20.4 21 12 3.4 3.6l-.01 6.53L15 12 3.39 13.87z",
9
+ image: "M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5z",
10
+ camera: "M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4zM9 2 7.17 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-3.17L15 2z",
11
+ translate: "m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2zm-2.62 7 1.62-4.33L19.12 17z",
9
12
  stop: "M7 7h10v10H7z",
10
13
  plus: "M11 5h2v6h6v2h-6v6h-2v-6H5v-2h6z",
11
14
  history: "M13 3a9 9 0 0 0-9 9H1l3.9 3.9.1.1L9 12H6a7 7 0 1 1 2.05 4.95l-1.42 1.42A9 9 0 1 0 13 3zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z",
@@ -17,6 +20,8 @@ export const iconPaths = {
17
20
  warning: "M1 21h22L12 2zm12-3h-2v-2h2zm0-4h-2v-4h2z",
18
21
  shield: "M12 1 3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11z",
19
22
  info: "M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z",
23
+ /** After a link that opens in a new tab. */
24
+ external: "M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z",
20
25
  edit: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75z",
21
26
  retry: "M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z",
22
27
  trash: "M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6zM19 4h-3.5l-1-1h-5l-1 1H5v2h14z",
package/dist/icons.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"icons.js","sourceRoot":"","sources":["../src/icons.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,qDAAqD;IAC3D,IAAI,EAAE,eAAe;IACrB,IAAI,EAAE,kCAAkC;IACxC,OAAO,EAAE,8HAA8H;IACvI,OAAO,EAAE,0CAA0C;IACnD,IAAI,EAAE,sHAAsH;IAC5H,KAAK,EAAE,6CAA6C;IACpD,KAAK,EAAE,2FAA2F;IAClG,IAAI,EAAE,0KAA0K;IAChL,OAAO,EAAE,2CAA2C;IACpD,MAAM,EAAE,mHAAmH;IAC3H,IAAI,EAAE,sJAAsJ;IAC5J,IAAI,EAAE,uHAAuH;IAC7H,KAAK,EAAE,6GAA6G;IACpH,KAAK,EAAE,yEAAyE;IAChF,OAAO,EAAE,2DAA2D;IACpE,IAAI,EAAE,4EAA4E;IAClF,KAAK,EAAE,mRAAmR;IAC1R,KAAK,EAAE,0CAA0C;IACjD,IAAI,EAAE,iGAAiG;IACvG,MAAM,EAAE,yIAAyI;IACjJ,IAAI,EAAE,2FAA2F;IACjG,QAAQ,EAAE,+nBAA+nB;CACjoB,CAAC"}
1
+ {"version":3,"file":"icons.js","sourceRoot":"","sources":["../src/icons.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,qDAAqD;IAC3D,KAAK,EAAE,8GAA8G;IACrH,MAAM,EAAE,4IAA4I;IACpJ,SAAS,EAAE,4QAA4Q;IACvR,IAAI,EAAE,eAAe;IACrB,IAAI,EAAE,kCAAkC;IACxC,OAAO,EAAE,8HAA8H;IACvI,OAAO,EAAE,0CAA0C;IACnD,IAAI,EAAE,sHAAsH;IAC5H,KAAK,EAAE,6CAA6C;IACpD,KAAK,EAAE,2FAA2F;IAClG,IAAI,EAAE,0KAA0K;IAChL,OAAO,EAAE,2CAA2C;IACpD,MAAM,EAAE,mHAAmH;IAC3H,IAAI,EAAE,sJAAsJ;IAC5J,4CAA4C;IAC5C,QAAQ,EAAE,6CAA6C;IACvD,IAAI,EAAE,uHAAuH;IAC7H,KAAK,EAAE,6GAA6G;IACpH,KAAK,EAAE,yEAAyE;IAChF,OAAO,EAAE,2DAA2D;IACpE,IAAI,EAAE,4EAA4E;IAClF,KAAK,EAAE,mRAAmR;IAC1R,KAAK,EAAE,0CAA0C;IACjD,IAAI,EAAE,iGAAiG;IACvG,MAAM,EAAE,yIAAyI;IACjJ,IAAI,EAAE,2FAA2F;IACjG,QAAQ,EAAE,+nBAA+nB;CACjoB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { iconPaths, type IconName } from "./icons.js";
2
2
  export { agentAvatar, type AgentAvatarOptions, type SvgNode } from "./avatar.js";
3
- export { COPIED_MS, composerKey, dismissOnOutside, fitToContent, focusAtEnd, stickToBottom } from "./dom.js";
3
+ export { COPIED_MS, composerKey, dismissOnOutside, fitToContent, focusAtEnd, picturesIn, readPicture, stickToBottom, type ComposerAction, type ReadPicture, } from "./dom.js";
4
4
  export { approvalIcon, diffMarker, headingTag, noticeIcon, noticeRole, toolBadges, toolClassName, toolStatusIcon, turnMeta, type Badge, } from "./presentation.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7G,OAAO,EACL,YAAY,EACZ,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,QAAQ,EACR,KAAK,KAAK,GACX,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,QAAQ,EACR,KAAK,KAAK,GACX,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { iconPaths } from "./icons.js";
2
2
  export { agentAvatar } from "./avatar.js";
3
- export { COPIED_MS, composerKey, dismissOnOutside, fitToContent, focusAtEnd, stickToBottom } from "./dom.js";
3
+ export { COPIED_MS, composerKey, dismissOnOutside, fitToContent, focusAtEnd, picturesIn, readPicture, stickToBottom, } from "./dom.js";
4
4
  export { approvalIcon, diffMarker, headingTag, noticeIcon, noticeRole, toolBadges, toolClassName, toolStatusIcon, turnMeta, } from "./presentation.js";
5
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAyC,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7G,OAAO,EACL,YAAY,EACZ,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,QAAQ,GAET,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAiB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAyC,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,UAAU,EACV,WAAW,EACX,aAAa,GAGd,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,YAAY,EACZ,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,cAAc,EACd,QAAQ,GAET,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fadenstack/ui-web",
3
- "version": "0.2.0-rc.2",
3
+ "version": "0.3.0-rc.1",
4
4
  "description": "The web look of the Fadenstack chat for any framework: the stylesheet, icons, the agent's avatar and the panel's browser behaviours",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Emagin8 UG (haftungsbeschränkt)",
@@ -41,6 +41,6 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@fadenstack/ui": "0.2.0-rc.2"
44
+ "@fadenstack/ui": "0.3.0-rc.1"
45
45
  }
46
46
  }
package/src/dom.ts CHANGED
@@ -56,15 +56,63 @@ export function focusAtEnd(box: HTMLTextAreaElement): void {
56
56
  box.setSelectionRange(end, end);
57
57
  }
58
58
 
59
+ /** What a key in the composer does (`composerKey`). */
60
+ export type ComposerAction = "send" | "stop" | "pick" | "next" | "previous" | "close";
61
+
59
62
  /**
60
63
  * What a key in the composer does: Enter sends (Shift+Enter is a new line, and an input method that is still
61
- * composing keeps its Enter), Escape stops the running answer.
64
+ * composing keeps its Enter), Escape stops the running answer. While the skill picker is open (`pickerOpen`), the
65
+ * arrows move in it, Enter and Tab pick, and Escape closes it.
62
66
  */
63
- export function composerKey(e: Pick<KeyboardEvent, "key" | "shiftKey" | "isComposing">, busy: boolean): "send" | "stop" | undefined {
67
+ export function composerKey(e: Pick<KeyboardEvent, "key" | "shiftKey" | "isComposing">, busy: boolean, pickerOpen = false): ComposerAction | undefined {
68
+ if (pickerOpen && !e.isComposing) {
69
+ if (e.key === "ArrowDown") return "next";
70
+ if (e.key === "ArrowUp") return "previous";
71
+ if ((e.key === "Enter" && !e.shiftKey) || e.key === "Tab") return "pick";
72
+ if (e.key === "Escape") return "close";
73
+ }
64
74
  if (e.key === "Enter" && !e.shiftKey && !e.isComposing) return "send";
65
75
  if (e.key === "Escape" && busy) return "stop";
66
76
  return undefined;
67
77
  }
68
78
 
79
+ /** The pictures among pasted or dropped files. */
80
+ export function picturesIn(transfer: DataTransfer | null | undefined): File[] {
81
+ return Array.from(transfer?.files ?? []).filter((f) => f.type.startsWith("image/"));
82
+ }
83
+
84
+ /** A picture made ready for a message: a `data:` URL, and its size in pixels. */
85
+ export interface ReadPicture {
86
+ dataUrl: string;
87
+ width: number;
88
+ height: number;
89
+ }
90
+
91
+ /**
92
+ * A picture as a message takes it: at most `maxSide` pixels on its longer side (models scale larger ones down
93
+ * anyway, and the transcript keeps it), as JPEG on white (transparent parts of a screenshot stay readable).
94
+ * Throws when the browser cannot read it as a picture.
95
+ */
96
+ export async function readPicture(picture: Blob, options: { maxSide?: number; quality?: number } = {}): Promise<ReadPicture> {
97
+ const maxSide = options.maxSide ?? 1600;
98
+ const bitmap = await createImageBitmap(picture);
99
+ try {
100
+ const scale = Math.min(1, maxSide / Math.max(bitmap.width, bitmap.height, 1));
101
+ const width = Math.max(1, Math.round(bitmap.width * scale));
102
+ const height = Math.max(1, Math.round(bitmap.height * scale));
103
+ const canvas = document.createElement("canvas");
104
+ canvas.width = width;
105
+ canvas.height = height;
106
+ const context = canvas.getContext("2d");
107
+ if (!context) throw new Error("No canvas to draw the picture on.");
108
+ context.fillStyle = "#fff";
109
+ context.fillRect(0, 0, width, height);
110
+ context.drawImage(bitmap, 0, 0, width, height);
111
+ return { dataUrl: canvas.toDataURL("image/jpeg", options.quality ?? 0.85), width, height };
112
+ } finally {
113
+ bitmap.close();
114
+ }
115
+ }
116
+
69
117
  /** How long a copy button shows its tick. */
70
118
  export const COPIED_MS = 1500;
package/src/icons.ts CHANGED
@@ -6,6 +6,9 @@
6
6
  */
7
7
  export const iconPaths = {
8
8
  send: "M3.4 20.4 21 12 3.4 3.6l-.01 6.53L15 12 3.39 13.87z",
9
+ image: "M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5z",
10
+ camera: "M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4zM9 2 7.17 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-3.17L15 2z",
11
+ translate: "m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2zm-2.62 7 1.62-4.33L19.12 17z",
9
12
  stop: "M7 7h10v10H7z",
10
13
  plus: "M11 5h2v6h6v2h-6v6h-2v-6H5v-2h6z",
11
14
  history: "M13 3a9 9 0 0 0-9 9H1l3.9 3.9.1.1L9 12H6a7 7 0 1 1 2.05 4.95l-1.42 1.42A9 9 0 1 0 13 3zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z",
@@ -17,6 +20,8 @@ export const iconPaths = {
17
20
  warning: "M1 21h22L12 2zm12-3h-2v-2h2zm0-4h-2v-4h2z",
18
21
  shield: "M12 1 3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11z",
19
22
  info: "M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z",
23
+ /** After a link that opens in a new tab. */
24
+ external: "M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z",
20
25
  edit: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75z",
21
26
  retry: "M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z",
22
27
  trash: "M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6zM19 4h-3.5l-1-1h-5l-1 1H5v2h14z",
package/src/index.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  export { iconPaths, type IconName } from "./icons.js";
2
2
  export { agentAvatar, type AgentAvatarOptions, type SvgNode } from "./avatar.js";
3
- export { COPIED_MS, composerKey, dismissOnOutside, fitToContent, focusAtEnd, stickToBottom } from "./dom.js";
3
+ export {
4
+ COPIED_MS,
5
+ composerKey,
6
+ dismissOnOutside,
7
+ fitToContent,
8
+ focusAtEnd,
9
+ picturesIn,
10
+ readPicture,
11
+ stickToBottom,
12
+ type ComposerAction,
13
+ type ReadPicture,
14
+ } from "./dom.js";
4
15
  export {
5
16
  approvalIcon,
6
17
  diffMarker,
package/styles.css CHANGED
@@ -22,7 +22,9 @@
22
22
  --fdn-surface-hover: rgba(31, 31, 31, 0.06);
23
23
  --fdn-text: #1f1f1f;
24
24
  --fdn-text-muted: #474747;
25
- --fdn-text-faint: #757775;
25
+ /* Quiet text on the panel's background: 5.2:1 on white, 4.7:1 on the surface (a chip's subtitle). Text in a menu,
26
+ an approval card, a notice or a code block is muted, which keeps 4.5:1 on their hover and soft colours too. */
27
+ --fdn-text-faint: #6b6d6b;
26
28
  --fdn-border: #c7c7c7;
27
29
  --fdn-hairline: #e3e3e3;
28
30
  --fdn-accent: #0b57d0;
@@ -221,6 +223,13 @@
221
223
  color: var(--fdn-text);
222
224
  }
223
225
 
226
+ /* A button whose menu is open looks pressed; the gear also while About, which its menu opened, is open. */
227
+ .fdn-icon-button[aria-expanded="true"],
228
+ .fdn-icon-button.fdn-open {
229
+ background: var(--fdn-surface-hover);
230
+ color: var(--fdn-text);
231
+ }
232
+
224
233
  .fdn-icon-button:disabled {
225
234
  opacity: 0.38;
226
235
  cursor: default;
@@ -299,7 +308,7 @@
299
308
  .fdn-menu-description,
300
309
  .fdn-menu-note {
301
310
  display: block;
302
- color: var(--fdn-text-faint);
311
+ color: var(--fdn-text-muted);
303
312
  font-size: 12px;
304
313
  }
305
314
 
@@ -307,14 +316,19 @@
307
316
  padding: 6px 10px;
308
317
  }
309
318
 
310
- /* The settings: a heading over each group (the languages), the host's own items below a line. */
319
+ /* The settings: a heading over each group (the languages), the host's own items below a line. A heading on a menu
320
+ (and About's title) is muted, as all text on a surface. */
321
+ .fdn-menu-heading,
322
+ .fdn-about-title {
323
+ color: var(--fdn-text-muted);
324
+ font-size: 12px;
325
+ font-weight: 500;
326
+ }
327
+
311
328
  .fdn-menu-heading {
312
329
  display: block;
313
330
  padding-block: 6px 2px;
314
331
  padding-inline: 10px;
315
- color: var(--fdn-text-faint);
316
- font-size: 12px;
317
- font-weight: 500;
318
332
  }
319
333
 
320
334
  .fdn-menu-extra {
@@ -328,6 +342,16 @@
328
342
  overflow-y: auto;
329
343
  }
330
344
 
345
+ /* The settings name languages in their own scripts; the fonts for Chinese have no weight 500 and fall back to 400, so
346
+ every item here is 400 (as the host's own items are), and the picked one 600, which those fonts show as bold. */
347
+ .fdn-settings .fdn-menu-label {
348
+ font-weight: 400;
349
+ }
350
+
351
+ .fdn-settings [aria-checked="true"] .fdn-menu-label {
352
+ font-weight: 600;
353
+ }
354
+
331
355
  .fdn-history {
332
356
  max-height: min(420px, 70vh);
333
357
  overflow-y: auto;
@@ -366,7 +390,7 @@
366
390
  }
367
391
 
368
392
  .fdn-history-when {
369
- color: var(--fdn-text-faint);
393
+ color: var(--fdn-text-muted);
370
394
  font-size: 12px;
371
395
  white-space: nowrap;
372
396
  }
@@ -477,8 +501,8 @@
477
501
  align-items: center;
478
502
  gap: 2px;
479
503
  margin-inline-start: -4px;
504
+ color: var(--fdn-text-muted); /* quiet icons; not by opacity, which took the meta's text under 4.5:1 */
480
505
  font-size: 12px;
481
- opacity: 0.85;
482
506
  }
483
507
 
484
508
  .fdn-turn-actions .fdn-muted {
@@ -624,7 +648,7 @@
624
648
  min-height: 30px;
625
649
  padding-block: 2px 0;
626
650
  padding-inline: 12px 4px;
627
- color: var(--fdn-text-faint);
651
+ color: var(--fdn-text-muted);
628
652
  font-size: 12px;
629
653
  }
630
654
 
@@ -786,7 +810,7 @@
786
810
  }
787
811
 
788
812
  .fdn-label {
789
- color: var(--fdn-text-faint);
813
+ color: var(--fdn-text-muted);
790
814
  font-size: 11.5px;
791
815
  font-weight: 500;
792
816
  }
@@ -845,6 +869,12 @@
845
869
  color: var(--fdn-text-muted);
846
870
  }
847
871
 
872
+ .fdn-approval-target {
873
+ margin: 8px 0 0;
874
+ font-weight: 600;
875
+ overflow-wrap: anywhere;
876
+ }
877
+
848
878
  .fdn-approval-args {
849
879
  margin-top: 8px;
850
880
  padding: 8px 10px;
@@ -852,16 +882,26 @@
852
882
  background: var(--fdn-bg);
853
883
  }
854
884
 
855
- .fdn-approval-actions {
885
+ /* The answers in two pairs (Allow, Allow for this chat | Allow always, Deny): one row where they fit, else a pair a
886
+ row, never three and one. Narrower padding than other buttons, so all four fit in a 420 px task pane. */
887
+ .fdn-approval-actions,
888
+ .fdn-approval-pair {
856
889
  display: flex;
857
890
  flex-wrap: wrap;
858
891
  gap: 6px;
892
+ }
893
+
894
+ .fdn-approval-actions {
859
895
  margin-top: 10px;
860
896
  }
861
897
 
898
+ .fdn-chat .fdn-approval-actions .fdn-button {
899
+ padding-inline: 10px;
900
+ }
901
+
862
902
  .fdn-approval-state {
863
903
  margin-top: 8px;
864
- color: var(--fdn-text-faint);
904
+ color: var(--fdn-text-muted);
865
905
  font-size: 12px;
866
906
  }
867
907
 
@@ -870,7 +910,9 @@
870
910
  color: var(--fdn-success);
871
911
  }
872
912
 
873
- .fdn-button {
913
+ /* Text buttons (approval cards, About). Under `.fdn-chat`, so their colour and weight outrank the reset that has
914
+ every button in the panel inherit its font and colour (`.fdn-chat button`). */
915
+ .fdn-chat .fdn-button {
874
916
  height: 32px;
875
917
  padding: 0 14px;
876
918
  border: 1px solid var(--fdn-border);
@@ -881,27 +923,27 @@
881
923
  cursor: pointer;
882
924
  }
883
925
 
884
- .fdn-button:hover:not(:disabled) {
926
+ .fdn-chat .fdn-button:hover:not(:disabled) {
885
927
  background: var(--fdn-surface-hover);
886
928
  }
887
929
 
888
- .fdn-button.fdn-primary {
930
+ .fdn-chat .fdn-button.fdn-primary {
889
931
  border-color: var(--fdn-accent);
890
932
  background: var(--fdn-accent);
891
933
  color: var(--fdn-on-accent);
892
934
  }
893
935
 
894
- .fdn-button.fdn-primary:hover:not(:disabled) {
936
+ .fdn-chat .fdn-button.fdn-primary:hover:not(:disabled) {
895
937
  background: var(--fdn-accent);
896
938
  filter: brightness(1.08);
897
939
  }
898
940
 
899
- .fdn-button.fdn-quiet {
941
+ .fdn-chat .fdn-button.fdn-quiet {
900
942
  border-color: transparent;
901
943
  color: var(--fdn-text-muted);
902
944
  }
903
945
 
904
- .fdn-button:disabled {
946
+ .fdn-chat .fdn-button:disabled {
905
947
  opacity: 0.45;
906
948
  cursor: default;
907
949
  }
@@ -938,7 +980,7 @@
938
980
  }
939
981
 
940
982
  .fdn-notice-detail {
941
- color: var(--fdn-text-faint);
983
+ color: var(--fdn-text-muted);
942
984
  font-size: 11.5px;
943
985
  }
944
986
 
@@ -1090,6 +1132,7 @@
1090
1132
  }
1091
1133
 
1092
1134
  .fdn-composer {
1135
+ position: relative; /* the skill picker opens above it */
1093
1136
  margin: 0 10px 10px;
1094
1137
  padding-block: 8px 6px; /* the send button sits 6 in from the 12 corner */
1095
1138
  padding-inline: 12px 6px;
@@ -1098,6 +1141,117 @@
1098
1141
  background: var(--fdn-bg);
1099
1142
  }
1100
1143
 
1144
+ /* Under the composer: answers come from an AI and can be wrong. Quiet, but always there. Below it, on a line of
1145
+ its own and as quiet, the Fadenstack link (`branding: false` hides it). */
1146
+ .fdn-footer {
1147
+ margin: -4px 12px 8px;
1148
+ color: var(--fdn-text-faint);
1149
+ font-size: 11.5px;
1150
+ line-height: 1.4;
1151
+ text-align: center;
1152
+ }
1153
+
1154
+ .fdn-ai-notice,
1155
+ .fdn-footer-brand {
1156
+ margin: 0;
1157
+ }
1158
+
1159
+ /* A notice that wraps does so in even lines, never one word alone on the last; where balance is not supported,
1160
+ pretty keeps the last line from being one word, and older engines wrap as usual. */
1161
+ .fdn-ai-notice {
1162
+ text-wrap: pretty;
1163
+ text-wrap: balance;
1164
+ }
1165
+
1166
+ /* Just enough to tell the link's line from a notice that wraps. */
1167
+ .fdn-ai-notice + .fdn-footer-brand {
1168
+ margin-block-start: 2px;
1169
+ }
1170
+
1171
+ /* Muted like the notice, underlined so it is not told apart by colour alone; no arrow on so small a line. */
1172
+ .fdn-footer-link {
1173
+ color: inherit;
1174
+ text-decoration: underline;
1175
+ text-underline-offset: 2px;
1176
+ white-space: nowrap;
1177
+ }
1178
+
1179
+ .fdn-footer-link:hover {
1180
+ color: var(--fdn-text-muted);
1181
+ }
1182
+
1183
+ /* A link that opens in a new tab: a small arrow after its text, pointing the way the text runs. */
1184
+ .fdn-external .fdn-icon {
1185
+ display: inline-block;
1186
+ margin-inline-start: 2px;
1187
+ vertical-align: -1px;
1188
+ }
1189
+
1190
+ .fdn-external .fdn-icon:dir(rtl) {
1191
+ transform: scaleX(-1);
1192
+ }
1193
+
1194
+ /* About: an overlay below the settings' gear, like the menus. Its text starts where the menu items' labels start
1195
+ (6 the menu + 6 the item + 16 the check + 8 the gap), so it opens in place of the item that opened it. */
1196
+ .fdn-about {
1197
+ display: flex;
1198
+ flex-direction: column;
1199
+ gap: 10px;
1200
+ padding-block: 6px 12px;
1201
+ padding-inline: 36px 6px;
1202
+ }
1203
+
1204
+ .fdn-about-head {
1205
+ display: flex;
1206
+ align-items: center;
1207
+ justify-content: space-between;
1208
+ gap: 8px;
1209
+ }
1210
+
1211
+ .fdn-about-agent,
1212
+ .fdn-about-environment {
1213
+ display: flex;
1214
+ flex-direction: column;
1215
+ gap: 1px;
1216
+ padding-inline-end: 6px;
1217
+ overflow-wrap: anywhere;
1218
+ }
1219
+
1220
+ .fdn-about-name {
1221
+ font-size: 14px;
1222
+ font-weight: 500;
1223
+ }
1224
+
1225
+ .fdn-about-line {
1226
+ color: var(--fdn-text-muted);
1227
+ font-size: 12px;
1228
+ }
1229
+
1230
+ /* One link per line: nothing wraps unevenly, whatever the language makes of their names. */
1231
+ .fdn-about-links {
1232
+ display: flex;
1233
+ flex-direction: column;
1234
+ align-items: flex-start;
1235
+ gap: 4px;
1236
+ }
1237
+
1238
+ .fdn-about-link {
1239
+ color: var(--fdn-accent);
1240
+ text-decoration: none;
1241
+ }
1242
+
1243
+ .fdn-about-link:hover {
1244
+ text-decoration: underline;
1245
+ text-underline-offset: 2px;
1246
+ }
1247
+
1248
+ .fdn-about .fdn-button {
1249
+ display: inline-flex;
1250
+ align-items: center;
1251
+ align-self: flex-start;
1252
+ gap: 6px;
1253
+ }
1254
+
1101
1255
  .fdn-composer:focus-within {
1102
1256
  border-color: var(--fdn-accent);
1103
1257
  box-shadow: 0 0 0 1px var(--fdn-accent);
@@ -1225,8 +1379,14 @@
1225
1379
  white-space: nowrap;
1226
1380
  }
1227
1381
 
1382
+ /* Short by intent (a site, a page count); a longer one is cut to one line, never wrapped out of the chip. */
1228
1383
  .fdn-chip-sub {
1384
+ min-width: 0;
1385
+ max-width: 50%;
1386
+ overflow: hidden;
1229
1387
  color: var(--fdn-text-faint);
1388
+ text-overflow: ellipsis;
1389
+ white-space: nowrap;
1230
1390
  }
1231
1391
 
1232
1392
  .fdn-chip-remove {
@@ -1244,3 +1404,37 @@
1244
1404
  .fdn-chip-remove:hover {
1245
1405
  background: var(--fdn-surface-hover);
1246
1406
  }
1407
+
1408
+ /* A picture on its chip: small, cut to a square. */
1409
+ .fdn-chip-image {
1410
+ width: 16px;
1411
+ height: 16px;
1412
+ border-radius: 3px;
1413
+ object-fit: cover;
1414
+ }
1415
+
1416
+ .fdn-chip-skill .fdn-icon {
1417
+ color: var(--fdn-accent);
1418
+ }
1419
+
1420
+ /* The skills `$` lists: above the composer, as wide as it, the active one marked as the pointer marks one. */
1421
+ .fdn-skill-picker {
1422
+ top: auto;
1423
+ bottom: calc(100% + 4px);
1424
+ inset-inline: 0;
1425
+ min-width: 0;
1426
+ max-width: none;
1427
+ }
1428
+
1429
+ .fdn-skill-list {
1430
+ max-height: min(320px, 50vh);
1431
+ overflow-y: auto;
1432
+ }
1433
+
1434
+ .fdn-skill-option[aria-selected="true"] {
1435
+ background: var(--fdn-surface-hover);
1436
+ }
1437
+
1438
+ .fdn-skill-hint {
1439
+ padding-block-end: 2px;
1440
+ }