@siteping/widget 0.10.4 → 0.10.6
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/README.md +25 -222
- package/dist/{chunk-33HB3HZI.js → chunk-D2AVRF24.js} +2 -2
- package/dist/chunk-D2AVRF24.js.map +1 -0
- package/dist/{chunk-RHP4S23A.cjs → chunk-GRKBXAGP.cjs} +2 -2
- package/dist/chunk-GRKBXAGP.cjs.map +1 -0
- package/dist/filters.d.cts +3 -1
- package/dist/filters.d.ts +3 -1
- package/dist/index.cjs +31 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +56 -56
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/{panel-N7TAQKDR.cjs → panel-LMVRKAQH.cjs} +2 -2
- package/dist/{panel-N7TAQKDR.cjs.map → panel-LMVRKAQH.cjs.map} +1 -1
- package/dist/{panel-BSNEQHAQ.js → panel-OXUE7PJY.js} +2 -2
- package/dist/{panel-BSNEQHAQ.js.map → panel-OXUE7PJY.js.map} +1 -1
- package/dist/react.cjs +31 -31
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +10 -10
- package/dist/react.js.map +1 -1
- package/dist/types.d.cts +4 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/chunk-33HB3HZI.js.map +0 -1
- package/dist/chunk-RHP4S23A.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
[](https://www.npmjs.com/package/@siteping/widget)
|
|
2
2
|
[](https://siteping.dev/demo)
|
|
3
|
+
[](https://siteping.dev/docs/widget)
|
|
3
4
|
[](https://www.typescriptlang.org/)
|
|
4
5
|
|
|
5
6
|
# @siteping/widget
|
|
6
7
|
|
|
7
8
|
**Client feedback, pinned to the pixel.**
|
|
8
9
|
|
|
9
|
-
A lightweight feedback widget that lets your clients annotate websites during development. Draw rectangles, leave comments, track bugs — directly on the live site.
|
|
10
|
+
A lightweight feedback widget that lets your clients annotate websites during development. Draw rectangles (or right-click), leave comments, track bugs — directly on the live site, anchored to the exact DOM element.
|
|
10
11
|
|
|
11
|
-
Part of
|
|
12
|
+
Part of [SitePing](https://github.com/NeosiaNexus/SitePing) — **[live demo](https://siteping.dev/demo)** · **[documentation](https://siteping.dev/docs/widget)**.
|
|
12
13
|
|
|
13
14
|
## Install
|
|
14
15
|
|
|
@@ -16,241 +17,43 @@ Part of the [@siteping](https://github.com/NeosiaNexus/SitePing) monorepo — **
|
|
|
16
17
|
npm install @siteping/widget
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
## Quick
|
|
20
|
+
## Quick start
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
// app/layout.tsx (or any client component)
|
|
23
|
-
'use client'
|
|
24
|
-
|
|
25
|
-
import { initSiteping } from '@siteping/widget'
|
|
26
|
-
import { useEffect } from 'react'
|
|
22
|
+
React (the hook survives StrictMode double-mounts and tears down on unmount):
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
endpoint: '/api/siteping',
|
|
32
|
-
projectName: 'my-project',
|
|
33
|
-
})
|
|
34
|
-
return destroy
|
|
35
|
-
}, [])
|
|
24
|
+
```tsx
|
|
25
|
+
"use client";
|
|
26
|
+
import { useSiteping } from "@siteping/widget/react";
|
|
36
27
|
|
|
37
|
-
|
|
28
|
+
export function Feedback() {
|
|
29
|
+
useSiteping({ endpoint: "/api/siteping", projectName: "my-app" });
|
|
30
|
+
return null;
|
|
38
31
|
}
|
|
39
32
|
```
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### Client-side mode (no server)
|
|
44
|
-
|
|
45
|
-
Use a `store` instead of an `endpoint` to bypass HTTP entirely:
|
|
34
|
+
Any other framework, or none:
|
|
46
35
|
|
|
47
36
|
```ts
|
|
48
|
-
import { initSiteping } from
|
|
49
|
-
import { LocalStorageStore } from '@siteping/adapter-localstorage'
|
|
37
|
+
import { initSiteping } from "@siteping/widget";
|
|
50
38
|
|
|
51
|
-
initSiteping({
|
|
52
|
-
|
|
53
|
-
projectName: 'my-demo',
|
|
54
|
-
})
|
|
39
|
+
const widget = initSiteping({ endpoint: "/api/siteping", projectName: "my-app" });
|
|
40
|
+
// widget.open() / .close() / .refresh() / .focusFeedback(id) / .on(...) / .destroy()
|
|
55
41
|
```
|
|
56
42
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
> **Framework-agnostic** — Works with any frontend framework (React, Vue, Svelte, Astro) or plain HTML. No framework dependency required.
|
|
60
|
-
|
|
61
|
-
> **~49 KB gzipped** today; after the upcoming bundle split (in progress), target is ~30 KB gzipped on first paint. Zero framework dependencies.
|
|
62
|
-
|
|
63
|
-
## Configuration
|
|
64
|
-
|
|
65
|
-
All configuration options for `initSiteping()`:
|
|
66
|
-
|
|
67
|
-
| Option | Type | Default | Description |
|
|
68
|
-
|--------|------|---------|-------------|
|
|
69
|
-
| `endpoint` | `string` | — | Your API route (e.g. `/api/siteping`). Required unless `store` is provided |
|
|
70
|
-
| `store` | `SitepingStore` | — | Direct store for client-side mode. When set, bypasses HTTP |
|
|
71
|
-
| `apiKey` | `string` | — | Sent as `Authorization: Bearer <apiKey>` on every HTTP request. **Visible to every visitor** — see [Authentication](#authentication) before using it on a public site. Ignored in store mode |
|
|
72
|
-
| `headers` | `Record<string, string>` or `() => headers` (sync or async) | — | Extra HTTP request headers, static or computed per request (e.g. a short-lived session token). An explicit `Authorization` entry overrides `apiKey`. Ignored in store mode |
|
|
73
|
-
| `projectName` | `string` | — | **Required.** Scopes feedbacks to this project |
|
|
74
|
-
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Widget FAB position |
|
|
75
|
-
| `accentColor` | `string` | `'#0066ff'` | Widget accent color — hex color (`#RGB`, `#RRGGBB`, `#RRGGBBAA`) |
|
|
76
|
-
| `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` | Widget color theme |
|
|
77
|
-
| `locale` | `'en' \| 'fr' \| 'de' \| 'es' \| 'it' \| 'pt' \| 'ru'` | `'en'` | Widget UI language. Unknown locales fall back to English |
|
|
78
|
-
| `forceShow` | `boolean` | `false` | Render the widget even when it would normally be skipped — bypasses **both** the production guard and the mobile-viewport guard. Does **not** bypass the SSR guard (the widget never renders without a DOM) |
|
|
79
|
-
| `minViewportWidth` | `number` | `768` | Minimum viewport width (px) for the widget to render; below it `onSkip('mobile')` fires. Set `0` to allow mobile viewports |
|
|
80
|
-
| `debug` | `boolean` | `false` | Enable debug logging to console |
|
|
81
|
-
| `identity` | `{ name: string; email: string }` | — | Pre-fill author identity from the host (SSO). When set, the widget skips the identity modal |
|
|
82
|
-
| `deepLink` | `boolean \| { param?: string }` | `false` | On initial load, focus the annotation referenced by `?siteping=<id>` (or a custom query key). SPA navigations are ignored — use `focusFeedback()` for route-change focus |
|
|
83
|
-
| `watchNavigation` | `boolean` | `true` | Auto re-fetch feedbacks on client-side (SPA) navigation. The widget patches the History API + listens for `popstate`/`hashchange`, so the panel list and markers follow route changes even when the widget is mounted once in a persistent layout (e.g. Next.js App Router). Re-fetches data only — it never re-scrolls. Set `false` to opt out and drive updates via `refresh()` |
|
|
84
|
-
| `enableRightClickComment` | `boolean` | `false` | Right-click anywhere on the page to instantly open the comment composer at the cursor. Modifier-key right-clicks (Shift/Ctrl/Alt/Meta) fall through to the native context menu. Right-clicks on SitePing's own UI are ignored. Note: on Android, `contextmenu` fires on long-press — relevant for ≥768 px tablets where the widget loads |
|
|
85
|
-
|
|
86
|
-
> **Custom translations** — Use `registerLocale(code, translations)` to add your own locale at runtime.
|
|
87
|
-
|
|
88
|
-
### Event callbacks
|
|
89
|
-
|
|
90
|
-
| Option | Signature | Description |
|
|
91
|
-
|--------|-----------|-------------|
|
|
92
|
-
| `onOpen` | `() => void` | Called when the feedback panel opens |
|
|
93
|
-
| `onClose` | `() => void` | Called when the feedback panel closes |
|
|
94
|
-
| `onFeedbackSent` | `(feedback) => void` | Called after a feedback is successfully submitted |
|
|
95
|
-
| `onError` | `(error) => void` | Called on API or internal errors |
|
|
96
|
-
| `onAnnotationStart` | `() => void` | Called when annotation drawing starts |
|
|
97
|
-
| `onAnnotationEnd` | `() => void` | Called when annotation drawing ends |
|
|
98
|
-
| `onSkip` | `(reason) => void` | Called when widget is skipped (production/mobile/ssr) |
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
initSiteping({
|
|
102
|
-
endpoint: '/api/siteping',
|
|
103
|
-
projectName: 'my-project',
|
|
104
|
-
position: 'bottom-right',
|
|
105
|
-
accentColor: '#0066ff',
|
|
106
|
-
theme: 'light',
|
|
107
|
-
locale: 'en',
|
|
108
|
-
forceShow: false,
|
|
109
|
-
debug: false,
|
|
110
|
-
onOpen: () => {},
|
|
111
|
-
onClose: () => {},
|
|
112
|
-
onFeedbackSent: (feedback) => {},
|
|
113
|
-
onError: (error) => {},
|
|
114
|
-
onAnnotationStart: () => {},
|
|
115
|
-
onAnnotationEnd: () => {},
|
|
116
|
-
onSkip: (reason) => {},
|
|
117
|
-
})
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Authentication
|
|
121
|
-
|
|
122
|
-
With the default [`@siteping/adapter-prisma`](https://www.npmjs.com/package/@siteping/adapter-prisma) handlers and an `apiKey` configured server-side, **POST** and **OPTIONS** stay public — anonymous visitors can keep submitting feedback with zero widget config — while **GET**, **PATCH**, and **DELETE** require `Authorization: Bearer <apiKey>`. Without auth, the widget can submit but cannot list markers, resolve, or delete. See the [adapter-prisma Authentication docs](https://www.npmjs.com/package/@siteping/adapter-prisma#authentication) for the server-side setup.
|
|
123
|
-
|
|
124
|
-
For **internal tools already behind your own login**, a static key is fine:
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
initSiteping({
|
|
128
|
-
endpoint: '/api/siteping',
|
|
129
|
-
projectName: 'my-project',
|
|
130
|
-
apiKey: process.env.NEXT_PUBLIC_SITEPING_KEY, // shipped to the browser!
|
|
131
|
-
})
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
> **⚠️ The widget runs in every visitor's browser.** Anything you put in `apiKey` (or static `headers`) is readable in your page source and grants GET/PATCH/DELETE on your feedback API. Never ship a static key on a public site.
|
|
135
|
-
|
|
136
|
-
For **public sites**, use the `headers` callback to send a short-lived token minted by your backend for the signed-in reviewer. It runs once per request (sync or async), and an explicit `Authorization` entry overrides `apiKey`:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
initSiteping({
|
|
140
|
-
endpoint: '/api/siteping',
|
|
141
|
-
projectName: 'my-project',
|
|
142
|
-
headers: async () => {
|
|
143
|
-
const { token } = await fetch('/api/siteping-token').then((r) => r.json())
|
|
144
|
-
return { Authorization: `Bearer ${token}` }
|
|
145
|
-
},
|
|
146
|
-
})
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Queued offline feedbacks never store headers — auth is re-computed when the queue is flushed on the next page load.
|
|
150
|
-
|
|
151
|
-
Two constraints to know:
|
|
152
|
-
|
|
153
|
-
- **Values are read at init.** Changing `apiKey` or swapping the `headers` value after `initSiteping()` has run has no effect until you destroy and re-init. The `headers` **callback** is the escape hatch: it's invoked on every request, so read rotating tokens from a ref or module variable inside it rather than closing over React state.
|
|
154
|
-
- **Cross-origin:** adapter-prisma's CORS preflight allows the `Content-Type` and `Authorization` headers only. Put credentials in `Authorization` (not a custom header name), or keep the endpoint same-origin.
|
|
155
|
-
|
|
156
|
-
### How production detection works
|
|
157
|
-
|
|
158
|
-
The widget skips rendering when `NODE_ENV` is `production`. The shipped bundle
|
|
159
|
-
keeps the literal `process.env.NODE_ENV` expression, so:
|
|
160
|
-
|
|
161
|
-
- **Bundled apps** (Next.js, Vite, webpack…): your bundler inlines its
|
|
162
|
-
environment at build time — the widget auto-hides in production builds and
|
|
163
|
-
shows in dev builds.
|
|
164
|
-
- **Node/SSR and test runners**: the real `process.env.NODE_ENV` is read at
|
|
165
|
-
runtime (server-side, the widget always skips with `onSkip('ssr')`).
|
|
166
|
-
- **Plain `<script>` tags** with no bundler and no `process` global: there is
|
|
167
|
-
no environment signal, so the widget renders — gate the `<script>` tag
|
|
168
|
-
yourself, or use `onSkip`/`forceShow` to control visibility explicitly.
|
|
169
|
-
|
|
170
|
-
## Return value API
|
|
171
|
-
|
|
172
|
-
`initSiteping()` returns a `SitepingInstance` with the following methods:
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
const widget = initSiteping({ ... })
|
|
176
|
-
|
|
177
|
-
widget.open() // Open the feedback panel
|
|
178
|
-
widget.close() // Close the feedback panel
|
|
179
|
-
widget.refresh() // Refresh feedbacks from the server
|
|
180
|
-
widget.destroy() // Remove the widget and clean up all DOM elements + listeners
|
|
181
|
-
|
|
182
|
-
// Scroll a specific annotation into view, pin its highlight, and pulse the
|
|
183
|
-
// marker. Counterpart to the `deepLink` config option for hosts that drive
|
|
184
|
-
// focus from JS (e.g. a notification click handler) instead of a URL query.
|
|
185
|
-
// Returns `false` when no visible marker matches the given ID (unknown ID,
|
|
186
|
-
// filtered by `scopeAnnotationsByUrl`, or markers not yet loaded — initial
|
|
187
|
-
// fetch is async).
|
|
188
|
-
widget.focusFeedback('feedback-id') // => boolean
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Event system
|
|
192
|
-
|
|
193
|
-
Use `widget.on()` / `widget.off()` as an alternative to config callbacks:
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
const widget = initSiteping({ ... })
|
|
197
|
-
|
|
198
|
-
// Subscribe to events
|
|
199
|
-
const unsub = widget.on('feedback:sent', (feedback) => {
|
|
200
|
-
console.log('New feedback:', feedback.id)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
widget.on('feedback:deleted', (id) => {
|
|
204
|
-
console.log('Feedback deleted:', id)
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
widget.on('panel:open', () => {
|
|
208
|
-
console.log('Panel opened')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
widget.on('panel:close', () => {
|
|
212
|
-
console.log('Panel closed')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
// Unsubscribe
|
|
216
|
-
unsub() // via returned function
|
|
217
|
-
widget.off('feedback:sent', handler) // via off()
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### All public events
|
|
221
|
-
|
|
222
|
-
| Event | Payload | Description |
|
|
223
|
-
|-------|---------|-------------|
|
|
224
|
-
| `feedback:sent` | `FeedbackResponse` | Fired after a feedback is successfully submitted |
|
|
225
|
-
| `feedback:deleted` | `string` (feedback id) | Fired after a feedback is deleted |
|
|
226
|
-
| `panel:open` | — | Fired when the feedback panel opens |
|
|
227
|
-
| `panel:close` | — | Fired when the feedback panel closes |
|
|
228
|
-
|
|
229
|
-
## CSP Requirements
|
|
230
|
-
|
|
231
|
-
The widget uses Shadow DOM (closed mode) for encapsulation, but overlay components (annotation layer, screenshot flash) live outside the shadow root. If your site enforces a strict Content Security Policy, you need to allow inline styles:
|
|
232
|
-
|
|
233
|
-
```
|
|
234
|
-
style-src 'unsafe-inline';
|
|
235
|
-
```
|
|
43
|
+
No server? Pass `store: new LocalStorageStore()` (from `@siteping/adapter-localstorage`) instead of `endpoint` and the whole loop runs in the browser.
|
|
236
44
|
|
|
237
|
-
##
|
|
45
|
+
## Highlights
|
|
238
46
|
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
47
|
+
- **DOM-anchored annotations** — CSS selector + XPath + text fallbacks; they survive deploys and layout changes
|
|
48
|
+
- **Dev-only by default** — hides in production builds (`NODE_ENV`) and under 768 px; `forceShow: true` for staging
|
|
49
|
+
- **Opt-in extras** — screenshots of the annotated area (with `data-siteping-ignore="true"` privacy masking), console/network diagnostics, instant right-click comments that never hijack keyboard or modifier-key menus
|
|
50
|
+
- **Reliable** — retry with backoff plus a localStorage queue; a flaky network never loses a comment
|
|
51
|
+
- **Isolated & light** — closed Shadow DOM, ~30 KB gzip (ESM); panel, screenshot engine, and non-English locales load on demand
|
|
52
|
+
- **7 built-in locales** — en, fr, de, es, it, pt, ru (BCP-47 tags like `fr-CA` resolve automatically)
|
|
245
53
|
|
|
246
|
-
##
|
|
54
|
+
## Documentation
|
|
247
55
|
|
|
248
|
-
|
|
249
|
-
|---------|-------------|
|
|
250
|
-
| [`@siteping/adapter-prisma`](https://www.npmjs.com/package/@siteping/adapter-prisma) | Server-side Prisma adapter |
|
|
251
|
-
| [`@siteping/adapter-memory`](https://www.npmjs.com/package/@siteping/adapter-memory) | In-memory adapter (testing, demos) |
|
|
252
|
-
| [`@siteping/adapter-localstorage`](https://www.npmjs.com/package/@siteping/adapter-localstorage) | Client-side localStorage adapter |
|
|
253
|
-
| [`@siteping/cli`](https://www.npmjs.com/package/@siteping/cli) | CLI for project setup |
|
|
56
|
+
Every option with its real default and behavior: **[siteping.dev/docs/widget/configuration](https://siteping.dev/docs/widget/configuration)** — plus [screenshots & masking](https://siteping.dev/docs/widget/screenshots), [right-click comments](https://siteping.dev/docs/widget/right-click), and [how anchoring works](https://siteping.dev/docs/widget/anchoring).
|
|
254
57
|
|
|
255
58
|
## License
|
|
256
59
|
|
|
@@ -1792,5 +1792,5 @@ function c(t){let n=document.createRange().createContextualFragment(t).firstElem
|
|
|
1792
1792
|
transition-duration: 0.01ms !important;
|
|
1793
1793
|
}
|
|
1794
1794
|
}
|
|
1795
|
-
`,Ue='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',ae=class{constructor(e,s,n){this.t=n;this.keyMap=new Map([["j",()=>s.onNavigate("down")],["k",()=>s.onNavigate("up")],["r",()=>s.onResolve()],["d",()=>s.onDelete()],["f",()=>s.onFocusSearch()],["/",()=>s.onFocusSearch()],["x",()=>s.onToggleSelect()],["?",()=>this.toggleHelp()]]),this.helpOverlay=this.buildOverlay(),this.hintButton=this.buildHintButton(),this.boundHandler=r=>this.handleKeydown(r);}t;helpOverlay;hintButton;keyMap;boundHandler;shadowRoot=null;enabled=false;helpVisible=false;destroyed=false;enable(e){if(this.destroyed||this.enabled)return;e&&(this.shadowRoot=e),(this.shadowRoot??document).addEventListener("keydown",this.boundHandler),this.enabled=true;}disable(){if(!this.enabled)return;(this.shadowRoot??document).removeEventListener("keydown",this.boundHandler),this.enabled=false,this.helpVisible&&this.hideHelp();}toggleHelp(){this.helpVisible?this.hideHelp():this.showHelp();}destroy(){this.destroyed||(this.disable(),this.helpOverlay.remove(),this.hintButton.remove(),this.destroyed=true);}handleKeydown(e){if(e.key==="Escape"){this.helpVisible&&(e.preventDefault(),e.stopPropagation(),this.hideHelp());return}if(this.helpVisible)return;let s=e.composedPath()[0];if(s){let r=s.tagName?.toLowerCase();if(r==="input"||r==="textarea"||r==="select"||s.isContentEditable)return}if(e.ctrlKey||e.altKey||e.metaKey)return;let n=this.keyMap.get(e.key);n&&(e.preventDefault(),e.stopPropagation(),n());}showHelp(){this.helpVisible=true,this.helpOverlay.classList.add("sp-shortcuts-overlay--visible"),this.helpOverlay.querySelector(".sp-shortcuts-close")?.focus();}hideHelp(){this.helpVisible=false,this.helpOverlay.classList.remove("sp-shortcuts-overlay--visible");}buildOverlay(){let e=o("div",{class:"sp-shortcuts-overlay"});e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),e.setAttribute("aria-label",this.t("shortcuts.title")),e.addEventListener("click",d=>{d.target===e&&this.hideHelp();});let s=o("div",{class:"sp-shortcuts-card"}),n=o("div",{class:"sp-shortcuts-title"});n.appendChild(c(Ke));let r=o("span");a(r,this.t("shortcuts.title")),n.appendChild(r),s.appendChild(n);let i=document.createElement("button");i.className="sp-shortcuts-close",i.setAttribute("aria-label",this.t("shortcuts.close")),i.appendChild(c(Ue)),i.addEventListener("click",()=>this.hideHelp()),s.appendChild(i);let l=o("div",{class:"sp-shortcuts-grid"});for(let d of ze){let p=o("div",{class:"sp-shortcuts-row"}),h=o("div",{class:"sp-shortcuts-keys"});d.keys.forEach((v,m)=>{if(m>0){let g=o("span",{class:"sp-shortcuts-separator"});a(g,"/"),h.appendChild(g);}let f=o("span",{class:"sp-kbd"});a(f,v),h.appendChild(f);});let u=o("span",{class:"sp-shortcuts-desc"});a(u,this.t(d.label)),p.appendChild(h),p.appendChild(u),l.appendChild(p);}return s.appendChild(l),e.appendChild(s),e}buildHintButton(){let e=document.createElement("button");return e.className="sp-shortcuts-hint",e.setAttribute("aria-label",this.t("shortcuts.hint")),a(e,"?"),e.addEventListener("click",s=>{s.stopPropagation(),this.toggleHelp();}),e}};export{ie as $,$ as A,gt as B,E as C,O as D,N as E,D as F,ge as G,be as H,z as I,C as J,xe as K,Pt as L,Ot as M,Nt as N,G as O,It as P,X as Q,$t as R,J as S,Gt as T,se as U,Qt as V,Jt as W,Zt as X,re as Y,es as Z,rs as _,c as a,as as aa,o as b,ls as ba,a as c,ps as ca,F as d,ae as da,Ve as e,Ge as f,qe as g,We as h,Xe as i,Qe as j,Je as k,Ze as l,et as m,tt as n,st as o,nt as p,rt as q,ot as r,it as s,at as t,lt as u,pt as v,ct as w,M as x,ut as y,ht as z};//# sourceMappingURL=chunk-
|
|
1796
|
-
//# sourceMappingURL=chunk-
|
|
1795
|
+
`,Ue='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',ae=class{constructor(e,s,n){this.t=n;this.keyMap=new Map([["j",()=>s.onNavigate("down")],["k",()=>s.onNavigate("up")],["r",()=>s.onResolve()],["d",()=>s.onDelete()],["f",()=>s.onFocusSearch()],["/",()=>s.onFocusSearch()],["x",()=>s.onToggleSelect()],["?",()=>this.toggleHelp()]]),this.helpOverlay=this.buildOverlay(),this.hintButton=this.buildHintButton(),this.boundHandler=r=>this.handleKeydown(r);}t;helpOverlay;hintButton;keyMap;boundHandler;shadowRoot=null;enabled=false;helpVisible=false;destroyed=false;enable(e){if(this.destroyed||this.enabled)return;e&&(this.shadowRoot=e),(this.shadowRoot??document).addEventListener("keydown",this.boundHandler),this.enabled=true;}disable(){if(!this.enabled)return;(this.shadowRoot??document).removeEventListener("keydown",this.boundHandler),this.enabled=false,this.helpVisible&&this.hideHelp();}toggleHelp(){this.helpVisible?this.hideHelp():this.showHelp();}destroy(){this.destroyed||(this.disable(),this.helpOverlay.remove(),this.hintButton.remove(),this.destroyed=true);}handleKeydown(e){if(e.key==="Escape"){this.helpVisible&&(e.preventDefault(),e.stopPropagation(),this.hideHelp());return}if(this.helpVisible)return;let s=e.composedPath()[0];if(s){let r=s.tagName?.toLowerCase();if(r==="input"||r==="textarea"||r==="select"||s.isContentEditable)return}if(e.ctrlKey||e.altKey||e.metaKey)return;let n=this.keyMap.get(e.key);n&&(e.preventDefault(),e.stopPropagation(),n());}showHelp(){this.helpVisible=true,this.helpOverlay.classList.add("sp-shortcuts-overlay--visible"),this.helpOverlay.querySelector(".sp-shortcuts-close")?.focus();}hideHelp(){this.helpVisible=false,this.helpOverlay.classList.remove("sp-shortcuts-overlay--visible");}buildOverlay(){let e=o("div",{class:"sp-shortcuts-overlay"});e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true"),e.setAttribute("aria-label",this.t("shortcuts.title")),e.addEventListener("click",d=>{d.target===e&&this.hideHelp();});let s=o("div",{class:"sp-shortcuts-card"}),n=o("div",{class:"sp-shortcuts-title"});n.appendChild(c(Ke));let r=o("span");a(r,this.t("shortcuts.title")),n.appendChild(r),s.appendChild(n);let i=document.createElement("button");i.className="sp-shortcuts-close",i.setAttribute("aria-label",this.t("shortcuts.close")),i.appendChild(c(Ue)),i.addEventListener("click",()=>this.hideHelp()),s.appendChild(i);let l=o("div",{class:"sp-shortcuts-grid"});for(let d of ze){let p=o("div",{class:"sp-shortcuts-row"}),h=o("div",{class:"sp-shortcuts-keys"});d.keys.forEach((v,m)=>{if(m>0){let g=o("span",{class:"sp-shortcuts-separator"});a(g,"/"),h.appendChild(g);}let f=o("span",{class:"sp-kbd"});a(f,v),h.appendChild(f);});let u=o("span",{class:"sp-shortcuts-desc"});a(u,this.t(d.label)),p.appendChild(h),p.appendChild(u),l.appendChild(p);}return s.appendChild(l),e.appendChild(s),e}buildHintButton(){let e=document.createElement("button");return e.className="sp-shortcuts-hint",e.setAttribute("aria-label",this.t("shortcuts.hint")),a(e,"?"),e.addEventListener("click",s=>{s.stopPropagation(),this.toggleHelp();}),e}};export{ie as $,$ as A,gt as B,E as C,O as D,N as E,D as F,ge as G,be as H,z as I,C as J,xe as K,Pt as L,Ot as M,Nt as N,G as O,It as P,X as Q,$t as R,J as S,Gt as T,se as U,Qt as V,Jt as W,Zt as X,re as Y,es as Z,rs as _,c as a,as as aa,o as b,ls as ba,a as c,ps as ca,F as d,ae as da,Ve as e,Ge as f,qe as g,We as h,Xe as i,Qe as j,Je as k,Ze as l,et as m,tt as n,st as o,nt as p,rt as q,ot as r,it as s,at as t,lt as u,pt as v,ct as w,M as x,ut as y,ht as z};//# sourceMappingURL=chunk-D2AVRF24.js.map
|
|
1796
|
+
//# sourceMappingURL=chunk-D2AVRF24.js.map
|