@kite-copilot/chat-panel 0.2.23 → 0.2.24

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/auto.cjs CHANGED
@@ -938,7 +938,7 @@ function renderMarkdown(text) {
938
938
  "a",
939
939
  {
940
940
  href: linkMatch[2],
941
- className: "text-blue-600 hover:underline",
941
+ className: "kite-link",
942
942
  target: "_blank",
943
943
  rel: "noopener noreferrer",
944
944
  children: linkMatch[1]
@@ -949,7 +949,44 @@ function renderMarkdown(text) {
949
949
  remaining = remaining.slice(linkMatch[0].length);
950
950
  continue;
951
951
  }
952
- const nextSpecial = remaining.search(/[`*\[]/);
952
+ const urlMatch = remaining.match(/^(https?:\/\/[^\s<>]+|www\.[^\s<>]+)/);
953
+ if (urlMatch) {
954
+ const url = urlMatch[1];
955
+ const href = url.startsWith("www.") ? `https://${url}` : url;
956
+ parts.push(
957
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
958
+ "a",
959
+ {
960
+ href,
961
+ className: "kite-link",
962
+ target: "_blank",
963
+ rel: "noopener noreferrer",
964
+ children: url
965
+ },
966
+ keyIndex++
967
+ )
968
+ );
969
+ remaining = remaining.slice(url.length);
970
+ continue;
971
+ }
972
+ const emailMatch = remaining.match(/^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);
973
+ if (emailMatch) {
974
+ const email = emailMatch[1];
975
+ parts.push(
976
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
977
+ "a",
978
+ {
979
+ href: `mailto:${email}`,
980
+ className: "kite-link",
981
+ children: email
982
+ },
983
+ keyIndex++
984
+ )
985
+ );
986
+ remaining = remaining.slice(email.length);
987
+ continue;
988
+ }
989
+ const nextSpecial = remaining.search(/[`*\[@h]/);
953
990
  if (nextSpecial === -1) {
954
991
  parts.push(remaining);
955
992
  break;
@@ -1269,6 +1306,7 @@ var initialMessages = [];
1269
1306
  function ChatPanel({
1270
1307
  isOpen = true,
1271
1308
  onClose,
1309
+ onOpen,
1272
1310
  onBack,
1273
1311
  onNavigate,
1274
1312
  onActionComplete,
@@ -1370,6 +1408,9 @@ function ChatPanel({
1370
1408
  const [pendingBulkSession, setPendingBulkSession] = React4.useState(null);
1371
1409
  const pendingBulkSessionRef = React4.useRef(null);
1372
1410
  const fileInputRef = React4.useRef(null);
1411
+ const [searchExpanded, setSearchExpanded] = React4.useState(false);
1412
+ const [searchInput, setSearchInput] = React4.useState("");
1413
+ const searchInputRef = React4.useRef(null);
1373
1414
  React4.useEffect(() => {
1374
1415
  if (!activeGuide || activeGuide.id !== "add-api-key" || activeGuide.stepIndex !== 2) {
1375
1416
  return;
@@ -1719,7 +1760,11 @@ function ChatPanel({
1719
1760
  const isRespondingToNotification = lastAssistantMessage?.isNotificationMessage === true;
1720
1761
  const now = Date.now();
1721
1762
  const userMessage = { id: now, role: "user", content: userText };
1722
- setMessages((prev) => [...prev, userMessage]);
1763
+ setMessages(
1764
+ (prev) => prev.map(
1765
+ (m) => m.followups && m.followups.length > 0 ? { ...m, followupSelected: true } : m
1766
+ ).concat(userMessage)
1767
+ );
1723
1768
  if (isRespondingToNotification) {
1724
1769
  const thankYouMessageId = Date.now() + 1;
1725
1770
  const thankYouMessage = {
@@ -2597,6 +2642,81 @@ ${userText}`
2597
2642
  ]);
2598
2643
  }
2599
2644
  }
