@pixelmatters/markup 0.0.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/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/react.d.ts +21 -0
- package/dist/react.js +4755 -0
- package/dist/widget.d.ts +26 -0
- package/dist/widget.js +4739 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pixelmatters
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @pixelmatters/markup
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@pixelmatters/markup)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://bundlephobia.com/package/@pixelmatters/markup)
|
|
6
|
+
|
|
7
|
+
Pin-anchored feedback for live web apps. Drop in a script tag (or one React component) and your stakeholders can leave threaded comments, reactions, and annotated screenshots on top of any page — without touching the host's CSS, build, or routing.
|
|
8
|
+
|
|
9
|
+
## Highlights
|
|
10
|
+
|
|
11
|
+
- **Pin anywhere** — click anywhere on the page to attach a comment to that exact element. Pins re-anchor across reflow using a CSS selector + viewport-fraction fallback.
|
|
12
|
+
- **Threads, in real time** — replies stream in via WebSocket. Per-comment edit, delete (with tombstones), and emoji reactions.
|
|
13
|
+
- **Annotated screenshots** — opt-in capture with the pin marker drawn on the image and embedded fonts so the snapshot matches what the user saw.
|
|
14
|
+
- **Drop-in identity** — anonymous by default, with a popup-based sign-in that survives Safari ITP / Chrome storage partitioning. Verified users get a team badge.
|
|
15
|
+
- **Style-isolated** — runs inside an open shadow root with `:host { all: initial }`, so host CSS can't bleed in and widget CSS can't bleed out.
|
|
16
|
+
- **SPA-aware** — patches `history.pushState` / `replaceState` and follows `popstate` to refresh threads on route changes.
|
|
17
|
+
- **Respects the platform** — honours `prefers-reduced-motion` and `prefers-color-scheme`; full keyboard navigation with focus traps in popovers.
|
|
18
|
+
- **Tiny surface, tiny config** — `init({ apiUrl, apiKey })` is enough to start. No global CSS to import, no provider to wrap.
|
|
19
|
+
|
|
20
|
+
Built with [Preact](https://preactjs.com). Ships as ESM with a vanilla entry and a React/Preact wrapper.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# pnpm
|
|
26
|
+
pnpm install @pixelmatters/markup
|
|
27
|
+
# or yarn
|
|
28
|
+
yarn add @pixelmatters/markup
|
|
29
|
+
# or npm
|
|
30
|
+
npm install @pixelmatters/markup
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
CDN drop-in (auto-init on `DOMContentLoaded`):
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<script
|
|
37
|
+
type="module"
|
|
38
|
+
src="https://unpkg.com/@pixelmatters/markup"
|
|
39
|
+
data-markup-widget="true"
|
|
40
|
+
data-api-url="https://your-deployment.convex.site"
|
|
41
|
+
data-api-key="markup_..."
|
|
42
|
+
data-position="bottom-right"
|
|
43
|
+
></script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The `data-markup-widget="true"` attribute is required — it's how the bootstrap locates its own `<script>` tag (since `document.currentScript` is `null` for `type="module"`).
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
### Vanilla JS / TypeScript
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { init, destroy } from '@pixelmatters/markup'
|
|
54
|
+
|
|
55
|
+
const stop = init({
|
|
56
|
+
apiUrl: 'https://your-deployment.convex.site',
|
|
57
|
+
apiKey: 'markup_...',
|
|
58
|
+
position: 'bottom-right', // optional
|
|
59
|
+
theme: 'light', // optional
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Tear down on logout / route change / page unload:
|
|
63
|
+
stop() // equivalent to destroy()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### React / Preact
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { MarkupWidget } from '@pixelmatters/markup/react'
|
|
70
|
+
|
|
71
|
+
export default function App() {
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
{/* your app */}
|
|
75
|
+
<MarkupWidget
|
|
76
|
+
apiUrl={import.meta.env.VITE_MARKUP_API_URL}
|
|
77
|
+
apiKey={import.meta.env.VITE_MARKUP_KEY}
|
|
78
|
+
position="bottom-right" // optional
|
|
79
|
+
theme="light" // optional
|
|
80
|
+
/>
|
|
81
|
+
</>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
### `init(config) → destroy`
|
|
89
|
+
|
|
90
|
+
Mounts the widget. Idempotent: re-calling with the same config is a no-op; calling with a different config tears down the previous instance first. Returns the `destroy` function.
|
|
91
|
+
|
|
92
|
+
| Option | Type | Default | Description |
|
|
93
|
+
| ---------- | --------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `apiUrl` | `string` | required | Convex deployment site URL (`https://*.convex.site`) |
|
|
95
|
+
| `apiKey` | `string` | required | Project API key — mint one in the dashboard |
|
|
96
|
+
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Where the floating action button sits |
|
|
97
|
+
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Visual theme — `'light'` or `'dark'`, or `'auto'` (default) to follow the host's `prefers-color-scheme` |
|
|
98
|
+
|
|
99
|
+
### `destroy()`
|
|
100
|
+
|
|
101
|
+
Unmounts the widget and removes the host element. Safe to call when nothing is mounted.
|
|
102
|
+
|
|
103
|
+
### Keyboard
|
|
104
|
+
|
|
105
|
+
| Shortcut | Action |
|
|
106
|
+
| ------------------ | ----------------------------------------------- |
|
|
107
|
+
| `cmd/ctrl + .` | Toggle HUD visibility |
|
|
108
|
+
| `cmd/ctrl + click` | Click the FAB to hide the HUD with a hint toast |
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
- The widget mounts `<div id="markup-widget">` on `document.body` and attaches an open shadow root.
|
|
113
|
+
- All UI lives in that shadow root, with `:host { all: initial }` blocking style inheritance.
|
|
114
|
+
- The host element is `position: fixed; inset: 0; pointer-events: none`, so the widget paints over the entire viewport without blocking the host's clicks; only the FAB and active popovers opt back in to pointer events.
|
|
115
|
+
- Pins are anchored as `(x, y)` fractions of the document plus a best-effort CSS selector (via [`@medv/finder`](https://github.com/antonmedv/finder)). The selector wins when it still resolves; the fraction is the fallback so pins survive layout changes.
|
|
116
|
+
- Identity lives in **host-page** `localStorage` under `markup.identity`, keyed to the top-level site. New visitors get a random `clientId` so the backend can group their comments across sessions.
|
|
117
|
+
|
|
118
|
+
## Identity
|
|
119
|
+
|
|
120
|
+
Anonymous by default, with two opt-in upgrade paths:
|
|
121
|
+
|
|
122
|
+
- **Sign in with Markup** — opens a popup to `${apiUrl}/widget/auth`. Because the popup is first-party to the deployment origin, the better-auth session cookie is sent normally (sidestepping third-party cookie blocks). The popup `postMessage`s a JWT-backed verified identity back to the host page, which persists it under `markup.identity`.
|
|
123
|
+
- **Continue as a guest** — name (and optional email), stored under the same key without `isVerified`.
|
|
124
|
+
|
|
125
|
+
The popup origin is validated against the project's `allowedDomains` before any identity is returned, so only embeds on approved domains can resolve dashboard sessions.
|
|
126
|
+
|
|
127
|
+
> **Why a popup, not auto-detect?** Safari ITP, Chrome Storage Partitioning, and Firefox TCP all partition third-party storage and cookies by top-level site. A cross-origin fetch from `customer.com` to `convex.site` cannot see the dashboard session. The popup is the only reliable way to bridge identity across sites without per-host configuration.
|
|
128
|
+
|
|
129
|
+
## Getting an API key
|
|
130
|
+
|
|
131
|
+
1. Sign in to the Markup dashboard.
|
|
132
|
+
2. Open your project → **Settings → API Keys**.
|
|
133
|
+
3. Click **New key**, label it, and copy the raw key — it's shown once.
|
|
134
|
+
4. Open **Settings → Domains** and add the host domain (`app.example.com`, `*.staging.example.com`). `localhost` is auto-allowed in dev.
|
|
135
|
+
|
|
136
|
+
## Browser support
|
|
137
|
+
|
|
138
|
+
Modern evergreen browsers (Chrome, Edge, Firefox, Safari) and their mobile equivalents. The widget uses native ESM, shadow DOM, and `IntersectionObserver` — no IE11 / legacy bundle.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pnpm dev # vite dev server with the playground page
|
|
144
|
+
pnpm build # production build → dist/{widget,react}.{js,d.ts}
|
|
145
|
+
pnpm typecheck # tsc --noEmit
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
To test against a local Convex deployment, pass the printed `convex.site` URL as `apiUrl`.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
[MIT](./LICENSE) © Pixelmatters
|
|
153
|
+
|
|
154
|
+
Issues and PRs welcome at [Pixelmatters/markup](https://github.com/Pixelmatters/markup).
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { WidgetTheme } from './widget';
|
|
2
|
+
export interface MarkupWidgetProps {
|
|
3
|
+
/** Convex deployment site URL, e.g. `https://your-deployment.convex.site` */
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
/** Raw API key minted from a project's settings page */
|
|
6
|
+
apiKey: string;
|
|
7
|
+
/** Where the floating button sits. Defaults to bottom-right. */
|
|
8
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
9
|
+
/** 'light' | 'dark' | 'auto' (default 'auto' — follow prefers-color-scheme) */
|
|
10
|
+
theme?: WidgetTheme;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* React-host entrypoint. Renders nothing — the actual UI mounts itself into
|
|
14
|
+
* a sibling shadow DOM at `document.body` so it never tangles with the host
|
|
15
|
+
* tree's CSS.
|
|
16
|
+
*
|
|
17
|
+
* `useEffect` is imported from `react`, which the consumer's bundler
|
|
18
|
+
* resolves: real React in a React app, or `preact/hooks` re-exported via
|
|
19
|
+
* `preact/compat` in a Preact app that has aliased `react` → `preact/compat`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function MarkupWidget(props: MarkupWidgetProps): null;
|