@hexclave/next 1.0.75 → 1.0.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{admin-app-impl-sTQD2d-3.d.ts → admin-app-impl-CxDpE89P.d.ts} +12 -10
- package/dist/admin-app-impl-CxDpE89P.d.ts.map +1 -0
- package/dist/clickmap/clickmap-core.d.ts +1 -1
- package/dist/clickmap/index.d.ts +1 -1
- package/dist/{client-app-BCT5p_70.d.ts → client-app-wFodsYml.d.ts} +4 -1
- package/dist/client-app-wFodsYml.d.ts.map +1 -0
- package/dist/common-BK_sydxF.d.ts.map +1 -1
- package/dist/components/elements/sidebar-layout.d.ts.map +1 -1
- package/dist/components/elements/sidebar-layout.js +12 -2
- package/dist/components/elements/sidebar-layout.js.map +1 -1
- package/dist/components/selected-team-switcher.d.ts +1 -1
- package/dist/components/team-icon.d.ts +1 -1
- package/dist/components/team-switcher.d.ts +1 -1
- package/dist/components-page/account-settings/payments/payments-page.d.ts +1 -1
- package/dist/components-page/account-settings/teams/leave-team-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-api-keys-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-display-name-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-member-invitation-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-member-list-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-page.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-profile-image-section.d.ts +1 -1
- package/dist/components-page/account-settings/teams/team-profile-user-section.d.ts +1 -1
- package/dist/components-page/hexclave-handler-client.d.ts +1 -1
- package/dist/components-page/hexclave-handler.d.ts +1 -1
- package/dist/components-page/team-invitation-cache.d.ts +5 -0
- package/dist/components-page/team-invitation-cache.d.ts.map +1 -0
- package/dist/components-page/team-invitation-cache.js +12 -0
- package/dist/components-page/team-invitation-cache.js.map +1 -0
- package/dist/components-page/team-invitation-cache.test.d.ts +1 -0
- package/dist/components-page/team-invitation-cache.test.js +23 -0
- package/dist/components-page/team-invitation-cache.test.js.map +1 -0
- package/dist/components-page/team-invitation.js +8 -7
- package/dist/components-page/team-invitation.js.map +1 -1
- package/dist/dev-tool/dev-tool-core.d.ts +1 -1
- package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/dev-tool/dev-tool-core.js +2 -2
- package/dist/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/dev-tool/index.d.ts +1 -1
- package/dist/dev-tool/index.d.ts.map +1 -1
- package/dist/dev-tool/index.js +2 -8
- package/dist/dev-tool/index.js.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.d.ts.map +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +12 -2
- package/dist/esm/components/elements/sidebar-layout.js.map +1 -1
- package/dist/esm/components-page/team-invitation-cache.d.ts +5 -0
- package/dist/esm/components-page/team-invitation-cache.d.ts.map +1 -0
- package/dist/esm/components-page/team-invitation-cache.js +11 -0
- package/dist/esm/components-page/team-invitation-cache.js.map +1 -0
- package/dist/esm/components-page/team-invitation-cache.test.d.ts +1 -0
- package/dist/esm/components-page/team-invitation-cache.test.js +24 -0
- package/dist/esm/components-page/team-invitation-cache.test.js.map +1 -0
- package/dist/esm/components-page/team-invitation.d.ts.map +1 -1
- package/dist/esm/components-page/team-invitation.js +8 -7
- package/dist/esm/components-page/team-invitation.js.map +1 -1
- package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/esm/dev-tool/dev-tool-core.js +1 -1
- package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/esm/dev-tool/index.d.ts.map +1 -1
- package/dist/esm/dev-tool/index.js +2 -8
- package/dist/esm/dev-tool/index.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.d.ts +2 -2
- package/dist/esm/in-page-ui/dev-environment.d.ts +5 -0
- package/dist/esm/in-page-ui/dev-environment.d.ts.map +1 -0
- package/dist/esm/in-page-ui/dev-environment.js +18 -0
- package/dist/esm/in-page-ui/dev-environment.js.map +1 -0
- package/dist/esm/in-page-ui/issue-card.d.ts +52 -0
- package/dist/esm/in-page-ui/issue-card.d.ts.map +1 -0
- package/dist/esm/in-page-ui/issue-card.js +392 -0
- package/dist/esm/in-page-ui/issue-card.js.map +1 -0
- package/dist/esm/in-page-ui/issue-card.test.d.ts +1 -0
- package/dist/esm/in-page-ui/issue-card.test.js +60 -0
- package/dist/esm/in-page-ui/issue-card.test.js.map +1 -0
- package/dist/esm/in-page-ui/logo.d.ts +5 -0
- package/dist/esm/in-page-ui/logo.d.ts.map +1 -0
- package/dist/esm/in-page-ui/logo.js +6 -0
- package/dist/esm/in-page-ui/logo.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +4 -4
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js +30 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +140 -15
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +5 -3
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +184 -28
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js +83 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.d.ts +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js +120 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/common.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js +4 -3
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/index.js +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +2 -2
- package/dist/esm/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -1
- package/dist/esm/lib/hexclave-app/teams/index.d.ts +3 -0
- package/dist/esm/lib/hexclave-app/teams/index.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/teams/index.js.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.js +42 -343
- package/dist/esm/pushed-config-error-overlay/index.js.map +1 -1
- package/dist/esm/setup-error-overlay/index.d.ts +16 -0
- package/dist/esm/setup-error-overlay/index.d.ts.map +1 -0
- package/dist/esm/setup-error-overlay/index.js +106 -0
- package/dist/esm/setup-error-overlay/index.js.map +1 -0
- package/dist/esm/setup-error-overlay/index.test.d.ts +1 -0
- package/dist/esm/setup-error-overlay/index.test.js +125 -0
- package/dist/esm/setup-error-overlay/index.test.js.map +1 -0
- package/dist/esm/utils/monkey-patch.d.ts +7 -0
- package/dist/esm/utils/monkey-patch.d.ts.map +1 -0
- package/dist/esm/utils/monkey-patch.js +20 -0
- package/dist/esm/utils/monkey-patch.js.map +1 -0
- package/dist/esm/utils/react.d.ts +17 -0
- package/dist/esm/utils/react.d.ts.map +1 -0
- package/dist/esm/utils/react.js +60 -0
- package/dist/esm/utils/react.js.map +1 -0
- package/dist/generated/quetzal-translations.d.ts +1 -1
- package/dist/{hexclave-provider-BKxi3Vy-.d.ts → hexclave-provider-CMz2mt4I.d.ts} +3 -3
- package/dist/{hexclave-provider-BKxi3Vy-.d.ts.map → hexclave-provider-CMz2mt4I.d.ts.map} +1 -1
- package/dist/{hooks-CUe5bc3s.d.ts → hooks-CUHt4Y9C.d.ts} +2 -2
- package/dist/{hooks-CUe5bc3s.d.ts.map → hooks-CUHt4Y9C.d.ts.map} +1 -1
- package/dist/in-page-ui/dev-environment.d.ts +5 -0
- package/dist/in-page-ui/dev-environment.d.ts.map +1 -0
- package/dist/in-page-ui/dev-environment.js +19 -0
- package/dist/in-page-ui/dev-environment.js.map +1 -0
- package/dist/in-page-ui/issue-card.d.ts +52 -0
- package/dist/in-page-ui/issue-card.d.ts.map +1 -0
- package/dist/in-page-ui/issue-card.js +397 -0
- package/dist/in-page-ui/issue-card.js.map +1 -0
- package/dist/in-page-ui/issue-card.test.d.ts +1 -0
- package/dist/in-page-ui/issue-card.test.js +59 -0
- package/dist/in-page-ui/issue-card.test.js.map +1 -0
- package/dist/in-page-ui/logo.d.ts +5 -0
- package/dist/in-page-ui/logo.d.ts.map +1 -0
- package/dist/in-page-ui/logo.js +7 -0
- package/dist/in-page-ui/logo.js.map +1 -0
- package/dist/{index-CvB7XacB.d.ts → index-fR0LkUK7.d.ts} +2 -2
- package/dist/{index-CvB7XacB.d.ts.map → index-fR0LkUK7.d.ts.map} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js +29 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +140 -15
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +2 -2
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +182 -25
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js +82 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.d.ts +1 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js +119 -0
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.js.map +1 -0
- package/dist/lib/hexclave-app/apps/implementations/common.js +4 -3
- package/dist/lib/hexclave-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/index.js +1 -1
- package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.js +1 -1
- package/dist/lib/hexclave-app/apps/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/interfaces/server-app.d.ts +1 -1
- package/dist/lib/hexclave-app/index.d.ts +1 -1
- package/dist/lib/hexclave-app/projects/index.d.ts +1 -1
- package/dist/lib/hexclave-app/teams/index.d.ts +1 -1
- package/dist/lib/hexclave-app/teams/index.js.map +1 -1
- package/dist/lib/hexclave-app/users/index.d.ts +1 -1
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/providers/hexclave-context.d.ts +1 -1
- package/dist/providers/hexclave-provider-client.d.ts +1 -1
- package/dist/providers/hexclave-provider.d.ts +1 -1
- package/dist/providers/translation-provider.d.ts +1 -1
- package/dist/pushed-config-error-overlay/index.d.ts +1 -1
- package/dist/pushed-config-error-overlay/index.d.ts.map +1 -1
- package/dist/pushed-config-error-overlay/index.js +41 -342
- package/dist/pushed-config-error-overlay/index.js.map +1 -1
- package/dist/{quetzal-translations-XeH3zQ9l.d.ts → quetzal-translations-B2jT2SWm.d.ts} +3 -3
- package/dist/{quetzal-translations-XeH3zQ9l.d.ts.map → quetzal-translations-B2jT2SWm.d.ts.map} +1 -1
- package/dist/setup-error-overlay/index.d.ts +16 -0
- package/dist/setup-error-overlay/index.d.ts.map +1 -0
- package/dist/setup-error-overlay/index.js +108 -0
- package/dist/setup-error-overlay/index.js.map +1 -0
- package/dist/setup-error-overlay/index.test.d.ts +1 -0
- package/dist/setup-error-overlay/index.test.js +124 -0
- package/dist/setup-error-overlay/index.test.js.map +1 -0
- package/dist/team-invitation-B5Ol7vdd.d.ts.map +1 -1
- package/dist/{translation-provider-WOaZXqAz.d.ts → translation-provider-6505O9P2.d.ts} +2 -2
- package/dist/{translation-provider-WOaZXqAz.d.ts.map → translation-provider-6505O9P2.d.ts.map} +1 -1
- package/dist/utils/monkey-patch.d.ts +7 -0
- package/dist/utils/monkey-patch.d.ts.map +1 -0
- package/dist/utils/monkey-patch.js +23 -0
- package/dist/utils/monkey-patch.js.map +1 -0
- package/dist/utils/react.d.ts +17 -0
- package/dist/utils/react.d.ts.map +1 -0
- package/dist/utils/react.js +63 -0
- package/dist/utils/react.js.map +1 -0
- package/package.json +4 -4
- package/src/components/elements/sidebar-layout.tsx +16 -1
- package/src/components-page/team-invitation-cache.test.ts +25 -0
- package/src/components-page/team-invitation-cache.ts +13 -0
- package/src/components-page/team-invitation.tsx +16 -7
- package/src/dev-tool/dev-tool-core.ts +1 -4
- package/src/dev-tool/index.ts +4 -15
- package/src/in-page-ui/dev-environment.ts +33 -0
- package/src/in-page-ui/issue-card.test.ts +67 -0
- package/src/in-page-ui/issue-card.ts +500 -0
- package/src/in-page-ui/logo.ts +8 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.automatic-side-effects.test.ts +38 -1
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +157 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.sign-out.test.ts +96 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +255 -37
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.untrusted-redirect-card.test.ts +146 -0
- package/src/lib/hexclave-app/apps/implementations/common.ts +2 -1
- package/src/lib/hexclave-app/apps/implementations/redirect-page-urls.test.ts +1 -0
- package/src/lib/hexclave-app/teams/index.ts +3 -0
- package/src/pushed-config-error-overlay/index.ts +45 -386
- package/src/setup-error-overlay/index.test.ts +133 -0
- package/src/setup-error-overlay/index.ts +150 -0
- package/src/utils/monkey-patch.tsx +31 -0
- package/src/utils/react.tsx +93 -0
- package/dist/admin-app-impl-sTQD2d-3.d.ts.map +0 -1
- package/dist/client-app-BCT5p_70.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-environment.d.ts","names":[],"sources":["../../../src/in-page-ui/dev-environment.ts"],"mappings":";iBAWgB,8BAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isLocalhost } from "@hexclave/shared/dist/utils/urls";
|
|
2
|
+
import { envVars } from "../generated/env.js";
|
|
3
|
+
//#region src/in-page-ui/dev-environment.ts
|
|
4
|
+
function isLikelyDevelopmentEnvironment() {
|
|
5
|
+
const nodeEnv = envVars.NODE_ENV;
|
|
6
|
+
if (nodeEnv !== void 0) return nodeEnv === "development";
|
|
7
|
+
if (typeof window === "undefined") return false;
|
|
8
|
+
try {
|
|
9
|
+
if (new URL(window.location.href).protocol === "file:") return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return isLocalhost(window.location.href);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { isLikelyDevelopmentEnvironment };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=dev-environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-environment.js","names":[],"sources":["../../../src/in-page-ui/dev-environment.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// Shared \"are we in a development-like environment?\" check for Hexclave's in-page UIs. Some of them (eg. surfacing an\n// unexpected internal error) are only appropriate while developing, and the SDK cannot ask the server about it because\n// the check has to work synchronously and before the app is authenticated.\n\nimport { isLocalhost } from \"@hexclave/shared/dist/utils/urls\";\nimport { envVars } from \"../generated/env\";\n\nexport function isLikelyDevelopmentEnvironment(): boolean {\n // NODE_ENV is the most reliable signal, but plenty of bundlers (eg. Vite-based ones) don't define it in the browser\n // bundle at all, so fall back to guessing from the current URL.\n const nodeEnv = envVars.NODE_ENV;\n if (nodeEnv !== undefined) {\n return nodeEnv === \"development\";\n }\n\n if (typeof window === \"undefined\") {\n return false;\n }\n\n try {\n const url = new URL(window.location.href);\n if (url.protocol === \"file:\") {\n return true;\n }\n } catch {\n return false;\n }\n return isLocalhost(window.location.href);\n}\n"],"mappings":";;;AAWA,SAAgB,iCAA0C;CAGxD,MAAM,UAAU,QAAQ;CACxB,IAAI,YAAY,KAAA,GACd,OAAO,YAAY;CAGrB,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,IAAI;EAEF,IAAI,IADY,IAAI,OAAO,SAAS,IAC9B,CAAC,CAAC,aAAa,SACnB,OAAO;CAEX,QAAQ;EACN,OAAO;CACT;CACA,OAAO,YAAY,OAAO,SAAS,IAAI;AACzC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/in-page-ui/issue-card.d.ts
|
|
2
|
+
type IssueCardKind = "error" | "warning";
|
|
3
|
+
declare function getIssueCardCSS(scopeSelector: string): string;
|
|
4
|
+
declare function copyTextToClipboard(text: string): Promise<void>;
|
|
5
|
+
type IssueCardOptions = {
|
|
6
|
+
kind: IssueCardKind; /** Short uppercase label in the card's badge, eg. "Config error". */
|
|
7
|
+
badge: string;
|
|
8
|
+
title: string; /** One or two sentences on what this means for the running app. */
|
|
9
|
+
bodyText: string;
|
|
10
|
+
messageLabel: string;
|
|
11
|
+
message: string; /** Concrete steps the developer should take; rendered as a list above the footer. */
|
|
12
|
+
howToFix?: readonly string[];
|
|
13
|
+
footerText: string;
|
|
14
|
+
ariaLabel: string; /** Prompt for the "copy AI prompt" button, ie. the message plus enough context for a coding agent to fix it. */
|
|
15
|
+
aiPrompt: string;
|
|
16
|
+
onCopyError: (error: unknown) => void;
|
|
17
|
+
onCopyAiPromptError: (error: unknown) => void;
|
|
18
|
+
onMinimize: () => void;
|
|
19
|
+
};
|
|
20
|
+
declare function renderIssueCard(options: IssueCardOptions): HTMLElement;
|
|
21
|
+
type IssueCardFocusTrap = {
|
|
22
|
+
/**
|
|
23
|
+
* Whatever had focus before the card first appeared. Callers that rebind the trap to a re-rendered card pass it back
|
|
24
|
+
* in, so focus still returns to where the developer left it once the card is gone for good.
|
|
25
|
+
*/
|
|
26
|
+
previouslyFocused: HTMLElement | null;
|
|
27
|
+
release: (releaseOptions?: {
|
|
28
|
+
restoreFocus?: boolean;
|
|
29
|
+
}) => void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Keeps keyboard focus inside a mounted card, which claims to be an `aria-modal` alertdialog and therefore has to behave
|
|
33
|
+
* like one: the flow behind the card is dead until the problem is fixed, so tabbing into it would only let a keyboard
|
|
34
|
+
* user wander through a page that cannot work while the card stays invisible to them.
|
|
35
|
+
*
|
|
36
|
+
* Call right after mounting the card, and release the trap when the card is minimized, replaced or removed. Releasing
|
|
37
|
+
* hands focus back to `previouslyFocused` unless `restoreFocus: false` is passed, which is what a caller that is about
|
|
38
|
+
* to rebind the trap to a freshly rendered card wants.
|
|
39
|
+
*/
|
|
40
|
+
declare function trapFocusInIssueCard(cardRoot: HTMLElement, options?: {
|
|
41
|
+
moveFocusIntoCard?: boolean;
|
|
42
|
+
previouslyFocused?: HTMLElement | null;
|
|
43
|
+
}): IssueCardFocusTrap;
|
|
44
|
+
declare function renderIssuePill(options: {
|
|
45
|
+
kind: IssueCardKind;
|
|
46
|
+
label: string;
|
|
47
|
+
ariaLabel: string;
|
|
48
|
+
onClick: () => void;
|
|
49
|
+
}): HTMLElement;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { IssueCardFocusTrap, IssueCardKind, IssueCardOptions, copyTextToClipboard, getIssueCardCSS, renderIssueCard, renderIssuePill, trapFocusInIssueCard };
|
|
52
|
+
//# sourceMappingURL=issue-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-card.d.ts","names":[],"sources":["../../../src/in-page-ui/issue-card.ts"],"mappings":";KAcY,aAAA;AAAA,iBAII,eAAA,CAAgB,aAAqB;AAAA,iBAyP/B,mBAAA,CAAoB,IAAA,WAAe,OAAO;AAAA,KAkEpD,gBAAA;EACV,IAAA,EAAM,aAAa,EAhUI;EAkUvB,KAAA;EACA,KAAA;EAEA,QAAA;EACA,YAAA;EACA,OAAA,UA1EuC;EA4EvC,QAAA;EACA,UAAA;EACA,SAAA,UAZU;EAcV,QAAA;EACA,WAAA,GAAc,KAAA;EACd,mBAAA,GAAsB,KAAA;EACtB,UAAA;AAAA;AAAA,iBAGc,eAAA,CAAgB,OAAA,EAAS,gBAAA,GAAmB,WAAW;AAAA,KA+D3D,kBAAA;EA7EV;;;;EAkFA,iBAAA,EAAmB,WAAW;EAC9B,OAAA,GAAU,cAAA;IAAmB,YAAA;EAAA;AAAA;;;;;AAxEnB;AAGZ;;;;iBAiFgB,oBAAA,CAAqB,QAAA,EAAU,WAAA,EAAa,OAAA;EAC1D,iBAAA;EACA,iBAAA,GAAoB,WAAA;AAAA,IACb,kBAAA;AAAA,iBA6CO,eAAA,CAAgB,OAAA;EAC9B,IAAA,EAAM,aAAA;EACN,KAAA;EACA,SAAA;EACA,OAAA;AAAA,IACE,WAAW"}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
|
2
|
+
import { getInPageUiBaseCSS } from "./base-styles.js";
|
|
3
|
+
import { h, setHtml } from "./dom.js";
|
|
4
|
+
import { HEXCLAVE_LOGO_SVG } from "./logo.js";
|
|
5
|
+
//#region src/in-page-ui/issue-card.ts
|
|
6
|
+
const COPY_ICON_SVG = "<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>";
|
|
7
|
+
function getIssueCardCSS(scopeSelector) {
|
|
8
|
+
return getInPageUiBaseCSS(scopeSelector) + `
|
|
9
|
+
${scopeSelector} .hic-backdrop {
|
|
10
|
+
position: fixed;
|
|
11
|
+
inset: 0;
|
|
12
|
+
z-index: 2147483647;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
padding: 24px;
|
|
17
|
+
background: rgba(0, 0, 0, 0.46);
|
|
18
|
+
backdrop-filter: blur(6px);
|
|
19
|
+
overflow: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
${scopeSelector} .hic-card {
|
|
23
|
+
--hic-status: var(--sdt-error);
|
|
24
|
+
width: min(720px, calc(100vw - 32px));
|
|
25
|
+
max-height: min(640px, calc(100dvh - 48px));
|
|
26
|
+
border: 1px solid color-mix(in srgb, var(--hic-status) 35%, var(--sdt-border));
|
|
27
|
+
border-radius: 18px;
|
|
28
|
+
background: var(--sdt-overlay-bg);
|
|
29
|
+
box-shadow: var(--sdt-shadow);
|
|
30
|
+
backdrop-filter: blur(18px);
|
|
31
|
+
display: flex;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
${scopeSelector} .hic-card-warning {
|
|
36
|
+
--hic-status: var(--sdt-warning);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
${scopeSelector} .hic-card-inner {
|
|
40
|
+
padding: 18px;
|
|
41
|
+
width: 100%;
|
|
42
|
+
overflow: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
${scopeSelector} .hic-header {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: flex-start;
|
|
48
|
+
justify-content: space-between;
|
|
49
|
+
gap: 12px;
|
|
50
|
+
margin-bottom: 12px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
${scopeSelector} .hic-title-row {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: flex-start;
|
|
56
|
+
gap: 10px;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
${scopeSelector} .hic-logo {
|
|
61
|
+
flex-shrink: 0;
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
width: 34px;
|
|
66
|
+
height: 34px;
|
|
67
|
+
border-radius: 10px;
|
|
68
|
+
background: var(--hic-status);
|
|
69
|
+
color: white;
|
|
70
|
+
box-shadow: 0 10px 30px color-mix(in srgb, var(--hic-status) 32%, transparent);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
${scopeSelector} .hic-badge {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
padding: 2px 6px;
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
background: var(--hic-status);
|
|
79
|
+
color: white;
|
|
80
|
+
font-size: 10px;
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
letter-spacing: 0.06em;
|
|
83
|
+
text-transform: uppercase;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
${scopeSelector} .hic-title {
|
|
87
|
+
color: var(--sdt-text);
|
|
88
|
+
margin-top: 4px;
|
|
89
|
+
font-size: 18px;
|
|
90
|
+
font-weight: 700;
|
|
91
|
+
line-height: 1.25;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
${scopeSelector} .hic-actions {
|
|
95
|
+
display: flex;
|
|
96
|
+
gap: 4px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
${scopeSelector} .hic-icon-btn {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
width: 28px;
|
|
104
|
+
height: 28px;
|
|
105
|
+
border: 1px solid var(--sdt-border);
|
|
106
|
+
border-radius: 8px;
|
|
107
|
+
background: var(--sdt-bg-elevated);
|
|
108
|
+
color: var(--sdt-text-secondary);
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
font: inherit;
|
|
111
|
+
line-height: 1;
|
|
112
|
+
vertical-align: top;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
${scopeSelector} .hic-icon-btn svg {
|
|
116
|
+
display: block;
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
${scopeSelector} .hic-text-btn {
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 6px;
|
|
123
|
+
min-height: 28px;
|
|
124
|
+
padding: 0 10px;
|
|
125
|
+
width: auto;
|
|
126
|
+
font-size: 12px;
|
|
127
|
+
line-height: 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
${scopeSelector} .hic-icon-btn:hover {
|
|
131
|
+
background: var(--sdt-bg-hover);
|
|
132
|
+
color: var(--sdt-text);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
${scopeSelector} .hic-body {
|
|
136
|
+
color: var(--sdt-text-secondary);
|
|
137
|
+
font-size: 14px;
|
|
138
|
+
line-height: 1.5;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
${scopeSelector} .hic-message-header {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
justify-content: space-between;
|
|
145
|
+
gap: 10px;
|
|
146
|
+
margin-top: 14px;
|
|
147
|
+
margin-bottom: 8px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
${scopeSelector} .hic-message-label {
|
|
151
|
+
color: var(--sdt-text);
|
|
152
|
+
font-size: 12px;
|
|
153
|
+
font-weight: 650;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
${scopeSelector} .hic-message {
|
|
157
|
+
padding: 12px;
|
|
158
|
+
max-height: min(260px, max(96px, 30dvh));
|
|
159
|
+
overflow: auto;
|
|
160
|
+
border: 1px solid var(--sdt-border-subtle);
|
|
161
|
+
border-radius: 10px;
|
|
162
|
+
background: var(--sdt-bg-subtle);
|
|
163
|
+
color: var(--sdt-text);
|
|
164
|
+
font-family: var(--sdt-font-mono);
|
|
165
|
+
font-size: 12px;
|
|
166
|
+
white-space: pre-wrap;
|
|
167
|
+
overflow-wrap: anywhere;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
${scopeSelector} .hic-fix-list {
|
|
171
|
+
margin: 8px 0 0;
|
|
172
|
+
padding-left: 18px;
|
|
173
|
+
color: var(--sdt-text);
|
|
174
|
+
font-size: 13px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
${scopeSelector} .hic-fix-list li + li {
|
|
178
|
+
margin-top: 4px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
${scopeSelector} .hic-footer {
|
|
182
|
+
margin-top: 10px;
|
|
183
|
+
color: var(--sdt-text-tertiary);
|
|
184
|
+
font-size: 12px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
${scopeSelector} .hic-pill {
|
|
188
|
+
position: fixed;
|
|
189
|
+
right: 18px;
|
|
190
|
+
bottom: 18px;
|
|
191
|
+
z-index: 2147483647;
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: 8px;
|
|
195
|
+
padding: 8px 12px 8px 8px;
|
|
196
|
+
--hic-status: var(--sdt-error);
|
|
197
|
+
border: 1px solid color-mix(in srgb, var(--hic-status) 35%, var(--sdt-border));
|
|
198
|
+
border-radius: 999px;
|
|
199
|
+
background: var(--sdt-overlay-bg);
|
|
200
|
+
box-shadow: var(--sdt-trigger-shadow);
|
|
201
|
+
color: var(--sdt-text);
|
|
202
|
+
cursor: pointer;
|
|
203
|
+
font: inherit;
|
|
204
|
+
backdrop-filter: blur(18px);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
${scopeSelector} .hic-pill-warning {
|
|
208
|
+
--hic-status: var(--sdt-warning);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
${scopeSelector} .hic-pill-logo {
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
justify-content: center;
|
|
215
|
+
width: 26px;
|
|
216
|
+
height: 26px;
|
|
217
|
+
border-radius: 999px;
|
|
218
|
+
background: var(--hic-status);
|
|
219
|
+
color: white;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@media (max-height: 520px) {
|
|
223
|
+
${scopeSelector} .hic-backdrop {
|
|
224
|
+
align-items: flex-start;
|
|
225
|
+
padding: 12px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
${scopeSelector} .hic-card {
|
|
229
|
+
width: min(720px, calc(100vw - 24px));
|
|
230
|
+
max-height: calc(100dvh - 24px);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
${scopeSelector} .hic-card-inner {
|
|
234
|
+
padding: 12px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
${scopeSelector} .hic-header {
|
|
238
|
+
margin-bottom: 8px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
${scopeSelector} .hic-title {
|
|
242
|
+
font-size: 16px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
${scopeSelector} .hic-body {
|
|
246
|
+
font-size: 13px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
${scopeSelector} .hic-message {
|
|
250
|
+
max-height: max(80px, 24dvh);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
async function copyTextToClipboard(text) {
|
|
256
|
+
const clipboard = Reflect.get(navigator, "clipboard");
|
|
257
|
+
const writeText = clipboard != null && typeof clipboard === "object" ? Reflect.get(clipboard, "writeText") : null;
|
|
258
|
+
if (typeof writeText === "function") {
|
|
259
|
+
await writeText.call(clipboard, text);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const textarea = h("textarea", {
|
|
263
|
+
style: {
|
|
264
|
+
position: "fixed",
|
|
265
|
+
left: "-9999px",
|
|
266
|
+
top: "0",
|
|
267
|
+
opacity: "0"
|
|
268
|
+
},
|
|
269
|
+
readonly: "true"
|
|
270
|
+
});
|
|
271
|
+
textarea.value = text;
|
|
272
|
+
document.body.appendChild(textarea);
|
|
273
|
+
textarea.select();
|
|
274
|
+
const copied = document.execCommand("copy");
|
|
275
|
+
textarea.remove();
|
|
276
|
+
if (!copied) throw new Error("Browser refused to copy the message.");
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* A button that copies `getText()` and briefly reports the outcome in its own label. Copying can fail for reasons
|
|
280
|
+
* outside our control (permissions, insecure contexts), so the failure is reported inline instead of being thrown into
|
|
281
|
+
* a click handler where nothing would show it.
|
|
282
|
+
*/
|
|
283
|
+
function renderCopyButton(options) {
|
|
284
|
+
const idleHtml = options.variant === "text" ? `${COPY_ICON_SVG}Copy` : COPY_ICON_SVG;
|
|
285
|
+
const button = h("button", {
|
|
286
|
+
className: options.variant === "text" ? "hic-icon-btn hic-text-btn" : "hic-icon-btn",
|
|
287
|
+
type: "button",
|
|
288
|
+
title: options.title,
|
|
289
|
+
"aria-label": options.ariaLabel,
|
|
290
|
+
onClick: () => {
|
|
291
|
+
runAsynchronously(async () => {
|
|
292
|
+
await copyTextToClipboard(options.getText());
|
|
293
|
+
button.textContent = options.variant === "text" ? "Copied" : "✓";
|
|
294
|
+
setTimeout(() => setHtml(button, idleHtml), 1500);
|
|
295
|
+
}, {
|
|
296
|
+
noErrorLogging: true,
|
|
297
|
+
onError: (error) => {
|
|
298
|
+
options.onCopyError(error);
|
|
299
|
+
button.textContent = options.variant === "text" ? "Copy failed" : "!";
|
|
300
|
+
setTimeout(() => setHtml(button, idleHtml), 1500);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
setHtml(button, idleHtml);
|
|
306
|
+
return button;
|
|
307
|
+
}
|
|
308
|
+
function renderIssueCard(options) {
|
|
309
|
+
const logoSpan = h("span", { className: "hic-logo" });
|
|
310
|
+
setHtml(logoSpan, HEXCLAVE_LOGO_SVG);
|
|
311
|
+
return h("div", { className: "hic-backdrop" }, h("div", {
|
|
312
|
+
className: options.kind === "error" ? "hic-card" : "hic-card hic-card-warning",
|
|
313
|
+
role: "alertdialog",
|
|
314
|
+
"aria-modal": "true",
|
|
315
|
+
"aria-label": options.ariaLabel,
|
|
316
|
+
tabindex: "-1"
|
|
317
|
+
}, h("div", { className: "hic-card-inner" }, h("div", { className: "hic-header" }, h("div", { className: "hic-title-row" }, logoSpan, h("div", null, h("span", { className: "hic-badge" }, options.badge), h("div", { className: "hic-title" }, options.title))), h("div", { className: "hic-actions" }, renderCopyButton({
|
|
318
|
+
variant: "icon",
|
|
319
|
+
title: "Copy AI prompt",
|
|
320
|
+
ariaLabel: `Copy AI prompt for ${options.ariaLabel}`,
|
|
321
|
+
getText: () => options.aiPrompt,
|
|
322
|
+
onCopyError: options.onCopyAiPromptError
|
|
323
|
+
}), h("button", {
|
|
324
|
+
className: "hic-icon-btn",
|
|
325
|
+
type: "button",
|
|
326
|
+
title: "Minimize",
|
|
327
|
+
"aria-label": `Minimize ${options.ariaLabel}`,
|
|
328
|
+
onClick: options.onMinimize
|
|
329
|
+
}, "–"))), h("div", { className: "hic-body" }, options.bodyText, h("div", { className: "hic-message-header" }, h("div", { className: "hic-message-label" }, options.messageLabel), renderCopyButton({
|
|
330
|
+
variant: "text",
|
|
331
|
+
title: `Copy ${options.messageLabel.toLowerCase()}`,
|
|
332
|
+
ariaLabel: `Copy ${options.ariaLabel} message`,
|
|
333
|
+
getText: () => options.message,
|
|
334
|
+
onCopyError: options.onCopyError
|
|
335
|
+
})), h("div", { className: "hic-message" }, options.message), options.howToFix == null || options.howToFix.length === 0 ? null : h("div", null, h("div", { className: "hic-message-header" }, h("div", { className: "hic-message-label" }, "How to fix")), h("ul", { className: "hic-fix-list" }, ...options.howToFix.map((step) => h("li", null, step)))), h("div", { className: "hic-footer" }, options.footerText)))));
|
|
336
|
+
}
|
|
337
|
+
const FOCUSABLE_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])";
|
|
338
|
+
/**
|
|
339
|
+
* Keeps keyboard focus inside a mounted card, which claims to be an `aria-modal` alertdialog and therefore has to behave
|
|
340
|
+
* like one: the flow behind the card is dead until the problem is fixed, so tabbing into it would only let a keyboard
|
|
341
|
+
* user wander through a page that cannot work while the card stays invisible to them.
|
|
342
|
+
*
|
|
343
|
+
* Call right after mounting the card, and release the trap when the card is minimized, replaced or removed. Releasing
|
|
344
|
+
* hands focus back to `previouslyFocused` unless `restoreFocus: false` is passed, which is what a caller that is about
|
|
345
|
+
* to rebind the trap to a freshly rendered card wants.
|
|
346
|
+
*/
|
|
347
|
+
function trapFocusInIssueCard(cardRoot, options = {}) {
|
|
348
|
+
const previouslyFocused = options.previouslyFocused !== void 0 ? options.previouslyFocused : document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
349
|
+
const card = cardRoot.querySelector(".hic-card");
|
|
350
|
+
if (!(card instanceof HTMLElement)) return {
|
|
351
|
+
previouslyFocused,
|
|
352
|
+
release: () => {}
|
|
353
|
+
};
|
|
354
|
+
const onKeyDown = (event) => {
|
|
355
|
+
if (event.key !== "Tab") return;
|
|
356
|
+
const focusable = [...card.querySelectorAll(FOCUSABLE_SELECTOR)].filter((element) => element instanceof HTMLElement);
|
|
357
|
+
const first = focusable[0] ?? card;
|
|
358
|
+
const last = focusable[focusable.length - 1] ?? card;
|
|
359
|
+
const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
360
|
+
let target = null;
|
|
361
|
+
if (active == null || !card.contains(active)) target = event.shiftKey ? last : first;
|
|
362
|
+
else if (active === last && !event.shiftKey) target = first;
|
|
363
|
+
else if (active === first && event.shiftKey) target = last;
|
|
364
|
+
if (target == null) return;
|
|
365
|
+
event.preventDefault();
|
|
366
|
+
target.focus();
|
|
367
|
+
};
|
|
368
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
369
|
+
if (options.moveFocusIntoCard !== false) card.focus();
|
|
370
|
+
return {
|
|
371
|
+
previouslyFocused,
|
|
372
|
+
release: (releaseOptions) => {
|
|
373
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
374
|
+
if (releaseOptions?.restoreFocus === false) return;
|
|
375
|
+
if (previouslyFocused != null && previouslyFocused.isConnected) previouslyFocused.focus();
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function renderIssuePill(options) {
|
|
380
|
+
const logoSpan = h("span", { className: "hic-pill-logo" });
|
|
381
|
+
setHtml(logoSpan, HEXCLAVE_LOGO_SVG);
|
|
382
|
+
return h("button", {
|
|
383
|
+
className: options.kind === "error" ? "hic-pill" : "hic-pill hic-pill-warning",
|
|
384
|
+
type: "button",
|
|
385
|
+
"aria-label": options.ariaLabel,
|
|
386
|
+
onClick: options.onClick
|
|
387
|
+
}, logoSpan, h("span", null, options.label));
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
export { copyTextToClipboard, getIssueCardCSS, renderIssueCard, renderIssuePill, trapFocusInIssueCard };
|
|
391
|
+
|
|
392
|
+
//# sourceMappingURL=issue-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-card.js","names":[],"sources":["../../../src/in-page-ui/issue-card.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// Shared renderer for Hexclave's in-page issue cards: the modal-style card (plus its minimized pill) that the SDK uses\n// to tell developers about problems it cannot recover from, like a broken pushed config or an untrusted auth domain.\n// Every issue surface renders the same shape, so the markup, styles and clipboard handling live here and each feature\n// only supplies its copy and callbacks.\n\nimport { runAsynchronously } from \"@hexclave/shared/dist/utils/promises\";\nimport { getInPageUiBaseCSS } from \"./base-styles\";\nimport { h, setHtml } from \"./dom\";\nimport { HEXCLAVE_LOGO_SVG } from \"./logo\";\n\nexport type IssueCardKind = \"error\" | \"warning\";\n\nconst COPY_ICON_SVG = '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n\nexport function getIssueCardCSS(scopeSelector: string): string {\n return getInPageUiBaseCSS(scopeSelector) + `\n ${scopeSelector} .hic-backdrop {\n position: fixed;\n inset: 0;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(0, 0, 0, 0.46);\n backdrop-filter: blur(6px);\n overflow: auto;\n }\n\n ${scopeSelector} .hic-card {\n --hic-status: var(--sdt-error);\n width: min(720px, calc(100vw - 32px));\n max-height: min(640px, calc(100dvh - 48px));\n border: 1px solid color-mix(in srgb, var(--hic-status) 35%, var(--sdt-border));\n border-radius: 18px;\n background: var(--sdt-overlay-bg);\n box-shadow: var(--sdt-shadow);\n backdrop-filter: blur(18px);\n display: flex;\n overflow: hidden;\n }\n\n ${scopeSelector} .hic-card-warning {\n --hic-status: var(--sdt-warning);\n }\n\n ${scopeSelector} .hic-card-inner {\n padding: 18px;\n width: 100%;\n overflow: auto;\n }\n\n ${scopeSelector} .hic-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n }\n\n ${scopeSelector} .hic-title-row {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n }\n\n ${scopeSelector} .hic-logo {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n border-radius: 10px;\n background: var(--hic-status);\n color: white;\n box-shadow: 0 10px 30px color-mix(in srgb, var(--hic-status) 32%, transparent);\n }\n\n ${scopeSelector} .hic-badge {\n display: inline-flex;\n flex-shrink: 0;\n padding: 2px 6px;\n border-radius: 999px;\n background: var(--hic-status);\n color: white;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n\n ${scopeSelector} .hic-title {\n color: var(--sdt-text);\n margin-top: 4px;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.25;\n }\n\n ${scopeSelector} .hic-actions {\n display: flex;\n gap: 4px;\n }\n\n ${scopeSelector} .hic-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--sdt-border);\n border-radius: 8px;\n background: var(--sdt-bg-elevated);\n color: var(--sdt-text-secondary);\n cursor: pointer;\n font: inherit;\n line-height: 1;\n vertical-align: top;\n }\n\n ${scopeSelector} .hic-icon-btn svg {\n display: block;\n flex-shrink: 0;\n }\n\n ${scopeSelector} .hic-text-btn {\n align-items: center;\n gap: 6px;\n min-height: 28px;\n padding: 0 10px;\n width: auto;\n font-size: 12px;\n line-height: 1;\n }\n\n ${scopeSelector} .hic-icon-btn:hover {\n background: var(--sdt-bg-hover);\n color: var(--sdt-text);\n }\n\n ${scopeSelector} .hic-body {\n color: var(--sdt-text-secondary);\n font-size: 14px;\n line-height: 1.5;\n }\n\n ${scopeSelector} .hic-message-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-top: 14px;\n margin-bottom: 8px;\n }\n\n ${scopeSelector} .hic-message-label {\n color: var(--sdt-text);\n font-size: 12px;\n font-weight: 650;\n }\n\n ${scopeSelector} .hic-message {\n padding: 12px;\n max-height: min(260px, max(96px, 30dvh));\n overflow: auto;\n border: 1px solid var(--sdt-border-subtle);\n border-radius: 10px;\n background: var(--sdt-bg-subtle);\n color: var(--sdt-text);\n font-family: var(--sdt-font-mono);\n font-size: 12px;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n }\n\n ${scopeSelector} .hic-fix-list {\n margin: 8px 0 0;\n padding-left: 18px;\n color: var(--sdt-text);\n font-size: 13px;\n }\n\n ${scopeSelector} .hic-fix-list li + li {\n margin-top: 4px;\n }\n\n ${scopeSelector} .hic-footer {\n margin-top: 10px;\n color: var(--sdt-text-tertiary);\n font-size: 12px;\n }\n\n ${scopeSelector} .hic-pill {\n position: fixed;\n right: 18px;\n bottom: 18px;\n z-index: 2147483647;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 8px 8px;\n --hic-status: var(--sdt-error);\n border: 1px solid color-mix(in srgb, var(--hic-status) 35%, var(--sdt-border));\n border-radius: 999px;\n background: var(--sdt-overlay-bg);\n box-shadow: var(--sdt-trigger-shadow);\n color: var(--sdt-text);\n cursor: pointer;\n font: inherit;\n backdrop-filter: blur(18px);\n }\n\n ${scopeSelector} .hic-pill-warning {\n --hic-status: var(--sdt-warning);\n }\n\n ${scopeSelector} .hic-pill-logo {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border-radius: 999px;\n background: var(--hic-status);\n color: white;\n }\n\n @media (max-height: 520px) {\n ${scopeSelector} .hic-backdrop {\n align-items: flex-start;\n padding: 12px;\n }\n\n ${scopeSelector} .hic-card {\n width: min(720px, calc(100vw - 24px));\n max-height: calc(100dvh - 24px);\n }\n\n ${scopeSelector} .hic-card-inner {\n padding: 12px;\n }\n\n ${scopeSelector} .hic-header {\n margin-bottom: 8px;\n }\n\n ${scopeSelector} .hic-title {\n font-size: 16px;\n }\n\n ${scopeSelector} .hic-body {\n font-size: 13px;\n }\n\n ${scopeSelector} .hic-message {\n max-height: max(80px, 24dvh);\n }\n }\n`;\n}\n\nexport async function copyTextToClipboard(text: string): Promise<void> {\n const clipboard: unknown = Reflect.get(navigator, \"clipboard\");\n const writeText = clipboard != null && typeof clipboard === \"object\"\n ? Reflect.get(clipboard, \"writeText\")\n : null;\n if (typeof writeText === \"function\") {\n await writeText.call(clipboard, text);\n return;\n }\n\n const textarea = h(\"textarea\", {\n style: {\n position: \"fixed\",\n left: \"-9999px\",\n top: \"0\",\n opacity: \"0\",\n },\n readonly: \"true\",\n });\n textarea.value = text;\n document.body.appendChild(textarea);\n textarea.select();\n const copied = document.execCommand(\"copy\");\n textarea.remove();\n if (!copied) {\n throw new Error(\"Browser refused to copy the message.\");\n }\n}\n\n/**\n * A button that copies `getText()` and briefly reports the outcome in its own label. Copying can fail for reasons\n * outside our control (permissions, insecure contexts), so the failure is reported inline instead of being thrown into\n * a click handler where nothing would show it.\n */\nfunction renderCopyButton(options: {\n variant: \"icon\" | \"text\",\n title: string,\n ariaLabel: string,\n getText: () => string,\n onCopyError: (error: unknown) => void,\n}): HTMLElement {\n const idleHtml = options.variant === \"text\" ? `${COPY_ICON_SVG}Copy` : COPY_ICON_SVG;\n const button = h(\"button\", {\n className: options.variant === \"text\" ? \"hic-icon-btn hic-text-btn\" : \"hic-icon-btn\",\n type: \"button\",\n title: options.title,\n \"aria-label\": options.ariaLabel,\n onClick: () => {\n runAsynchronously(async () => {\n await copyTextToClipboard(options.getText());\n button.textContent = options.variant === \"text\" ? \"Copied\" : \"✓\";\n setTimeout(() => setHtml(button, idleHtml), 1500);\n }, {\n noErrorLogging: true,\n onError: (error) => {\n options.onCopyError(error);\n button.textContent = options.variant === \"text\" ? \"Copy failed\" : \"!\";\n setTimeout(() => setHtml(button, idleHtml), 1500);\n },\n });\n },\n });\n setHtml(button, idleHtml);\n return button;\n}\n\nexport type IssueCardOptions = {\n kind: IssueCardKind,\n /** Short uppercase label in the card's badge, eg. \"Config error\". */\n badge: string,\n title: string,\n /** One or two sentences on what this means for the running app. */\n bodyText: string,\n messageLabel: string,\n message: string,\n /** Concrete steps the developer should take; rendered as a list above the footer. */\n howToFix?: readonly string[],\n footerText: string,\n ariaLabel: string,\n /** Prompt for the \"copy AI prompt\" button, ie. the message plus enough context for a coding agent to fix it. */\n aiPrompt: string,\n onCopyError: (error: unknown) => void,\n onCopyAiPromptError: (error: unknown) => void,\n onMinimize: () => void,\n};\n\nexport function renderIssueCard(options: IssueCardOptions): HTMLElement {\n const logoSpan = h(\"span\", { className: \"hic-logo\" });\n setHtml(logoSpan, HEXCLAVE_LOGO_SVG);\n\n return h(\"div\", { className: \"hic-backdrop\" },\n h(\"div\", { className: options.kind === \"error\" ? \"hic-card\" : \"hic-card hic-card-warning\", role: \"alertdialog\", \"aria-modal\": \"true\", \"aria-label\": options.ariaLabel, tabindex: \"-1\" },\n h(\"div\", { className: \"hic-card-inner\" },\n h(\"div\", { className: \"hic-header\" },\n h(\"div\", { className: \"hic-title-row\" },\n logoSpan,\n h(\"div\", null,\n h(\"span\", { className: \"hic-badge\" }, options.badge),\n h(\"div\", { className: \"hic-title\" }, options.title),\n ),\n ),\n h(\"div\", { className: \"hic-actions\" },\n renderCopyButton({\n variant: \"icon\",\n title: \"Copy AI prompt\",\n ariaLabel: `Copy AI prompt for ${options.ariaLabel}`,\n getText: () => options.aiPrompt,\n onCopyError: options.onCopyAiPromptError,\n }),\n h(\"button\", {\n className: \"hic-icon-btn\",\n type: \"button\",\n title: \"Minimize\",\n \"aria-label\": `Minimize ${options.ariaLabel}`,\n onClick: options.onMinimize,\n }, \"–\"),\n ),\n ),\n h(\"div\", { className: \"hic-body\" },\n options.bodyText,\n h(\"div\", { className: \"hic-message-header\" },\n h(\"div\", { className: \"hic-message-label\" }, options.messageLabel),\n renderCopyButton({\n variant: \"text\",\n title: `Copy ${options.messageLabel.toLowerCase()}`,\n ariaLabel: `Copy ${options.ariaLabel} message`,\n getText: () => options.message,\n onCopyError: options.onCopyError,\n }),\n ),\n h(\"div\", { className: \"hic-message\" }, options.message),\n options.howToFix == null || options.howToFix.length === 0 ? null : h(\"div\", null,\n h(\"div\", { className: \"hic-message-header\" },\n h(\"div\", { className: \"hic-message-label\" }, \"How to fix\"),\n ),\n h(\"ul\",\n { className: \"hic-fix-list\" },\n ...options.howToFix.map((step) => h(\"li\", null, step)),\n ),\n ),\n h(\"div\", { className: \"hic-footer\" }, options.footerText),\n ),\n ),\n ),\n );\n}\n\nconst FOCUSABLE_SELECTOR = \"a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])\";\n\nexport type IssueCardFocusTrap = {\n /**\n * Whatever had focus before the card first appeared. Callers that rebind the trap to a re-rendered card pass it back\n * in, so focus still returns to where the developer left it once the card is gone for good.\n */\n previouslyFocused: HTMLElement | null,\n release: (releaseOptions?: { restoreFocus?: boolean }) => void,\n};\n\n/**\n * Keeps keyboard focus inside a mounted card, which claims to be an `aria-modal` alertdialog and therefore has to behave\n * like one: the flow behind the card is dead until the problem is fixed, so tabbing into it would only let a keyboard\n * user wander through a page that cannot work while the card stays invisible to them.\n *\n * Call right after mounting the card, and release the trap when the card is minimized, replaced or removed. Releasing\n * hands focus back to `previouslyFocused` unless `restoreFocus: false` is passed, which is what a caller that is about\n * to rebind the trap to a freshly rendered card wants.\n */\nexport function trapFocusInIssueCard(cardRoot: HTMLElement, options: {\n moveFocusIntoCard?: boolean,\n previouslyFocused?: HTMLElement | null,\n} = {}): IssueCardFocusTrap {\n const previouslyFocused = options.previouslyFocused !== undefined\n ? options.previouslyFocused\n : document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const card = cardRoot.querySelector(\".hic-card\");\n if (!(card instanceof HTMLElement)) return { previouslyFocused, release: () => {} };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Tab\") return;\n const focusable = [...card.querySelectorAll(FOCUSABLE_SELECTOR)].filter((element): element is HTMLElement => element instanceof HTMLElement);\n const first = focusable[0] ?? card;\n const last = focusable[focusable.length - 1] ?? card;\n const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n\n let target: HTMLElement | null = null;\n if (active == null || !card.contains(active)) {\n // Focus is outside the card (the page moved it, or it never got in): pull it back to the card's near end.\n target = event.shiftKey ? last : first;\n } else if (active === last && !event.shiftKey) {\n target = first;\n } else if (active === first && event.shiftKey) {\n target = last;\n }\n if (target == null) return;\n event.preventDefault();\n target.focus();\n };\n\n document.addEventListener(\"keydown\", onKeyDown, true);\n if (options.moveFocusIntoCard !== false) {\n card.focus();\n }\n\n return {\n previouslyFocused,\n release: (releaseOptions) => {\n document.removeEventListener(\"keydown\", onKeyDown, true);\n if (releaseOptions?.restoreFocus === false) return;\n if (previouslyFocused != null && previouslyFocused.isConnected) {\n previouslyFocused.focus();\n }\n },\n };\n}\n\nexport function renderIssuePill(options: {\n kind: IssueCardKind,\n label: string,\n ariaLabel: string,\n onClick: () => void,\n}): HTMLElement {\n const logoSpan = h(\"span\", { className: \"hic-pill-logo\" });\n setHtml(logoSpan, HEXCLAVE_LOGO_SVG);\n return h(\"button\", {\n className: options.kind === \"error\" ? \"hic-pill\" : \"hic-pill hic-pill-warning\",\n type: \"button\",\n \"aria-label\": options.ariaLabel,\n onClick: options.onClick,\n },\n logoSpan,\n h(\"span\", null, options.label),\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { renderIssueCard, trapFocusInIssueCard } from "./issue-card.js";
|
|
3
|
+
//#region src/in-page-ui/issue-card.test.ts
|
|
4
|
+
// @vitest-environment jsdom
|
|
5
|
+
function mountCard() {
|
|
6
|
+
const root = document.body.appendChild(document.createElement("div"));
|
|
7
|
+
root.appendChild(renderIssueCard({
|
|
8
|
+
kind: "error",
|
|
9
|
+
badge: "Config error",
|
|
10
|
+
title: "Something is misconfigured",
|
|
11
|
+
bodyText: "Body",
|
|
12
|
+
messageLabel: "Error message",
|
|
13
|
+
message: "Message",
|
|
14
|
+
footerText: "Footer",
|
|
15
|
+
ariaLabel: "Hexclave config error",
|
|
16
|
+
aiPrompt: "prompt",
|
|
17
|
+
onCopyError: () => {},
|
|
18
|
+
onCopyAiPromptError: () => {},
|
|
19
|
+
onMinimize: () => {}
|
|
20
|
+
}));
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
function pressTab() {
|
|
24
|
+
document.dispatchEvent(new KeyboardEvent("keydown", {
|
|
25
|
+
key: "Tab",
|
|
26
|
+
bubbles: true,
|
|
27
|
+
cancelable: true
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function cardButtons(root) {
|
|
31
|
+
return [...root.querySelectorAll(".hic-card button")];
|
|
32
|
+
}
|
|
33
|
+
describe("issue card focus trap", () => {
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
document.body.replaceChildren();
|
|
36
|
+
});
|
|
37
|
+
it("follows a re-rendered card without moving focus or losing the original focus owner", () => {
|
|
38
|
+
const pageButton = document.body.appendChild(document.createElement("button"));
|
|
39
|
+
pageButton.focus();
|
|
40
|
+
const firstRoot = mountCard();
|
|
41
|
+
const firstTrap = trapFocusInIssueCard(firstRoot);
|
|
42
|
+
expect(document.activeElement).toBe(firstRoot.querySelector(".hic-card"));
|
|
43
|
+
firstRoot.remove();
|
|
44
|
+
const secondRoot = mountCard();
|
|
45
|
+
firstTrap.release({ restoreFocus: false });
|
|
46
|
+
const secondTrap = trapFocusInIssueCard(secondRoot, {
|
|
47
|
+
moveFocusIntoCard: false,
|
|
48
|
+
previouslyFocused: firstTrap.previouslyFocused
|
|
49
|
+
});
|
|
50
|
+
expect(document.activeElement).not.toBe(secondRoot.querySelector(".hic-card"));
|
|
51
|
+
pressTab();
|
|
52
|
+
expect(document.activeElement).toBe(cardButtons(secondRoot)[0]);
|
|
53
|
+
secondTrap.release();
|
|
54
|
+
expect(document.activeElement).toBe(pageButton);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//#endregion
|
|
58
|
+
export {};
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=issue-card.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue-card.test.js","names":[],"sources":["../../../src/in-page-ui/issue-card.test.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// @vitest-environment jsdom\n\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { renderIssueCard, trapFocusInIssueCard } from \"./issue-card\";\n\nfunction mountCard(): HTMLElement {\n const root = document.body.appendChild(document.createElement(\"div\"));\n root.appendChild(renderIssueCard({\n kind: \"error\",\n badge: \"Config error\",\n title: \"Something is misconfigured\",\n bodyText: \"Body\",\n messageLabel: \"Error message\",\n message: \"Message\",\n footerText: \"Footer\",\n ariaLabel: \"Hexclave config error\",\n aiPrompt: \"prompt\",\n onCopyError: () => {},\n onCopyAiPromptError: () => {},\n onMinimize: () => {},\n }));\n return root;\n}\n\nfunction pressTab() {\n document.dispatchEvent(new KeyboardEvent(\"keydown\", { key: \"Tab\", bubbles: true, cancelable: true }));\n}\n\nfunction cardButtons(root: HTMLElement): HTMLButtonElement[] {\n return [...root.querySelectorAll<HTMLButtonElement>(\".hic-card button\")];\n}\n\ndescribe(\"issue card focus trap\", () => {\n afterEach(() => {\n document.body.replaceChildren();\n });\n\n it(\"follows a re-rendered card without moving focus or losing the original focus owner\", () => {\n const pageButton = document.body.appendChild(document.createElement(\"button\"));\n pageButton.focus();\n\n const firstRoot = mountCard();\n const firstTrap = trapFocusInIssueCard(firstRoot);\n expect(document.activeElement).toBe(firstRoot.querySelector(\".hic-card\"));\n\n // What the pushed-config overlay does on every poll: throw the rendered card away and render a fresh one.\n firstRoot.remove();\n const secondRoot = mountCard();\n firstTrap.release({ restoreFocus: false });\n const secondTrap = trapFocusInIssueCard(secondRoot, {\n moveFocusIntoCard: false,\n previouslyFocused: firstTrap.previouslyFocused,\n });\n expect(document.activeElement).not.toBe(secondRoot.querySelector(\".hic-card\"));\n\n // The trap now belongs to the visible card, so Tab lands inside it instead of inside the detached one.\n pressTab();\n expect(document.activeElement).toBe(cardButtons(secondRoot)[0]);\n\n secondTrap.release();\n expect(document.activeElement).toBe(pageButton);\n });\n});\n"],"mappings":";;;;AASA,SAAS,YAAyB;CAChC,MAAM,OAAO,SAAS,KAAK,YAAY,SAAS,cAAc,KAAK,CAAC;CACpE,KAAK,YAAY,gBAAgB;EAC/B,MAAM;EACN,OAAO;EACP,OAAO;EACP,UAAU;EACV,cAAc;EACd,SAAS;EACT,YAAY;EACZ,WAAW;EACX,UAAU;EACV,mBAAmB,CAAC;EACpB,2BAA2B,CAAC;EAC5B,kBAAkB,CAAC;CACrB,CAAC,CAAC;CACF,OAAO;AACT;AAEA,SAAS,WAAW;CAClB,SAAS,cAAc,IAAI,cAAc,WAAW;EAAE,KAAK;EAAO,SAAS;EAAM,YAAY;CAAK,CAAC,CAAC;AACtG;AAEA,SAAS,YAAY,MAAwC;CAC3D,OAAO,CAAC,GAAG,KAAK,iBAAoC,kBAAkB,CAAC;AACzE;AAEA,SAAS,+BAA+B;CACtC,gBAAgB;EACd,SAAS,KAAK,gBAAgB;CAChC,CAAC;CAED,GAAG,4FAA4F;EAC7F,MAAM,aAAa,SAAS,KAAK,YAAY,SAAS,cAAc,QAAQ,CAAC;EAC7E,WAAW,MAAM;EAEjB,MAAM,YAAY,UAAU;EAC5B,MAAM,YAAY,qBAAqB,SAAS;EAChD,OAAO,SAAS,aAAa,CAAC,CAAC,KAAK,UAAU,cAAc,WAAW,CAAC;EAGxE,UAAU,OAAO;EACjB,MAAM,aAAa,UAAU;EAC7B,UAAU,QAAQ,EAAE,cAAc,MAAM,CAAC;EACzC,MAAM,aAAa,qBAAqB,YAAY;GAClD,mBAAmB;GACnB,mBAAmB,UAAU;EAC/B,CAAC;EACD,OAAO,SAAS,aAAa,CAAC,CAAC,IAAI,KAAK,WAAW,cAAc,WAAW,CAAC;EAG7E,SAAS;EACT,OAAO,SAAS,aAAa,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC,EAAE;EAE9D,WAAW,QAAQ;EACnB,OAAO,SAAS,aAAa,CAAC,CAAC,KAAK,UAAU;CAChD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
//#region src/in-page-ui/logo.d.ts
|
|
2
|
+
declare const HEXCLAVE_LOGO_SVG = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linejoin=\"miter\"><path d=\"M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(120 24 24)\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(240 24 24)\"/></svg>";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { HEXCLAVE_LOGO_SVG };
|
|
5
|
+
//# sourceMappingURL=logo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logo.d.ts","names":[],"sources":["../../../src/in-page-ui/logo.ts"],"mappings":";cAOa,iBAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/in-page-ui/logo.ts
|
|
2
|
+
const HEXCLAVE_LOGO_SVG = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linejoin=\"miter\"><path d=\"M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(120 24 24)\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(240 24 24)\"/></svg>";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { HEXCLAVE_LOGO_SVG };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=logo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logo.js","names":[],"sources":["../../../src/in-page-ui/logo.ts"],"sourcesContent":["\n//===========================================\n// THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template\n//===========================================\n// Hexclave mark — hexagon outline with three radial bars, monochrome via currentColor so it inherits the surrounding\n// text color. Sourced from apps/dashboard/public/hexclave-icon.svg (gradient + glow stripped; this is a tiny glyph, not\n// the full brand mark). Shared by every in-page UI so the mark cannot drift between them.\nexport const HEXCLAVE_LOGO_SVG = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-linejoin=\"miter\"><path d=\"M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(120 24 24)\"/><path d=\"M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z\" fill=\"currentColor\" stroke=\"none\" transform=\"rotate(240 24 24)\"/></svg>';\n"],"mappings":";AAOA,MAAa,oBAAoB"}
|
|
@@ -205,7 +205,7 @@ declare class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
205
205
|
} & {
|
|
206
206
|
uniqueIdentifier: string;
|
|
207
207
|
};
|
|
208
|
-
setCurrentUser: (userJsonPromise: Promise<import("@hexclave/shared/dist/current-user-
|
|
208
|
+
setCurrentUser: (userJsonPromise: Promise<import("@hexclave/shared/dist/current-user-BO0U7S87").t["Client"]["Read"] | null>) => void;
|
|
209
209
|
getConstructorOptions: () => {
|
|
210
210
|
baseUrl?: string | {
|
|
211
211
|
browser: string;
|
|
@@ -238,11 +238,11 @@ declare class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Pro
|
|
|
238
238
|
sendAnalyticsEventBatch: (body: string, options: {
|
|
239
239
|
keepalive: boolean;
|
|
240
240
|
}) => Promise<Result<Response, Error>>;
|
|
241
|
-
addRequestListener: (listener: import("@hexclave/shared/dist/client-interface-
|
|
241
|
+
addRequestListener: (listener: import("@hexclave/shared/dist/client-interface-D3ro3nO_").r) => () => void;
|
|
242
242
|
sendRequest: (path: string, requestOptions: RequestInit, requestType?: "client" | "server" | "admin") => Promise<Response & {
|
|
243
243
|
usedTokens: {
|
|
244
|
-
accessToken: import("@hexclave/shared/dist/sessions-
|
|
245
|
-
refreshToken: import("@hexclave/shared/dist/sessions-
|
|
244
|
+
accessToken: import("@hexclave/shared/dist/sessions-CljaxQdd").t;
|
|
245
|
+
refreshToken: import("@hexclave/shared/dist/sessions-CljaxQdd").i | null;
|
|
246
246
|
} | null;
|
|
247
247
|
}>;
|
|
248
248
|
getUrls: () => Readonly<import("@hexclave/shared/dist/handler-urls-BHt3v8Ai").s>;
|