2645
+ if (!isOpen) {
2646
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-50", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex items-center gap-3 rounded-2xl bg-white border border-gray-200 shadow-lg px-4 py-2 hover:shadow-xl transition-all duration-300 ease-out focus-within:border-gray-700 ${searchExpanded ? "w-[480px]" : "w-[320px]"}`, children: !searchExpanded ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-3 w-full", children: [
2647
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2648
+ "button",
2649
+ {
2650
+ onClick: () => {
2651
+ setSearchExpanded(true);
2652
+ setTimeout(() => searchInputRef.current?.focus(), 100);
2653
+ },
2654
+ className: "flex items-center gap-3 flex-1 group",
2655
+ children: [
2656
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-sm text-gray-500 flex-1 text-left", children: "Ask a question..." }),
2657
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-gray-400 font-medium", children: "\u2318I" })
2658
+ ]
2659
+ }
2660
+ ),
2661
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2662
+ "button",
2663
+ {
2664
+ onClick: () => {
2665
+ onOpen?.();
2666
+ },
2667
+ className: "h-7 w-7 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors",
2668
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" })
2669
+ }
2670
+ )
2671
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2672
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2673
+ "input",
2674
+ {
2675
+ ref: searchInputRef,
2676
+ type: "text",
2677
+ value: searchInput,
2678
+ onChange: (e) => setSearchInput(e.target.value),
2679
+ onKeyDown: (e) => {
2680
+ if (e.key === "Enter" && searchInput.trim()) {
2681
+ e.preventDefault();
2682
+ onOpen?.();
2683
+ startChatFlow(searchInput.trim());
2684
+ setSearchInput("");
2685
+ setSearchExpanded(false);
2686
+ } else if (e.key === "Escape") {
2687
+ setSearchExpanded(false);
2688
+ setSearchInput("");
2689
+ }
2690
+ },
2691
+ onBlur: () => {
2692
+ if (!searchInput.trim()) {
2693
+ setTimeout(() => setSearchExpanded(false), 200);
2694
+ }
2695
+ },
2696
+ placeholder: "Ask a question...",
2697
+ className: "flex-1 text-sm text-gray-700 outline-none bg-transparent"
2698
+ }
2699
+ ),
2700
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2", children: [
2701
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-xs text-gray-400 font-medium", children: "\u21B5 Enter" }),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2703
+ "button",
2704
+ {
2705
+ onClick: () => {
2706
+ onOpen?.();
2707
+ if (searchInput.trim()) {
2708
+ setInput(searchInput);
2709
+ }
2710
+ setSearchInput("");
2711
+ setSearchExpanded(false);
2712
+ },
2713
+ className: "h-7 w-7 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors cursor-pointer",
2714
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" })
2715
+ }
2716
+ )
2717
+ ] })
2718
+ ] }) }) });
2719
+ }
2600
2720
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2601
2721
  "section",
