@netlify/spark-ui 1.13.3 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
2
  import "../../../chunks/hooks.module.DGkdD5n9.js";
3
- import { A as d } from "../../../chunks/AskNetlify.CNYCiNSp.js";
3
+ import { A as d } from "../../../chunks/AskNetlify.hLTCaFLd.js";
4
4
  import "./components/QuestionList/questions.js";
5
5
  import "./components/QuestionList/QuestionList.js";
6
6
  import "./components/BackButton.js";
@@ -1,6 +1,6 @@
1
1
  import { u as e } from "../../../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
2
  import { A as g, y as h, d as k } from "../../../../../chunks/hooks.module.DGkdD5n9.js";
3
- import { A as w, K as b } from "../../../../../chunks/AskNetlify.CNYCiNSp.js";
3
+ import { A as w, K as b } from "../../../../../chunks/AskNetlify.hLTCaFLd.js";
4
4
  import K from "../../../Modal/Modal.js";
5
5
  import d from "../../../KeyboardKey/KeyboardKey.js";
6
6
  import L from "../SearchWindow.js";
@@ -26,8 +26,8 @@ import '../../../../../assets/SearchModal.css';function D(t) {
26
26
  }, [t]), n;
27
27
  }
28
28
  function re(t = { className: "", children: void 0, debug: !1, kapaIntegrationId: void 0 }) {
29
- const { children: n, className: o, debug: m, ...T } = t, [s, c] = k({ current: "search" }), [i, f] = k(!1), l = () => f(!0), u = () => f(!1), a = g(null), v = D(u), { state: r, actions: p } = C();
30
- q(l, c), R(p.setSearchTerm, l), P(i, l, u), Q(a, i, u), E(r, m);
29
+ const { children: n, className: o, debug: m, ...T } = t, [c, s] = k({ current: "search" }), [i, f] = k(!1), l = () => f(!0), u = () => f(!1), a = g(null), v = D(u), { state: r, actions: p } = C();
30
+ q(l, s), R(l, p.setSearchTerm, s), P(i, l, u), Q(a, i, u), E(r, m);
31
31
  const { handleNewQuery: A, handleAnswer: I } = x();
32
32
  return h(() => {
33
33
  a.current && (i ? a.current.showModal() : a.current.close());
@@ -35,7 +35,7 @@ function re(t = { className: "", children: void 0, debug: !1, kapaIntegrationId:
35
35
  var y;
36
36
  const S = (y = a.current) == null ? void 0 : y.querySelector("input");
37
37
  S && S.focus();
38
- }, [s.current]), /* @__PURE__ */ e(
38
+ }, [c.current]), /* @__PURE__ */ e(
39
39
  b,
40
40
  {
41
41
  integrationId: t.kapaIntegrationId || "",
@@ -50,21 +50,21 @@ function re(t = { className: "", children: void 0, debug: !1, kapaIntegrationId:
50
50
  {
51
51
  ref: a,
52
52
  className: `n-search-modal ${o || ""}`,
53
- "data-state": s.current,
53
+ "data-state": c.current,
54
54
  ...T,
55
55
  children: /* @__PURE__ */ e("div", { ref: v, className: "n-search-container", children: [
56
- s.current === "ask" && /* @__PURE__ */ e(w, { setModalState: c, initialQuery: r.searchTerm }),
57
- s.current === "search" && /* @__PURE__ */ e(N, { children: [
56
+ c.current === "ask" && /* @__PURE__ */ e(w, { setModalState: s, initialQuery: r.searchTerm }),
57
+ c.current === "search" && /* @__PURE__ */ e(N, { children: [
58
58
  /* @__PURE__ */ e(M, { setSearchTerm: p.setSearchTerm, initialValue: r.searchTerm }),
59
59
  /* @__PURE__ */ e(
60
60
  L,
61
61
  {
62
- setModalState: c,
62
+ setModalState: s,
63
63
  searchLoading: r.isLoading,
64
64
  searchTerm: r.searchTerm,
65
65
  results: r.results,
66
66
  children: /* @__PURE__ */ e(N, { children: [
67
- t.kapaIntegrationId && /* @__PURE__ */ e(O, { searchTerm: r.searchTerm, setModalState: c }),
67
+ t.kapaIntegrationId && /* @__PURE__ */ e(O, { searchTerm: r.searchTerm, setModalState: s }),
68
68
  n
69
69
  ] })
70
70
  }
@@ -1,11 +1,20 @@
1
1
  /**
2
- * Custom hook that checks the current URL for a `search` query parameter
3
- * and, if found, updates the search term and opens the search interface.
2
+ * Custom hook that checks the current URL for query parameters and updates
3
+ * search state or modal state accordingly:
4
4
  *
5
- * Useful for enabling deep linking to a search view with a pre-filled term,
6
- * such as when sharing URLs like `?search=netlify`.
5
+ * - If `?ask` is present (optionally with `?msg` or `?message`), it opens the modal and
6
+ * optionally pre-fills the search term.
7
+ * - If only `?msg` or `?message` is present, it behaves similarly, opening the modal
8
+ * and pre-filling the search term.
9
+ * - If only `?search` is present, it pre-fills the search input and opens the search view.
10
+ *
11
+ * The hook also waits for `window.pagefind` to load before evaluating parameters,
12
+ * ensuring deep linking doesn't interfere with pagefind initialization.
7
13
  *
8
- * @param setSearchTerm - Function to update the search input state.
9
14
  * @param onOpen - Function to trigger opening of the search interface (e.g., modal or drawer).
15
+ * @param setSearchTerm - Function to update the search input state.
16
+ * @param setModalState - (Optional) Function to set modal state, useful for distinguishing modal views.
10
17
  */
11
- export declare function useSearchParams(setSearchTerm: (term: string) => void, onOpen: () => void): void;
18
+ export declare function useSearchParams(onOpen: () => void, setSearchTerm: (term: string) => void, setModalState?: (e: {
19
+ current: string;
20
+ }) => void): void;
@@ -1,18 +1,24 @@
1
- import { d as c, y as n } from "../../../../chunks/hooks.module.DGkdD5n9.js";
2
- function d(t, o) {
3
- const [a, i] = c(!1);
4
- n(() => {
1
+ import { d as u, y as t } from "../../../../chunks/hooks.module.DGkdD5n9.js";
2
+ function l(s, a, r) {
3
+ const [c, d] = u(!1);
4
+ t(() => {
5
5
  let e;
6
- const r = () => {
7
- window.pagefind && (i(!0), e && clearInterval(e));
6
+ const n = () => {
7
+ window.pagefind && (d(!0), e && clearInterval(e));
8
8
  };
9
- return r(), e = setInterval(r, 100), () => clearInterval(e);
10
- }, []), n(() => {
11
- if (typeof location > "u" || a) return;
12
- const e = (location == null ? void 0 : location.search) && new URLSearchParams(location.search).get("search");
13
- e && (t(e), o());
14
- }, [a]);
9
+ return n(), e = setInterval(n, 100), () => clearInterval(e);
10
+ }, []), t(() => {
11
+ if (typeof location > "u" || c) return;
12
+ const e = new URLSearchParams(location.search), n = e.has("ask"), i = e.get("msg") || e.get("message"), o = e.get("search"), g = (f) => {
13
+ f && a(f), r == null || r({ current: "ask" }), s();
14
+ };
15
+ if (n || i) {
16
+ g(i);
17
+ return;
18
+ }
19
+ o && (a(o), s());
20
+ }, [c]);
15
21
  }
16
22
  export {
17
- d as useSearchParams
23
+ l as useSearchParams
18
24
  };
@@ -4,7 +4,7 @@ import { default as m } from "./Icon/index.js";
4
4
  import { default as s } from "./SearchModal/components/SearchLink/SearchLink.js";
5
5
  import { default as u } from "./SearchModal/components/SearchHeading/SearchHeading.js";
6
6
  import { default as i } from "./SearchModal/components/SearchToggle/SearchToggle.js";
7
- import { A as S } from "../../chunks/AskNetlify.CNYCiNSp.js";
7
+ import { A as S } from "../../chunks/AskNetlify.hLTCaFLd.js";
8
8
  import "preact";
9
9
  import "../../chunks/hooks.module.DGkdD5n9.js";
10
10
  import { default as n } from "./SearchModal/components/SearchModal/SearchModal.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/spark-ui",
3
3
  "description": "Assets, design tokens, components, and utilities",
4
- "version": "1.13.3",
4
+ "version": "1.14.0",
5
5
  "type": "module",
6
6
  "main": "dist/components/preact/index.js",
7
7
  "types": "dist/components/preact/index.d.ts",
@@ -19,6 +19,7 @@ export default function AskNetlify({ setModalState, initialQuery }: Props) {
19
19
  const [newMessageReady, setNewMessageReady] = useState(false);
20
20
  const chatRef = useRef<HTMLDivElement>(null);
21
21
  const menuRef = useRef<HTMLUListElement>(null);
22
+ const inputRef = useRef<HTMLInputElement>(null);
22
23
  const lastMessageRef = useRef<HTMLDivElement | null>(null);
23
24
 
24
25
  const { conversation, submitQuery, isGeneratingAnswer, isPreparingAnswer, stopGeneration } =
@@ -50,19 +51,38 @@ export default function AskNetlify({ setModalState, initialQuery }: Props) {
50
51
  }, [newMessageReady]);
51
52
 
52
53
  useEffect(() => {
53
- if (initialQuery) {
54
- submitQuery(initialQuery.trim());
55
- setNewMessageReady(true);
54
+ if (!initialQuery) return;
55
+
56
+ const trimmedQuery = initialQuery.trim();
57
+
58
+ /**
59
+ * When reCAPTCHA is ready, we know this was triggered by a user action
60
+ * (like clicking a button), so we can safely submit the query right away.
61
+ * If reCAPTCHA isn't ready, the modal was likely opened from an outside source,
62
+ * in that case, we populate the input and focus it, requiring manual submission
63
+ * to ensure a user action triggers reCAPTCHA.
64
+ */
65
+ if (window.grecaptcha) {
66
+ window.grecaptcha.enterprise.ready(() => {
67
+ submitQuery(trimmedQuery);
68
+ });
69
+ } else {
70
+ setMessage(trimmedQuery);
71
+ inputRef?.current?.focus();
56
72
  }
73
+
74
+ setNewMessageReady(true);
57
75
  }, [initialQuery]);
58
76
 
59
77
  return (
60
78
  <div className="n-ask-chat-container">
61
79
  <BackButton onClick={() => setModalState?.({ current: 'search' })} />
62
80
  <div ref={chatRef} className="n-ask-conversation n-search-window">
63
- {!conversation.length ? (
81
+ {!conversation.length && !initialQuery && (
64
82
  <QuestionList onClick={handleSubmit} menuRef={menuRef} />
65
- ) : (
83
+ )}
84
+
85
+ {conversation.length > 0 && (
66
86
  <ChatMessages
67
87
  isPreparingAnswer={isPreparingAnswer}
68
88
  conversation={conversation}
@@ -73,6 +93,7 @@ export default function AskNetlify({ setModalState, initialQuery }: Props) {
73
93
 
74
94
  <form onSubmit={handleSubmit} className="n-ask-form n-search-input-wrapper">
75
95
  <input
96
+ ref={inputRef}
76
97
  className="n-ask-input n-search-input"
77
98
  type="text"
78
99
  value={message}
@@ -1,5 +1,5 @@
1
1
  import { type ComponentChildren } from 'preact';
2
- import { useState, useEffect, useRef } from 'preact/hooks';
2
+ import { useState, useEffect, useRef, useMemo } from 'preact/hooks';
3
3
 
4
4
  import { KapaProvider } from '@kapaai/react-sdk';
5
5
 
@@ -45,7 +45,7 @@ function SearchModal(
45
45
 
46
46
  const { state, actions } = useSearch();
47
47
  useSearchToggles(openSearch, setModalState);
48
- useSearchParams(actions.setSearchTerm, openSearch);
48
+ useSearchParams(openSearch, actions.setSearchTerm, setModalState);
49
49
  useSearchHotkeys(searchOpen, openSearch, closeSearch);
50
50
  useKeyboardNav(modalRef, searchOpen, closeSearch);
51
51
  useSearchAnalytics(state, debug);
@@ -1,17 +1,28 @@
1
1
  import { useEffect, useState } from 'preact/hooks';
2
2
 
3
3
  /**
4
- * Custom hook that checks the current URL for a `search` query parameter
5
- * and, if found, updates the search term and opens the search interface.
4
+ * Custom hook that checks the current URL for query parameters and updates
5
+ * search state or modal state accordingly:
6
6
  *
7
- * Useful for enabling deep linking to a search view with a pre-filled term,
8
- * such as when sharing URLs like `?search=netlify`.
7
+ * - If `?ask` is present (optionally with `?msg` or `?message`), it opens the modal and
8
+ * optionally pre-fills the search term.
9
+ * - If only `?msg` or `?message` is present, it behaves similarly, opening the modal
10
+ * and pre-filling the search term.
11
+ * - If only `?search` is present, it pre-fills the search input and opens the search view.
12
+ *
13
+ * The hook also waits for `window.pagefind` to load before evaluating parameters,
14
+ * ensuring deep linking doesn't interfere with pagefind initialization.
9
15
  *
10
- * @param setSearchTerm - Function to update the search input state.
11
16
  * @param onOpen - Function to trigger opening of the search interface (e.g., modal or drawer).
17
+ * @param setSearchTerm - Function to update the search input state.
18
+ * @param setModalState - (Optional) Function to set modal state, useful for distinguishing modal views.
12
19
  */
13
20
 
14
- export function useSearchParams(setSearchTerm: (term: string) => void, onOpen: () => void) {
21
+ export function useSearchParams(
22
+ onOpen: () => void,
23
+ setSearchTerm: (term: string) => void,
24
+ setModalState?: (e: { current: string }) => void
25
+ ) {
15
26
  const [isPagefindLoaded, setIsPagefindLoaded] = useState(false);
16
27
 
17
28
  // Poll for the existence of window.pagefind.
@@ -34,11 +45,27 @@ export function useSearchParams(setSearchTerm: (term: string) => void, onOpen: (
34
45
  }, []);
35
46
 
36
47
  useEffect(() => {
37
- // Cleanup the interval when the component unmounts.
38
48
  if (typeof location === 'undefined' || isPagefindLoaded) return;
39
- const query = location?.search && new URLSearchParams(location.search).get('search');
40
- if (query) {
41
- setSearchTerm(query);
49
+
50
+ const params = new URLSearchParams(location.search);
51
+
52
+ const hasAsk = params.has('ask');
53
+ const msg = params.get('msg') || params.get('message');
54
+ const search = params.get('search');
55
+
56
+ const openAskModal = (term?: string | null) => {
57
+ if (term) setSearchTerm(term);
58
+ setModalState?.({ current: 'ask' });
59
+ onOpen();
60
+ };
61
+
62
+ if (hasAsk || msg) {
63
+ openAskModal(msg);
64
+ return;
65
+ }
66
+
67
+ if (search) {
68
+ setSearchTerm(search);
42
69
  onOpen();
43
70
  }
44
71
  }, [isPagefindLoaded]);