docblocks-react-ui 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # docblocks-react-ui
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5f5ab54: The presence selection's visibility over the room stands grant-gated (the
8
+ privacy stand): the `EditorProvider`'s additive `presence` prop carries
9
+ the local user's identity (`userId` / the optional `name`) and the
10
+ optional `PresenceCrypto` cipher; the new `usePresenceSharing()` hook
11
+ drives the local grant — the session-state default is **off** (the
12
+ selection does not publish, only the light identity frame the roster
13
+ needs), and the consumer flips it to `everyone` (the plaintext wire
14
+ shape, the legacy stand) or to a targeted user-id list (the selection
15
+ publishes as opaque ciphertext entries, one per grant member — the
16
+ entries carry no target id, a recipient opens only the entry addressed
17
+ to it, an unaddressed peer sees the sharer's identity but never the
18
+ selection). The library orchestrates the publish gating, the receive's
19
+ decrypt, and the per-peer sequence guard over the async decrypt; the
20
+ cryptography and the sharing's UI are the consumer's stand (the package
21
+ ships no built-in control). A provider without the `presence` prop keeps
22
+ the legacy stand (the anonymous plaintext selection over the room).
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies [5971d30]
27
+ - docblocks-core@0.1.3
28
+
3
29
  ## 0.2.0
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -109,6 +109,7 @@ mention catalog). The full props list:
109
109
  | `accessLevel?` | The consumer's user access level (default `editor`): `viewer` forces the `published` mode (the current state is never rendered to a reader). |
110
110
  | `mode?` | The document mode (default `edit`): `view` is a read-only live document; `published` renders the `publishedDoc` snapshot read-only. |
111
111
  | `publishedDoc?` | The published snapshot (a hydrated core `Doc`; must not change after mount — remount over `key` for a new snapshot). The consumer owns the publish action and the snapshot storage. |
112
+ | `presence?` | The local user's presence identity + the selection-sharing cipher (see Presence sharing below). Without the prop the editor keeps the legacy stand (the anonymous plaintext selection, always shared over the room). |
112
113
 
113
114
  Two rules for the optional props:
114
115
 
@@ -256,6 +257,62 @@ function App() {
256
257
  `updatedAt`); staleness (dropping stale peers) is a consumer concern —
257
258
  the payload's `updatedAt` (Unix ms) is the only input.
258
259
 
260
+ ### Presence sharing
261
+
262
+ The selection's visibility over the room is grant-gated (the privacy
263
+ stand): with the `presence` prop the grant defaults to **off** — the
264
+ selection does not publish, only the light identity frame (the roster). The
265
+ consumer flips the grant through the hook (session state — not document
266
+ data, not on the wire; it lives until the page reload or the provider's
267
+ unmount):
268
+
269
+ ```tsx
270
+ import { usePresenceSharing } from "docblocks-react-ui";
271
+
272
+ function ShareControl() {
273
+ const { sharing, setSharing, peers } = usePresenceSharing();
274
+ // sharing: "off" | "everyone" | string[] (the local grant — the full
275
+ // information stands over the sharer's pane: what is shared, and to whom)
276
+ // setSharing(mode) — a no-op over the read-only modes; a targeted grant
277
+ // requires the presence prop's userId + crypto (the degraded stand
278
+ // stands a console warn, no crash)
279
+ // peers: { peerId, userId?, name?, updatedAt, selectionVisible }[] —
280
+ // the live roster (the identity frames are always published)
281
+ return <button onClick={() => setSharing(sharing === "off" ? "everyone" : "off")}>{sharing}</button>;
282
+ }
283
+ ```
284
+
285
+ The grant's modes:
286
+
287
+ - **`off`** — the selection never publishes (the identity frame only).
288
+ - **`everyone`** — the plaintext `selection` (the legacy wire shape — the
289
+ peers of any version render it).
290
+ - **`[userId, …]`** (targeted) — the selection publishes as opaque
291
+ ciphertext entries (`selections`), one per grant member, produced by the
292
+ consumer's `PresenceCrypto` cipher. The entries carry **no target id**:
293
+ a recipient tries each entry with its own `decrypt`; an unaddressed peer
294
+ sees the sharer's identity in the roster but never the selection (and
295
+ never learns the audience).
296
+
297
+ ```ts
298
+ interface PresenceCrypto {
299
+ /** One opaque entry per grant member (grant order; no target id inside). */
300
+ encrypt(selection: PresenceSelection, grant: string[]): Promise<string[]>;
301
+ /** Null when the entry is not addressed to the local user (or broken). */
302
+ decrypt(entry: string): Promise<PresenceSelection | null>;
303
+ }
304
+ ```
305
+
306
+ The library orchestrates (publish gating, the debounce, the per-peer
307
+ sequence guard over the async decrypt, the roster) and renders the
308
+ accepted selections through the existing overlay; the cryptography and
309
+ the keys are the consumer's, and the sharing's UI (the toggle, the target
310
+ picker, the indicator) is the consumer's stand over the hook — the package
311
+ ships no built-in control. Without `crypto` the targeted mode degrades
312
+ (the setter stands a no-op); `off` / `everyone` stand regardless. A
313
+ provider **without** the `presence` prop keeps the legacy stand (the
314
+ anonymous plaintext selection over the room — the 0.2.0 behavior).
315
+
259
316
  ### Blobs
260
317
 
261
318
  The document holds only an opaque blob key (`image.src`); the bytes live
@@ -525,6 +582,8 @@ value actually changes (the store's reference-stability contract):
525
582
  local state; `null` before the mount effect ran and after unmount).
526
583
  - `usePresence()` — the remote peers' ephemeral state (the selection +
527
584
  the `updatedAt` per peer).
585
+ - `usePresenceSharing()` — the selection-sharing grant (the session state
586
+ - the setter + the live roster; see Presence sharing).
528
587
  - `useMeta()` — the document meta (the reference is replaced only when
529
588
  the title changed).