2602
2722
  {
@@ -3909,26 +4029,6 @@ ${userText}`
3909
4029
  }
3910
4030
  );
3911
4031
  }
3912
- function PanelToggle({
3913
- isOpen,
3914
- onClick,
3915
- className = ""
3916
- }) {
3917
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3918
- "button",
3919
- {
3920
- type: "button",
3921
- onClick,
3922
- className: `fixed top-1/2 z-50 flex items-center justify-center w-6 h-16 bg-gray-100 hover:bg-gray-200 border border-gray-200 border-r-0 rounded-l-lg text-gray-600 hover:text-gray-800 shadow-md transition-all duration-300 ${className}`,
3923
- "aria-label": isOpen ? "Close help panel" : "Open help panel",
3924
- style: {
3925
- right: isOpen ? `${PANEL_WIDTH}px` : "0px",
3926
- transform: "translateY(-50%)"
3927
- },
3928
- children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.ChevronLeft, { className: "h-4 w-4" })
3929
- }
3930
- );
3931
- }
3932
4032
  function ChatPanelWithToggle({
3933
4033
  onNavigate,
3934
4034
  onActionComplete,
@@ -3958,22 +4058,20 @@ function ChatPanelWithToggle({
3958
4058
  document.body.style.transition = originalTransition;
3959
4059
  };
3960
4060
  }, [isOpen]);
3961
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3962
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PanelToggle, { isOpen, onClick: () => setIsOpen(!isOpen) }),
3963
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3964
- ChatPanel,
3965
- {
3966
- isOpen,
3967
- onClose: () => setIsOpen(false),
3968
- onNavigate,
3969
- onActionComplete,
3970
- currentPage,
3971
- agentUrl,
3972
- startingQuestions,
3973
- startingQuestionsEndpoint
3974
- }
3975
- )
3976
- ] });
4061
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4062
+ ChatPanel,
4063
+ {
4064
+ isOpen,
4065
+ onClose: () => setIsOpen(false),
4066
+ onOpen: () => setIsOpen(true),
4067
+ onNavigate,
4068
+ onActionComplete,
4069
+ currentPage,
4070
+ agentUrl,
4071
+ startingQuestions,
4072
+ startingQuestionsEndpoint
4073
+ }
4074
+ );
3977
4075
  }
3978
4076
 
3979
4077
  // src/createKiteChat.tsx
package/dist/auto.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-BMLaQQQk.cjs';
1
+ import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-CyoN-YV4.cjs';
2
2
  import 'react/jsx-runtime';
3
3
 
4
4
  /**
package/dist/auto.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-BMLaQQQk.js';
1
+ import { K as KiteChatConfig, a as KiteChatInstance } from './createKiteChat-CyoN-YV4.js';
2
2
  import 'react/jsx-runtime';
3
3
 
4
4
  /**
package/dist/auto.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createKiteChat
3
- } from "./chunk-MIJSRC3X.js";
3
+ } from "./chunk-TBMYN3GD.js";
4
4
 
5
5
  // src/auto.ts
6
6
  function mountKiteChat(config) {
@@ -937,7 +937,7 @@ function renderMarkdown(text) {
937
937
  "a",
938
938
  {
939
939
  href: linkMatch[2],
940
- className: "text-blue-600 hover:underline",
940
+ className: "kite-link",
941
941
  target: "_blank",
942
942
  rel: "noopener noreferrer",
943
943
  children: linkMatch[1]
@@ -948,7 +948,44 @@ function renderMarkdown(text) {
948
948
  remaining = remaining.slice(linkMatch[0].length);
949
949
  continue;
950
950
  }
951
- const nextSpecial = remaining.search(/[`*\[]/);
951
+ const urlMatch = remaining.match(/^(https?:\/\/[^\s<>]+|www\.[^\s<>]+)/);
952
+ if (urlMatch) {
953
+ const url = urlMatch[1];
954
+ const href = url.startsWith("www.") ? `https://${url}` : url;
955
+ parts.push(
956
+ /* @__PURE__ */ jsx9(
957
+ "a",
958
+ {
959
+ href,
960
+ className: "kite-link",
961
+ target: "_blank",
962
+ rel: "noopener noreferrer",
963
+ children: url
964
+ },
965
+ keyIndex++
966
+ )
967
+ );
968
+ remaining = remaining.slice(url.length);
969
+ continue;
970
+ }
971
+ const emailMatch = remaining.match(/^([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/);
972
+ if (emailMatch) {
973
+ const email = emailMatch[1];
974
+ parts.push(
975
+ /* @__PURE__ */ jsx9(
976
+ "a",
977
+ {
978
+ href: `mailto:${email}`,
979
+ className: "kite-link",
980
+ children: email
981
+ },
982
+ keyIndex++
983
+ )
984
+ );
985
+ remaining = remaining.slice(email.length);
986
+ continue;
987
+ }
988
+ const nextSpecial = remaining.search(/[`*\[@h]/);
952
989
  if (nextSpecial === -1) {
953
990
  parts.push(remaining);
954
991
  break;
@@ -1268,6 +1305,7 @@ var initialMessages = [];
1268
1305
  function ChatPanel({
1269
1306
  isOpen = true,
1270
1307
  onClose,
1308
+ onOpen,
1271
1309
  onBack,
1272
1310
  onNavigate,
1273
1311
  onActionComplete,
@@ -1369,6 +1407,9 @@ function ChatPanel({
1369
1407
  const [pendingBulkSession, setPendingBulkSession] = React4.useState(null);
1370
1408
  const pendingBulkSessionRef = React4.useRef(null);
1371
1409
  const fileInputRef = React4.useRef(null);
1410
+ const [searchExpanded, setSearchExpanded] = React4.useState(false);
1411
+ const [searchInput, setSearchInput] = React4.useState("");
1412
+ const searchInputRef = React4.useRef(null);
1372
1413
  React4.useEffect(() => {
1373
1414
  if (!activeGuide || activeGuide.id !== "add-api-key" || activeGuide.stepIndex !== 2) {
1374
1415
  return;
@@ -1718,7 +1759,11 @@ function ChatPanel({
1718
1759
  const isRespondingToNotification = lastAssistantMessage?.isNotificationMessage === true;
1719
1760
  const now = Date.now();
1720
1761
  const userMessage = { id: now, role: "user", content: userText };
1721
- setMessages((prev) => [...prev, userMessage]);
1762
+ setMessages(
1763
+ (prev) => prev.map(
1764
+ (m) => m.followups && m.followups.length > 0 ? { ...m, followupSelected: true } : m
1765
+ ).concat(userMessage)
1766
+ );
1722
1767
  if (isRespondingToNotification) {
1723
1768
  const thankYouMessageId = Date.now() + 1;
1724
1769
  const thankYouMessage = {
@@ -2596,6 +2641,81 @@ ${userText}`
2596
2641
  ]);
2597
2642
  }
2598
2643
  }
