Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_trigger_css = require("./trigger.css-D6ouqWyQ.cjs"); let react = require("react"); let _tanstack_react_query = require("@tanstack/react-query"); let react_jsx_runtime = require("react/jsx-runtime"); let _reqdesk_sdk_react = require("@reqdesk/sdk-react"); let react_dom = require("react-dom"); //#region src/auth/widget-auth.ts const STORAGE_KEY = "reqdesk-widget-auth"; const EXPIRY_MARGIN_S = 30; const POPUP_FEATURES = "width=520,height=680,menubar=no,toolbar=no,location=no,status=no"; const POPUP_NAME = "reqdesk-widget-login"; let config = null; let listeners = []; let initPromise = null; let refreshTimer = null; let tokens = readStoredSync(); let authState = tokens && Date.now() < tokens.expiresAt - EXPIRY_MARGIN_S * 1e3 ? { isAuthenticated: true, isLoading: false, userEmail: tokens.userEmail, userName: tokens.userName } : { isAuthenticated: false, isLoading: false }; function readStoredSync() { try { if (typeof localStorage === "undefined") return null; const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (!parsed.accessToken || !parsed.expiresAt) return null; return parsed; } catch { return null; } } function notify() { for (const listener of listeners) try { listener(authState); } catch {} } function setState(update) { authState = { ...authState, ...update }; notify(); } function getAuthState() { return authState; } function onAuthStateChange(listener) { listeners.push(listener); return () => { listeners = listeners.filter((l) => l !== listener); }; } function isAuthConfigured() { return config !== null; } function base64UrlEncode(buffer) { const bytes = new Uint8Array(buffer); let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function randomBase64Url(bytes = 32) { const arr = new Uint8Array(bytes); crypto.getRandomValues(arr); return base64UrlEncode(arr.buffer); } async function sha256(input) { const data = new TextEncoder().encode(input); return base64UrlEncode(await crypto.subtle.digest("SHA-256", data)); } function decodeJwtPayload(token) { try { const parts = token.split("."); if (parts.length < 2) return null; const payload = parts[1] + "===".slice((parts[1].length + 3) % 4); return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/"))); } catch { return null; } } function saveStored(t) { try { if (t) { localStorage.setItem(STORAGE_KEY, JSON.stringify(t)); console.debug("[reqdesk-widget] tokens saved to localStorage", { expiresAt: new Date(t.expiresAt).toISOString() }); } else { localStorage.removeItem(STORAGE_KEY); console.debug("[reqdesk-widget] tokens removed from localStorage"); } } catch (err) { console.warn("[reqdesk-widget] localStorage write failed:", err); } } function authEndpoint(cfg) { return `${cfg.issuerUri}/protocol/openid-connect/auth`; } function tokenEndpoint(cfg) { return `${cfg.issuerUri}/protocol/openid-connect/token`; } function logoutEndpoint(cfg) { return `${cfg.issuerUri}/protocol/openid-connect/logout`; } function redirectUri() { return `${window.location.origin}/reqdesk-widget-auth-callback`; } function scheduleRefresh() { if (refreshTimer) clearTimeout(refreshTimer); if (!tokens?.refreshToken) return; const msUntilRefresh = Math.max(0, tokens.expiresAt - Date.now() - EXPIRY_MARGIN_S * 1e3); refreshTimer = setTimeout(() => { refresh(); }, msUntilRefresh); } function applyTokens(t) { tokens = t; saveStored(t); setState({ isAuthenticated: true, isLoading: false, userEmail: t.userEmail, userName: t.userName }); scheduleRefresh(); } function clearTokens() { tokens = null; saveStored(null); if (refreshTimer) { clearTimeout(refreshTimer); refreshTimer = null; } setState({ isAuthenticated: false, isLoading: false, userEmail: void 0, userName: void 0 }); } function parseTokenResponse(data) { const accessToken = String(data.access_token); const refreshToken = data.refresh_token ? String(data.refresh_token) : null; const expiresIn = Number(data.expires_in ?? 300); const expiresAt = Date.now() + expiresIn * 1e3; const payload = decodeJwtPayload(accessToken) ?? {}; return { accessToken, refreshToken, expiresAt, userEmail: typeof payload.email === "string" ? payload.email : void 0, userName: typeof payload.name === "string" ? payload.name : typeof payload.preferred_username === "string" ? payload.preferred_username : void 0 }; } async function refresh() { if (!config || !tokens?.refreshToken) { clearTokens(); return; } try { const res = await fetch(tokenEndpoint(config), { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", client_id: config.clientId, refresh_token: tokens.refreshToken }) }); if (!res.ok) { const body = await res.text().catch(() => ""); throw new Error(`refresh failed: ${res.status} ${body}`); } applyTokens(parseTokenResponse(await res.json())); } catch (err) { console.warn("[reqdesk-widget] token refresh failed:", err); clearTokens(); } } async function getAccessToken() { if (!tokens) return null; if (Date.now() < tokens.expiresAt - EXPIRY_MARGIN_S * 1e3) return tokens.accessToken; if (tokens.refreshToken) { await refresh(); return tokens?.accessToken ?? null; } clearTokens(); return null; } function initWidgetAuth(authConfig) { config = authConfig; initPromise = _initWidgetAuth(); return initPromise; } async function _initWidgetAuth() { setState({ isLoading: true }); require_trigger_css.setOidcTokenProvider(async () => { return { accessToken: await getAccessToken() ?? "" }; }); const stored = readStoredSync(); console.debug("[reqdesk-widget] init: stored tokens?", stored ? "yes" : "no", stored ? { expiresAt: new Date(stored.expiresAt).toISOString(), hasRefresh: !!stored.refreshToken } : null); if (stored) { tokens = stored; if (Date.now() >= stored.expiresAt - EXPIRY_MARGIN_S * 1e3) { console.debug("[reqdesk-widget] init: tokens expired, attempting refresh"); await refresh(); } else { console.debug("[reqdesk-widget] init: tokens valid, using as-is"); setState({ isAuthenticated: true, isLoading: false, userEmail: stored.userEmail, userName: stored.userName }); scheduleRefresh(); } } else setState({ isLoading: false }); } async function login() { if (!config) { console.warn("[reqdesk-widget] login() called before initWidgetAuth()"); return; } const cfg = config; const state = randomBase64Url(16); const codeVerifier = randomBase64Url(32); const codeChallenge = await sha256(codeVerifier); const authUrl = new URL(authEndpoint(cfg)); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("client_id", cfg.clientId); authUrl.searchParams.set("redirect_uri", redirectUri()); authUrl.searchParams.set("scope", "openid email profile"); authUrl.searchParams.set("state", state); authUrl.searchParams.set("code_challenge", codeChallenge); authUrl.searchParams.set("code_challenge_method", "S256"); const popup = window.open(authUrl.toString(), POPUP_NAME, POPUP_FEATURES); if (!popup) { console.warn("[reqdesk-widget] login popup was blocked by the browser"); return; } const code = await new Promise((resolve) => { let settled = false; const finish = (result, err) => { if (settled) return; settled = true; clearInterval(poll); try { if (!popup.closed) popup.close(); } catch {} if (err) console.warn("[reqdesk-widget] login failed:", err); resolve(result); }; const poll = setInterval(() => { if (popup.closed) { finish(null, "popup closed"); return; } let href = null; try { href = popup.location.href; } catch { return; } if (!href || !href.startsWith(redirectUri())) return; const url = new URL(href); const returnedState = url.searchParams.get("state"); const returnedCode = url.searchParams.get("code"); const error = url.searchParams.get("error"); if (error) { finish(null, error); return; } if (returnedState !== state) { finish(null, "state mismatch"); return; } finish(returnedCode, null); }, 400); }); if (!code) return; setState({ isLoading: true }); try { const res = await fetch(tokenEndpoint(cfg), { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", client_id: cfg.clientId, code, redirect_uri: redirectUri(), code_verifier: codeVerifier }) }); if (!res.ok) throw new Error(`token exchange failed: ${res.status}`); applyTokens(parseTokenResponse(await res.json())); } catch (err) { console.warn("[reqdesk-widget] token exchange failed:", err); setState({ isLoading: false }); } } async function logout() { if (!config) { clearTokens(); return; } const cfg = config; const refreshToken = tokens?.refreshToken; clearTokens(); if (refreshToken) try { await fetch(logoutEndpoint(cfg), { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: cfg.clientId, refresh_token: refreshToken }) }); } catch {} } //#endregion //#region src/auth/identity-resolver.ts const KNOWN_MODES = [ "sso", "email", "signed" ]; const SAFE_DEFAULT = ["sso", "email"]; /** * Single source of truth that decides which identity UX the widget should show next. Pure function; * trivially unit-testable. See `sdk/widget/src/auth/identity-resolver.test.ts` for coverage of every * branch in the decision table below. * * Precedence: * 1. If the host user is already SSO-authenticated, always use SSO. * 2. effectiveAllowed = intersection(serverAllowed, override ?? serverAllowed). If the result is * empty we fall back to the safe default so the widget is never completely blocked. * 3. signed identity present and allowed → `signed`. * 4. plain email (no userHash) present and email mode allowed → `unsigned`. * 5. SSO allowed → `sso`. * 6. email allowed → `email-prompt`. * 7. otherwise → `error: no-auth-configured`. */ function resolveIdentityMode(args) { if (args.isSsoAuthenticated) return { kind: "sso" }; const sanitizedServer = sanitizeModes(args.serverAllowed); const baseServer = sanitizedServer.length > 0 ? sanitizedServer : [...SAFE_DEFAULT]; const override = normalizeOverride(args.override); const effective = override.length > 0 ? baseServer.filter((mode) => override.includes(mode)) : baseServer; const allowed = effective.length > 0 ? effective : baseServer; const email = args.customer?.email?.trim(); const name = args.customer?.name; const userHash = args.customer?.userHash; const userHashTimestamp = args.customer?.userHashTimestamp; if (email && userHash && typeof userHashTimestamp === "number" && allowed.includes("signed")) return { kind: "signed", email, name, userHash, userHashTimestamp }; if (email && allowed.includes("email")) return { kind: "unsigned", email, name }; if (allowed.includes("sso")) return { kind: "sso" }; if (allowed.includes("email")) return { kind: "email-prompt" }; return { kind: "error", reason: "no-auth-configured" }; } function sanitizeModes(modes) { if (!Array.isArray(modes)) return []; const unique = /* @__PURE__ */ new Set(); for (const mode of modes) if (KNOWN_MODES.includes(mode)) unique.add(mode); return Array.from(unique); } function normalizeOverride(override) { if (!override) return []; if (Array.isArray(override)) return sanitizeModes(override); return KNOWN_MODES.includes(override) ? [override] : []; } //#endregion //#region src/react/registry-context.tsx const RegistryContext = (0, react.createContext)(null); function RegistryProvider({ registry, children }) { const instance = registry ?? require_trigger_css.getRegistry(); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, { value: instance, children }); } function useRegistry() { const ctx = (0, react.useContext)(RegistryContext); if (ctx) return ctx; return require_trigger_css.getRegistry(); } function useRegistrySnapshot(selector) { const registry = useRegistry(); return (0, react.useSyncExternalStore)(registry.subscribe, () => selector(registry.getSnapshot()), () => selector(registry.getSnapshot())); } //#endregion //#region src/react/notifications.tsx const NotificationContext = (0, react.createContext)(null); let currentPush = null; let currentDismiss = null; let currentClear = null; /** * Push a notification from outside React (e.g. from a TanStack `QueryCache.onError`). Silently * no-ops when no `` is mounted — error handlers call this eagerly, the * absence of a mounted provider is not itself a failure. */ function pushNotificationExternal(n) { if (!currentPush) return null; return currentPush(n); } const DEFAULT_AUTO_DISMISS_MS = { success: 4e3, info: 6e3, warning: 8e3, error: null }; const MAX_VISIBLE = 3; function NotificationProvider({ children }) { const [notifications, setNotifications] = (0, react.useState)([]); const timeoutsRef = (0, react.useRef)(/* @__PURE__ */ new Map()); const dismiss = (0, react.useCallback)((id) => { const handle = timeoutsRef.current.get(id); if (handle) { clearTimeout(handle); timeoutsRef.current.delete(id); } setNotifications((prev) => prev.filter((n) => n.id !== id)); }, []); const push = (0, react.useCallback)((incoming) => { const id = incoming.id ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `rqd-notif-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`); const autoDismissMs = incoming.autoDismissMs === void 0 ? DEFAULT_AUTO_DISMISS_MS[incoming.kind] : incoming.autoDismissMs; const next = { id, kind: incoming.kind, title: incoming.title, detail: incoming.detail, autoDismissMs }; setNotifications((prev) => { const queue = [...prev.filter((n) => !(n.kind === next.kind && n.title === next.title)), next]; while (queue.length > MAX_VISIBLE) { const dropped = queue.shift(); if (dropped) { const h = timeoutsRef.current.get(dropped.id); if (h) { clearTimeout(h); timeoutsRef.current.delete(dropped.id); } } } return queue; }); if (typeof autoDismissMs === "number" && autoDismissMs > 0) { const handle = setTimeout(() => dismiss(id), autoDismissMs); timeoutsRef.current.set(id, handle); } return id; }, [dismiss]); const clear = (0, react.useCallback)(() => { for (const handle of timeoutsRef.current.values()) clearTimeout(handle); timeoutsRef.current.clear(); setNotifications([]); }, []); (0, react.useEffect)(() => { currentPush = push; currentDismiss = dismiss; currentClear = clear; return () => { if (currentPush === push) currentPush = null; if (currentDismiss === dismiss) currentDismiss = null; if (currentClear === clear) currentClear = null; }; }, [ push, dismiss, clear ]); (0, react.useEffect)(() => { const timeouts = timeoutsRef.current; return () => { for (const handle of timeouts.values()) clearTimeout(handle); timeouts.clear(); }; }, []); const value = (0, react.useMemo)(() => ({ notifications, push, dismiss, clear }), [ notifications, push, dismiss, clear ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationContext.Provider, { value, children }); } /** * Push / dismiss / clear notifications. Usable from any component inside `` — * hosts can reuse the same stack for their own in-widget messages via `useReqdesk().` wrappers. */ function useNotify() { const ctx = (0, react.useContext)(NotificationContext); if (!ctx) return { push: () => "", dismiss: () => {}, clear: () => {} }; return { push: ctx.push, dismiss: ctx.dismiss, clear: ctx.clear }; } function useNotifications() { return (0, react.useContext)(NotificationContext)?.notifications ?? []; } /** * Render the notification queue at the top of the widget panel. Accessible: * - `aria-live="polite"` so screen readers announce new items without interrupting. * - Per-item `role="alert"` on errors (critical), `role="status"` on others. * - Keyboard-dismissible via the ✕ button. * * Place inside the widget's shadow DOM — no host-page leakage. */ function NotificationStack({ dismissLabel = "Dismiss" }) { const notifications = useNotifications(); const ctx = (0, react.useContext)(NotificationContext); if (!ctx || notifications.length === 0) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-notification-stack", "aria-live": "polite", "aria-atomic": "false", children: notifications.map((n) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-notification rqd-notification-${n.kind}`, role: n.kind === "error" ? "alert" : "status", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-notification-icon", "aria-hidden": "true", children: iconFor(n.kind) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-notification-body", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { className: "rqd-notification-title", children: n.title }), n.detail && n.detail.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { className: "rqd-notification-list", children: n.detail.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: d }, i)) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-notification-dismiss", onClick: () => ctx.dismiss(n.id), "aria-label": dismissLabel, children: "✕" }) ] }, n.id)) }); } function iconFor(kind) { switch (kind) { case "error": return "⚠"; case "warning": return "⚠"; case "info": return "ℹ"; case "success": return "✓"; } } //#endregion //#region src/react/sdk-react-adapter.tsx /** * Build an ApiClient-shaped object from the widget's standalone fetch * functions. The widget historically called these directly; sdk-react * views consume them via context. This adapter is the seam that lets * us collapse the widget's view duplicates onto sdk-react canonicals * without rewriting the underlying HTTP layer. */ function buildApiClientAdapter() { return { async submitTicket(projectId, data) { return require_trigger_css.submitTicket(projectId, data); }, async getTicketDetail(ticketId, includeMedia = true) { return require_trigger_css.getTicketDetail(ticketId, includeMedia); }, async listMyTickets(projectId, userId, page = 1, pageSize = 20, showAll = false) { return require_trigger_css.listMyTickets(projectId, userId, page, pageSize, showAll); }, async closeTicket(ticketId) { await require_trigger_css.closeTicket(ticketId); }, async resolveTicket(ticketId) { await require_trigger_css.closeTicket(ticketId); }, async submitReply(ticketId, body) { return require_trigger_css.submitReply(ticketId, body); }, async submitTrackingReply(token, body) { await require_trigger_css.submitTrackingReply(token, body); }, async trackTicket(token) { return await require_trigger_css.trackTicket(token); }, async getCategories(projectId, parentId) { return require_trigger_css.getCategories(projectId, parentId); }, async createCategory(projectId, name, parentId) { return require_trigger_css.createCategory(projectId, name, parentId); }, async listProjectTags(projectId) { return require_trigger_css.listProjectTags(projectId); }, async createTag(projectId, name, color) { return require_trigger_css.createTag(projectId, name, color); }, async getWidgetConfig(projectId) { return require_trigger_css.getWidgetConfig(projectId); }, async listProjects(workspaceId) { return require_trigger_css.listProjects(workspaceId); }, async uploadAttachment(ticketId, file, onProgress) { return require_trigger_css.uploadAttachment(ticketId, file, onProgress); }, async uploadReplyAttachment(ticketId, replyId, file, onProgress) { return require_trigger_css.uploadReplyAttachment(ticketId, replyId, file, onProgress); }, async getAttachmentDownloadUrl(attachmentId) { return require_trigger_css.getAttachmentDownloadUrl(attachmentId); }, async validateKey() { throw new Error("validateKey is not supported by the widget adapter"); }, async checkServerIdentity(_baseUrl) { throw new Error("checkServerIdentity is not supported by the widget adapter"); }, async resolveWidgetUser(projectId, email) { return require_trigger_css.resolveWidgetUser(projectId, email); } }; } /** * Storage adapter backed by `localStorage`, namespaced by api key. * Mirrors the widget's existing per-key storage isolation so the * sdk-react views don't see another tab's state. */ function buildStorageAdapter(apiKey) { const prefix = `reqdesk:${apiKey}:`; return { async get(key) { try { const raw = localStorage.getItem(prefix + key); if (raw == null) return null; try { return JSON.parse(raw); } catch { return raw; } } catch { return null; } }, async set(key, value) { try { const payload = typeof value === "string" ? value : JSON.stringify(value); localStorage.setItem(prefix + key, payload); } catch {} }, async remove(key) { try { localStorage.removeItem(prefix + key); } catch {} } }; } /** * Bridges the widget's existing ReqdeskContext (used by FloatingWidget * shell) to sdk-react's ReqdeskContext (used by the canonical views). * Render once at the top of the React tree, inside the widget provider * but above the FloatingWidget body, so children can read either * context interchangeably. */ function SdkReactBridge({ children }) { const widgetCtx = useReqdeskContext(); const apiClient = (0, react.useMemo)(() => buildApiClientAdapter(), []); const storage = (0, react.useMemo)(() => buildStorageAdapter(widgetCtx.apiKey), [widgetCtx.apiKey]); const authProvider = (0, react.useMemo)(() => { if (!widgetCtx.isAuthenticated) return void 0; return { getAccessToken: async () => null, isAuthenticated: () => widgetCtx.isAuthenticated, getUserInfo: async () => widgetCtx.userEmail ? { id: widgetCtx.userEmail, email: widgetCtx.userEmail, name: widgetCtx.userName ?? null } : null }; }, [ widgetCtx.isAuthenticated, widgetCtx.userEmail, widgetCtx.userName ]); const customer = (0, react.useMemo)(() => { if (widgetCtx.customer) return widgetCtx.customer; if (widgetCtx.isAuthenticated && widgetCtx.userEmail) return { email: widgetCtx.userEmail, name: widgetCtx.userName ?? void 0 }; }, [ widgetCtx.customer, widgetCtx.isAuthenticated, widgetCtx.userEmail, widgetCtx.userName ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.ReqdeskProvider, { apiClient, authProvider, storage, projectId: widgetCtx.projectId, language: widgetCtx.language, translations: widgetCtx.translations, theme: widgetCtx.theme, customer, children }); } //#endregion //#region src/react/ReqdeskProvider.tsx const LOCALE_TABLES = { en: require_trigger_css.en, ar: require_trigger_css.ar }; const SAFE_DEFAULT_MODES = ["sso", "email"]; const ReqdeskContext = (0, react.createContext)(null); function useReqdeskContext() { const ctx = (0, react.useContext)(ReqdeskContext); if (!ctx) throw new Error("useReqdesk must be used within a "); return ctx; } /** Internal singletons so every `` mount reuses the same query cache while * still letting tests reset the module with a fresh QueryClient if they import this directly. */ function translate(locale, key, translations) { if (translations?.[key]) return translations[key]; return (LOCALE_TABLES[locale ?? "en"] ?? LOCALE_TABLES.en)[key] ?? key; } /** * Build the widget's QueryClient with a `QueryCache` + `MutationCache` that funnels every * unhandled error into the notification stack. Per-query / per-mutation opt-outs flow through * `meta: WidgetQueryMeta | WidgetMutationMeta`: * - `meta.silent` — suppress the toast entirely. * - `meta.allow404` — skip the 404 toast (probe-style queries). * - `meta.form` — skip the 422 toast (the view renders per-field errors inline via * ``). Non-422 errors still toast so network / 5xx failures never hide. * * `translate` is passed in so the cache callbacks can localize copy against the current locale * without reaching into a stale module-level snapshot. */ function buildWidgetQueryClient(getLocale) { const notifyFromError = (error, meta, source) => { const widgetErr = asWidgetError(error); if (shouldSuppressToast(widgetErr, meta, source)) return; const { locale, translations } = getLocale(); const t = (k) => translate(locale, k, translations); pushNotificationExternal({ kind: "error", title: widgetErr.message || defaultTitleFor(widgetErr, t), detail: widgetErr.errors && widgetErr.errors.length > 0 ? widgetErr.errors.filter((fe) => !fe.field).map((fe) => fe.detail || fe.code).filter(Boolean) : void 0, autoDismissMs: null }); }; return new _tanstack_react_query.QueryClient({ queryCache: new _tanstack_react_query.QueryCache({ onError: (error, query) => notifyFromError(error, query.meta, "query") }), mutationCache: new _tanstack_react_query.MutationCache({ onError: (error, _vars, _ctx, mutation) => notifyFromError(error, mutation.meta, "mutation") }), defaultOptions: { queries: { staleTime: 300 * 1e3, gcTime: 600 * 1e3, retry: (failureCount, error) => { const status = error?.status; if (status && status >= 400 && status < 500 && status !== 408) return false; if (status && status >= 500) return false; return failureCount < 2; }, retryDelay: (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 3e4), refetchOnWindowFocus: false }, mutations: { retry: false } } }); } function asWidgetError(error) { if (error instanceof Error && "code" in error) return error; if (error && typeof error === "object" && "code" in error && "message" in error) return error; return { code: "UNKNOWN", message: error instanceof Error ? error.message : "Something went wrong." }; } function shouldSuppressToast(err, meta, source) { if (!meta) return false; if (meta.silent) return true; if (source === "query") { if (meta.allow404 && err.status === 404) return true; } if (source === "mutation") { if (meta.form && err.status === 422) return true; } return false; } function defaultTitleFor(err, t) { if (!err.status) return t("error.network"); if (err.status === 401) return t("error.unauthenticated"); if (err.status === 403) return t("error.forbidden"); if (err.status === 404) return t("error.notFound"); if (err.status >= 500) return t("error.server"); if (err.status === 422) return t("error.validation"); return t("error.generic"); } let sharedQueryClient = null; const localeGetterRef = { current: () => ({ locale: "en", translations: void 0 }) }; function getWidgetQueryClient() { if (!sharedQueryClient) sharedQueryClient = buildWidgetQueryClient(() => localeGetterRef.current()); return sharedQueryClient; } function ReqdeskProvider({ apiKey, apiUrl, auth, theme, language, customer, translations, authMode, display, actions, initialPreferences, userPreferences, onPreferencesChange, menuCloseOnAction, hideFab, hideDisplayModePicker, fabIcon, children }) { const initialized = (0, react.useRef)(false); const [authState, setAuthState] = (0, react.useState)(getAuthState); const [serverConfig, setServerConfig] = (0, react.useState)(null); const isWorkspaceKey = apiKey.startsWith("rqd_ws_"); const [projects, setProjects] = (0, react.useState)([]); const [selectedProjectId, setSelectedProjectId] = (0, react.useState)(null); const [isProjectLoading, setIsProjectLoading] = (0, react.useState)(false); const saved = initialized.current ? null : require_trigger_css.loadWidgetConfig(apiKey); const resolvedLanguage = language ?? saved?.language ?? "en"; const resolvedTheme = theme ?? saved?.theme; const registryInitialized = (0, react.useRef)(false); if (!registryInitialized.current) { const registry = require_trigger_css.getRegistry(); registry.setApiKeyForStorage(apiKey); registry.setInitialPreferences(initialPreferences ?? {}); registry.setConfigDisplay(display); registry.setMenuCloseOnAction(menuCloseOnAction ?? true); registry.setHostPreferencesCallback(onPreferencesChange); const resolvedPrefs = require_trigger_css.resolvePreferences({ apiKey, initialPreferences, userPreferences }); registry.initPreferencesSnapshot(resolvedPrefs); if (actions) for (const action of actions) registry.addAction(action); registryInitialized.current = true; } (0, react.useEffect)(() => { require_trigger_css.getRegistry().setHostPreferencesCallback(onPreferencesChange); }, [onPreferencesChange]); (0, react.useEffect)(() => { require_trigger_css.getRegistry().setConfigDisplay(display); }, [display]); (0, react.useEffect)(() => { require_trigger_css.getRegistry().setMenuCloseOnAction(menuCloseOnAction ?? true); }, [menuCloseOnAction]); (0, react.useEffect)(() => { if (!initialized.current) { require_trigger_css.configureWidgetClient(apiUrl || window.location.origin, apiKey); require_trigger_css.setWidgetCustomer(customer ?? null); initialized.current = true; if (!isWorkspaceKey) require_trigger_css.getWidgetConfig("_current").then((config) => { setServerConfig(config); if (!auth && config.authIssuerUri && config.authClientId) initWidgetAuth({ issuerUri: config.authIssuerUri, clientId: config.authClientId }); }).catch(() => {}); if (auth) initWidgetAuth(auth); } }, [ apiKey, apiUrl, auth, isWorkspaceKey ]); (0, react.useEffect)(() => { setAuthState(getAuthState()); return onAuthStateChange(setAuthState); }, []); (0, react.useEffect)(() => { require_trigger_css.setWidgetCustomer(customer ?? null); }, [customer]); (0, react.useEffect)(() => { require_trigger_css.saveWidgetConfig(apiKey, { language: resolvedLanguage, theme: resolvedTheme }); }, [ apiKey, resolvedLanguage, resolvedTheme ]); (0, react.useEffect)(() => { if (!authState.isAuthenticated) { setSelectedProjectId(isWorkspaceKey ? null : "_current"); setProjects([]); setIsProjectLoading(false); return; } setIsProjectLoading(true); require_trigger_css.listProjects(serverConfig?.workspaceId ?? void 0).then((projectList) => { setProjects(projectList); const userKey = authState.userEmail ?? "anon"; const saved = require_trigger_css.loadSelectedProject(apiKey, userKey); if (saved && projectList.some((p) => p.id === saved)) setSelectedProjectId(saved); else if (serverConfig?.id && projectList.some((p) => p.id === serverConfig.id)) { setSelectedProjectId(serverConfig.id); require_trigger_css.saveSelectedProject(apiKey, userKey, serverConfig.id); } else if (projectList.length === 1) { setSelectedProjectId(projectList[0].id); require_trigger_css.saveSelectedProject(apiKey, userKey, projectList[0].id); } else if (projectList.length > 1) setSelectedProjectId(null); else setSelectedProjectId(isWorkspaceKey ? null : "_current"); }).catch(() => { setSelectedProjectId(isWorkspaceKey ? null : "_current"); }).finally(() => setIsProjectLoading(false)); }, [ authState.isAuthenticated, serverConfig, apiKey, authState.userEmail, isWorkspaceKey ]); const selectProject = (0, react.useCallback)((id) => { setSelectedProjectId(id); require_trigger_css.saveSelectedProject(apiKey, authState.userEmail ?? "anon", id); }, [apiKey, authState.userEmail]); const mergedTheme = (0, react.useMemo)(() => { const base = { ...resolvedTheme ?? {} }; if (serverConfig) { if (!base.brandName) base.brandName = serverConfig.brandName || serverConfig.projectName; if (!base.logo && serverConfig.logoUrl) base.logo = serverConfig.logoUrl; } return base; }, [resolvedTheme, serverConfig]); const mergedAuth = auth ?? (serverConfig?.authIssuerUri && serverConfig?.authClientId ? { issuerUri: serverConfig.authIssuerUri, clientId: serverConfig.authClientId } : void 0); const resolvedProjectId = selectedProjectId ?? serverConfig?.id ?? "_current"; const isMultiProject = authState.isAuthenticated && projects.length > 1; const serverAllowedModes = (0, react.useMemo)(() => { const raw = serverConfig?.allowedAuthModes; if (!Array.isArray(raw) || raw.length === 0) return SAFE_DEFAULT_MODES; return raw.filter((m) => m === "sso" || m === "email" || m === "signed"); }, [serverConfig?.allowedAuthModes]); const resolvedIdentityMode = (0, react.useMemo)(() => resolveIdentityMode({ customer, serverAllowed: serverAllowedModes, override: authMode, isSsoAuthenticated: authState.isAuthenticated }), [ customer, serverAllowedModes, authMode, authState.isAuthenticated ]); const value = (0, react.useMemo)(() => ({ apiKey, auth: mergedAuth, theme: mergedTheme, language: resolvedLanguage, customer, translations, isAuthenticated: authState.isAuthenticated, userEmail: authState.userEmail, userName: authState.userName, showAllTickets: serverConfig?.showAllTickets ?? false, projectId: resolvedProjectId, projects, isMultiProject, selectProject, isProjectLoading, serverAllowedModes, resolvedIdentityMode, hideFab, hideDisplayModePicker, fabIcon }), [ apiKey, mergedAuth, mergedTheme, resolvedLanguage, customer, translations, authState, serverConfig, resolvedProjectId, projects, isMultiProject, selectProject, isProjectLoading, serverAllowedModes, resolvedIdentityMode, hideFab, hideDisplayModePicker, fabIcon ]); (0, react.useEffect)(() => { require_trigger_css.getRegistry().setContext({ user: customer ? { email: customer.email, name: customer.name } : null, projectId: resolvedProjectId, locale: resolvedLanguage }); }, [ customer, resolvedProjectId, resolvedLanguage ]); (0, react.useEffect)(() => { localeGetterRef.current = () => ({ locale: resolvedLanguage, translations }); }, [resolvedLanguage, translations]); const queryClient = getWidgetQueryClient(); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReqdeskContext.Provider, { value, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_query.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SdkReactBridge, { children }) }) }) }) }); } //#endregion //#region src/react/useReqdesk.ts function useReqdesk() { const ctx = useReqdeskContext(); const registry = useRegistry(); const [isLoading, setIsLoading] = (0, react.useState)(false); const [error, setError] = (0, react.useState)(null); return { submitTicket: (0, react.useCallback)(async (data) => { setIsLoading(true); setError(null); try { return await require_trigger_css.submitTicket(ctx.projectId, data); } catch (err) { const widgetError = err; setError(widgetError); throw widgetError; } finally { setIsLoading(false); } }, [ctx.projectId]), trackTicket: (0, react.useCallback)(async (token) => { setIsLoading(true); setError(null); try { return await require_trigger_css.trackTicket(token); } catch (err) { const widgetError = err; setError(widgetError); throw widgetError; } finally { setIsLoading(false); } }, []), submitTrackingReply: (0, react.useCallback)(async (token, body) => { setIsLoading(true); setError(null); try { await require_trigger_css.submitTrackingReply(token, body); } catch (err) { const widgetError = err; setError(widgetError); throw widgetError; } finally { setIsLoading(false); } }, []), isLoading, error, isOpen: useRegistrySnapshot((s) => s.isOpen), currentView: useRegistrySnapshot((s) => s.currentView), currentDisplayMode: useRegistrySnapshot((s) => s.currentDisplayMode), preferences: useRegistrySnapshot((s) => s.preferences), ...(0, react.useMemo)(() => ({ open: registry.open, close: registry.close, toggle: registry.toggle, openMenu: registry.openMenu, openAction: registry.openAction, setDisplayMode: registry.setDisplayMode, setPreferences: registry.setPreferences, addAction: registry.addAction, removeAction: registry.removeAction, on: registry.on }), [registry]) }; } //#endregion //#region src/react/shadow-root.tsx function ShadowRoot({ children }) { const hostRef = (0, react.useRef)(null); const [mountPoint, setMountPoint] = (0, react.useState)(null); (0, react.useEffect)(() => { const host = hostRef.current; if (!host || host.shadowRoot) return; const shadow = host.attachShadow({ mode: "open" }); const style = document.createElement("style"); style.textContent = require_trigger_css.getWidgetStyles(); shadow.appendChild(style); const mount = document.createElement("div"); mount.className = "rqd-root"; shadow.appendChild(mount); setMountPoint(mount); }, []); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ref: hostRef, children: mountPoint && (0, react_dom.createPortal)(children, mountPoint) }); } //#endregion //#region src/react/TicketForm.tsx const translations$1 = { en: require_trigger_css.en, ar: require_trigger_css.ar }; function TicketForm({ mode = "inline", onTicketCreated, onError, className, style }) { const ctx = useReqdeskContext(); const { submitTicket, isLoading } = useReqdesk(); const [success, setSuccess] = (0, react.useState)(null); const [errors, setErrors] = (0, react.useState)({}); const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations$1[ctx.language] ?? translations$1.en)[key] ?? key; }, [ctx.language, ctx.translations]); const handleSubmit = (0, react.useCallback)(async (e) => { e.preventDefault(); const form = e.currentTarget; const formData = new FormData(form); const title = formData.get("title")?.trim() ?? ""; const email = formData.get("email")?.trim() ?? ""; const newErrors = {}; if (!title) newErrors.title = t("error.required"); else if (title.length < 5) newErrors.title = t("error.titleMin"); if (!email) newErrors.email = t("error.required"); else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) newErrors.email = t("error.emailInvalid"); if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } setErrors({}); const data = { title, description: formData.get("description")?.trim() || void 0, email, priority: formData.get("priority") ?? "medium" }; try { const result = await submitTicket(data); if (result.trackingToken) require_trigger_css.saveTrackingToken(ctx.apiKey, result.trackingToken); setSuccess(result); onTicketCreated?.(result); } catch (err) { onError?.(err); } }, [ submitTicket, ctx.apiKey, ctx.translations, ctx.language, onTicketCreated, onError, t ]); const cssVars = require_trigger_css.themeToStyle(ctx.theme); const content = success ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-success", style: { textAlign: "center", padding: "24px 0" }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { fontSize: 48, marginBottom: 12 }, children: "✅" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { style: { margin: "0 0 8px", fontSize: 18 }, children: t("success.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [t("success.ticketNumber"), success.ticketNumber] }), success.trackingToken && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { fontSize: 13, color: "var(--rqd-text-secondary)" }, children: t("success.trackingHint") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-token-box", children: success.trackingToken }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", onClick: () => navigator.clipboard.writeText(success.trackingToken), children: t("success.copyToken") }) ] }) ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", { className: "rqd-form", onSubmit: handleSubmit, noValidate: true, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", name: "title", placeholder: t("form.titlePlaceholder"), required: true }), errors.title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-error-text", children: errors.title }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.description") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", { className: "rqd-textarea", name: "description", placeholder: t("form.descriptionPlaceholder") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.email") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", name: "email", type: "email", placeholder: t("form.emailPlaceholder"), required: true }), errors.email && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-error-text", children: errors.email }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.priority") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", { className: "rqd-select", name: "priority", defaultValue: "medium", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "low", children: t("form.priorityLow") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "medium", children: t("form.priorityMedium") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "high", children: t("form.priorityHigh") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "urgent", children: t("form.priorityUrgent") }) ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-primary", type: "submit", disabled: isLoading, children: isLoading ? t("form.submitting") : t("form.submit") }) ] }); if (mode === "floating") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className, style: { ...style, ...cssVars }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-body", children: content }) }) }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `rqd-inline ${className ?? ""}`, style: cssVars, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-body", children: content }) }) }); } //#endregion //#region src/react/SupportPortal.tsx function SupportPortal({ className }) { const ctx = useReqdeskContext(); const { isLoading } = useReqdesk(); const cssVars = require_trigger_css.themeToStyle(ctx.theme); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `rqd-inline ${className ?? ""}`, style: cssVars, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-body", children: isLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { textAlign: "center", color: "var(--rqd-text-secondary)" }, children: "Loading..." }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { textAlign: "center", color: "var(--rqd-text-secondary)", padding: "24px 0" }, children: "Support Portal — coming in a future update." }) }) }) }); } //#endregion //#region src/react/views/SubmitTicketView.tsx /** * Re-export of `@reqdesk/sdk-react` canonical view, with a thin shim * that translates the widget's historical prop names into the * canonical's API. Pre-attached File handles get unwrapped into * dataURL strings; slot props are preserved on the type but currently * pass through as a no-op (the canonical's slot wiring lives one * commit ahead). */ function SubmitTicketView$1({ projectId: _projectId, onSuccess, onError, isAuthenticated, userEmail, slots: _slots, preAttachedFiles, preFilledMetadata: _preFilledMetadata, onDraftChange }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.SubmitTicketView, { onSuccess, onError, isAuthenticated, userEmail, attachments: (0, react.useMemo)(() => { if (!preAttachedFiles?.length) return void 0; return preAttachedFiles.map((p) => p.dataUrl).filter((s) => typeof s === "string" && s.length > 0); }, [preAttachedFiles]), onDraftChange }); } //#endregion //#region src/react/views/MyTicketsView.tsx function MyTicketsView$1({ onSelectTicket, isAuthenticated, userEmail }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.MyTicketsView, { onSelectTicket, isAuthenticated, userEmail }); } //#endregion //#region src/react/views/TicketDetailView.tsx function TicketDetailView$1({ ticketId, onBack }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.TicketDetailView, { ticketId, onBack }); } //#endregion //#region src/react/views/TrackTicketView.tsx function TrackTicketView$1({ onTrackSuccess }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.TrackTicketView, { onTrackSuccess }); } //#endregion //#region src/react/views/keyboard-nav.ts /** * Resolve a keydown event into an abstract nav action. * * ArrowDown → focus next (wraps) * ArrowUp → focus previous (wraps) * Home → focus first * End → focus last * Enter → activate current * Space → activate current * Escape → close * * @param key `KeyboardEvent.key` string * @param currentIndex current focused index * @param itemCount total number of visible menuitems */ function resolveMenuNav(key, currentIndex, itemCount) { if (itemCount <= 0) { if (key === "Escape") return { kind: "close" }; return { kind: "noop" }; } switch (key) { case "ArrowDown": return { kind: "focus", index: (currentIndex + 1) % itemCount }; case "ArrowUp": return { kind: "focus", index: (currentIndex - 1 + itemCount) % itemCount }; case "Home": return { kind: "focus", index: 0 }; case "End": return { kind: "focus", index: itemCount - 1 }; case "Enter": case " ": case "Space": return { kind: "activate", index: currentIndex }; case "Escape": return { kind: "close" }; default: return { kind: "noop" }; } } //#endregion //#region src/react/views/MenuList.tsx function resolveLocalizedString(value, locale) { if (value === void 0 || value === null) return ""; if (typeof value === "string") return value; const direct = value[locale]; if (direct) return direct; const en = value.en; if (en) return en; return Object.values(value)[0] ?? ""; } function SvgPath({ path }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 22, height: 22, viewBox: "0 0 24 24", fill: "currentColor", style: { display: "block", flexShrink: 0 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: path }) }); } function renderActionIcon(icon) { if (icon === void 0 || icon === null) return null; if (typeof icon === "string") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgPath, { path: icon }); if (typeof icon === "object" && "kind" in icon) { if (icon.kind === "path") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgPath, { path: icon.path }); if (icon.kind === "url") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: icon.src, width: 22, height: 22, alt: "", "aria-hidden": "true" }); return null; } return icon; } function renderBadge(value) { if (value === null || value === void 0 || value === "") return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-label": `${value}`, style: { marginInlineStart: "auto", padding: "2px 8px", borderRadius: 999, background: "var(--rqd-primary-light, rgba(15,94,86,0.12))", color: "var(--rqd-primary-dark, #0F5E56)", fontSize: 11, fontWeight: 700, lineHeight: 1.2 }, children: String(value) }); } /** * WAI-ARIA menu pattern implementation. Single tab stop (roving tabindex), arrow-key traversal, * Home/End jumps, Enter/Space activation, Escape closes the widget. Custom actions merged with * built-ins via registry.resolveRenderOrder; visibility predicates re-run each render. */ function MenuList({ builtIns }) { const registry = useRegistry(); const actions = useRegistrySnapshot((s) => s.actions); const locale = useRegistrySnapshot((s) => s.locale); const user = useRegistrySnapshot((s) => s.user); const projectId = useRegistrySnapshot((s) => s.projectId); const predicateCtx = (0, react.useMemo)(() => ({ actionId: "", input: void 0, close: registry.close, keepOpen: () => {}, setBadge: () => {}, setLoading: () => {}, user, projectId, locale, openAction: registry.openAction }), [ registry, user, projectId, locale ]); const visibleItems = (0, react.useMemo)(() => { const builtInMap = new Map(builtIns.map((b) => [b.id, b])); const actionMap = new Map(actions.map((a) => [a.id, a])); const order = registry.resolveRenderOrder(builtIns.map((b) => b.id)); const items = []; for (const entry of order) if (entry.kind === "built-in") { const b = builtInMap.get(entry.id); if (!b || b.hidden) continue; items.push({ kind: "built-in", item: b }); } else { const a = actionMap.get(entry.id); if (!a) continue; if (typeof a.visible === "boolean" && !a.visible) continue; if (typeof a.visible === "function") try { if (!a.visible({ ...predicateCtx, actionId: a.id })) continue; } catch (cause) { registry.emitEvent("error", { code: "action-visibility-failed", message: `Visibility predicate for "${a.id}" threw`, actionId: a.id, cause }); continue; } items.push({ kind: "custom", item: a }); } return items; }, [ actions, builtIns, registry, predicateCtx ]); const [focusedIndex, setFocusedIndex] = (0, react.useState)(0); const refs = (0, react.useRef)([]); (0, react.useEffect)(() => { if (focusedIndex >= visibleItems.length) setFocusedIndex(0); }, [visibleItems.length, focusedIndex]); const focusAt = (0, react.useCallback)((index) => { const el = refs.current[index]; if (el) { setFocusedIndex(index); el.focus(); } }, []); const handleActivate = (0, react.useCallback)((index) => { const entry = visibleItems[index]; if (!entry) return; const el = refs.current[index]; if (el) registry.recordActivator(el); if (entry.kind === "built-in") entry.item.onActivate(); else registry.openAction(entry.item.id); }, [visibleItems, registry]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { role: "menu", className: "rqd-menu", onKeyDown: (0, react.useCallback)((e) => { const action = resolveMenuNav(e.key, focusedIndex, visibleItems.length); switch (action.kind) { case "focus": e.preventDefault(); focusAt(action.index); break; case "activate": e.preventDefault(); handleActivate(action.index); break; case "close": e.preventDefault(); registry.close(); break; default: break; } }, [ visibleItems.length, focusedIndex, focusAt, handleActivate, registry ]), "aria-orientation": "vertical", children: visibleItems.map((entry, index) => { const isCustom = entry.kind === "custom"; const id = isCustom ? entry.item.id : entry.item.id; const label = isCustom ? resolveLocalizedString(entry.item.label, locale) : entry.item.label; const description = isCustom ? resolveLocalizedString(entry.item.description, locale) : entry.item.description; const iconNode = isCustom ? renderActionIcon(entry.item.icon) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgPath, { path: entry.item.iconPath }); const badge = isCustom ? renderBadge(entry.item.badge) : null; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { ref: (el) => { refs.current[index] = el; }, type: "button", role: "menuitem", tabIndex: index === focusedIndex ? 0 : -1, className: "rqd-menu-item", onClick: () => handleActivate(index), onFocus: () => setFocusedIndex(index), children: [ iconNode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-menu-icon", children: iconNode }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-menu-text", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-label", children: label }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-desc", children: description })] }), badge ] }, id); }) }); } //#endregion //#region src/react/FloatingWidget.tsx const translations = { en: require_trigger_css.en, ar: require_trigger_css.ar }; const CORNER_LABEL_KEY = { "top-start": "prefs.topStart", "top-end": "prefs.topEnd", "bottom-start": "prefs.bottomStart", "bottom-end": "prefs.bottomEnd" }; const CORNERS = [ "top-start", "top-end", "bottom-start", "bottom-end" ]; const ICONS = { close: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z", newTicket: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z", tickets: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11zM8 15.01l1.41 1.41L11 14.84V19h2v-4.16l1.59 1.59L16 15.01 12.01 11z", track: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z", kb: "M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z", back: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z", backRtl: "M4 11h12.17l-5.59-5.59L12 4l8 8-8 8-1.41-1.41L16.17 13H4v-2z", settings: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.6 3.6 0 0112 15.6z", switchProject: "M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z", expand: "M5 5h5V3H3v7h2V5zm5 14H5v-5H3v7h7v-2zm4-16v2h5v5h2V3h-7zm5 16h-5v2h7v-7h-2v5z", collapse: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z", logout: "M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5-5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z" }; function SvgIcon({ path, size = 20 }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", style: { display: "block", flexShrink: 0 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: path }) }); } /** * Resolves the host-supplied `fabIcon` value. A `ReactNode` renders as-is so hosts can drop in * any JSX (SVG, icon-font , ). A string is treated as a preset key or raw SVG path and * routed through the shared resolver so vanilla + React bundles stay visually identical. */ function renderFabIcon(fabIcon, size) { if (fabIcon != null && typeof fabIcon !== "string") { if ((0, react.isValidElement)(fabIcon) || Array.isArray(fabIcon) || typeof fabIcon === "number") return fabIcon; } return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: require_trigger_css.resolveFabIconPath(typeof fabIcon === "string" ? fabIcon : void 0), size }); } function buildPanelClassName(args) { const parts = ["rqd-panel"]; if (args.mode === "popover") parts.push(args.posClass); else if (args.mode === "side-sheet") parts.push("rqd-mode-side-sheet", `rqd-side-${args.side}`); else parts.push("rqd-mode-bottom-sheet"); if (args.contained) parts.push("rqd-contained"); if (!args.isOpen) parts.push("rqd-hidden"); if (args.isExpanded && args.mode === "popover") parts.push("rqd-expanded"); if (args.isRepositioning) parts.push("rqd-repositioning"); return parts.join(" "); } function buildPanelStyle(baseVars, display) { const style = { ...baseVars }; if (display.mode === "side-sheet" && display.width) style["--rqd-sheet-width"] = display.width; if (display.mode === "bottom-sheet" && display.height) style["--rqd-sheet-height"] = display.height; return style; } function FloatingWidget({ position: propPosition = "bottom-end", contained = false, expandedWidth, hideFab: hideFabProp, onTicketCreated, onError }) { const ctx = useReqdeskContext(); const registry = useRegistry(); const hideFab = hideFabProp ?? ctx.hideFab ?? false; const currentDisplayMode = useRegistrySnapshot((s) => s.currentDisplayMode); const [isOpen, setIsOpen] = (0, react.useState)(false); const [isExpanded, setIsExpanded] = (0, react.useState)(false); const [view, setView] = (0, react.useState)("home"); const [selectedTicketId, setSelectedTicketId] = (0, react.useState)(null); const [emailCleared, setEmailCleared] = (0, react.useState)(false); const savedPrefs = require_trigger_css.loadWidgetConfig(ctx.apiKey); const [userLang, setUserLang] = (0, react.useState)(savedPrefs?.language ?? null); const [userThemeMode, setUserThemeMode] = (0, react.useState)(savedPrefs?.theme?.mode ?? null); const [userColor, setUserColor] = (0, react.useState)(savedPrefs?.theme?.primaryColor ?? null); const [position, setPosition] = (0, react.useState)(() => require_trigger_css.normalizePosition(savedPrefs?.position ?? propPosition)); const [isRepositioning, setIsRepositioning] = (0, react.useState)(false); const repositionTimer = (0, react.useRef)(null); (0, react.useEffect)(() => { if (!isExpanded) return; const onKey = (e) => { if (e.key === "Escape") setIsExpanded(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [isExpanded]); (0, react.useEffect)(() => { if (!isOpen) return; if (currentDisplayMode.mode === "popover") return; const onKey = (e) => { if (e.key === "Escape") { setIsOpen(false); registry.close(); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [ isOpen, currentDisplayMode.mode, registry ]); const registryIsOpen = useRegistrySnapshot((s) => s.isOpen); (0, react.useEffect)(() => { if (registryIsOpen && !isOpen) setIsOpen(true); if (!registryIsOpen && isOpen) setIsOpen(false); }, [registryIsOpen, isOpen]); (0, react.useEffect)(() => () => { if (repositionTimer.current) clearTimeout(repositionTimer.current); }, []); const activeLang = userLang ?? ctx.language ?? "en"; const activeThemeMode = userThemeMode ?? ctx.theme?.mode ?? "auto"; const activeColor = userColor || ctx.theme?.primaryColor || "#0F5E56"; const activeTheme = { ...ctx.theme, mode: activeThemeMode, primaryColor: activeColor }; const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations[activeLang] ?? translations.en)[key] ?? key; }, [activeLang, ctx.translations]); const isRtl = activeLang === "ar"; const cssVars = { ...require_trigger_css.themeToStyle(activeTheme), ...expandedWidth ? { "--rqd-expanded-width": expandedWidth } : {} }; const posClass = `rqd-${position}`; const sideClass = `rqd-fab-side-${require_trigger_css.positionSide(position)}`; const repositionClass = isRepositioning ? " rqd-repositioning" : ""; const containedClass = contained ? " rqd-contained" : ""; const brandName = ctx.theme?.brandName; const brandLogo = ctx.theme?.logo; const hideBranding = ctx.theme?.hideBranding === true; const fabLabel = brandName ?? t("widget.title"); const projectId = ctx.projectId; const isWorkspaceKey = ctx.apiKey.startsWith("rqd_ws_"); const needsProjectSelection = !ctx.isProjectLoading && ctx.projectId === "_current" && (ctx.isAuthenticated && ctx.projects.length > 1 || isWorkspaceKey); function toggleOpen() { registry.toggle(); if (!isOpen) { setView(needsProjectSelection ? "select-project" : "home"); setSelectedTicketId(null); } } function goHome() { setView("home"); setSelectedTicketId(null); setEmailCleared(false); } function handleLangChange(lang) { setUserLang(lang); require_trigger_css.saveWidgetConfig(ctx.apiKey, { language: lang }); } function handleThemeChange(mode) { setUserThemeMode(mode); require_trigger_css.saveWidgetConfig(ctx.apiKey, { theme: { ...ctx.theme, mode, primaryColor: activeColor } }); } function handleColorChange(color) { setUserColor(color); require_trigger_css.saveWidgetConfig(ctx.apiKey, { theme: { ...ctx.theme, mode: activeThemeMode, primaryColor: color } }); } function handleCornerChange(corner) { if (corner === position) return; if (repositionTimer.current) clearTimeout(repositionTimer.current); setIsRepositioning(true); repositionTimer.current = setTimeout(() => { setPosition(corner); require_trigger_css.saveWidgetConfig(ctx.apiKey, { position: corner }); repositionTimer.current = setTimeout(() => { setIsRepositioning(false); repositionTimer.current = null; }, 20); }, 120); } const activeDisplayMode = currentDisplayMode.mode; const activeSheetSide = currentDisplayMode.side ?? "end"; function handleDisplayModeChange(mode) { if (mode === activeDisplayMode) return; registry.setPreferences({ display: { mode, side: mode === "side-sheet" ? activeSheetSide : void 0 } }); } function handleSheetSideChange(side) { if (side === activeSheetSide) return; registry.setPreferences({ display: { mode: activeDisplayMode, side } }); } const COLOR_PRESETS = [ { color: "#0F5E56", label: "Green" }, { color: "#3b82f6", label: "Blue" }, { color: "#8b5cf6", label: "Purple" }, { color: "#f59e0b", label: "Orange" }, { color: "#ef4444", label: "Red" } ]; function handleClearEmail() { require_trigger_css.clearWidgetEmail(ctx.apiKey); setEmailCleared(true); setTimeout(() => setEmailCleared(false), 2e3); } function openTicketDetail(ticketId) { setSelectedTicketId(ticketId); setView("ticket-detail"); } const currentProjectName = ctx.projects.find((p) => p.id === ctx.projectId)?.name; const menuItems = [ { key: "new-ticket", icon: ICONS.newTicket, label: t("menu.newTicket"), desc: t("menu.newTicketDesc") }, { key: "my-tickets", icon: ICONS.tickets, label: t("menu.myTickets"), desc: t("menu.myTicketsDesc") }, { key: "track", icon: ICONS.track, label: t("menu.trackTicket"), desc: t("menu.trackTicketDesc") }, { key: "preferences", icon: ICONS.settings, label: t("menu.preferences"), desc: t("menu.preferencesDesc") } ]; if (ctx.isMultiProject) menuItems.splice(-1, 0, { key: "select-project", icon: ICONS.switchProject, label: t("project.switch"), desc: currentProjectName ?? t("project.switchDesc") }); function renderProjectPicker() { if (ctx.isProjectLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-loading", children: t("project.loading") }); if (isWorkspaceKey && !ctx.isAuthenticated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("auth.loginRequired") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-primary-btn", onClick: () => login(), children: t("auth.login") })] }); if (ctx.projects.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-placeholder", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("project.noProjects") }) }); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-menu", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { padding: "0 16px 8px", margin: 0, fontSize: 13, opacity: .7 }, children: t("project.select") }), ctx.projects.map((project) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { className: `rqd-menu-item${project.id === ctx.projectId ? " rqd-active" : ""}`, onClick: () => { ctx.selectProject(project.id); setView("home"); }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-menu-icon", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.switchProject, size: 22 }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-menu-text", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-label", children: project.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-desc", children: project.slug })] })] }, project.id))] }); } function renderViewContent() { if (needsProjectSelection && view !== "select-project") return renderProjectPicker(); switch (view) { case "home": { const builtIns = menuItems.map((item) => ({ id: item.key, iconPath: item.icon, label: item.label, description: item.desc, onActivate: () => setView(item.key) })); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [ctx.isMultiProject && currentProjectName && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-project-badge", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.switchProject, size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: currentProjectName })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuList, { builtIns })] }); } case "select-project": return renderProjectPicker(); case "new-ticket": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubmitTicketView$1, { projectId, onSuccess: (result) => { onTicketCreated?.(result); }, onError, isAuthenticated: ctx.isAuthenticated, userEmail: ctx.userEmail }); case "my-tickets": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MyTicketsView$1, { projectId, onSelectTicket: openTicketDetail, isAuthenticated: ctx.isAuthenticated, userEmail: ctx.userEmail }); case "ticket-detail": if (!selectedTicketId) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TicketDetailView$1, { ticketId: selectedTicketId, onBack: () => setView("my-tickets") }); case "track": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrackTicketView$1, { onTrackSuccess: openTicketDetail }); case "kb": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.kb, size: 40 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("menu.kbPlaceholder") })] }); case "preferences": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { padding: 16, display: "flex", flexDirection: "column", gap: 12 }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefsRow, { index: "01", label: t("prefs.language"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerSegmented, { value: activeLang, options: [{ value: "en", label: "English" }, { value: "ar", label: "العربية" }], onChange: handleLangChange }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefsRow, { index: "02", label: t("prefs.theme"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerSegmented, { value: activeThemeMode, options: [ { value: "light", label: t("prefs.light") }, { value: "dark", label: t("prefs.dark") }, { value: "auto", label: t("prefs.auto") } ], onChange: handleThemeChange }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefsRow, { index: "03", label: t("prefs.accentColor"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: COLOR_PRESETS.map((preset) => { const isActive = activeColor.toLowerCase() === preset.color.toLowerCase(); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", onClick: () => handleColorChange(preset.color), "aria-label": preset.label, "aria-pressed": isActive, title: preset.label, style: { position: "relative", width: 30, height: 30, background: preset.color, border: isActive ? `2px solid var(--rqd-text)` : `1px solid var(--rqd-border)`, boxShadow: isActive ? `0 0 0 2px var(--rqd-bg)` : "none", cursor: "pointer", padding: 0 } }, preset.color); }) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefsRow, { index: "04", label: t("prefs.position"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { display: "grid", gridTemplateColumns: "repeat(2, 38px)", gridTemplateRows: "repeat(2, 38px)", gap: 6, width: "fit-content" }, children: CORNERS.map((corner) => { const label = t(CORNER_LABEL_KEY[corner]); const isActive = position === corner; const isTop = corner.startsWith("top"); const isStart = corner.endsWith("start"); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", onClick: () => handleCornerChange(corner), "aria-label": label, "aria-pressed": isActive, title: label, style: { position: "relative", width: 38, height: 38, background: isActive ? "var(--rqd-bg-secondary)" : "var(--rqd-bg)", border: isActive ? "1px solid var(--rqd-primary)" : "1px solid var(--rqd-border)", cursor: "pointer", padding: 0 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": true, style: { position: "absolute", width: 9, height: 9, background: isActive ? "var(--rqd-primary)" : "var(--rqd-text-secondary)", ...isTop ? { top: 7 } : { bottom: 7 }, ...isStart ? { insetInlineStart: 7 } : { insetInlineEnd: 7 } } }) }, corner); }) }) }), !ctx.hideDisplayModePicker && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PrefsRow, { index: "05", label: t("prefs.displayMode"), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerSegmented, { value: activeDisplayMode, options: [ { value: "popover", label: t("prefs.display.popover") }, { value: "side-sheet", label: t("prefs.display.side-sheet") }, { value: "bottom-sheet", label: t("prefs.display.bottom-sheet") } ], onChange: handleDisplayModeChange }), activeDisplayMode === "side-sheet" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerSegmented, { value: activeSheetSide, options: [{ value: "start", label: t("prefs.side.start") }, { value: "end", label: t("prefs.side.end") }], onChange: handleSheetSideChange }) })] }), require_trigger_css.loadWidgetEmail(ctx.apiKey) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerButton, { variant: "ghost", onClick: handleClearEmail, children: emailCleared ? t("prefs.emailCleared") : t("prefs.clearEmail") }), registry.hasInitialPreferences() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerButton, { variant: "ghost", onClick: () => { if (typeof window !== "undefined" && window.confirm?.(t("prefs.resetConfirm")) === false) return; registry.resetToInitialPreferences(); const next = registry.getPreferences(); if (next.language) setUserLang(next.language); if (next.themeMode) setUserThemeMode(next.themeMode); if (next.primaryColor) setUserColor(next.primaryColor); if (next.position) setPosition(next.position); }, children: t("prefs.resetToDefaults") }) ] }); } } const viewTitle = { home: brandName ?? t("widget.title"), "select-project": t("project.select"), "new-ticket": t("widget.newTicket"), "my-tickets": t("menu.myTickets"), "ticket-detail": t("menu.myTickets"), track: t("widget.trackTicket"), kb: t("menu.knowledgeBase"), preferences: t("prefs.title") }; const canGoBack = view !== "home"; const goBackTarget = view === "ticket-detail" ? "my-tickets" : "home"; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: cssVars, ...isRtl ? { dir: "rtl" } : {}, children: [ !hideFab && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { className: `rqd-fab ${posClass}${containedClass}${!isOpen ? ` rqd-fab-labeled ${sideClass}` : ""}${repositionClass}`, style: cssVars, onClick: toggleOpen, "aria-label": isOpen ? t("widget.close") : fabLabel, children: [isOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.close, size: 24 }) : renderFabIcon(ctx.fabIcon, 24), !isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-fab-label", "aria-hidden": "true", children: fabLabel })] }), isOpen && isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-backdrop", onClick: () => setIsExpanded(false), "aria-hidden": "true" }), isOpen && currentDisplayMode.mode !== "popover" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-backdrop-sheet", "aria-hidden": "true", onClick: () => { if (currentDisplayMode.dismissOnBackdrop !== false) { setIsOpen(false); registry.close(); } } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: buildPanelClassName({ mode: currentDisplayMode.mode, side: currentDisplayMode.side, posClass, contained, isOpen, isExpanded, isRepositioning }), style: buildPanelStyle(cssVars, currentDisplayMode), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-header", style: { display: "flex", alignItems: "center", gap: 10, padding: "14px 14px 12px", background: "var(--rqd-bg-secondary)", borderBottom: "1px solid var(--rqd-border)" }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, minWidth: 0, flex: 1 }, children: [ canGoBack ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-header-close", onClick: () => goBackTarget === "home" ? goHome() : setView(goBackTarget), "aria-label": t("tracker.back"), style: { width: 28, height: 28, display: "inline-flex", alignItems: "center", justifyContent: "center", background: "transparent", border: "1px solid var(--rqd-border)", cursor: "pointer", color: "var(--rqd-text)", flex: "0 0 auto" }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: isRtl ? ICONS.backRtl : ICONS.back, size: 14 }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrandMark, { size: 18 }), view === "home" && brandLogo && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: brandLogo, alt: "", style: { height: 22, width: "auto", display: "block", flex: "0 0 auto" } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { style: { fontFamily: "var(--rqd-font-mono)", fontSize: 9, letterSpacing: "var(--rqd-track-eyebrow)", textTransform: "uppercase", color: "var(--rqd-text-secondary)", display: "inline-flex", alignItems: "center", gap: 5 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": true, style: { color: "var(--rqd-primary)" }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrandMark, { size: 9 }) }), "Reqdesk"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { fontFamily: "var(--rqd-font-display)", fontStyle: "italic", fontSize: 17, lineHeight: 1.1, color: "var(--rqd-text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: viewTitle[view] })] }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [ (ctx.auth || isAuthConfigured()) && view === "home" && ctx.serverAllowedModes.includes("sso") && (ctx.isAuthenticated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { title: ctx.userEmail ?? ctx.userName, style: { display: "inline-flex", alignItems: "center", gap: 6, paddingInlineStart: 8, paddingInlineEnd: 4, height: 28, border: "1px solid var(--rqd-border)", maxWidth: 170 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { fontFamily: "var(--rqd-font-mono)", fontSize: 10, color: "var(--rqd-text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: ctx.userName ?? ctx.userEmail }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { onClick: () => logout(), "aria-label": t("auth.logout"), title: t("auth.logout"), style: { display: "inline-flex", alignItems: "center", justifyContent: "center", width: 22, height: 22, background: "transparent", border: "none", cursor: "pointer", color: "var(--rqd-text-secondary)" }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.logout, size: 13 }) })] }) : ctx.resolvedIdentityMode.kind !== "signed" && ctx.resolvedIdentityMode.kind !== "unsigned" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reqdesk_sdk_react.LedgerButton, { variant: "ghost", onClick: () => login(), children: t("auth.login") })), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeaderIconButton, { onClick: () => setIsExpanded((v) => !v), ariaLabel: t(isExpanded ? "widget.collapse" : "widget.expand"), iconPath: isExpanded ? ICONS.collapse : ICONS.expand }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeaderIconButton, { onClick: toggleOpen, ariaLabel: t("widget.close"), iconPath: ICONS.close }) ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-body", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationStack, { dismissLabel: t("notification.dismiss") }), renderViewContent()] }), !hideBranding && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-footer", style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 6, padding: "10px 14px", borderTop: "1px solid var(--rqd-border)", background: "var(--rqd-bg-secondary)", fontFamily: "var(--rqd-font-mono)", fontSize: 9, letterSpacing: "var(--rqd-track-eyebrow)", textTransform: "uppercase", color: "var(--rqd-text-secondary)" }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("branding.poweredBy") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", { href: "https://reqdesk.support", target: "_blank", rel: "noopener noreferrer", style: { display: "inline-flex", alignItems: "center", gap: 5, color: "var(--rqd-primary)", textDecoration: "none", borderBottom: "1px solid transparent" }, onMouseEnter: (e) => { e.currentTarget.style.borderBottomColor = "var(--rqd-primary)"; }, onMouseLeave: (e) => { e.currentTarget.style.borderBottomColor = "transparent"; }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BrandMark, { size: 11 }), "Reqdesk"] })] }) ] }) ] }) }); } /** * Reqdesk brand mark — three stacked ledger entries, the same motif used * by the SignInScreen, HomeShell, and browser-extension popup. Ships * inline as SVG so it's identical across hosts and inherits the * surrounding `color` (cedar in the footer, muted in the eyebrow). */ function BrandMark({ size = 14 }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", { width: size, height: size, viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", style: { display: "block", flexShrink: 0 }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", { x: "1.5", y: "2.5", width: "11", height: "1.6", fill: "currentColor" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", { x: "1.5", y: "6.2", width: "7", height: "1.6", fill: "currentColor" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", { x: "1.5", y: "9.9", width: "11", height: "1.6", fill: "currentColor" }) ] }); } function HeaderIconButton({ onClick, ariaLabel, iconPath }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { onClick, "aria-label": ariaLabel, title: ariaLabel, style: { width: 28, height: 28, display: "inline-flex", alignItems: "center", justifyContent: "center", background: "transparent", border: "1px solid color-mix(in srgb, var(--rqd-ink, #0a0e0f) 12%, transparent)", cursor: "pointer", color: "var(--rqd-ink, #0a0e0f)" }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: iconPath, size: 14 }) }); } function PrefsRow({ label, index, children }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { style: { display: "flex", flexDirection: "column", gap: 10, padding: "14px 14px", background: "var(--rqd-bg)", border: "1px solid var(--rqd-border)", position: "relative" }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": true, style: { position: "absolute", insetInlineStart: 0, top: 14, bottom: 14, width: 2, background: "var(--rqd-primary)", opacity: .6 } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "baseline", gap: 10 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { fontFamily: "var(--rqd-font-mono)", fontSize: 10.5, fontVariantNumeric: "tabular-nums", letterSpacing: "var(--rqd-track-eyebrow)", textTransform: "uppercase", color: "var(--rqd-text-secondary)" }, children: index }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { fontFamily: "var(--rqd-font-display)", fontStyle: "italic", fontSize: 15, lineHeight: 1.1, color: "var(--rqd-text)" }, children: label })] }), children ] }); } //#endregion //#region src/react/useReqdeskTrigger.ts function applyDisplayOverride(setDisplayMode, display) { if (!display) return; if (typeof display === "string") setDisplayMode(display); else if (display.mode) setDisplayMode(display.mode, display.side); } function useReqdeskTrigger(opts = {}) { const registry = useRegistry(); const isOpen = useRegistrySnapshot((s) => s.isOpen); const controlsId = (0, react.useId)(); const targetRef = opts.target; const displayRef = opts.display; return { onClick: (0, react.useCallback)((e) => { if (e?.currentTarget) registry.recordActivator(e.currentTarget); applyDisplayOverride(registry.setDisplayMode, displayRef); if (!targetRef || targetRef === "menu") registry.openMenu(); else registry.openAction(targetRef.actionId); }, [ registry, targetRef, displayRef ]), isOpen, ariaProps: (0, react.useMemo)(() => ({ "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": `rqd-panel-${controlsId}` }), [isOpen, controlsId]) }; } //#endregion //#region src/react/Trigger.tsx const CHAT_PATH = "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"; function DefaultChatIcon() { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: CHAT_PATH }) }); } function renderIcon(icon) { if (icon === null) return null; if (icon === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultChatIcon, {}); if (typeof icon === "string") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: icon }) }); if (typeof icon === "object" && "kind" in icon) { if (icon.kind === "path") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: icon.path }) }); if (icon.kind === "url") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: icon.src, width: 18, height: 18, alt: "", "aria-hidden": "true" }); return null; } return icon; } function ReqdeskTrigger({ variant = "pill", icon, className, style, children, render, target, display }) { (0, react.useEffect)(() => { require_trigger_css.injectTriggerStyles(); }, []); const { onClick, isOpen, ariaProps } = useReqdeskTrigger({ target, display }); if (render) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: render({ onClick, isOpen, ariaProps }) }); const classes = `rqd-trigger rqd-trigger--${variant}${className ? ` ${className}` : ""}`; const iconNode = renderIcon(icon); const labelNode = variant === "icon" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-trigger-label", children: children ?? "Open support" }) : children ?? null; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: classes, style, onClick, ...ariaProps, children: [iconNode, labelNode] }); } //#endregion //#region src/react/form-errors.tsx const FormErrorContext = (0, react.createContext)(null); function FormErrorProvider({ mutation, clientErrors, children }) { const idPrefix = (0, react.useId)(); const serverErrors = useServerErrors(mutation); const errors = (0, react.useMemo)(() => { const merged = { ...serverErrors }; if (clientErrors) { for (const [field, detail] of Object.entries(clientErrors)) if (detail) merged[field] = detail; } return merged; }, [serverErrors, clientErrors]); const errorId = (0, react.useCallback)((name) => `${idPrefix}-err-${name}`, [idPrefix]); const value = (0, react.useMemo)(() => ({ errors, errorId }), [errors, errorId]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormErrorContext.Provider, { value, children }); } /** * Extract a per-field error map from a `useMutation`'s current error object. Clears automatically * as soon as the mutation leaves the `'error'` state (i.e. on retry or success) so the user never * sees stale "priority invalid" red after they've fixed it and re-submitted. */ function useServerErrors(mutation) { const [serverErrors, setServerErrors] = (0, react.useState)({}); const lastSeenAttemptRef = (0, react.useRef)(void 0); (0, react.useEffect)(() => { if (!mutation) { setServerErrors({}); return; } if (mutation.status !== "error") { if (Object.keys(serverErrors).length > 0) setServerErrors({}); return; } if (lastSeenAttemptRef.current === mutation.submittedAt) return; lastSeenAttemptRef.current = mutation.submittedAt; const err = mutation.error; if (!err || !err.errors || err.errors.length === 0) { setServerErrors({}); return; } const next = {}; for (const fe of err.errors) { const key = fe.field; if (!key) continue; if (!(key in next)) next[key] = fe.detail || fe.code; } setServerErrors(next); }, [ mutation, mutation?.status, mutation?.submittedAt, mutation?.error, serverErrors ]); return serverErrors; } /** * Reads the current error for a named field. Returns the detail + pre-computed a11y props so the * caller can spread them onto any native input/select/textarea with zero branching. */ function useFieldError(name) { const ctx = (0, react.useContext)(FormErrorContext); const error = ctx?.errors[name] ?? null; const invalid = error !== null; const id = ctx?.errorId(name) ?? `rqd-err-${name}`; return { error, invalid, inputProps: invalid ? { "aria-invalid": "true", "aria-describedby": id, className: "rqd-field-error" } : {}, errorId: id }; } /** * Renders the per-field error message under an input. Uses `role="alert"` so screen readers * announce it when it appears (e.g. after a failed submission). Renders nothing when there's no * error — safe to leave in the markup unconditionally. */ function FieldError({ name, as = "div", className }) { const ctx = (0, react.useContext)(FormErrorContext); const detail = ctx?.errors[name]; if (!detail) return null; const commonProps = { id: ctx.errorId(name), role: "alert", className: className ? `rqd-error-text ${className}` : "rqd-error-text" }; switch (as) { case "span": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { ...commonProps, children: detail }); case "p": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { ...commonProps, children: detail }); default: return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...commonProps, children: detail }); } } /** * Common label + input + `` layout for a form field inside a ``. * The child input MUST carry a `name` attribute matching `props.name`; the group injects the * `aria-invalid` / `aria-describedby` / error-class props by cloning the child. * * For layouts that don't fit this shape (e.g. file drop zones, category tree, diagnostic checkbox * blocks), skip this component and call `useFieldError(name)` directly. */ function FormFieldGroup({ name, label, hint, className, children }) { const { inputProps } = useFieldError(name); const rootCls = className ? `rqd-form-group ${className}` : "rqd-form-group"; const merged = (0, react.isValidElement)(children) ? cloneWithMergedProps(children, inputProps) : children; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: rootCls, children: [ label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: label }), merged, hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-field-hint", children: hint }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldError, { name }) ] }); } function cloneWithMergedProps(child, extra) { const existing = child.props ?? {}; const mergedClass = [typeof existing.className === "string" ? existing.className : "", extra.className].filter(Boolean).join(" ").trim() || void 0; return (0, react.cloneElement)(child, { ...existing, "aria-invalid": extra["aria-invalid"] ?? existing["aria-invalid"], "aria-describedby": extra["aria-describedby"] ?? existing["aria-describedby"], className: mergedClass }); } //#endregion exports.FieldError = FieldError; exports.FloatingWidget = FloatingWidget; exports.FormErrorProvider = FormErrorProvider; exports.FormFieldGroup = FormFieldGroup; Object.defineProperty(exports, "MyTicketsView", { enumerable: true, get: function() { return _reqdesk_sdk_react.MyTicketsView; } }); exports.NotificationProvider = NotificationProvider; exports.NotificationStack = NotificationStack; exports.RegistryProvider = RegistryProvider; exports.ReqdeskProvider = ReqdeskProvider; exports.ReqdeskTrigger = ReqdeskTrigger; exports.ShadowRoot = ShadowRoot; Object.defineProperty(exports, "SubmitTicketView", { enumerable: true, get: function() { return _reqdesk_sdk_react.SubmitTicketView; } }); exports.SupportPortal = SupportPortal; Object.defineProperty(exports, "TicketDetailView", { enumerable: true, get: function() { return _reqdesk_sdk_react.TicketDetailView; } }); exports.TicketForm = TicketForm; Object.defineProperty(exports, "TrackTicketView", { enumerable: true, get: function() { return _reqdesk_sdk_react.TrackTicketView; } }); exports.WidgetFetchError = require_trigger_css.WidgetFetchError; exports.getWidgetDefaults = require_trigger_css.loadWidgetConfig; exports.useFieldError = useFieldError; exports.useNotify = useNotify; exports.useRegistry = useRegistry; exports.useRegistrySnapshot = useRegistrySnapshot; exports.useReqdesk = useReqdesk; exports.useReqdeskTrigger = useReqdeskTrigger;