530
589
  - `useEditorContext()` — the whole provider context (the store, the
@@ -1116,6 +1116,45 @@ function usePresence() {
1116
1116
  const { store } = useEditorContext();
1117
1117
  return useStore(store, (state) => state.presence);
1118
1118
  }
1119
+ /**
1120
+ * The presence sharing's session state (per provider instance): the local
1121
+ * grant (the default "off" — the privacy stand), the grant's setter (a
1122
+ * no-op over the read-only stand), and the live roster of the connected
1123
+ * peers (the identity frames are always present, the selection only over
1124
+ * the grant it stands shared).
1125
+ */
1126
+ function usePresenceSharing() {
1127
+ const { store, viewMode, presence: presenceConfig } = useEditorContext();
1128
+ const sharing = useStore(store, (state) => state.sharing);
1129
+ const setSharing = useStore(store, (state) => state.setSharing);
1130
+ const presence = useStore(store, (state) => state.presence);
1131
+ const peers = useMemo(() => presence.map((peer) => ({
1132
+ peerId: peer.peerId,
1133
+ userId: peer.userId,
1134
+ name: peer.name,
1135
+ updatedAt: peer.updatedAt,
1136
+ selectionVisible: peer.selection !== null
1137
+ })), [presence]);
1138
+ const setSharingRef = useRef(setSharing);
1139
+ setSharingRef.current = setSharing;
1140
+ return useMemo(() => ({
1141
+ sharing,
1142
+ setSharing: (mode) => {
1143
+ if (viewMode) return;
1144
+ if (Array.isArray(mode) && (presenceConfig === null || presenceConfig.userId === void 0 || presenceConfig.crypto === void 0)) {
1145
+ console.warn("docblocks-react-ui: the targeted presence sharing requires the presence prop's userId and crypto — the grant stands off.");
1146
+ return;
1147
+ }
1148
+ setSharingRef.current(mode);
1149
+ },
1150
+ peers
1151
+ }), [
1152
+ sharing,
1153
+ viewMode,
1154
+ peers,
1155
+ presenceConfig
1156
+ ]);
1157
+ }
1119
1158
  /** The document meta; the reference is replaced only when the title changed. */
1120
1159
  function useMeta() {
1121
1160
  const { store } = useEditorContext();
@@ -11410,6 +11449,293 @@ function useSlashMenu(rootRef) {
11410
11449
  };
11411
11450
  }
11412
11451
  //#endregion
11452
+ //#region src/presence/utils.ts
11453
+ /** Deterministic hue (0–359) from the peer id (pure function, no peer state). */
11454
+ function peerHue(peerId) {
11455
+ let hash = 0;
11456
+ for (let i = 0; i < peerId.length; i++) hash = hash * 31 + peerId.charCodeAt(i) >>> 0;
11457
+ return hash % 360;
11458
+ }
11459
+ /** The caret endpoints a peer's wire selection draws; a range draws range boxes instead. */
11460
+ function caretPointsOf(selection) {
11461
+ if (selection === null) return [];
11462
+ if ("blockId" in selection) return [{
11463
+ blockId: selection.blockId,
11464
+ offset: selection.offset
11465
+ }];
11466
+ return [];
11467
+ }
11468
+ /**
11469
+ * The per-chain-block segments of a peer's range selection: the from block's
11470
+ * tail, the middle text blocks in full, the to block's head. Chain blocks
11471
+ * missing from the store (stale presence — the remote selection points at a
11472
+ * removed block) are skipped leniently, and the offsets clamp to the current
11473
+ * block lengths.
11474
+ */
11475
+ function rangeSegmentsOf(selection, doc, blocks) {
11476
+ if (selection === null || !("from" in selection)) return [];
11477
+ const chain = selection.blocks ?? (selection.from.blockId === selection.to.blockId ? [selection.from.blockId] : [selection.from.blockId, selection.to.blockId]);
11478
+ const segments = [];
11479
+ for (const blockId of chain) {
11480
+ if (blocks[blockId] === void 0) continue;
11481
+ const length = doc.getTextLength(blockId);
11482
+ const from = blockId === selection.from.blockId ? selection.from.offset : 0;
11483
+ const to = blockId === selection.to.blockId ? Math.min(selection.to.offset, length) : length;
11484
+ if (from >= to || from >= length) continue;
11485
+ segments.push({
11486
+ blockId,
11487
+ from,
11488
+ to
11489
+ });
11490
+ }
11491
+ return segments;
11492
+ }
11493
+ /**
11494
+ * The client rects of a segment inside the block element, relative to the
11495
+ * root's rect. Environments without layout (jsdom — no getClientRects)
11496
+ * yield no rects, so a missing rect maps to a single zero box.
11497
+ */
11498
+ function rangeRects(root, block, from, to) {
11499
+ const rootRect = root.getBoundingClientRect();
11500
+ const start = locatePosition(block, from);
11501
+ const end = locatePosition(block, to);
11502
+ const range = document.createRange();
11503
+ range.setStart(start.node, start.offset);
11504
+ range.setEnd(end.node, end.offset);
11505
+ if (typeof range.getClientRects !== "function") return [];
11506
+ return Array.from(range.getClientRects()).map((rect) => ({
11507
+ left: rect.left - rootRect.left,
11508
+ top: rect.top - rootRect.top,
11509
+ width: rect.width,
11510
+ height: rect.height
11511
+ }));
11512
+ }
11513
+ /**
11514
+ * The client rect of a collapsed range at the flat offset inside the
11515
+ * block element, relative to the root's rect. Environments without layout
11516
+ * (jsdom — no getClientRects) yield zero rects, so a missing rect maps to 0.
11517
+ */
11518
+ function caretGeometry(root, block, offset) {
11519
+ const position = locatePosition(block, offset);
11520
+ const range = document.createRange();
11521
+ range.setStart(position.node, position.offset);
11522
+ range.collapse(true);
11523
+ let rect;
11524
+ if (typeof range.getClientRects === "function") {
11525
+ const rects = range.getClientRects();
11526
+ rect = rects.length > 0 ? rects[0] : void 0;
11527
+ }
11528
+ const rootRect = root.getBoundingClientRect();
11529
+ return {
11530
+ left: rect !== void 0 ? rect.left - rootRect.left : 0,
11531
+ top: rect !== void 0 ? rect.top - rootRect.top : 0,
11532
+ height: rect !== void 0 ? rect.height : 0
11533
+ };
11534
+ }
11535
+ //#endregion
11536
+ //#region src/presence/PresenceCaretOverlay.tsx
11537
+ /** Renders remote peers' selections as absolutely positioned boxes over
11538
+ * the editor root — a pure view of the presence slice (it never
11539
+ * mutates). A caret selection draws a caret bar; a range selection
11540
+ * (cross-block included) draws its highlight boxes — one per client rect
11541
+ * of each chain-block segment. The boxes are recomputed on presence
11542
+ * changes, on block snapshot changes (text shifts the pixels), and on
11543
+ * window resize; a peer whose block is not rendered (removed, unknown
11544
+ * type) draws nothing. The structural classes of the overlay boxes (the
11545
+ * per-peer hue comes from the inline --docblocks-presence-hue property,
11546
+ * the geometry from the inline layout properties): the caret bar is the
11547
+ * fixed width token; the range highlight is a softened box of the same
11548
+ * hue. */
11549
+ /**
11550
+ * The block ids a peer's selection references (the caret's block, the
11551
+ * range's endpoints + the chain blocks). The overlay stands no boxes over
11552
+ * the other blocks — those stand out of its subscription.
11553
+ */
11554
+ function peerBlockIdsOf(presence) {
11555
+ const ids = /* @__PURE__ */ new Set();
11556
+ for (const peer of presence) {
11557
+ const selection = peer.selection;
11558
+ if (selection === null) continue;
11559
+ if ("blockId" in selection) ids.add(selection.blockId);
11560
+ else if ("from" in selection) {
11561
+ ids.add(selection.from.blockId);
11562
+ ids.add(selection.to.blockId);
11563
+ for (const id of selection.blocks ?? []) ids.add(id);
11564
+ }
11565
+ }
11566
+ return [...ids].sort();
11567
+ }
11568
+ /** The peer-referenced blocks' snapshots (the absent ones stand dropped). The ids come from the remote presence frames — the open-key stand: `__proto__` stands the own property (no prototype reassignment over the derived map). */
11569
+ function peerBlocksOf(presence, blocks) {
11570
+ const next = {};
11571
+ for (const id of peerBlockIdsOf(presence)) {
11572
+ const block = blocks[id];
11573
+ if (block !== void 0) Object.defineProperty(next, id, {
11574
+ value: block,
11575
+ enumerable: true,
11576
+ writable: true,
11577
+ configurable: true
11578
+ });
11579
+ }
11580
+ return next;
11581
+ }
11582
+ /**
11583
+ * The structural classes of the overlay boxes (the per-peer hue comes
11584
+ * from the inline --docblocks-presence-hue property, the geometry from
11585
+ * the inline layout properties). The caret bar is the fixed width token;
11586
+ * the range highlight is a softened box of the same hue.
11587
+ */
11588
+ const CARET_CLASS = "pointer-events-none absolute w-(--docblocks-presence-caret-width) bg-[hsl(var(--docblocks-presence-hue,200)_var(--docblocks-presence-caret-saturation)_var(--docblocks-presence-caret-lightness))]";
11589
+ const RANGE_CLASS = "pointer-events-none absolute rounded-[2px] bg-[hsl(var(--docblocks-presence-hue,200)_var(--docblocks-presence-caret-saturation)_var(--docblocks-presence-caret-lightness)/var(--docblocks-presence-range-opacity))]";
11590
+ function PresenceCaretOverlay({ rootRef }) {
11591
+ const { store, doc } = useEditorContext();
11592
+ const presence = useStore(store, (state) => state.presence);
11593
+ const peerIdsKeyRef = useRef("");
11594
+ const peerBlocksCacheRef = useRef(null);
11595
+ const peerBlocks = useStore(store, (state) => {
11596
+ if (state.presence.length === 0) return null;
11597
+ const ids = peerBlockIdsOf(state.presence);
11598
+ const key = ids.join("\0");
11599
+ const cache = peerBlocksCacheRef.current;
11600
+ if (key === peerIdsKeyRef.current && cache !== null) {
11601
+ let unchanged = true;
11602
+ for (const id of ids) if (cache[id] !== state.blocks[id]) {
11603
+ unchanged = false;
11604
+ break;
11605
+ }
11606
+ if (unchanged) return cache;
11607
+ }
11608
+ const next = peerBlocksOf(state.presence, state.blocks);
11609
+ peerIdsKeyRef.current = key;
11610
+ peerBlocksCacheRef.current = next;
11611
+ return next;
11612
+ });
11613
+ const [boxes, setBoxes] = useState([]);
11614
+ useLayoutEffect(() => {
11615
+ const root = rootRef.current;
11616
+ if (root === null) return;
11617
+ const recompute = () => {
11618
+ const next = [];
11619
+ for (const peer of presence) {
11620
+ for (const point of caretPointsOf(peer.selection)) {
11621
+ const block = root.querySelector(`[data-block-id="${CSS.escape(point.blockId)}"][data-slot="docblocks/block-content"]`) ?? root.querySelector(`[data-block-id="${CSS.escape(point.blockId)}"]`);
11622
+ if (block === null) continue;
11623
+ next.push({
11624
+ key: `${peer.peerId}:caret:${point.blockId}:${point.offset}`,
11625
+ peerId: peer.peerId,
11626
+ slot: "docblocks/presence-caret",
11627
+ ...caretGeometry(root, block, point.offset)
11628
+ });
11629
+ }
11630
+ for (const segment of rangeSegmentsOf(peer.selection, doc, peerBlocks ?? {})) {
11631
+ const block = root.querySelector(`[data-block-id="${CSS.escape(segment.blockId)}"][data-slot="docblocks/block-content"]`);
11632
+ if (block === null) continue;
11633
+ const rects = rangeRects(root, block, segment.from, segment.to);
11634
+ (rects.length > 0 ? rects : [{
11635
+ left: 0,
11636
+ top: 0,
11637
+ width: 0,
11638
+ height: 0
11639
+ }]).forEach((rect, index) => {
11640
+ next.push({
11641
+ key: `${peer.peerId}:range:${segment.blockId}:${segment.from}-${segment.to}:${index}`,
11642
+ peerId: peer.peerId,
11643
+ slot: "docblocks/presence-range",
11644
+ blockId: segment.blockId,
11645
+ ...rect
11646
+ });
11647
+ });
11648
+ }
11649
+ }
11650
+ setBoxes((prev) => JSON.stringify(prev) === JSON.stringify(next) ? prev : next);
11651
+ };
11652
+ let frame = 0;
11653
+ const scheduleRecompute = () => {
11654
+ cancelAnimationFrame(frame);
11655
+ frame = requestAnimationFrame(() => {
11656
+ frame = 0;
11657
+ recompute();
11658
+ });
11659
+ };
11660
+ recompute();
11661
+ scheduleRecompute();
11662
+ window.addEventListener("resize", scheduleRecompute);
11663
+ return () => {
11664
+ cancelAnimationFrame(frame);
11665
+ window.removeEventListener("resize", scheduleRecompute);
11666
+ };
11667
+ }, [
11668
+ presence,
11669
+ peerBlocks,
11670
+ doc,
11671
+ rootRef
11672
+ ]);
11673
+ return /* @__PURE__ */ jsx(Fragment$1, { children: boxes.map((box) => /* @__PURE__ */ jsx("span", {
11674
+ "data-slot": box.slot,
11675
+ "data-peer": box.peerId,
11676
+ "data-range-block": box.blockId,
11677
+ className: box.slot === "docblocks/presence-caret" ? CARET_CLASS : RANGE_CLASS,
11678
+ style: {
11679
+ "--docblocks-presence-hue": peerHue(box.peerId),
11680
+ left: box.left,
11681
+ top: box.top,
11682
+ height: box.height,
11683
+ ...box.width !== void 0 ? { width: box.width } : null
11684
+ }
11685
+ }, box.key)) });
11686
+ }
11687
+ //#endregion
11688
+ //#region src/presence/sharing.ts
11689
+ /**
11690
+ * Build the awareness payload of one publish. Returns null when the grant
11691
+ * is targeted over no cipher (the caller keeps the previous payload — the
11692
+ * degradation stands, no crash, no partial publish).
11693
+ */
11694
+ async function buildPresencePayload(params) {
11695
+ const { selection, sharing, viewMode, identity, crypto, seq, updatedAt } = params;
11696
+ const payload = {
11697
+ updatedAt,
11698
+ seq
11699
+ };
11700
+ if (identity.userId !== void 0) payload.userId = identity.userId;
11701
+ if (identity.name !== void 0) payload.name = identity.name;
11702
+ if (viewMode || selection === null) {
11703
+ payload.selection = null;
11704
+ return payload;
11705
+ }
11706
+ if (sharing === "off") {
11707
+ payload.selection = identity.userId === void 0 ? selection : null;
11708
+ return payload;
11709
+ }
11710
+ if (sharing === "everyone") {
11711
+ payload.selection = selection;
11712
+ return payload;
11713
+ }
11714
+ if (crypto === null) return null;
11715
+ payload.selection = null;
11716
+ payload.selections = await crypto.encrypt(selection, sharing);
11717
+ return payload;
11718
+ }
11719
+ /**
11720
+ * Resolve one received peer's selection: the plaintext one (the everyone /
11721
+ * legacy stand) stands as is; a targeted stand resolves to the first entry
11722
+ * the local cipher opens (the entries carry no target id, so every entry
11723
+ * is tried); without a cipher a targeted selection never stands (the
11724
+ * peer's identity frame still lists it in the roster).
11725
+ */
11726
+ async function resolvePeerSelection(params) {
11727
+ const { peer, crypto } = params;
11728
+ if (peer.selection !== null) return peer;
11729
+ if (peer.selections !== void 0 && crypto !== null && peer.selections.length > 0) {
11730
+ const selection = (await Promise.all(peer.selections.map((entry) => crypto.decrypt(entry)))).find((value) => value !== null) ?? null;
11731
+ return {
11732
+ ...peer,
11733
+ selection
11734
+ };
11735
+ }
11736
+ return peer;
11737
+ }
11738
+ //#endregion
11413
11739
  //#region src/state/utils.ts
11414
11740
  /** The editor store's projection utils: the known-type blocks filter
11415
11741
  * (the store's projection holds only known-type blocks — an unknown-type
@@ -11534,9 +11860,11 @@ function createEditorStore(doc, rootId) {
11534
11860
  selection: { kind: "none" },
11535
11861
  collapsedIds: [],
11536
11862
  presence: [],
11863
+ sharing: "off",
11537
11864
  canvasClipboard: null,
11538
11865
  applyChange,
11539
11866
  setPresence: (peers) => store.setState({ presence: peers }),
11867
+ setSharing: (mode) => store.setState({ sharing: mode }),
11540
11868
  setSelection: (selection) => store.setState({ selection }),
11541
11869
  setCollapsed: (id, collapsed) => {
11542
11870
  const ids = store.getState().collapsedIds;
@@ -11616,7 +11944,7 @@ function publishedStandKey(doc) {
11616
11944
  * its own. Safe under StrictMode double-mount: the cleanup detaches every
11617
11945
  * subscription and destroys the view between the two mounts.
11618
11946
  */
11619
- function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blobStore, databaseBridge, syncEmbedResolver, pdfPreview, mentionProvider, embedSandbox, rootBlockId, commandPalettePlaceholder, accessLevel, mode, publishedDoc, children }) {
11947
+ function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blobStore, databaseBridge, syncEmbedResolver, pdfPreview, mentionProvider, embedSandbox, rootBlockId, commandPalettePlaceholder, accessLevel, mode, publishedDoc, presence, children }) {
11620
11948
  const rootId = rootBlockId ?? doc.meta.id;
11621
11949
  const [store] = useState(() => createEditorStore(doc, rootId));
11622
11950
  const docRef = useRef(doc);
@@ -11626,6 +11954,10 @@ function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blob
11626
11954
  const effectiveAccessLevel = accessLevel ?? parentContext?.accessLevel ?? "editor";
11627
11955
  const effectiveMode = effectiveAccessLevel === "viewer" ? "published" : mode ?? parentContext?.mode ?? "edit";
11628
11956
  const editingStandRef = useRef(effectiveMode === "edit");
11957
+ const presenceRef = useRef(presence);
11958
+ presenceRef.current = presence;
11959
+ const viewModeRef = useRef(effectiveMode !== "edit");
11960
+ viewModeRef.current = effectiveMode !== "edit";
11629
11961
  useEffect(() => {
11630
11962
  if (docRef.current !== doc) throw new Error("EditorProvider: the doc instance must not change after mount.");
11631
11963
  docRef.current = doc;
@@ -11663,21 +11995,54 @@ function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blob
11663
11995
  useEffect(() => {
11664
11996
  if (provider === void 0) return;
11665
11997
  let lastPeers = "";
11666
- const unsubscribeAwareness = provider.onAwareness((peers) => {
11667
- const serialized = JSON.stringify(peers);
11998
+ const latest = /* @__PURE__ */ new Map();
11999
+ let generation = 0;
12000
+ const unsubscribeAwareness = provider.onAwareness((rawPeers) => {
12001
+ const serialized = JSON.stringify(rawPeers);
11668
12002
  if (serialized === lastPeers) return;
11669
12003
  lastPeers = serialized;
11670
- store.getState().setPresence(peers);
12004
+ const rawIds = new Set(rawPeers.map((peer) => peer.peerId));
12005
+ for (const id of [...latest.keys()]) if (!rawIds.has(id)) latest.delete(id);
12006
+ for (const peer of rawPeers) {
12007
+ const prev = latest.get(peer.peerId);
12008
+ if (peer.seq !== void 0 && prev?.seq !== void 0 && peer.seq < prev.seq) continue;
12009
+ latest.set(peer.peerId, peer);
12010
+ }
12011
+ const gen = ++generation;
12012
+ const crypto = presenceRef.current?.crypto ?? null;
12013
+ const input = [...latest.values()];
12014
+ (async () => {
12015
+ const resolved = [];
12016
+ for (const peer of input) resolved.push(await resolvePeerSelection({
12017
+ peer,
12018
+ crypto
12019
+ }));
12020
+ if (gen !== generation) return;
12021
+ store.getState().setPresence(resolved);
12022
+ })();
11671
12023
  });
11672
12024
  return () => unsubscribeAwareness();
11673
12025
  }, [provider, store]);
11674
12026
  useEffect(() => {
11675
12027
  if (provider === void 0) return;
12028
+ let seq = 0;
12029
+ let publishToken = 0;
11676
12030
  const publish = (selection) => {
11677
- provider.setAwareness({
11678
- selection: presenceSelectionOf(selection),
11679
- updatedAt: Date.now()
11680
- });
12031
+ const token = ++publishToken;
12032
+ const sharing = store.getState().sharing;
12033
+ (async () => {
12034
+ const payload = await buildPresencePayload({
12035
+ selection: presenceSelectionOf(selection),
12036
+ sharing,
12037
+ viewMode: viewModeRef.current,
12038
+ identity: presenceRef.current ?? {},
12039
+ crypto: presenceRef.current?.crypto ?? null,
12040
+ seq: ++seq,
12041
+ updatedAt: Date.now()
12042
+ });
12043
+ if (token !== publishToken || payload === null) return;
12044
+ provider.setAwareness(payload);
12045
+ })();
11681
12046
  };
11682
12047
  let timer;
11683
12048
  const queue = (selection) => {
@@ -11687,28 +12052,29 @@ function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blob
11687
12052
  publish(selection);
11688
12053
  }, 50);
11689
12054
  };
11690
- const publishNoneNow = () => {
12055
+ const publishNow = (selection) => {
11691
12056
  if (timer !== void 0) {
11692
12057
  clearTimeout(timer);
11693
12058
  timer = void 0;
11694
12059
  }
11695
- publish({ kind: "none" });
11696
- };
11697
- const publishCurrentNow = () => {
11698
- if (timer !== void 0) return;
11699
- publish(store.getState().selection);
12060
+ publish(selection);
11700
12061
  };
11701
12062
  const unsubscribe = store.subscribe((state, prev) => {
11702
12063
  if (state.selection !== prev.selection) queue(state.selection);
12064
+ if (state.sharing !== prev.sharing) publish(store.getState().selection);
11703
12065
  });
11704
- window.addEventListener("blur", publishNoneNow);
11705
- window.addEventListener("beforeunload", publishNoneNow);
11706
- window.addEventListener("focus", publishCurrentNow);
12066
+ const onBlur = () => publishNow({ kind: "none" });
12067
+ const onFocus = () => {
12068
+ if (timer !== void 0) return;
12069
+ publishNow(store.getState().selection);
12070
+ };
12071
+ window.addEventListener("blur", onBlur);
12072
+ window.addEventListener("beforeunload", onBlur);
12073
+ window.addEventListener("focus", onFocus);
11707
12074
  return () => {
11708
12075
  unsubscribe();
11709
- window.removeEventListener("blur", publishNoneNow);
11710
- window.removeEventListener("beforeunload", publishNoneNow);
11711
- window.removeEventListener("focus", publishCurrentNow);
12076
+ window.removeEventListener("blur", onBlur);
12077
+ window.removeEventListener("beforeunload", onBlur);
11712
12078
  if (timer !== void 0) clearTimeout(timer);
11713
12079
  };
11714
12080
  }, [provider, store]);
@@ -11737,7 +12103,8 @@ function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blob
11737
12103
  commandPalettePlaceholder: commandPalettePlaceholder ?? parent?.commandPalettePlaceholder ?? "Type '/' for commands",
11738
12104
  accessLevel: effectiveAccessLevel,
11739
12105
  mode: effectiveMode,
11740
- viewMode: effectiveMode !== "edit"
12106
+ viewMode: effectiveMode !== "edit",
12107
+ presence: presence ?? null
11741
12108
  };
11742
12109
  }, [
11743
12110
  store,
@@ -11753,7 +12120,8 @@ function EditorProviderStand({ doc, provider, blockRenderers, blobResolver, blob
11753
12120
  mentionProvider,
11754
12121
  embedSandbox,
11755
12122
  effectiveAccessLevel,
11756
- effectiveMode
12123
+ effectiveMode,
12124
+ presence
11757
12125
  ]);
11758
12126
  const inheritedPublishedStand = parentContext !== null && parentContext.mode === "published";
11759
12127
  const rendered = effectiveAccessLevel === "viewer" && parentContext === null ? children : effectiveMode === "published" ? publishedDoc !== void 0 && publishedDoc !== null ? /* @__PURE__ */ jsx(EditorProvider, {
@@ -28570,242 +28938,6 @@ function useMotionDrag({ doc, rootBlockId, store, rootRef, rowKeys, nodesById })
28570
28938
  };
28571
28939
  }
28572
28940
  //#endregion
28573
- //#region src/presence/utils.ts
28574
- /** Deterministic hue (0–359) from the peer id (pure function, no peer state). */
28575
- function peerHue(peerId) {
28576
- let hash = 0;
28577
- for (let i = 0; i < peerId.length; i++) hash = hash * 31 + peerId.charCodeAt(i) >>> 0;
28578
- return hash % 360;
28579
- }
28580
- /** The caret endpoints a peer's wire selection draws; a range draws range boxes instead. */
28581
- function caretPointsOf(selection) {
28582
- if (selection === null) return [];
28583
- if ("blockId" in selection) return [{
28584
- blockId: selection.blockId,
28585
- offset: selection.offset
28586
- }];
28587
- return [];
28588
- }
28589
- /**
28590
- * The per-chain-block segments of a peer's range selection: the from block's
28591
- * tail, the middle text blocks in full, the to block's head. Chain blocks
28592
- * missing from the store (stale presence — the remote selection points at a
28593
- * removed block) are skipped leniently, and the offsets clamp to the current
28594
- * block lengths.
28595
- */
28596
- function rangeSegmentsOf(selection, doc, blocks) {
28597
- if (selection === null || !("from" in selection)) return [];
28598
- const chain = selection.blocks ?? (selection.from.blockId === selection.to.blockId ? [selection.from.blockId] : [selection.from.blockId, selection.to.blockId]);
28599
- const segments = [];
28600
- for (const blockId of chain) {
28601
- if (blocks[blockId] === void 0) continue;
28602
- const length = doc.getTextLength(blockId);
28603
- const from = blockId === selection.from.blockId ? selection.from.offset : 0;
28604
- const to = blockId === selection.to.blockId ? Math.min(selection.to.offset, length) : length;
28605
- if (from >= to || from >= length) continue;
28606
- segments.push({
28607
- blockId,
28608
- from,
28609
- to
28610
- });
28611
- }
28612
- return segments;
28613
- }
28614
- /**
28615
- * The client rects of a segment inside the block element, relative to the
28616
- * root's rect. Environments without layout (jsdom — no getClientRects)
28617
- * yield no rects, so a missing rect maps to a single zero box.
28618
- */
28619
- function rangeRects(root, block, from, to) {
28620
- const rootRect = root.getBoundingClientRect();
28621
- const start = locatePosition(block, from);
28622
- const end = locatePosition(block, to);
28623
- const range = document.createRange();
28624
- range.setStart(start.node, start.offset);
28625
- range.setEnd(end.node, end.offset);
28626
- if (typeof range.getClientRects !== "function") return [];
28627
- return Array.from(range.getClientRects()).map((rect) => ({
28628
- left: rect.left - rootRect.left,
28629
- top: rect.top - rootRect.top,
28630
- width: rect.width,
28631
- height: rect.height
28632
- }));
28633
- }
28634
- /**
28635
- * The client rect of a collapsed range at the flat offset inside the
28636
- * block element, relative to the root's rect. Environments without layout
28637
- * (jsdom — no getClientRects) yield zero rects, so a missing rect maps to 0.
28638
- */
28639
- function caretGeometry(root, block, offset) {
28640
- const position = locatePosition(block, offset);
28641
- const range = document.createRange();
28642
- range.setStart(position.node, position.offset);
28643
- range.collapse(true);
28644
- let rect;
28645
- if (typeof range.getClientRects === "function") {
28646
- const rects = range.getClientRects();
28647
- rect = rects.length > 0 ? rects[0] : void 0;
28648
- }
28649
- const rootRect = root.getBoundingClientRect();
28650
- return {
28651
- left: rect !== void 0 ? rect.left - rootRect.left : 0,
28652
- top: rect !== void 0 ? rect.top - rootRect.top : 0,
28653
- height: rect !== void 0 ? rect.height : 0
28654
- };
28655
- }
28656
- //#endregion
28657
- //#region src/presence/PresenceCaretOverlay.tsx
28658
- /** Renders remote peers' selections as absolutely positioned boxes over
28659
- * the editor root — a pure view of the presence slice (it never
28660
- * mutates). A caret selection draws a caret bar; a range selection
28661
- * (cross-block included) draws its highlight boxes — one per client rect
28662
- * of each chain-block segment. The boxes are recomputed on presence
28663
- * changes, on block snapshot changes (text shifts the pixels), and on
28664
- * window resize; a peer whose block is not rendered (removed, unknown
28665
- * type) draws nothing. The structural classes of the overlay boxes (the
28666
- * per-peer hue comes from the inline --docblocks-presence-hue property,
28667
- * the geometry from the inline layout properties): the caret bar is the
28668
- * fixed width token; the range highlight is a softened box of the same
28669
- * hue. */
28670
- /**
28671
- * The block ids a peer's selection references (the caret's block, the
28672
- * range's endpoints + the chain blocks). The overlay stands no boxes over
28673
- * the other blocks — those stand out of its subscription.
28674
- */
28675
- function peerBlockIdsOf(presence) {
28676
- const ids = /* @__PURE__ */ new Set();
28677
- for (const peer of presence) {
28678
- const selection = peer.selection;
28679
- if (selection === null) continue;
28680
- if ("blockId" in selection) ids.add(selection.blockId);
28681
- else if ("from" in selection) {
28682
- ids.add(selection.from.blockId);
28683
- ids.add(selection.to.blockId);
28684
- for (const id of selection.blocks ?? []) ids.add(id);
28685
- }
28686
- }
28687
- return [...ids].sort();
28688
- }
28689
- /** The peer-referenced blocks' snapshots (the absent ones stand dropped). The ids come from the remote presence frames — the open-key stand: `__proto__` stands the own property (no prototype reassignment over the derived map). */
28690
- function peerBlocksOf(presence, blocks) {
28691
- const next = {};
28692
- for (const id of peerBlockIdsOf(presence)) {
28693
- const block = blocks[id];
28694
- if (block !== void 0) Object.defineProperty(next, id, {
28695
- value: block,
28696
- enumerable: true,
28697
- writable: true,
28698
- configurable: true
28699
- });
28700
- }
28701
- return next;
28702
- }
28703
- /**
28704
- * The structural classes of the overlay boxes (the per-peer hue comes
28705
- * from the inline --docblocks-presence-hue property, the geometry from
28706
- * the inline layout properties). The caret bar is the fixed width token;
28707
- * the range highlight is a softened box of the same hue.
28708
- */
28709
- const CARET_CLASS = "pointer-events-none absolute w-(--docblocks-presence-caret-width) bg-[hsl(var(--docblocks-presence-hue,200)_var(--docblocks-presence-caret-saturation)_var(--docblocks-presence-caret-lightness))]";
28710
- const RANGE_CLASS = "pointer-events-none absolute rounded-[2px] bg-[hsl(var(--docblocks-presence-hue,200)_var(--docblocks-presence-caret-saturation)_var(--docblocks-presence-caret-lightness)/var(--docblocks-presence-range-opacity))]";
28711
- function PresenceCaretOverlay({ rootRef }) {
28712
- const { store, doc } = useEditorContext();
28713
- const presence = useStore(store, (state) => state.presence);
28714
- const peerIdsKeyRef = useRef("");
28715
- const peerBlocksCacheRef = useRef(null);
28716
- const peerBlocks = useStore(store, (state) => {
28717
- if (state.presence.length === 0) return null;
28718
- const ids = peerBlockIdsOf(state.presence);
28719
- const key = ids.join("\0");
28720
- const cache = peerBlocksCacheRef.current;
28721
- if (key === peerIdsKeyRef.current && cache !== null) {
28722
- let unchanged = true;
28723
- for (const id of ids) if (cache[id] !== state.blocks[id]) {
28724
- unchanged = false;
28725
- break;
28726
- }
28727
- if (unchanged) return cache;
28728
- }
28729
- const next = peerBlocksOf(state.presence, state.blocks);
28730
- peerIdsKeyRef.current = key;
28731
- peerBlocksCacheRef.current = next;
28732
- return next;
28733
- });
28734
- const [boxes, setBoxes] = useState([]);
28735
- useLayoutEffect(() => {
28736
- const root = rootRef.current;
28737
- if (root === null) return;
28738
- const recompute = () => {
28739
- const next = [];
28740
- for (const peer of presence) {
28741
- for (const point of caretPointsOf(peer.selection)) {
28742
- const block = root.querySelector(`[data-block-id="${CSS.escape(point.blockId)}"][data-slot="docblocks/block-content"]`) ?? root.querySelector(`[data-block-id="${CSS.escape(point.blockId)}"]`);
28743
- if (block === null) continue;
28744
- next.push({
28745
- key: `${peer.peerId}:caret:${point.blockId}:${point.offset}`,
28746
- peerId: peer.peerId,
28747
- slot: "docblocks/presence-caret",
28748
- ...caretGeometry(root, block, point.offset)
28749
- });
28750
- }
28751
- for (const segment of rangeSegmentsOf(peer.selection, doc, peerBlocks ?? {})) {
28752
- const block = root.querySelector(`[data-block-id="${CSS.escape(segment.blockId)}"][data-slot="docblocks/block-content"]`);
28753
- if (block === null) continue;
28754
- const rects = rangeRects(root, block, segment.from, segment.to);
28755
- (rects.length > 0 ? rects : [{
28756
- left: 0,
28757
- top: 0,
28758
- width: 0,
28759
- height: 0
28760
- }]).forEach((rect, index) => {
28761
- next.push({
28762
- key: `${peer.peerId}:range:${segment.blockId}:${segment.from}-${segment.to}:${index}`,
28763
- peerId: peer.peerId,
28764
- slot: "docblocks/presence-range",
28765
- blockId: segment.blockId,
28766
- ...rect
28767
- });
28768
- });
28769
- }
28770
- }
28771
- setBoxes((prev) => JSON.stringify(prev) === JSON.stringify(next) ? prev : next);
28772
- };
28773
- let frame = 0;
28774
- const scheduleRecompute = () => {
28775
- cancelAnimationFrame(frame);
28776
- frame = requestAnimationFrame(() => {
28777
- frame = 0;
28778
- recompute();
28779
- });
28780
- };
28781
- recompute();
28782
- scheduleRecompute();
28783
- window.addEventListener("resize", scheduleRecompute);
28784
- return () => {
28785
- cancelAnimationFrame(frame);
28786
- window.removeEventListener("resize", scheduleRecompute);
28787
- };
28788
- }, [
28789
- presence,
28790
- peerBlocks,
28791
- doc,
28792
- rootRef
28793
- ]);
28794
- return /* @__PURE__ */ jsx(Fragment$1, { children: boxes.map((box) => /* @__PURE__ */ jsx("span", {
28795
- "data-slot": box.slot,
28796
- "data-peer": box.peerId,
28797
- "data-range-block": box.blockId,
28798
- className: box.slot === "docblocks/presence-caret" ? CARET_CLASS : RANGE_CLASS,
28799
- style: {
28800
- "--docblocks-presence-hue": peerHue(box.peerId),
28801
- left: box.left,
28802
- top: box.top,
28803
- height: box.height,
28804
- ...box.width !== void 0 ? { width: box.width } : null
28805
- }
28806
- }, box.key)) });
28807
- }
28808
- //#endregion
28809
28941
  //#region src/Editor.tsx
28810
28942
  /**
28811
28943
  * The document's editor: the root is the editor's focus/keyboard scope
@@ -28947,4 +29079,4 @@ function Editor({ view = "page", formatPalette = FORMAT_PALETTE }) {
28947
29079
  });
28948
29080
  }
28949
29081
  //#endregion
28950
- export { PopoverHeader as $, SelectTrigger as A, isBridgeErrorCode as At, DropdownMenuRadioGroup as B, useMeta as Bt, SelectContent as C, BASELINE_CAPABILITIES as Ct, SelectScrollDownButton as D, BRIDGE_ERROR_CODES as Dt, SelectLabel as E, validateProtocolDescriptor as Et, DropdownMenuContent as F, columnAnnotation as Ft, DropdownMenuSubContent as G, INLINE_CODE_CLASS as Gt, DropdownMenuSeparator as H, useSelection as Ht, DropdownMenuGroup as I, ScrollArea as It, Separator as J, createMemoryBlobStore as Jt, DropdownMenuSubTrigger as K, renderRuns as Kt, DropdownMenuItem as L, Input as Lt, Label as M, conditionMatches as Mt, DropdownMenu as N, parseCellValue as Nt, SelectScrollUpButton as O, BridgeError as Ot, DropdownMenuCheckboxItem as P, TEXT_TYPES as Pt, PopoverDescription as Q, DropdownMenuLabel as R, useBlock as Rt, Select as S, AlertTitle as St, SelectItem as T, isDatabaseBridge as Tt, DropdownMenuShortcut as U, useSelectionView as Ut, DropdownMenuRadioItem as V, usePresence as Vt, DropdownMenuSub as W, useText as Wt, PopoverAnchor as X, Popover as Y, PopoverContent as Z, FilterBuilder as _, BareSelect as _t, SHAPE_FONT_STACKS as a, CardAction as at, Skeleton as b, Alert as bt, textOf as c, CardFooter as ct, useEditorContext as d, Pen as dt, PopoverTitle as et, DEFAULT_EMBED_SANDBOX as f, Eye as ft, newMutationContext as g, buttonVariants as gt, cellValueEqual as h, Button as ht, SHAPE_FONT_OPTIONS as i, Card as it, SelectValue as j, toBridgeError as jt, SelectSeparator as k, isBridgeError as kt, markToggleAction as l, CardHeader as lt, useDatabaseTable as m, BookOpen as mt, childrenSlot as n, DocblocksI18nProvider as nt, FORMAT_PALETTE as o, CardContent as ot, GridBody as p, CodeXml as pt, DropdownMenuTrigger as q, blobSubtypeOf as qt, CodeHeader as r, Checkbox as rt, TextBlock as s, CardDescription as st, Editor as t, PopoverTrigger as tt, EditorProvider as u, CardTitle as ut, emptyFilter as v, Badge as vt, SelectGroup as w, PROTOCOL_VERSION as wt, SegmentSelector as x, AlertDescription as xt, filterIsComplete as y, badgeVariants as yt, DropdownMenuPortal as z, useDoc as zt };
29082
+ export { PopoverHeader as $, SelectTrigger as A, isBridgeErrorCode as At, DropdownMenuRadioGroup as B, useMeta as Bt, SelectContent as C, BASELINE_CAPABILITIES as Ct, SelectScrollDownButton as D, BRIDGE_ERROR_CODES as Dt, SelectLabel as E, validateProtocolDescriptor as Et, DropdownMenuContent as F, columnAnnotation as Ft, DropdownMenuSubContent as G, useText as Gt, DropdownMenuSeparator as H, usePresenceSharing as Ht, DropdownMenuGroup as I, ScrollArea as It, Separator as J, blobSubtypeOf as Jt, DropdownMenuSubTrigger as K, INLINE_CODE_CLASS as Kt, DropdownMenuItem as L, Input as Lt, Label as M, conditionMatches as Mt, DropdownMenu as N, parseCellValue as Nt, SelectScrollUpButton as O, BridgeError as Ot, DropdownMenuCheckboxItem as P, TEXT_TYPES as Pt, PopoverDescription as Q, DropdownMenuLabel as R, useBlock as Rt, Select as S, AlertTitle as St, SelectItem as T, isDatabaseBridge as Tt, DropdownMenuShortcut as U, useSelection as Ut, DropdownMenuRadioItem as V, usePresence as Vt, DropdownMenuSub as W, useSelectionView as Wt, PopoverAnchor as X, Popover as Y, createMemoryBlobStore as Yt, PopoverContent as Z, FilterBuilder as _, BareSelect as _t, SHAPE_FONT_STACKS as a, CardAction as at, Skeleton as b, Alert as bt, textOf as c, CardFooter as ct, useEditorContext as d, Pen as dt, PopoverTitle as et, DEFAULT_EMBED_SANDBOX as f, Eye as ft, newMutationContext as g, buttonVariants as gt, cellValueEqual as h, Button as ht, SHAPE_FONT_OPTIONS as i, Card as it, SelectValue as j, toBridgeError as jt, SelectSeparator as k, isBridgeError as kt, markToggleAction as l, CardHeader as lt, useDatabaseTable as m, BookOpen as mt, childrenSlot as n, DocblocksI18nProvider as nt, FORMAT_PALETTE as o, CardContent as ot, GridBody as p, CodeXml as pt, DropdownMenuTrigger as q, renderRuns as qt, CodeHeader as r, Checkbox as rt, TextBlock as s, CardDescription as st, Editor as t, PopoverTrigger as tt, EditorProvider as u, CardTitle as ut, emptyFilter as v, Badge as vt, SelectGroup as w, PROTOCOL_VERSION as wt, SegmentSelector as x, AlertDescription as xt, filterIsComplete as y, badgeVariants as yt, DropdownMenuPortal as z, useDoc as zt };
@@ -1,6 +1,6 @@
1
1
  import { n as BlockRendererMap } from "./index-YJcgf8Hh.mjs";
2
2
  import { ComponentType, ReactNode } from "react";
3
- import { Block, BlockContent, BlockId, BlockType, CanvasTextAttrs, Change, ConnectorAttrs, DatabaseFilterPayload, Doc, DocMeta, Geometry, PresenceState, Selection, SelectionView, ShapeAttrs, SyncEmbedRef, SyncProvider } from "docblocks-core";
3
+ import { Block, BlockContent, BlockId, BlockType, CanvasTextAttrs, Change, ConnectorAttrs, DatabaseFilterPayload, Doc, DocMeta, Geometry, PresenceSelection, PresenceState, Selection, SelectionView, ShapeAttrs, SyncEmbedRef, SyncProvider } from "docblocks-core";
4
4
  import { StoreApi } from "zustand/vanilla";
5
5
  //#region src/blob-store.d.ts
6
6
  /**
@@ -796,6 +796,50 @@ declare function DatabaseTable({ bridge, tableId, readOnly, view, onViewChange }
796
796
  */
797
797
  declare function useTableList(bridge: DatabaseBridge): UseTableList;
798
798
  //#endregion
799
+ //#region src/presence/models.d.ts
800
+ /** The local selection's sharing grant: no sharing, sharing over the whole
801
+ * room (plaintext), or a targeted user-id list (encrypted per target). */
802
+ type PresenceSharingMode = "off" | "everyone" | string[];
803
+ /**
804
+ * The consumer's selection cipher (the privacy's stand): the library
805
+ * orchestrates the publish/receive pipeline and the consumer brings the
806
+ * keys. An entry is an opaque string — the algorithm is the consumer's
807
+ * choice.
808
+ */
809
+ interface PresenceCrypto {
810
+ /**
811
+ * Encrypt the local selection for the grant: one opaque entry per target
812
+ * (grant order; no target id inside an entry — the recipient tries each
813
+ * entry with its own `decrypt`).
814
+ */
815
+ encrypt(selection: PresenceSelection, grant: string[]): Promise<string[]>;
816
+ /** Try to decrypt a received entry for the local user. Null when the entry is not addressed to the local user. */
817
+ decrypt(entry: string): Promise<PresenceSelection | null>;
818
+ }
819
+ /** One live roster entry: the peer's identity and whether its selection stands visible to the local user. */
820
+ interface PresencePeer {
821
+ peerId: string;
822
+ userId?: string;
823
+ name?: string;
824
+ updatedAt: number;
825
+ /** True when the peer's selection is accepted locally (decrypted, everyone, or legacy plaintext). */
826
+ selectionVisible: boolean;
827
+ }
828
+ /** The presence sharing's session state (per EditorProvider instance). */
829
+ interface PresenceSharing {
830
+ /** The local grant (session state; the default "off"; not persisted, not on the wire). */
831
+ sharing: PresenceSharingMode;
832
+ /** Set the local grant. A no-op over the read-only modes. */
833
+ setSharing: (mode: PresenceSharingMode) => void;
834
+ /** The live roster of the connected peers (the identity frames are always published). */
835
+ peers: PresencePeer[];
836
+ }
837
+ /** The presence identity's wire payload: the stable user id + the optional display label. */
838
+ interface PresenceIdentity {
839
+ userId?: string;
840
+ name?: string;
841
+ }
842
+ //#endregion
799
843
  //#region src/canvas/clipboard.d.ts
800
844
  /** The one copied canvas item (the deep data; the ids stand the originals). */
801
845
  type ClipboardItem = {
@@ -872,6 +916,12 @@ interface EditorState {
872
916
  collapsedIds: BlockId[];
873
917
  /** Remote peers' ephemeral state; a separate slice for the same reason. */
874
918
  presence: PresenceState[];
919
+ /**
920
+ * The local selection's sharing grant (session state per provider
921
+ * instance; the default "off" — not document data, never on the wire).
922
+ * A separate slice so a grant change never re-renders blocks.
923
+ */
924
+ sharing: PresenceSharingMode;
875
925
  /**
876
926
  * The canvas clipboard: the deep copy of
877
927
  * the copied canvas items — the view state per editor (not doc data, no
@@ -886,6 +936,8 @@ interface EditorActions {
886
936
  applyChange: (change: Change) => void;
887
937
  /** Replaces the presence slice. */
888
938
  setPresence: (peers: PresenceState[]) => void;
939
+ /** Sets the local sharing grant. */
940
+ setSharing: (mode: PresenceSharingMode) => void;
889
941
  /** Replaces the selection slice. */
890
942
  setSelection: (selection: Selection) => void;
891
943
  /** Sets the collapsed state of one list item (view state, no doc command). */
@@ -937,6 +989,27 @@ interface MentionProvider {
937
989
  interface SelectionRef {
938
990
  view: SelectionView | null;
939
991
  }
992
+ /**
993
+ * The local user's presence identity and the selection-sharing cipher
994
+ * (consumer session state — never document data). Without `userId` the
995
+ * peer is unaddressable: targeted sharing stands unavailable (the roster
996
+ * still lists the peer by its transport-level `peerId`). Without `crypto`
997
+ * the grant degrades to "off" / "everyone" (a targeted grant is a no-op).
998
+ */
999
+ interface PresenceConfig {
1000
+ /** The stable user id — the addressable identity on the wire. */
1001
+ userId: string;
1002
+ /** The roster display label (optional). */
1003
+ name?: string;
1004
+ /**
1005
+ * The selection cipher (the targeted's stand): the local selection is
1006
+ * encrypted per target for a targeted grant, and a received ciphertext
1007
+ * entry is decrypted with the local user's keys. Opaque strings
1008
+ * throughout — the algorithm is the consumer's choice. Without it, only
1009
+ * "off" / "everyone" are available.
1010
+ */
1011
+ crypto?: PresenceCrypto;
1012
+ }
940
1013
  /** The consumer's user access level: the available document modes. "viewer" is locked to the published's view (the current state is never rendered); "editor" (the default) may edit and view. Session state — not document data, never on the wire. */
941
1014
  type AccessLevel = "viewer" | "editor";
942
1015
  /** The document's mode (consumer-controlled view state, like the page/canvas `view`): "edit" (the default) is the live document editable; "view" is the live document read-only (the current state, still syncing); "published" is the `publishedDoc`'s snapshot read-only (static). */
@@ -977,6 +1050,8 @@ interface EditorContextValue {
977
1050
  mode: EditorMode;
978
1051
  /** True over the non-edit modes ("view" / "published"): every editing surface gates off (the read-only stand). */
979
1052
  viewMode: boolean;
1053
+ /** The presence identity + cipher (the provider's `presence` prop); null when none was supplied (the legacy's anonymous stand). */
1054
+ presence: PresenceConfig | null;
980
1055
  }
981
1056
  interface EditorProviderProps {
982
1057
  /**
@@ -1119,6 +1194,22 @@ interface EditorProviderProps {
1119
1194
  * inherits the enclosing value, like the resolvers.
1120
1195
  */
1121
1196
  commandPalettePlaceholder?: string;
1197
+ /**
1198
+ * Optional presence identity + the selection-sharing cipher (consumer
1199
+ * session state, never document data): the local user's stable `userId`
1200
+ * (the addressable identity on the wire), the optional roster label, and
1201
+ * the optional `PresenceCrypto` cipher (without it a targeted grant
1202
+ * degrades to "off" / "everyone": the hook's setter stands a no-op over
1203
+ * a targeted grant without the identity or the cipher). With the prop
1204
+ * the selection sharing grant is session state
1205
+ * driven through `usePresenceSharing()` (the default "off" — the
1206
+ * privacy stand); without it the provider keeps the legacy stand (the
1207
+ * anonymous plaintext selection — always shared over the room).
1208
+ *
1209
+ * Like the other customization values, the value is read per render:
1210
+ * pass a stable reference.
1211
+ */
1212
+ presence?: PresenceConfig;
1122
1213
  children: ReactNode;
1123
1214
  }
1124
1215
  //#endregion
@@ -1150,4 +1241,4 @@ declare const DEFAULT_EMBED_SANDBOX = "allow-scripts allow-forms allow-popups";
1150
1241
  /** Read the per-instance editor context; throws outside an EditorProvider. */
1151
1242
  declare function useEditorContext(): EditorContextValue;
1152
1243
  //#endregion
1153
- export { SortInput as $, BridgeValueInput as A, MutationContext as B, emptyFilter as C, BridgeColumn as D, BatchOp as E, DomainEvent as F, ProtocolCapability as G, OperationStatus as H, FilterConditionInput as I, QueryOperator as J, ProtocolDescriptor as K, FilterInput as L, CellValueParse as M, Column as N, BridgeColumnAnnotation as O, DatabaseBridge as P, RowWire as Q, Id as R, UseDatabaseTable as S, FilterBuilder as T, PageQuery as U, MutationReceipt as V, PageSnapshot as W, Revision as X, QuerySemantics as Y, RowPage as Z, DatabaseTableView as _, BlobSubtype as _t, EditorContextValue as a, TableEvent as at, DatabaseTablePhase as b, EmbedSandboxPolicy as c, BRIDGE_ERROR_CODES as ct, PdfPreview as d, isBridgeError as dt, SubscriptionHandle as et, PdfPreviewProps as f, isBridgeErrorCode as ft, DatabaseTableProps as g, BlobStore as gt, DatabaseTable as h, BlobRef as ht, AccessLevel as i, TableEnvelopeBase as it, CellMap as j, BridgeTableDescription as k, MentionEntry as l, BridgeError as lt, useTableList as m, BlobMeta as mt, DEFAULT_EMBED_SANDBOX as n, SubscriptionStatus as nt, EditorMode as o, TableRef as ot, SyncEmbedResolver as p, toBridgeError as pt, ProtocolLimits as q, EditorProvider as r, TableDescription as rt, EditorProviderProps as s, TableSnapshot as st, useEditorContext as t, SubscriptionObserver as tt, MentionProvider as u, BridgeErrorCode as ut, UseTableList as v, blobSubtypeOf as vt, filterIsComplete as w, DatabaseTableRow as x, useDatabaseTable as y, createMemoryBlobStore as yt, IsoInstant as z };
1244
+ export { ProtocolLimits as $, filterIsComplete as A, DatabaseBridge as B, DatabaseTableView as C, BlobSubtype as Ct, DatabaseTableRow as D, DatabaseTablePhase as E, BridgeTableDescription as F, IsoInstant as G, FilterConditionInput as H, BridgeValueInput as I, OperationStatus as J, MutationContext as K, CellMap as L, BatchOp as M, BridgeColumn as N, UseDatabaseTable as O, BridgeColumnAnnotation as P, ProtocolDescriptor as Q, CellValueParse as R, DatabaseTableProps as S, BlobStore as St, useDatabaseTable as T, createMemoryBlobStore as Tt, FilterInput as U, DomainEvent as V, Id as W, PageSnapshot as X, PageQuery as Y, ProtocolCapability as Z, PresencePeer as _, isBridgeError as _t, EditorContextValue as a, SortInput as at, useTableList as b, BlobMeta as bt, EmbedSandboxPolicy as c, SubscriptionStatus as ct, PdfPreview as d, TableEvent as dt, QueryOperator as et, PdfPreviewProps as f, TableRef as ft, PresenceIdentity as g, BridgeErrorCode as gt, PresenceCrypto as h, BridgeError as ht, AccessLevel as i, RowWire as it, FilterBuilder as j, emptyFilter as k, MentionEntry as l, TableDescription as lt, SyncEmbedResolver as m, BRIDGE_ERROR_CODES as mt, DEFAULT_EMBED_SANDBOX as n, Revision as nt, EditorMode as o, SubscriptionHandle as ot, PresenceConfig as p, TableSnapshot as pt, MutationReceipt as q, EditorProvider as r, RowPage as rt, EditorProviderProps as s, SubscriptionObserver as st, useEditorContext as t, QuerySemantics as tt, MentionProvider as u, TableEnvelopeBase as ut, PresenceSharing as v, isBridgeErrorCode as vt, UseTableList as w, blobSubtypeOf as wt, DatabaseTable as x, BlobRef as xt, PresenceSharingMode as y, toBridgeError as yt, Column as z };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as BlockRendererMap, r as BlockRendererProps, t as BlockRenderer } from "./index-YJcgf8Hh.mjs";
2
- import { $ as SortInput, A as BridgeValueInput, B as MutationContext, C as emptyFilter, D as BridgeColumn, E as BatchOp, F as DomainEvent, G as ProtocolCapability, H as OperationStatus, I as FilterConditionInput, J as QueryOperator, K as ProtocolDescriptor, L as FilterInput, M as CellValueParse, N as Column, O as BridgeColumnAnnotation, P as DatabaseBridge, Q as RowWire, R as Id, S as UseDatabaseTable, T as FilterBuilder, U as PageQuery, V as MutationReceipt, W as PageSnapshot, X as Revision, Y as QuerySemantics, Z as RowPage, _ as DatabaseTableView, _t as BlobSubtype, a as EditorContextValue, at as TableEvent, b as DatabaseTablePhase, c as EmbedSandboxPolicy, ct as BRIDGE_ERROR_CODES, d as PdfPreview, dt as isBridgeError, et as SubscriptionHandle, f as PdfPreviewProps, ft as isBridgeErrorCode, g as DatabaseTableProps, gt as BlobStore, h as DatabaseTable, ht as BlobRef, i as AccessLevel, it as TableEnvelopeBase, j as CellMap, k as BridgeTableDescription, l as MentionEntry, lt as BridgeError, m as useTableList, mt as BlobMeta, n as DEFAULT_EMBED_SANDBOX, nt as SubscriptionStatus, o as EditorMode, ot as TableRef, p as SyncEmbedResolver, pt as toBridgeError, q as ProtocolLimits, r as EditorProvider, rt as TableDescription, s as EditorProviderProps, st as TableSnapshot, t as useEditorContext, tt as SubscriptionObserver, u as MentionProvider, ut as BridgeErrorCode, v as UseTableList, vt as blobSubtypeOf, w as filterIsComplete, x as DatabaseTableRow, y as useDatabaseTable, yt as createMemoryBlobStore, z as IsoInstant } from "./index-DOKS86aH.mjs";
2
+ import { $ as ProtocolLimits, A as filterIsComplete, B as DatabaseBridge, C as DatabaseTableView, Ct as BlobSubtype, D as DatabaseTableRow, E as DatabaseTablePhase, F as BridgeTableDescription, G as IsoInstant, H as FilterConditionInput, I as BridgeValueInput, J as OperationStatus, K as MutationContext, L as CellMap, M as BatchOp, N as BridgeColumn, O as UseDatabaseTable, P as BridgeColumnAnnotation, Q as ProtocolDescriptor, R as CellValueParse, S as DatabaseTableProps, St as BlobStore, T as useDatabaseTable, Tt as createMemoryBlobStore, U as FilterInput, V as DomainEvent, W as Id, X as PageSnapshot, Y as PageQuery, Z as ProtocolCapability, _ as PresencePeer, _t as isBridgeError, a as EditorContextValue, at as SortInput, b as useTableList, bt as BlobMeta, c as EmbedSandboxPolicy, ct as SubscriptionStatus, d as PdfPreview, dt as TableEvent, et as QueryOperator, f as PdfPreviewProps, ft as TableRef, g as PresenceIdentity, gt as BridgeErrorCode, h as PresenceCrypto, ht as BridgeError, i as AccessLevel, it as RowWire, j as FilterBuilder, k as emptyFilter, l as MentionEntry, lt as TableDescription, m as SyncEmbedResolver, mt as BRIDGE_ERROR_CODES, n as DEFAULT_EMBED_SANDBOX, nt as Revision, o as EditorMode, ot as SubscriptionHandle, p as PresenceConfig, pt as TableSnapshot, q as MutationReceipt, r as EditorProvider, rt as RowPage, s as EditorProviderProps, st as SubscriptionObserver, t as useEditorContext, tt as QuerySemantics, u as MentionProvider, ut as TableEnvelopeBase, v as PresenceSharing, vt as isBridgeErrorCode, w as UseTableList, wt as blobSubtypeOf, x as DatabaseTable, xt as BlobRef, y as PresenceSharingMode, yt as toBridgeError, z as Column } from "./index-DG0lJy_t.mjs";
3
3
  import * as React from "react";
4
4
  import { ComponentType, ReactNode } from "react";
5
5
  import { Block, BlockId, BlockType, Doc, DocMeta, PresenceState, Selection, SelectionView, SyncEmbedRef, TextRun } from "docblocks-core";
@@ -193,6 +193,14 @@ declare function useText(id: BlockId): string;
193
193
  declare function useSelection(): Selection;
194
194
  /** Remote peers' ephemeral state; a separate slice for the same reason. */
195
195
  declare function usePresence(): PresenceState[];
196
+ /**
197
+ * The presence sharing's session state (per provider instance): the local
198
+ * grant (the default "off" — the privacy stand), the grant's setter (a
199
+ * no-op over the read-only stand), and the live roster of the connected
200
+ * peers (the identity frames are always present, the selection only over
201
+ * the grant it stands shared).
202
+ */
203
+ declare function usePresenceSharing(): PresenceSharing;
196
204
  /** The document meta; the reference is replaced only when the title changed. */
197
205
  declare function useMeta(): DocMeta;
198
206
  /**
@@ -286,7 +294,7 @@ declare function AlertDescription({ className, ...props }: React.ComponentProps<
286
294
  //#endregion
287
295
  //#region src/shared/ui/Badge.d.ts
288
296
  declare const badgeVariants: (props?: ({
289
- variant?: "default" | "destructive" | "outline" | "secondary" | "custom" | null | undefined;
297
+ variant?: "default" | "destructive" | "custom" | "secondary" | "outline" | null | undefined;
290
298
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
291
299
  declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
292
300
  asChild?: boolean;
@@ -297,7 +305,7 @@ declare function BareSelect({ className, children, ...props }: React.ComponentPr
297
305
  //#endregion
298
306
  //#region src/shared/ui/Button.d.ts
299
307
  declare const buttonVariants: (props?: ({
300
- variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
308
+ variant?: "link" | "default" | "destructive" | "secondary" | "outline" | "ghost" | null | undefined;
301
309
  size?: "default" | "icon" | "sm" | "lg" | "xl" | null | undefined;
302
310
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
303
311
  declare function Button({ className, variant, size, asChild, type, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
@@ -431,4 +439,4 @@ declare function TooltipContent({ className, sideOffset, children, ...props }: R
431
439
  //#region src/shared/ui/cn.d.ts
432
440
  declare function cn(...inputs: ClassValue[]): string;
433
441
  //#endregion
434
- export { type AccessLevel, Alert, AlertDescription, AlertTitle, BASELINE_CAPABILITIES, BRIDGE_ERROR_CODES, Badge, BareSelect, type BatchOp, type BlobMeta, type BlobRef, type BlobStore, type BlobSubtype, type BlockRenderer, type BlockRendererMap, type BlockRendererProps, type BridgeColumn, type BridgeColumnAnnotation, BridgeError, type BridgeErrorCode, type BridgeTableDescription, type BridgeValueInput, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CellMap, type CellValueParse, Checkbox, type Column, DEFAULT_EMBED_SANDBOX, type DatabaseBridge, DatabaseTable, type DatabaseTablePhase, type DatabaseTableProps, type DatabaseTableRow, type DatabaseTableView, DocHeader, type DomainEvent, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Editor, type EditorContextValue, type EditorMode, EditorProvider, type EditorProviderProps, type EditorView, type EmbedSandboxPolicy, FORMAT_PALETTE, FilterBuilder, type FilterConditionInput, type FilterInput, type FormatMark, type Id, Input, type IsoInstant, Label, MONO_FONT_STACK, type MentionEntry, type MentionProvider, type MutationContext, type MutationReceipt, type OperationStatus, PROTOCOL_VERSION, type PageQuery, type PageSnapshot, type PdfPreview, type PdfPreviewProps, Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, type ProtocolCapability, type ProtocolDescriptor, type ProtocolLimits, type QueryOperator, type QuerySemantics, type Revision, type RowPage, type RowWire, SHAPE_FONT_OPTIONS, SHAPE_FONT_STACKS, ScrollArea, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Skeleton, type SortInput, type SubscriptionHandle, type SubscriptionObserver, type SubscriptionStatus, type SyncEmbedRef, type SyncEmbedResolver, TEXT_TYPES, type TableDescription, type TableEnvelopeBase, type TableEvent, type TableRef, type TableSnapshot, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UI_FONT_ID, UI_FONT_STACK, type UseDatabaseTable, type UseTableList, badgeVariants, blobSubtypeOf, buttonVariants, cellValueEqual, cn, columnAnnotation, conditionMatches, createMemoryBlobStore, emptyFilter, filterIsComplete, isBridgeError, isBridgeErrorCode, isDatabaseBridge, markToggleAction, newMutationContext, parseCellValue, renderRuns, toBridgeError, useBlock, useDatabaseTable, useDoc, useEditorContext, useMeta, usePresence, useSelection, useSelectionView, useTableList, useText, validateProtocolDescriptor };
442
+ export { type AccessLevel, Alert, AlertDescription, AlertTitle, BASELINE_CAPABILITIES, BRIDGE_ERROR_CODES, Badge, BareSelect, type BatchOp, type BlobMeta, type BlobRef, type BlobStore, type BlobSubtype, type BlockRenderer, type BlockRendererMap, type BlockRendererProps, type BridgeColumn, type BridgeColumnAnnotation, BridgeError, type BridgeErrorCode, type BridgeTableDescription, type BridgeValueInput, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CellMap, type CellValueParse, Checkbox, type Column, DEFAULT_EMBED_SANDBOX, type DatabaseBridge, DatabaseTable, type DatabaseTablePhase, type DatabaseTableProps, type DatabaseTableRow, type DatabaseTableView, DocHeader, type DomainEvent, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Editor, type EditorContextValue, type EditorMode, EditorProvider, type EditorProviderProps, type EditorView, type EmbedSandboxPolicy, FORMAT_PALETTE, FilterBuilder, type FilterConditionInput, type FilterInput, type FormatMark, type Id, Input, type IsoInstant, Label, MONO_FONT_STACK, type MentionEntry, type MentionProvider, type MutationContext, type MutationReceipt, type OperationStatus, PROTOCOL_VERSION, type PageQuery, type PageSnapshot, type PdfPreview, type PdfPreviewProps, Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, type PresenceConfig, type PresenceCrypto, type PresenceIdentity, type PresencePeer, type PresenceSharing, type PresenceSharingMode, type ProtocolCapability, type ProtocolDescriptor, type ProtocolLimits, type QueryOperator, type QuerySemantics, type Revision, type RowPage, type RowWire, SHAPE_FONT_OPTIONS, SHAPE_FONT_STACKS, ScrollArea, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Skeleton, type SortInput, type SubscriptionHandle, type SubscriptionObserver, type SubscriptionStatus, type SyncEmbedRef, type SyncEmbedResolver, TEXT_TYPES, type TableDescription, type TableEnvelopeBase, type TableEvent, type TableRef, type TableSnapshot, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UI_FONT_ID, UI_FONT_STACK, type UseDatabaseTable, type UseTableList, badgeVariants, blobSubtypeOf, buttonVariants, cellValueEqual, cn, columnAnnotation, conditionMatches, createMemoryBlobStore, emptyFilter, filterIsComplete, isBridgeError, isBridgeErrorCode, isDatabaseBridge, markToggleAction, newMutationContext, parseCellValue, renderRuns, toBridgeError, useBlock, useDatabaseTable, useDoc, useEditorContext, useMeta, usePresence, usePresenceSharing, useSelection, useSelectionView, useTableList, useText, validateProtocolDescriptor };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as PopoverHeader, A as SelectTrigger, At as isBridgeErrorCode, B as DropdownMenuRadioGroup, Bt as useMeta, C as SelectContent, Ct as BASELINE_CAPABILITIES, D as SelectScrollDownButton, Dt as BRIDGE_ERROR_CODES, E as SelectLabel, Et as validateProtocolDescriptor, F as DropdownMenuContent, Ft as columnAnnotation, G as DropdownMenuSubContent, H as DropdownMenuSeparator, Ht as useSelection, I as DropdownMenuGroup, It as ScrollArea, J as Separator, Jt as createMemoryBlobStore, K as DropdownMenuSubTrigger, Kt as renderRuns, L as DropdownMenuItem, Lt as Input, M as Label, Mt as conditionMatches, N as DropdownMenu, Nt as parseCellValue, O as SelectScrollUpButton, Ot as BridgeError, P as DropdownMenuCheckboxItem, Pt as TEXT_TYPES, Q as PopoverDescription, R as DropdownMenuLabel, Rt as useBlock, S as Select, St as AlertTitle, T as SelectItem, Tt as isDatabaseBridge, U as DropdownMenuShortcut, Ut as useSelectionView, V as DropdownMenuRadioItem, Vt as usePresence, W as DropdownMenuSub, Wt as useText, X as PopoverAnchor, Y as Popover, Z as PopoverContent, _ as FilterBuilder, _t as BareSelect, a as SHAPE_FONT_STACKS, at as CardAction, b as Skeleton, bt as Alert, ct as CardFooter, d as useEditorContext, dt as Pen, et as PopoverTitle, f as DEFAULT_EMBED_SANDBOX, ft as Eye, g as newMutationContext, gt as buttonVariants, h as cellValueEqual, ht as Button, i as SHAPE_FONT_OPTIONS, it as Card, j as SelectValue, jt as toBridgeError, k as SelectSeparator, kt as isBridgeError, l as markToggleAction, lt as CardHeader, m as useDatabaseTable, mt as BookOpen, nt as DocblocksI18nProvider, o as FORMAT_PALETTE, ot as CardContent, p as GridBody, q as DropdownMenuTrigger, qt as blobSubtypeOf, rt as Checkbox, st as CardDescription, t as Editor, tt as PopoverTrigger, u as EditorProvider, ut as CardTitle, v as emptyFilter, vt as Badge, w as SelectGroup, wt as PROTOCOL_VERSION, x as SegmentSelector, xt as AlertDescription, y as filterIsComplete, yt as badgeVariants, z as DropdownMenuPortal, zt as useDoc } from "./Editor-YAlRhGNW.mjs";
1
+ import { $ as PopoverHeader, A as SelectTrigger, At as isBridgeErrorCode, B as DropdownMenuRadioGroup, Bt as useMeta, C as SelectContent, Ct as BASELINE_CAPABILITIES, D as SelectScrollDownButton, Dt as BRIDGE_ERROR_CODES, E as SelectLabel, Et as validateProtocolDescriptor, F as DropdownMenuContent, Ft as columnAnnotation, G as DropdownMenuSubContent, Gt as useText, H as DropdownMenuSeparator, Ht as usePresenceSharing, I as DropdownMenuGroup, It as ScrollArea, J as Separator, Jt as blobSubtypeOf, K as DropdownMenuSubTrigger, L as DropdownMenuItem, Lt as Input, M as Label, Mt as conditionMatches, N as DropdownMenu, Nt as parseCellValue, O as SelectScrollUpButton, Ot as BridgeError, P as DropdownMenuCheckboxItem, Pt as TEXT_TYPES, Q as PopoverDescription, R as DropdownMenuLabel, Rt as useBlock, S as Select, St as AlertTitle, T as SelectItem, Tt as isDatabaseBridge, U as DropdownMenuShortcut, Ut as useSelection, V as DropdownMenuRadioItem, Vt as usePresence, W as DropdownMenuSub, Wt as useSelectionView, X as PopoverAnchor, Y as Popover, Yt as createMemoryBlobStore, Z as PopoverContent, _ as FilterBuilder, _t as BareSelect, a as SHAPE_FONT_STACKS, at as CardAction, b as Skeleton, bt as Alert, ct as CardFooter, d as useEditorContext, dt as Pen, et as PopoverTitle, f as DEFAULT_EMBED_SANDBOX, ft as Eye, g as newMutationContext, gt as buttonVariants, h as cellValueEqual, ht as Button, i as SHAPE_FONT_OPTIONS, it as Card, j as SelectValue, jt as toBridgeError, k as SelectSeparator, kt as isBridgeError, l as markToggleAction, lt as CardHeader, m as useDatabaseTable, mt as BookOpen, nt as DocblocksI18nProvider, o as FORMAT_PALETTE, ot as CardContent, p as GridBody, q as DropdownMenuTrigger, qt as renderRuns, rt as Checkbox, st as CardDescription, t as Editor, tt as PopoverTrigger, u as EditorProvider, ut as CardTitle, v as emptyFilter, vt as Badge, w as SelectGroup, wt as PROTOCOL_VERSION, x as SegmentSelector, xt as AlertDescription, y as filterIsComplete, yt as badgeVariants, z as DropdownMenuPortal, zt as useDoc } from "./Editor-BvKqUGXH.mjs";
2
2
  import { c as cn, o as preventDefault } from "./constants-D4FTL0ph.mjs";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -748,4 +748,4 @@ const UI_FONT_STACK = "'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif
748
748
  */
749
749
  const MONO_FONT_STACK = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
750
750
  //#endregion
751
- export { Alert, AlertDescription, AlertTitle, BASELINE_CAPABILITIES, BRIDGE_ERROR_CODES, Badge, BareSelect, BridgeError, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DEFAULT_EMBED_SANDBOX, DatabaseTable, DocHeader, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Editor, EditorProvider, FORMAT_PALETTE, FilterBuilder, Input, Label, MONO_FONT_STACK, PROTOCOL_VERSION, Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, SHAPE_FONT_OPTIONS, SHAPE_FONT_STACKS, ScrollArea, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Skeleton, TEXT_TYPES, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UI_FONT_ID, UI_FONT_STACK, badgeVariants, blobSubtypeOf, buttonVariants, cellValueEqual, cn, columnAnnotation, conditionMatches, createMemoryBlobStore, emptyFilter, filterIsComplete, isBridgeError, isBridgeErrorCode, isDatabaseBridge, markToggleAction, newMutationContext, parseCellValue, renderRuns, toBridgeError, useBlock, useDatabaseTable, useDoc, useEditorContext, useMeta, usePresence, useSelection, useSelectionView, useTableList, useText, validateProtocolDescriptor };
751
+ export { Alert, AlertDescription, AlertTitle, BASELINE_CAPABILITIES, BRIDGE_ERROR_CODES, Badge, BareSelect, BridgeError, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DEFAULT_EMBED_SANDBOX, DatabaseTable, DocHeader, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Editor, EditorProvider, FORMAT_PALETTE, FilterBuilder, Input, Label, MONO_FONT_STACK, PROTOCOL_VERSION, Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, SHAPE_FONT_OPTIONS, SHAPE_FONT_STACKS, ScrollArea, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Skeleton, TEXT_TYPES, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UI_FONT_ID, UI_FONT_STACK, badgeVariants, blobSubtypeOf, buttonVariants, cellValueEqual, cn, columnAnnotation, conditionMatches, createMemoryBlobStore, emptyFilter, filterIsComplete, isBridgeError, isBridgeErrorCode, isDatabaseBridge, markToggleAction, newMutationContext, parseCellValue, renderRuns, toBridgeError, useBlock, useDatabaseTable, useDoc, useEditorContext, useMeta, usePresence, usePresenceSharing, useSelection, useSelectionView, useTableList, useText, validateProtocolDescriptor };
@@ -1,4 +1,4 @@
1
- import { Gt as INLINE_CODE_CLASS, St as AlertTitle, b as Skeleton, bt as Alert, c as textOf, d as useEditorContext, ft as Eye, n as childrenSlot, pt as CodeXml, r as CodeHeader, s as TextBlock, x as SegmentSelector, xt as AlertDescription } from "../Editor-YAlRhGNW.mjs";
1
+ import { Kt as INLINE_CODE_CLASS, St as AlertTitle, b as Skeleton, bt as Alert, c as textOf, d as useEditorContext, ft as Eye, n as childrenSlot, pt as CodeXml, r as CodeHeader, s as TextBlock, x as SegmentSelector, xt as AlertDescription } from "../Editor-BvKqUGXH.mjs";
2
2
  import { n as TriangleAlert } from "../constants-D4FTL0ph.mjs";
3
3
  import { t as LoaderCircle } from "../LoaderCircle-AGVFE4IP.mjs";
4
4
  import { useEffect, useRef, useState } from "react";
@@ -1,4 +1,4 @@
1
- import { f as PdfPreviewProps } from "../index-DOKS86aH.mjs";
1
+ import { f as PdfPreviewProps } from "../index-DG0lJy_t.mjs";
2
2
  //#region src/pdf/PdfFirstPage.d.ts
3
3
  declare function PdfFirstPage({ id, url, name }: PdfPreviewProps): import("react").JSX.Element;
4
4
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docblocks-react-ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "React rendering + editing layer for docblocks: hooks, block renderers, caret/selection, keyboard, clipboard.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "peerDependencies": {
37
- "docblocks-core": "^0.1.2",
37
+ "docblocks-core": "^0.1.3",
38
38
  "framer-motion": "^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
39
39
  "mermaid": "^12.0.0",
40
40
  "pdfjs-dist": "^6.0.0",