2644
+ if (!isOpen) {
2645
+ return /* @__PURE__ */ jsx9("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-50", children: /* @__PURE__ */ jsx9("div", { className: `flex items-center gap-3 rounded-2xl bg-white border border-gray-200 shadow-lg px-4 py-2 hover:shadow-xl transition-all duration-300 ease-out focus-within:border-gray-700 ${searchExpanded ? "w-[480px]" : "w-[320px]"}`, children: !searchExpanded ? /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 w-full", children: [
2646
+ /* @__PURE__ */ jsxs5(
2647
+ "button",
2648
+ {
2649
+ onClick: () => {
2650
+ setSearchExpanded(true);
2651
+ setTimeout(() => searchInputRef.current?.focus(), 100);
2652
+ },
2653
+ className: "flex items-center gap-3 flex-1 group",
2654
+ children: [
2655
+ /* @__PURE__ */ jsx9("span", { className: "text-sm text-gray-500 flex-1 text-left", children: "Ask a question..." }),
2656
+ /* @__PURE__ */ jsx9("span", { className: "text-xs text-gray-400 font-medium", children: "\u2318I" })
2657
+ ]
2658
+ }
2659
+ ),
2660
+ /* @__PURE__ */ jsx9(
2661
+ "button",
2662
+ {
2663
+ onClick: () => {
2664
+ onOpen?.();
2665
+ },
2666
+ className: "h-7 w-7 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors",
2667
+ children: /* @__PURE__ */ jsx9(Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" })
2668
+ }
2669
+ )
2670
+ ] }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
2671
+ /* @__PURE__ */ jsx9(
2672
+ "input",
2673
+ {
2674
+ ref: searchInputRef,
2675
+ type: "text",
2676
+ value: searchInput,
2677
+ onChange: (e) => setSearchInput(e.target.value),
2678
+ onKeyDown: (e) => {
2679
+ if (e.key === "Enter" && searchInput.trim()) {
2680
+ e.preventDefault();
2681
+ onOpen?.();
2682
+ startChatFlow(searchInput.trim());
2683
+ setSearchInput("");
2684
+ setSearchExpanded(false);
2685
+ } else if (e.key === "Escape") {
2686
+ setSearchExpanded(false);
2687
+ setSearchInput("");
2688
+ }
2689
+ },
2690
+ onBlur: () => {
2691
+ if (!searchInput.trim()) {
2692
+ setTimeout(() => setSearchExpanded(false), 200);
2693
+ }
2694
+ },
2695
+ placeholder: "Ask a question...",
2696
+ className: "flex-1 text-sm text-gray-700 outline-none bg-transparent"
2697
+ }
2698
+ ),
2699
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
2700
+ /* @__PURE__ */ jsx9("span", { className: "text-xs text-gray-400 font-medium", children: "\u21B5 Enter" }),
2701
+ /* @__PURE__ */ jsx9(
2702
+ "button",
2703
+ {
2704
+ onClick: () => {
2705
+ onOpen?.();
2706
+ if (searchInput.trim()) {
2707
+ setInput(searchInput);
2708
+ }
2709
+ setSearchInput("");
2710
+ setSearchExpanded(false);
2711
+ },
2712
+ className: "h-7 w-7 rounded-lg bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors cursor-pointer",
2713
+ children: /* @__PURE__ */ jsx9(Sparkles, { className: "h-3.5 w-3.5 text-black", fill: "currentColor" })
2714
+ }
2715
+ )
2716
+ ] })
2717
+ ] }) }) });
2718
+ }
2599
2719
  return /* @__PURE__ */ jsxs5(
2600
2720
  "section",
2601
2721
  {
@@ -3957,22 +4077,20 @@ function ChatPanelWithToggle({
3957
4077
  document.body.style.transition = originalTransition;
3958
4078
  };
3959
4079
  }, [isOpen]);
3960
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
3961
- /* @__PURE__ */ jsx9(PanelToggle, { isOpen, onClick: () => setIsOpen(!isOpen) }),
3962
- /* @__PURE__ */ jsx9(
3963
- ChatPanel,
3964
- {
3965
- isOpen,
3966
- onClose: () => setIsOpen(false),
3967
- onNavigate,
3968
- onActionComplete,
3969
- currentPage,
3970
- agentUrl,
3971
- startingQuestions,
3972
- startingQuestionsEndpoint
3973
- }
3974
- )
3975
- ] });
4080
+ return /* @__PURE__ */ jsx9(
4081
+ ChatPanel,
4082
+ {
4083
+ isOpen,
4084
+ onClose: () => setIsOpen(false),
4085
+ onOpen: () => setIsOpen(true),
4086
+ onNavigate,
4087
+ onActionComplete,
4088
+ currentPage,
4089
+ agentUrl,
4090
+ startingQuestions,
4091
+ startingQuestionsEndpoint
4092
+ }
4093
+ );
3976
4094
  }
