@reticlehq/browser 2.14.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/actions-dom.d.ts +8 -0
- package/dist/actions/actions-dom.js +70 -3
- package/dist/actions/actions-press.d.ts +4 -0
- package/dist/actions/actions-press.js +46 -0
- package/dist/actions/actions.d.ts +1 -0
- package/dist/actions/actions.js +41 -8
- package/dist/actions/appeared-text.js +6 -0
- package/dist/actions/scroll.d.ts +8 -1
- package/dist/actions/scroll.js +28 -10
- package/dist/commands/commands.js +10 -3
- package/dist/dom/a11y.js +2 -2
- package/dist/dom/desktop-capture.js +1 -1
- package/dist/dom/dom-ignore.js +1 -1
- package/dist/dom/occlusion.js +31 -5
- package/dist/dom/paint-context.d.ts +6 -0
- package/dist/dom/paint-context.js +38 -0
- package/dist/dom/query.js +2 -2
- package/dist/dom/snapshot.js +1 -1
- package/dist/edit-epoch.d.ts +1 -1
- package/dist/edit-epoch.js +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.js +3 -5
- package/dist/observers/animation.js +1 -1
- package/dist/observers/blind-spots.js +1 -1
- package/dist/observers/dialogs.js +1 -1
- package/dist/observers/dom.js +28 -8
- package/dist/observers/download.js +1 -1
- package/dist/observers/health.js +1 -1
- package/dist/observers/{network-body.js → net-detail/network-body.js} +2 -2
- package/dist/observers/network-stream.js +2 -2
- package/dist/observers/network.d.ts +1 -1
- package/dist/observers/network.js +2 -2
- package/dist/observers/scroll.js +2 -2
- package/dist/presenter/chrome/layers.d.ts +2 -0
- package/dist/presenter/chrome/layers.js +2 -0
- package/dist/presenter/chrome/log-kinds.d.ts +12 -0
- package/dist/presenter/chrome/log-kinds.js +7 -0
- package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -1
- package/dist/presenter/{presenter-log.d.ts → chrome/presenter-log.d.ts} +3 -12
- package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +4 -9
- package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
- package/dist/presenter/chrome/presenter-safe-html.js +16 -0
- package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -1
- package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -0
- package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -0
- package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -0
- package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -0
- package/dist/presenter/presenter-account-styles.d.ts +1 -0
- package/dist/presenter/presenter-account-styles.js +99 -0
- package/dist/presenter/presenter-account.d.ts +40 -0
- package/dist/presenter/presenter-account.js +178 -0
- package/dist/presenter/presenter-config.d.ts +9 -2
- package/dist/presenter/presenter-config.js +3 -1
- package/dist/presenter/presenter-controls.d.ts +1 -6
- package/dist/presenter/presenter-controls.js +7 -2
- package/dist/presenter/presenter-dock-layout.js +1 -1
- package/dist/presenter/presenter-drag.d.ts +1 -19
- package/dist/presenter/presenter-drag.js +3 -50
- package/dist/presenter/presenter-glow.js +1 -1
- package/dist/presenter/presenter-hud-position.d.ts +18 -0
- package/dist/presenter/presenter-hud-position.js +50 -0
- package/dist/presenter/presenter-report-styles.js +16 -1
- package/dist/presenter/presenter-report.d.ts +2 -1
- package/dist/presenter/presenter-report.js +29 -22
- package/dist/presenter/presenter-run-state.d.ts +1 -1
- package/dist/presenter/presenter-run-state.js +1 -1
- package/dist/presenter/presenter-settings-styles.js +2 -1
- package/dist/presenter/presenter-settings.d.ts +4 -0
- package/dist/presenter/presenter-settings.js +20 -2
- package/dist/presenter/presenter-shell-styles.js +21 -15
- package/dist/presenter/presenter-shell.d.ts +4 -0
- package/dist/presenter/presenter-shell.js +25 -4
- package/dist/presenter/presenter-styles.d.ts +2 -1
- package/dist/presenter/presenter-styles.js +6 -3
- package/dist/presenter/presenter-workspace.d.ts +4 -0
- package/dist/presenter/presenter-workspace.js +29 -2
- package/dist/presenter/presenter.d.ts +3 -3
- package/dist/presenter/presenter.js +25 -14
- package/dist/presenter/tour/tour-view.d.ts +33 -0
- package/dist/presenter/tour/tour-view.js +188 -0
- package/dist/presenter/tour/tour.d.ts +25 -0
- package/dist/presenter/tour/tour.js +245 -0
- package/dist/recorder/recorder.js +6 -0
- package/dist/registry/declared-channels.d.ts +2 -0
- package/dist/registry/declared-channels.js +14 -0
- package/dist/registry/declared-platform.d.ts +4 -0
- package/dist/registry/declared-platform.js +34 -0
- package/dist/registry/emitter.js +1 -1
- package/dist/registry/store-adapters.js +1 -1
- package/dist/registry/stores.js +1 -1
- package/dist/reticle-presenter-helpers.js +10 -6
- package/dist/reticle-singleton.d.ts +2 -0
- package/dist/reticle-singleton.js +3 -0
- package/dist/reticle.js +95 -35
- package/dist/review/annotator-styles.js +1 -1
- package/dist/review/annotator.js +2 -2
- package/dist/review/mark-anchor.js +3 -3
- package/dist/transport/transport.js +2 -2
- package/package.json +5 -4
- package/dist/presenter/presenter-test-helpers.d.ts +0 -14
- package/dist/presenter/presenter-test-helpers.js +0 -39
- /package/dist/actions/{synthetic-input.d.ts → synthetic/synthetic-input.d.ts} +0 -0
- /package/dist/actions/{synthetic-input.js → synthetic/synthetic-input.js} +0 -0
- /package/dist/dom/{auto-anchor.d.ts → addressing/auto-anchor.d.ts} +0 -0
- /package/dist/dom/{auto-anchor.js → addressing/auto-anchor.js} +0 -0
- /package/dist/dom/{refs.d.ts → addressing/refs.d.ts} +0 -0
- /package/dist/dom/{refs.js → addressing/refs.js} +0 -0
- /package/dist/dom/{source.d.ts → addressing/source.d.ts} +0 -0
- /package/dist/dom/{source.js → addressing/source.js} +0 -0
- /package/dist/observers/{network-body.d.ts → net-detail/network-body.d.ts} +0 -0
- /package/dist/observers/{network-redact.d.ts → net-detail/network-redact.d.ts} +0 -0
- /package/dist/observers/{network-redact.js → net-detail/network-redact.js} +0 -0
- /package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -0
- /package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -0
- /package/dist/presenter/{presenter-brand.d.ts → chrome/presenter-brand.d.ts} +0 -0
- /package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -0
- /package/dist/presenter/{presenter-effects.d.ts → chrome/presenter-effects.d.ts} +0 -0
- /package/dist/presenter/{presenter-hud-chrome.d.ts → chrome/presenter-hud-chrome.d.ts} +0 -0
- /package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -0
- /package/dist/presenter/{presenter-report-copy.d.ts → chrome/presenter-report-copy.d.ts} +0 -0
- /package/dist/presenter/{presenter-report-copy.js → chrome/presenter-report-copy.js} +0 -0
- /package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -0
- /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
- /package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -0
- /package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -0
- /package/dist/registry/{adapters.js → stores/adapters.js} +0 -0
- /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
- /package/dist/timers/{native-console.js → console/native-console.js} +0 -0
- /package/dist/timers/{native-timers.d.ts → native/native-timers.d.ts} +0 -0
- /package/dist/timers/{native-timers.js → native/native-timers.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventType } from '@reticlehq/core';
|
|
2
2
|
import { observeSafely } from './types.js';
|
|
3
|
-
import { isSyntheticInput } from '../actions/synthetic-input.js';
|
|
3
|
+
import { isSyntheticInput } from '../actions/synthetic/synthetic-input.js';
|
|
4
4
|
const DECLINED = { alert: undefined, confirm: false, prompt: null };
|
|
5
5
|
const KINDS = ['alert', 'confirm', 'prompt'];
|
|
6
6
|
export function installDialogs(emit) {
|
package/dist/observers/dom.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventType, TruncationChannel } from '@reticlehq/core';
|
|
2
2
|
import { getAccessibleName, getRole, isVisible } from '../dom/a11y.js';
|
|
3
|
-
import { refs } from '../dom/refs.js';
|
|
3
|
+
import { refs } from '../dom/addressing/refs.js';
|
|
4
4
|
function regionKeyOf(target) {
|
|
5
5
|
const el = isElement(target) ? target : null;
|
|
6
6
|
if (null === el)
|
|
@@ -39,6 +39,26 @@ const MAX_PER_BATCH = 40;
|
|
|
39
39
|
function isMeaningful(role, name) {
|
|
40
40
|
return role !== 'generic' || name.length > 0;
|
|
41
41
|
}
|
|
42
|
+
const MOUNT_SCAN_LIMIT = 40;
|
|
43
|
+
function describeMount(node) {
|
|
44
|
+
const role = getRole(node);
|
|
45
|
+
const name = getAccessibleName(node);
|
|
46
|
+
if (isMeaningful(role, name))
|
|
47
|
+
return { role, name, el: node };
|
|
48
|
+
let scanned = 0;
|
|
49
|
+
for (const child of node.querySelectorAll('*')) {
|
|
50
|
+
if (scanned >= MOUNT_SCAN_LIMIT)
|
|
51
|
+
return undefined;
|
|
52
|
+
scanned += 1;
|
|
53
|
+
if (isReticleOverlay(child))
|
|
54
|
+
continue;
|
|
55
|
+
const childRole = getRole(child);
|
|
56
|
+
const childName = getAccessibleName(child);
|
|
57
|
+
if (isMeaningful(childRole, childName))
|
|
58
|
+
return { role: childRole, name: childName, el: child };
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
42
62
|
const TEXT_NODE = 3;
|
|
43
63
|
function textOf(nodes) {
|
|
44
64
|
let out = '';
|
|
@@ -112,12 +132,12 @@ export function installDom(emit) {
|
|
|
112
132
|
}
|
|
113
133
|
if (isReticleOverlay(node))
|
|
114
134
|
continue;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
if (!isMeaningful(role, name))
|
|
135
|
+
const mounted = describeMount(node);
|
|
136
|
+
if (mounted === undefined)
|
|
118
137
|
continue;
|
|
138
|
+
const { role, name } = mounted;
|
|
119
139
|
added += 1;
|
|
120
|
-
const ref = refs.refFor(
|
|
140
|
+
const ref = refs.refFor(mounted.el);
|
|
121
141
|
emit(EventType.DOM_ADDED, { role, name, region: regionKeyOf(record.target) }, ref);
|
|
122
142
|
if (DIALOG_ROLES.has(role) ||
|
|
123
143
|
LIVE_ROLES.has(role) ||
|
|
@@ -135,10 +155,10 @@ export function installDom(emit) {
|
|
|
135
155
|
}
|
|
136
156
|
if (isReticleOverlay(node))
|
|
137
157
|
continue;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
if (!isMeaningful(role, name))
|
|
158
|
+
const gone = describeMount(node);
|
|
159
|
+
if (gone === undefined)
|
|
141
160
|
continue;
|
|
161
|
+
const { role, name } = gone;
|
|
142
162
|
removed += 1;
|
|
143
163
|
emit(EventType.DOM_REMOVED, { role, name, region: regionKeyOf(record.target) });
|
|
144
164
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventType } from '@reticlehq/core';
|
|
2
2
|
import { captureMethod } from '../patching/capture-method.js';
|
|
3
|
-
import { projectBody } from './network-body.js';
|
|
3
|
+
import { projectBody } from './net-detail/network-body.js';
|
|
4
4
|
import { observeSafely, observeValue } from './types.js';
|
|
5
5
|
const TEXTUAL = /text\/|json|csv|xml|ndjson|javascript/i;
|
|
6
6
|
const PREVIEW_CHARS = 2000;
|
package/dist/observers/health.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BrowserBrand, EventType, HealthReason, RETICLE_IPC_GLOBAL, SESSION_HEALTH, } from '@reticlehq/core';
|
|
2
|
-
import { nativeSetInterval } from '../timers/native-timers.js';
|
|
2
|
+
import { nativeSetInterval } from '../timers/native/native-timers.js';
|
|
3
3
|
function detectRuntime() {
|
|
4
4
|
const w = window;
|
|
5
5
|
if (w['__TAURI_INTERNALS__'] !== undefined || w['__TAURI__'] !== undefined)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REDACTED_VALUE } from '@reticlehq/core';
|
|
2
|
-
import { isSensitiveKey, safeStringify, scrubKnownSecrets } from '
|
|
3
|
-
import { nativeClearTimeout, nativeSetTimeout } from '
|
|
2
|
+
import { isSensitiveKey, safeStringify, scrubKnownSecrets } from '../../security/serialization.js';
|
|
3
|
+
import { nativeClearTimeout, nativeSetTimeout } from '../../timers/native/native-timers.js';
|
|
4
4
|
const CAPTURABLE_CONTENT = /application\/json|text\/|application\/xml|x-www-form-urlencoded|graphql/i;
|
|
5
5
|
const STREAMING_CONTENT = /event-stream|x-ndjson|application\/stream/i;
|
|
6
6
|
const ENDLESS_BY_DESIGN = /event-stream/i;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventType, StreamDirection, StreamTransport } from '@reticlehq/core';
|
|
2
|
-
import { isStreamingBody } from './network-body.js';
|
|
3
|
-
import { nativeClearTimeout, nativeSetTimeout } from '../timers/native-timers.js';
|
|
2
|
+
import { isStreamingBody } from './net-detail/network-body.js';
|
|
3
|
+
import { nativeClearTimeout, nativeSetTimeout } from '../timers/native/native-timers.js';
|
|
4
4
|
const STREAM_WATCH_MS = 10_000;
|
|
5
5
|
export function watchStreamedBody(emit, res, id, url, contentType, contentLength) {
|
|
6
6
|
if (!isStreamingBody(contentType, contentLength))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Emit, type Teardown } from './types.js';
|
|
2
|
-
import { redactUrl, netUrlFields } from './network-redact.js';
|
|
2
|
+
import { redactUrl, netUrlFields } from './net-detail/network-redact.js';
|
|
3
3
|
export { redactUrl, netUrlFields };
|
|
4
4
|
type NetResponseReinterpreter = (url: string, header: (name: string) => string | null) => Record<string, unknown> | undefined;
|
|
5
5
|
interface NetworkOptions {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BlindSpotKind, EventType, RETICLE_WS_PATH, StreamTransport, StreamDirection, } from '@reticlehq/core';
|
|
2
2
|
import { captureMethod } from '../patching/capture-method.js';
|
|
3
3
|
import { observeSafely, observeValue } from './types.js';
|
|
4
|
-
import { isCapturableType, projectBody, projectErrorBody, shouldCaptureErrorBody, withBodyDeadline, } from './network-body.js';
|
|
5
|
-
import { redactUrl, netUrlFields } from './network-redact.js';
|
|
4
|
+
import { isCapturableType, projectBody, projectErrorBody, shouldCaptureErrorBody, withBodyDeadline, } from './net-detail/network-body.js';
|
|
5
|
+
import { redactUrl, netUrlFields } from './net-detail/network-redact.js';
|
|
6
6
|
import { watchStreamedBody } from './network-stream.js';
|
|
7
7
|
import { requireCapturedMethod } from '../util/captured-method.js';
|
|
8
8
|
export { redactUrl, netUrlFields };
|
package/dist/observers/scroll.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventType, ScrollDirection } from '@reticlehq/core';
|
|
2
|
-
import { refs } from '../dom/refs.js';
|
|
3
|
-
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
2
|
+
import { refs } from '../dom/addressing/refs.js';
|
|
3
|
+
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
|
|
4
4
|
const THROTTLE_MS = 100;
|
|
5
5
|
const REVEAL_SELECTOR = '[data-reticle-reveal], [data-reveal], section';
|
|
6
6
|
export function installScroll(emit) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const LOG_KIND: {
|
|
2
|
+
readonly READ: "read";
|
|
3
|
+
readonly ACT: "act";
|
|
4
|
+
readonly NARRATION: "narration";
|
|
5
|
+
readonly HUMAN: "human";
|
|
6
|
+
};
|
|
7
|
+
export type LogKind = (typeof LOG_KIND)[keyof typeof LOG_KIND];
|
|
8
|
+
export declare const LOG_RESULT: {
|
|
9
|
+
readonly PASS: "pass";
|
|
10
|
+
readonly FAIL: "fail";
|
|
11
|
+
};
|
|
12
|
+
export type LogResult = (typeof LOG_RESULT)[keyof typeof LOG_RESULT];
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { PresenterMode } from '@reticlehq/core';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readonly NARRATION: "narration";
|
|
6
|
-
readonly HUMAN: "human";
|
|
7
|
-
};
|
|
8
|
-
export type LogKind = (typeof LOG_KIND)[keyof typeof LOG_KIND];
|
|
9
|
-
export declare const LOG_RESULT: {
|
|
10
|
-
readonly PASS: "pass";
|
|
11
|
-
readonly FAIL: "fail";
|
|
12
|
-
};
|
|
13
|
-
export type LogResult = (typeof LOG_RESULT)[keyof typeof LOG_RESULT];
|
|
2
|
+
import { LOG_KIND, LOG_RESULT, type LogKind, type LogResult } from './log-kinds.js';
|
|
3
|
+
export { LOG_KIND, LOG_RESULT };
|
|
4
|
+
export type { LogKind, LogResult };
|
|
14
5
|
export declare const CHIP_LABEL: Record<PresenterMode, string>;
|
|
15
6
|
export declare const DATA_RETICLE_LOG = "data-reticle-log";
|
|
16
7
|
export declare const LOG_TIME_ATTR = "data-reticle-log-time";
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { PresenterMode } from '@reticlehq/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { LOG_KIND, LOG_RESULT } from './log-kinds.js';
|
|
3
|
+
import { nativeSetTimeout } from '../../timers/native/native-timers.js';
|
|
4
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, } from '../icons/presenter-icons.js';
|
|
4
5
|
const DEFAULT_LOG_MAX = 50;
|
|
5
|
-
export
|
|
6
|
-
READ: 'read',
|
|
7
|
-
ACT: 'act',
|
|
8
|
-
NARRATION: 'narration',
|
|
9
|
-
HUMAN: 'human',
|
|
10
|
-
};
|
|
11
|
-
export const LOG_RESULT = { PASS: 'pass', FAIL: 'fail' };
|
|
6
|
+
export { LOG_KIND, LOG_RESULT };
|
|
12
7
|
const LOG_CHIP = { read: 'READ', act: 'ACT', narration: '', human: '' };
|
|
13
8
|
const LOG_CHIP_ICON = {
|
|
14
9
|
read: PresenterIcon.VIEW,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function esc(value) {
|
|
2
|
+
return value
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"');
|
|
7
|
+
}
|
|
8
|
+
export function isSafeDashboardUrl(raw) {
|
|
9
|
+
try {
|
|
10
|
+
const scheme = new URL(raw).protocol;
|
|
11
|
+
return 'https:' === scheme || 'http:' === scheme;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LOG_RESULT } from './presenter-log.js';
|
|
2
|
-
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from '
|
|
2
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon } from '../icons/presenter-icons.js';
|
|
3
3
|
export function renderTally(el, runLog, prev) {
|
|
4
4
|
const next = countVerdicts(runLog);
|
|
5
5
|
if (el === undefined)
|
|
@@ -18,6 +18,7 @@ const BODIES = {
|
|
|
18
18
|
help: '<path d="M8.228 9c.549-1.165 2.03-2 3.772-2c2.21 0 4 1.343 4 3c0 1.4-1.278 2.575-3.006 2.907c-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0"/>',
|
|
19
19
|
'caret-right': '<path d="m9 5l7 7l-7 7"/>',
|
|
20
20
|
annotate: '<path d="m16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/>',
|
|
21
|
+
sync: '<path d="M4.5 12c0-1.232.046-2.453.138-3.662a4.006 4.006 0 0 1 3.7-3.7a48.678 48.678 0 0 1 7.324 0a4.006 4.006 0 0 1 3.7 3.7c.017.22.032.441.046.662M4.5 12l-3-3m3 3l3-3m12 3c0 1.232-.046 2.453-.138 3.662a4.006 4.006 0 0 1-3.7 3.7a48.657 48.657 0 0 1-7.324 0a4.006 4.006 0 0 1-3.7-3.7c-.017-.22-.032-.441-.046-.662M19.5 12l-3 3m3-3l3 3"/>',
|
|
21
22
|
};
|
|
22
23
|
export const HERO_ICON_BODIES = BODIES;
|
|
23
24
|
const SOLID_BODIES = {
|
|
@@ -19,6 +19,7 @@ export declare const PresenterIcon: {
|
|
|
19
19
|
readonly HELP: "help";
|
|
20
20
|
readonly CARET_RIGHT: "caret-right";
|
|
21
21
|
readonly ANNOTATE: "annotate";
|
|
22
|
+
readonly SYNC: "sync";
|
|
22
23
|
};
|
|
23
24
|
export type PresenterIconName = (typeof PresenterIcon)[keyof typeof PresenterIcon];
|
|
24
25
|
export declare const PRESENTER_ICON_SIZE: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ACCOUNT_CSS: string;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { HUD_DROP_SHADOW, HUD_SURFACE_PAINT } from './chrome/presenter-hud-chrome.js';
|
|
2
|
+
const AVATAR = 20;
|
|
3
|
+
const AVATAR_LG = 28;
|
|
4
|
+
export const ACCOUNT_CSS = `
|
|
5
|
+
/* The control establishes its own stacking context, and a high one.
|
|
6
|
+
Its menu is absolutely positioned inside a panel whose other children (the log well, the act strip)
|
|
7
|
+
are themselves positioned with their own z-index, so a z-index on the MENU alone was being compared
|
|
8
|
+
inside the wrong context and lost to a sibling with a lower number. Raising it here, on the thing
|
|
9
|
+
that contains the menu, is what actually puts the menu above them. 9 clears the workspace menu's 8,
|
|
10
|
+
which is the highest in-panel chrome. */
|
|
11
|
+
/* The header row has to out-stack the log well, or nothing inside it can.
|
|
12
|
+
.reticle-chat-head is position:relative with z-index:2, so it IS a stacking context and the number
|
|
13
|
+
below is scoped inside it -- while the log well is a SIBLING, also at z-index:2, and later in DOM
|
|
14
|
+
order. Equal z, later sibling wins, so the well painted over the whole header and took the open
|
|
15
|
+
menu with it. The menu measured correctly and was invisible, which is the shape of bug that reads
|
|
16
|
+
as "the dropdown does not work". Raising the header is what lets its children escape; the two only
|
|
17
|
+
overlap while a menu is open, so nothing else moves. */
|
|
18
|
+
.reticle-chat-head{z-index:3;}
|
|
19
|
+
.reticle-account{position:relative;z-index:9;display:inline-flex;align-items:center;gap:6px;flex:none;}
|
|
20
|
+
.reticle-account-avatar{
|
|
21
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
22
|
+
width:${String(AVATAR)}px;height:${String(AVATAR)}px;border-radius:999px;
|
|
23
|
+
font-size:9px;font-weight:600;letter-spacing:.02em;
|
|
24
|
+
background:rgba(255,255,255,.12);color:rgba(255,255,255,.92);user-select:none;}
|
|
25
|
+
.reticle-account-avatar--lg{
|
|
26
|
+
width:${String(AVATAR_LG)}px;height:${String(AVATAR_LG)}px;font-size:11px;}
|
|
27
|
+
|
|
28
|
+
/* The trigger is the avatar plus a hit area, never a second visual element: an avatar that looks
|
|
29
|
+
like a button reads as an action, and this one opens a menu about a state. */
|
|
30
|
+
.reticle-account-trigger{
|
|
31
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
32
|
+
padding:0;border:none;background:transparent;cursor:pointer;line-height:0;border-radius:999px;
|
|
33
|
+
transition:box-shadow .12s,opacity .12s;}
|
|
34
|
+
.reticle-account-trigger:hover{opacity:.85;}
|
|
35
|
+
.reticle-account-trigger:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(255,255,255,.35);}
|
|
36
|
+
.reticle-account-trigger[aria-expanded="true"]{box-shadow:0 0 0 2px rgba(255,255,255,.28);}
|
|
37
|
+
|
|
38
|
+
.reticle-account-signin{
|
|
39
|
+
border:none;border-radius:6px;padding:2px 9px;cursor:pointer;font:inherit;font-size:10.5px;
|
|
40
|
+
background:rgba(255,255,255,.06);color:rgba(255,255,255,.75);transition:background .12s,color .12s;}
|
|
41
|
+
.reticle-account-signin:hover{background:rgba(255,255,255,.12);color:#fff;}
|
|
42
|
+
|
|
43
|
+
/* Anchored to the control and opening DOWNWARD.
|
|
44
|
+
Not upward, which is what it looked like it wanted: all three surfaces mount this near the TOP of
|
|
45
|
+
a panel whose own overflow is hidden for its rounded corners, so a menu opening upward is laid
|
|
46
|
+
out correctly and then clipped away entirely -- present to the probe, invisible to the eye. Panels
|
|
47
|
+
grow downward and have the height, so down is the direction that stays inside the box. */
|
|
48
|
+
.reticle-account-menu{
|
|
49
|
+
position:absolute;right:0;top:calc(100% + 8px);z-index:6;
|
|
50
|
+
min-width:212px;max-width:280px;padding:10px;box-sizing:border-box;
|
|
51
|
+
display:flex;flex-direction:column;gap:8px;
|
|
52
|
+
border-radius:10px;
|
|
53
|
+
/* The HUD's own surface tokens, not a colour invented here. A menu with its own near-black is a
|
|
54
|
+
second design language in the same panel, and a translucent one lets the panel's empty-state text
|
|
55
|
+
read straight through the rows -- which is what the first version did. */
|
|
56
|
+
${HUD_SURFACE_PAINT}
|
|
57
|
+
box-shadow:inset 0 1px 0 rgba(255,255,255,.09),${HUD_DROP_SHADOW};
|
|
58
|
+
font-size:11px;color:rgba(255,255,255,.78);text-align:left;}
|
|
59
|
+
.reticle-account-menu[hidden]{display:none;}
|
|
60
|
+
|
|
61
|
+
.reticle-account-head{display:flex;align-items:center;gap:8px;min-width:0;}
|
|
62
|
+
.reticle-account-who{display:flex;flex-direction:column;min-width:0;gap:1px;}
|
|
63
|
+
.reticle-account-org{
|
|
64
|
+
font-size:11.5px;font-weight:600;color:rgba(255,255,255,.95);
|
|
65
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
66
|
+
.reticle-account-email{
|
|
67
|
+
font-size:10.5px;color:rgba(255,255,255,.55);
|
|
68
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
69
|
+
|
|
70
|
+
.reticle-account-rows{
|
|
71
|
+
display:flex;flex-direction:column;gap:4px;
|
|
72
|
+
padding-top:8px;border-top:1px solid rgba(255,255,255,.08);}
|
|
73
|
+
.reticle-account-row{display:flex;align-items:baseline;justify-content:space-between;gap:10px;}
|
|
74
|
+
.reticle-account-k{color:rgba(255,255,255,.5);flex:none;}
|
|
75
|
+
.reticle-account-v{
|
|
76
|
+
color:rgba(255,255,255,.88);min-width:0;
|
|
77
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
78
|
+
font-variant-numeric:tabular-nums;}
|
|
79
|
+
|
|
80
|
+
.reticle-account-actions{
|
|
81
|
+
display:flex;flex-direction:column;gap:2px;
|
|
82
|
+
padding-top:8px;border-top:1px solid rgba(255,255,255,.08);}
|
|
83
|
+
.reticle-account-action{
|
|
84
|
+
display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;
|
|
85
|
+
padding:5px 6px;border:none;border-radius:6px;cursor:pointer;
|
|
86
|
+
font:inherit;font-size:11px;text-align:left;text-decoration:none;
|
|
87
|
+
background:transparent;color:rgba(255,255,255,.8);transition:background .12s,color .12s;}
|
|
88
|
+
.reticle-account-action:hover{background:rgba(255,255,255,.08);color:#fff;}
|
|
89
|
+
.reticle-account-action:focus-visible{outline:none;background:rgba(255,255,255,.1);color:#fff;}
|
|
90
|
+
/* The unlinked hint is a sentence, not a verb: it wraps rather than truncating, because a clipped
|
|
91
|
+
instruction is worse than a tall menu. */
|
|
92
|
+
.reticle-account-action--hint{
|
|
93
|
+
color:rgba(255,255,255,.5);font-size:10.5px;line-height:1.35;white-space:normal;cursor:pointer;}
|
|
94
|
+
|
|
95
|
+
.reticle-account-dashboard{
|
|
96
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
97
|
+
border:none;background:transparent;color:rgba(255,255,255,.6);cursor:pointer;line-height:0;padding:2px;}
|
|
98
|
+
.reticle-account-dashboard:hover{color:#fff;}
|
|
99
|
+
`;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AccountState } from '@reticlehq/core';
|
|
2
|
+
export declare const ACCOUNT_ROOT_ATTR = "data-reticle-account";
|
|
3
|
+
export declare const ACCOUNT_SIGNIN_ATTR = "data-reticle-account-signin";
|
|
4
|
+
export declare const SYNC_BTN_ATTR = "data-reticle-sync-now";
|
|
5
|
+
export declare const ACCOUNT_TRIGGER_ATTR = "data-reticle-account-trigger";
|
|
6
|
+
export declare const ACCOUNT_MENU_ATTR = "data-reticle-account-menu";
|
|
7
|
+
export declare const ACCOUNT_SIGNOUT_ATTR = "data-reticle-account-signout";
|
|
8
|
+
export declare const ACCOUNT_TEXT: {
|
|
9
|
+
readonly SIGNED_OUT: "Sign in";
|
|
10
|
+
readonly SIGNIN_TITLE: "Run `reticle login` in your terminal — click to copy the command";
|
|
11
|
+
readonly SIGNIN_COMMAND: "reticle login";
|
|
12
|
+
readonly DASHBOARD_TITLE: "Open this project on the dashboard";
|
|
13
|
+
readonly SYNC_TITLE: "Push to the dashboard now, instead of waiting for the next sync";
|
|
14
|
+
readonly COPIED: "Copied";
|
|
15
|
+
readonly AVATAR_FALLBACK: "•";
|
|
16
|
+
readonly MENU_LABEL: "Reticle account";
|
|
17
|
+
readonly TRIGGER_TITLE: "Your Reticle account";
|
|
18
|
+
readonly SIGNED_IN_AS: "Signed in";
|
|
19
|
+
readonly DASHBOARD_ACTION: "Open dashboard";
|
|
20
|
+
readonly LINK_HINT: "Not linked — run `reticle link` to keep a record on the dashboard";
|
|
21
|
+
readonly LINK_COMMAND: "reticle link";
|
|
22
|
+
readonly SIGNOUT_ACTION: "Sign out";
|
|
23
|
+
readonly SIGNOUT_TITLE: "Run `reticle logout` in your terminal — click to copy the command";
|
|
24
|
+
readonly SIGNOUT_COMMAND: "reticle logout";
|
|
25
|
+
readonly PROJECT_LABEL: "Project";
|
|
26
|
+
readonly HOST_LABEL: "Host";
|
|
27
|
+
readonly VERDICTS_LABEL: "Verdicts";
|
|
28
|
+
readonly DEFECTS_LABEL: "Defects found";
|
|
29
|
+
};
|
|
30
|
+
export interface AccountDetails {
|
|
31
|
+
projectName?: string | undefined;
|
|
32
|
+
dashboardUrl?: string | undefined;
|
|
33
|
+
verdicts?: number | undefined;
|
|
34
|
+
defects?: number | undefined;
|
|
35
|
+
}
|
|
36
|
+
export declare function accountInitials(org: string | undefined): string;
|
|
37
|
+
export declare function accountControlHtml(account: AccountState | undefined, details?: AccountDetails, offerSignIn?: boolean): string;
|
|
38
|
+
export declare function accountCapsuleHtml(account: AccountState | undefined, dashboardUrl?: string, offerSignIn?: boolean): string;
|
|
39
|
+
export declare function syncButtonHtml(dashboardUrl: string | undefined): string;
|
|
40
|
+
export declare function mountAccountControl(root: HTMLElement): () => void;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { hiIconHtml, PRESENTER_ICON_SIZE, PresenterIcon } from './icons/presenter-icons.js';
|
|
2
|
+
import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
|
|
3
|
+
export const ACCOUNT_ROOT_ATTR = 'data-reticle-account';
|
|
4
|
+
export const ACCOUNT_SIGNIN_ATTR = 'data-reticle-account-signin';
|
|
5
|
+
export const SYNC_BTN_ATTR = 'data-reticle-sync-now';
|
|
6
|
+
export const ACCOUNT_TRIGGER_ATTR = 'data-reticle-account-trigger';
|
|
7
|
+
export const ACCOUNT_MENU_ATTR = 'data-reticle-account-menu';
|
|
8
|
+
export const ACCOUNT_SIGNOUT_ATTR = 'data-reticle-account-signout';
|
|
9
|
+
export const ACCOUNT_TEXT = {
|
|
10
|
+
SIGNED_OUT: 'Sign in',
|
|
11
|
+
SIGNIN_TITLE: 'Run `reticle login` in your terminal — click to copy the command',
|
|
12
|
+
SIGNIN_COMMAND: 'reticle login',
|
|
13
|
+
DASHBOARD_TITLE: 'Open this project on the dashboard',
|
|
14
|
+
SYNC_TITLE: 'Push to the dashboard now, instead of waiting for the next sync',
|
|
15
|
+
COPIED: 'Copied',
|
|
16
|
+
AVATAR_FALLBACK: '•',
|
|
17
|
+
MENU_LABEL: 'Reticle account',
|
|
18
|
+
TRIGGER_TITLE: 'Your Reticle account',
|
|
19
|
+
SIGNED_IN_AS: 'Signed in',
|
|
20
|
+
DASHBOARD_ACTION: 'Open dashboard',
|
|
21
|
+
LINK_HINT: 'Not linked — run `reticle link` to keep a record on the dashboard',
|
|
22
|
+
LINK_COMMAND: 'reticle link',
|
|
23
|
+
SIGNOUT_ACTION: 'Sign out',
|
|
24
|
+
SIGNOUT_TITLE: 'Run `reticle logout` in your terminal — click to copy the command',
|
|
25
|
+
SIGNOUT_COMMAND: 'reticle logout',
|
|
26
|
+
PROJECT_LABEL: 'Project',
|
|
27
|
+
HOST_LABEL: 'Host',
|
|
28
|
+
VERDICTS_LABEL: 'Verdicts',
|
|
29
|
+
DEFECTS_LABEL: 'Defects found',
|
|
30
|
+
};
|
|
31
|
+
const ACCOUNT_COPIED_MS = 1200;
|
|
32
|
+
export function accountInitials(org) {
|
|
33
|
+
const words = (org ?? '')
|
|
34
|
+
.trim()
|
|
35
|
+
.split(/[\s._-]+/)
|
|
36
|
+
.filter((word) => word.length > 0);
|
|
37
|
+
const letters = words
|
|
38
|
+
.slice(0, 2)
|
|
39
|
+
.map((word) => [...word][0] ?? '')
|
|
40
|
+
.join('');
|
|
41
|
+
return letters.length > 0 ? letters.toUpperCase() : ACCOUNT_TEXT.AVATAR_FALLBACK;
|
|
42
|
+
}
|
|
43
|
+
export function accountControlHtml(account, details = {}, offerSignIn = false) {
|
|
44
|
+
if (account === undefined)
|
|
45
|
+
return '';
|
|
46
|
+
if (!account.signedIn) {
|
|
47
|
+
if (!offerSignIn)
|
|
48
|
+
return '';
|
|
49
|
+
return `<div class="reticle-account" ${ACCOUNT_ROOT_ATTR}><button type="button" class="reticle-account-signin" ${ACCOUNT_SIGNIN_ATTR} title="${ACCOUNT_TEXT.SIGNIN_TITLE}" aria-label="${ACCOUNT_TEXT.SIGNIN_TITLE}">${ACCOUNT_TEXT.SIGNED_OUT}</button></div>`;
|
|
50
|
+
}
|
|
51
|
+
const org = account.org ?? '';
|
|
52
|
+
const label = org.length > 0 ? esc(org) : ACCOUNT_TEXT.SIGNED_IN_AS;
|
|
53
|
+
const avatar = `<span class="reticle-account-avatar" aria-hidden="true">${esc(accountInitials(account.org))}</span>`;
|
|
54
|
+
const trigger = `<button type="button" class="reticle-account-trigger" ${ACCOUNT_TRIGGER_ATTR}` +
|
|
55
|
+
` aria-haspopup="true" aria-expanded="false" title="${ACCOUNT_TEXT.TRIGGER_TITLE}"` +
|
|
56
|
+
` aria-label="${ACCOUNT_TEXT.TRIGGER_TITLE}: ${label}">${avatar}</button>`;
|
|
57
|
+
return `<div class="reticle-account" ${ACCOUNT_ROOT_ATTR}>${trigger}${accountMenuHtml(account, details)}</div>`;
|
|
58
|
+
}
|
|
59
|
+
function detailRow(key, value) {
|
|
60
|
+
if (value === undefined || 0 === value.length)
|
|
61
|
+
return '';
|
|
62
|
+
return `<div class="reticle-account-row"><span class="reticle-account-k">${esc(key)}</span><span class="reticle-account-v" title="${esc(value)}">${esc(value)}</span></div>`;
|
|
63
|
+
}
|
|
64
|
+
function accountMenuHtml(account, details) {
|
|
65
|
+
const org = account.org ?? '';
|
|
66
|
+
const identity = `<div class="reticle-account-head">` +
|
|
67
|
+
`<span class="reticle-account-avatar reticle-account-avatar--lg" aria-hidden="true">${esc(accountInitials(account.org))}</span>` +
|
|
68
|
+
`<span class="reticle-account-who">` +
|
|
69
|
+
`<span class="reticle-account-org">${org.length > 0 ? esc(org) : ACCOUNT_TEXT.SIGNED_IN_AS}</span>` +
|
|
70
|
+
(account.email !== undefined && account.email.length > 0
|
|
71
|
+
? `<span class="reticle-account-email" title="${esc(account.email)}">${esc(account.email)}</span>`
|
|
72
|
+
: `<span class="reticle-account-email">${ACCOUNT_TEXT.SIGNED_IN_AS}</span>`) +
|
|
73
|
+
`</span></div>`;
|
|
74
|
+
const rows = detailRow(ACCOUNT_TEXT.HOST_LABEL, account.host) +
|
|
75
|
+
detailRow(ACCOUNT_TEXT.PROJECT_LABEL, details.projectName) +
|
|
76
|
+
detailRow(ACCOUNT_TEXT.VERDICTS_LABEL, details.verdicts !== undefined ? String(details.verdicts) : undefined) +
|
|
77
|
+
detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined);
|
|
78
|
+
const linked = details.dashboardUrl !== undefined && isSafeDashboardUrl(details.dashboardUrl);
|
|
79
|
+
const dashboard = linked
|
|
80
|
+
? `<a class="reticle-account-action" href="${esc(details.dashboardUrl ?? '')}" target="_blank" rel="noreferrer noopener" title="${ACCOUNT_TEXT.DASHBOARD_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.HELP)}<span>${ACCOUNT_TEXT.DASHBOARD_ACTION}</span></a>`
|
|
81
|
+
:
|
|
82
|
+
`<button type="button" class="reticle-account-action reticle-account-action--hint" ${ACCOUNT_SIGNIN_ATTR} data-reticle-copy="${ACCOUNT_TEXT.LINK_COMMAND}" title="${ACCOUNT_TEXT.LINK_HINT}"><span>${ACCOUNT_TEXT.LINK_HINT}</span></button>`;
|
|
83
|
+
const signOut = `<button type="button" class="reticle-account-action" ${ACCOUNT_SIGNOUT_ATTR} data-reticle-copy="${ACCOUNT_TEXT.SIGNOUT_COMMAND}" title="${ACCOUNT_TEXT.SIGNOUT_TITLE}"><span>${ACCOUNT_TEXT.SIGNOUT_ACTION}</span></button>`;
|
|
84
|
+
return (`<div class="reticle-account-menu" ${ACCOUNT_MENU_ATTR} role="menu" aria-label="${ACCOUNT_TEXT.MENU_LABEL}" aria-hidden="true" hidden>` +
|
|
85
|
+
identity +
|
|
86
|
+
(rows.length > 0 ? `<div class="reticle-account-rows">${rows}</div>` : '') +
|
|
87
|
+
`<div class="reticle-account-actions">${dashboard}${signOut}</div>` +
|
|
88
|
+
`</div>`);
|
|
89
|
+
}
|
|
90
|
+
export function accountCapsuleHtml(account, dashboardUrl, offerSignIn = false) {
|
|
91
|
+
return accountControlHtml(account, { dashboardUrl }, offerSignIn);
|
|
92
|
+
}
|
|
93
|
+
export function syncButtonHtml(dashboardUrl) {
|
|
94
|
+
if (dashboardUrl === undefined || !isSafeDashboardUrl(dashboardUrl))
|
|
95
|
+
return '';
|
|
96
|
+
return `<button type="button" class="reticle-sync-now" ${SYNC_BTN_ATTR} title="${ACCOUNT_TEXT.SYNC_TITLE}" aria-label="${ACCOUNT_TEXT.SYNC_TITLE}">${hiIconHtml(PresenterIcon.SYNC, PRESENTER_ICON_SIZE.HELP)}</button>`;
|
|
97
|
+
}
|
|
98
|
+
export function mountAccountControl(root) {
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const { signal } = controller;
|
|
101
|
+
const closeAll = (except) => {
|
|
102
|
+
for (const menu of root.querySelectorAll(`[${ACCOUNT_MENU_ATTR}]`)) {
|
|
103
|
+
if (menu === except)
|
|
104
|
+
continue;
|
|
105
|
+
menu.setAttribute('hidden', '');
|
|
106
|
+
menu.setAttribute('aria-hidden', 'true');
|
|
107
|
+
}
|
|
108
|
+
for (const trigger of root.querySelectorAll(`[${ACCOUNT_TRIGGER_ATTR}]`)) {
|
|
109
|
+
if (except !== undefined && trigger.parentElement === except.parentElement)
|
|
110
|
+
continue;
|
|
111
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const flashCopied = (el) => {
|
|
115
|
+
const label = el.querySelector('span') ?? el;
|
|
116
|
+
const previous = label.textContent;
|
|
117
|
+
label.textContent = ACCOUNT_TEXT.COPIED;
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
label.textContent = previous;
|
|
120
|
+
}, ACCOUNT_COPIED_MS);
|
|
121
|
+
};
|
|
122
|
+
root.addEventListener('click', (event) => {
|
|
123
|
+
const target = event.target;
|
|
124
|
+
if (!(target instanceof Element))
|
|
125
|
+
return;
|
|
126
|
+
const trigger = target.closest(`[${ACCOUNT_TRIGGER_ATTR}]`);
|
|
127
|
+
if (trigger instanceof HTMLElement) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
event.stopPropagation();
|
|
130
|
+
const menu = trigger.parentElement?.querySelector(`[${ACCOUNT_MENU_ATTR}]`);
|
|
131
|
+
if (!(menu instanceof HTMLElement))
|
|
132
|
+
return;
|
|
133
|
+
const wasOpen = !menu.hasAttribute('hidden');
|
|
134
|
+
closeAll();
|
|
135
|
+
if (wasOpen)
|
|
136
|
+
return;
|
|
137
|
+
menu.removeAttribute('hidden');
|
|
138
|
+
menu.setAttribute('aria-hidden', 'false');
|
|
139
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const copier = target.closest('[data-reticle-copy]');
|
|
143
|
+
if (copier instanceof HTMLElement) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
const command = copier.getAttribute('data-reticle-copy') ?? '';
|
|
147
|
+
if (command.length > 0)
|
|
148
|
+
void navigator.clipboard?.writeText(command).catch(() => undefined);
|
|
149
|
+
flashCopied(copier);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const signin = target.closest(`[${ACCOUNT_SIGNIN_ATTR}]`);
|
|
153
|
+
if (signin instanceof HTMLElement) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
void navigator.clipboard?.writeText(ACCOUNT_TEXT.SIGNIN_COMMAND).catch(() => undefined);
|
|
157
|
+
flashCopied(signin);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
closeAll();
|
|
161
|
+
}, { signal });
|
|
162
|
+
document.addEventListener('pointerdown', (event) => {
|
|
163
|
+
const target = event.target;
|
|
164
|
+
if (target instanceof Node && root.contains(target))
|
|
165
|
+
return;
|
|
166
|
+
closeAll();
|
|
167
|
+
}, { signal });
|
|
168
|
+
document.addEventListener('keydown', (event) => {
|
|
169
|
+
if ('Escape' !== event.key)
|
|
170
|
+
return;
|
|
171
|
+
const open = root.querySelector(`[${ACCOUNT_MENU_ATTR}]:not([hidden])`);
|
|
172
|
+
if (null === open)
|
|
173
|
+
return;
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
closeAll();
|
|
176
|
+
}, { signal });
|
|
177
|
+
return () => controller.abort();
|
|
178
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HumanControlKind } from '@reticlehq/core';
|
|
2
2
|
export declare const BorderMode: {
|
|
3
3
|
readonly SESSION: "session";
|
|
4
4
|
readonly BUSY: "busy";
|
|
@@ -36,7 +36,9 @@ export declare const HEARTBEAT_MS = 1000;
|
|
|
36
36
|
export declare const IDLE_NOTICE_MS = 4000;
|
|
37
37
|
export declare const ACT_STRIP: {
|
|
38
38
|
readonly READY: "Ready";
|
|
39
|
-
readonly
|
|
39
|
+
readonly PLANNING_PREFIX: "planning next action · ";
|
|
40
|
+
readonly STOPPED: "stopped";
|
|
41
|
+
readonly STOPPED_PREFIX: "stopped · ";
|
|
40
42
|
readonly NOW: "now";
|
|
41
43
|
readonly SINCE_LAST: " since last action";
|
|
42
44
|
};
|
|
@@ -92,3 +94,8 @@ export declare const HUD_DOCK_MARGIN_PX = 8;
|
|
|
92
94
|
export declare const HUD_DRAG_IGNORE_SEL: string;
|
|
93
95
|
export declare const THROTTLED_ATTR = "data-reticle-throttled";
|
|
94
96
|
export declare const Z_HUD_TOOLTIP = 100;
|
|
97
|
+
export interface ControlIntent {
|
|
98
|
+
kind: HumanControlKind;
|
|
99
|
+
text?: string;
|
|
100
|
+
}
|
|
101
|
+
export type ControlHandler = (intent: ControlIntent) => void;
|