3977
4095
  function HelpButton({ onClick, className = "" }) {
3978
4096
  return /* @__PURE__ */ jsx9(
@@ -87,6 +87,8 @@ interface ChatPanelProps {
87
87
  isOpen?: boolean;
88
88
  /** Callback when the panel should close */
89
89
  onClose?: () => void;
90
+ /** Callback when the panel should open */
91
+ onOpen?: () => void;
90
92
  onBack?: () => void;
91
93
  onNavigate?: (page: Page, subtab?: SettingsTab) => void;
92
94
  onActionComplete?: (actionType: ActionType, data: ActionData) => void;
@@ -97,7 +99,7 @@ interface ChatPanelProps {
97
99
  /** API endpoint to fetch starting questions */
98
100
  startingQuestionsEndpoint?: string;
99
101
  }
100
- declare function ChatPanel({ isOpen, onClose, onBack, onNavigate, onActionComplete, currentPage, agentUrl, startingQuestions: startingQuestionsProp, startingQuestionsEndpoint, }?: ChatPanelProps): react_jsx_runtime.JSX.Element;
102
+ declare function ChatPanel({ isOpen, onClose, onOpen, onBack, onNavigate, onActionComplete, currentPage, agentUrl, startingQuestions: startingQuestionsProp, startingQuestionsEndpoint, }?: ChatPanelProps): react_jsx_runtime.JSX.Element;
101
103
  /**
102
104
  * PanelToggle - An arrow button on the right edge that toggles the side panel
103
105
  * Shows left arrow when closed (click to open), right arrow when open (click to close)
@@ -87,6 +87,8 @@ interface ChatPanelProps {
87
87
  isOpen?: boolean;
88
88
  /** Callback when the panel should close */
89
89
  onClose?: () => void;
90
+ /** Callback when the panel should open */
91
+ onOpen?: () => void;
90
92
  onBack?: () => void;
91
93
  onNavigate?: (page: Page, subtab?: SettingsTab) => void;
92
94
  onActionComplete?: (actionType: ActionType, data: ActionData) => void;
@@ -97,7 +99,7 @@ interface ChatPanelProps {
97
99
  /** API endpoint to fetch starting questions */
98
100
  startingQuestionsEndpoint?: string;
99
101
  }
100
- declare function ChatPanel({ isOpen, onClose, onBack, onNavigate, onActionComplete, currentPage, agentUrl, startingQuestions: startingQuestionsProp, startingQuestionsEndpoint, }?: ChatPanelProps): react_jsx_runtime.JSX.Element;
102
+ declare function ChatPanel({ isOpen, onClose, onOpen, onBack, onNavigate, onActionComplete, currentPage, agentUrl, startingQuestions: startingQuestionsProp, startingQuestionsEndpoint, }?: ChatPanelProps): react_jsx_runtime.JSX.Element;
101
103
  /**
102
104
  * PanelToggle - An arrow button on the right edge that toggles the side panel
103
105
  * Shows left arrow when closed (click to open), right arrow when open (click to close)