carboncanvas 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Roitch
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,160 @@
1
+ # carboncanvas
2
+
3
+ > **Carbon Canvas** — the free, open (**MIT**) client for the Carbon Canvas
4
+ > review platform. `0.1.0`. The canvas + Inspector need no account and no
5
+ > backend; team comments/realtime are an optional hosted add-on.
6
+
7
+ A **Reviewable Flow Canvas** for React: a pan/zoom spatial canvas you author in
8
+ JSX (`<DesignCanvas>` / `<DCSection>` / `<DCArtboard>` / `<DCPage>`), with a
9
+ click-to-inspect **Inspector** that reads real React fibers, and a
10
+ design-system **adapter seam** so token reverse-lookup + library detection work
11
+ with any (or no) design system.
12
+
13
+ The canvas + Inspector are fully client-side — no account, no backend. Add
14
+ in-place **comments** (with live cursors/presence) by pointing the `comments`
15
+ prop at a hosted Carbon Canvas backend (see [Comments](#comments) below); the
16
+ comments/realtime runtime — including `socket.io-client` — is code-split into a
17
+ lazy chunk, so it costs nothing until you enable it and pulls in no extra dep.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ npm i carboncanvas
23
+ # react + react-dom are PEER deps — they use your app's existing copy
24
+ ```
25
+
26
+ ## Use
27
+
28
+ ```tsx
29
+ import { DesignCanvas, DCSection, DCArtboard } from 'carboncanvas';
30
+
31
+ export function Review() {
32
+ return (
33
+ <DesignCanvas inspector>
34
+ <DCSection title="Checkout flow">
35
+ <DCArtboard label="Cart" width={390} height={844}>
36
+ <YourCartScreen />
37
+ </DCArtboard>
38
+ <DCArtboard label="Payment" width={390} height={844}>
39
+ <YourPaymentScreen state="connecting" />
40
+ </DCArtboard>
41
+ </DCSection>
42
+ </DesignCanvas>
43
+ );
44
+ }
45
+ ```
46
+
47
+ > **One `<DesignCanvas>` per page.** The canvas chrome locates the active
48
+ > viewport through a global `[data-dc-viewport]` lookup, so two sibling
49
+ > DesignCanvas instances on one page cross-wire. Render at most one per route.
50
+ > (Nested canvases inside an artboard are fine — they're scoped by nearest
51
+ > viewport.)
52
+
53
+ ## Consumer build requirements
54
+
55
+ Two build settings make the Inspector and durable comment pins work well. Both
56
+ live in **your** app's build, not in this package:
57
+
58
+ 1. **`esbuild: { keepNames: true }`** — **required for readable inspection.** The
59
+ Inspector reads React component display names off the fiber tree; without
60
+ `keepNames`, minification renames them and the Hierarchy tree shows garbage.
61
+
62
+ Vite example:
63
+
64
+ ```ts
65
+ // vite.config.ts
66
+ export default defineConfig({
67
+ esbuild: { keepNames: true },
68
+ });
69
+ ```
70
+
71
+ 2. **The `data-anchor` Babel plugin (optional, recommended for comments)** —
72
+ stamps every host element with a deterministic `data-anchor="file:line:col"`
73
+ so comment pins survive edits/HMR. Shipped as a subpath export:
74
+
75
+ ```ts
76
+ // vite.config.ts
77
+ import react from '@vitejs/plugin-react';
78
+ import dataAnchor from 'carboncanvas/babel-plugin-data-anchor';
79
+
80
+ export default defineConfig({
81
+ plugins: [react({ babel: { plugins: [dataAnchor] } })],
82
+ esbuild: { keepNames: true },
83
+ });
84
+ ```
85
+
86
+ On **React 19** this plugin is what makes the Inspector's Source row work at
87
+ all — React 19 removed `fiber._debugSource`, so the source path comes entirely
88
+ from `data-anchor`. Wire it if you want the Source row on React 19.
89
+
90
+ ### Shortcut: `carbonPreset` (`carboncanvas/vite`)
91
+
92
+ So you don't hand-copy `keepNames`, the package ships a Vite config fragment.
93
+ Spread it into your config, and (optionally) pass the re-exported plugin to your
94
+ `react()`:
95
+
96
+ ```ts
97
+ // vite.config.ts
98
+ import { defineConfig } from 'vite';
99
+ import react from '@vitejs/plugin-react';
100
+ import { carbonPreset, dataAnchorBabelPlugin } from 'carboncanvas/vite';
101
+
102
+ export default defineConfig({
103
+ ...carbonPreset(), // sets esbuild.keepNames
104
+ plugins: [react({ babel: { plugins: [dataAnchorBabelPlugin] } })],
105
+ });
106
+ ```
107
+
108
+ The fragment only sets `keepNames` (a config fragment can't reach into your
109
+ `react()` plugin's babel option), so the `data-anchor` plugin is a separate
110
+ re-export you wire yourself. `carboncanvas publish` verifies the built bundle
111
+ actually kept names and warns if it didn't.
112
+
113
+ ## Design-system adapter
114
+
115
+ The Inspector resolves design tokens + detects library components through one
116
+ swappable adapter. The default (`genericAdapter`) needs no design system and
117
+ reports raw computed values. Install a custom adapter once at app start:
118
+
119
+ ```tsx
120
+ import { setDesignSystemAdapter } from 'carboncanvas';
121
+ import { myAdapter } from './myAdapter'; // implements DesignSystemAdapter
122
+
123
+ setDesignSystemAdapter(myAdapter);
124
+ ```
125
+
126
+ ## Comments
127
+
128
+ In-place, pin-anchored comments (with live cursors + presence) are an optional
129
+ hosted add-on. Point the `comments` prop at your Carbon Canvas backend:
130
+
131
+ ```tsx
132
+ <DesignCanvas
133
+ inspector
134
+ comments={{
135
+ canvasId: 'cnv_…', // the permanent comment partition (write-once)
136
+ serviceUrl: 'https://your-backend', // the hosted comments/realtime backend
137
+ enableUrl: 'https://your-dashboard', // where the "Enable / Renew" CTA points
138
+ }}
139
+ >
140
+ ```
141
+
142
+ No client-side key: entitlement resolves **server-side** from the registered
143
+ canvas's owner account. For local development with no backend, use the in-memory
144
+ mock instead: `comments={{ canvasId: 'demo', devMode: true }}`. The comments tab
145
+ is always present — until the canvas is entitled it renders an upsell rather than
146
+ a thread list. `socket.io-client` ships inside the lazy comments chunk, so it is
147
+ **not** an additional install.
148
+
149
+ ## Exports
150
+
151
+ - **Canvas:** `DesignCanvas`, `DCSection`, `DCArtboard`, `DCPage`, `DCPostIt`,
152
+ `useArtboardActive`
153
+ - **Adapter seam:** `setDesignSystemAdapter`, `getDesignSystemAdapter`,
154
+ `genericAdapter`, and the `DesignSystemAdapter` type (+ `AdapterFiber`,
155
+ `ComponentKind`, `CssVarTokenSpec`, `TypographyTokenSpec`,
156
+ `UtilityClassPrefixes`, `AdapterTokens`)
157
+
158
+ ## Peer dependencies
159
+
160
+ `react >=18` and `react-dom >=18` (externalized — your app's copy is reused).
@@ -0,0 +1,134 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ function getAncestorScale(el) {
4
+ let scale = 1;
5
+ let cur = parentAcrossShadow(el);
6
+ while (cur) {
7
+ const t = window.getComputedStyle(cur).transform;
8
+ if (t && t !== "none") {
9
+ const m = new DOMMatrix(t);
10
+ if (m.a !== 0) scale *= m.a;
11
+ }
12
+ cur = parentAcrossShadow(cur);
13
+ }
14
+ return scale || 1;
15
+ }
16
+ __name(getAncestorScale, "getAncestorScale");
17
+ function parentAcrossShadow(el) {
18
+ if (el.parentElement) return el.parentElement;
19
+ const root = el.getRootNode();
20
+ return root instanceof ShadowRoot ? root.host : null;
21
+ }
22
+ __name(parentAcrossShadow, "parentAcrossShadow");
23
+ const FRAME_ATTR = "data-quilt-canvas-frame";
24
+ const FRAME_OVERLAY_ATTR = "data-quilt-frame-overlay";
25
+ function contentDocOf(frame) {
26
+ try {
27
+ const doc = frame.contentDocument;
28
+ return doc && doc.body ? doc : null;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ __name(contentDocOf, "contentDocOf");
34
+ function frameForTarget(target) {
35
+ var _a;
36
+ if (target instanceof HTMLIFrameElement && target.hasAttribute(FRAME_ATTR)) return target;
37
+ if (!target.hasAttribute(FRAME_OVERLAY_ATTR)) return null;
38
+ const wrap = target.parentElement;
39
+ return (_a = wrap == null ? void 0 : wrap.querySelector(`iframe[${FRAME_ATTR}]`)) != null ? _a : null;
40
+ }
41
+ __name(frameForTarget, "frameForTarget");
42
+ function isFrameEl(el) {
43
+ return enclosingFrameOf(el) !== null;
44
+ }
45
+ __name(isFrameEl, "isFrameEl");
46
+ function enclosingFrameOf(el) {
47
+ try {
48
+ const win = el.ownerDocument.defaultView;
49
+ if (!win || win === window) return null;
50
+ const frame = win.frameElement;
51
+ return frame instanceof HTMLIFrameElement && frame.hasAttribute(FRAME_ATTR) ? frame : null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ __name(enclosingFrameOf, "enclosingFrameOf");
57
+ function frameBodyOf(el) {
58
+ return enclosingFrameOf(el) ? el.ownerDocument.body : null;
59
+ }
60
+ __name(frameBodyOf, "frameBodyOf");
61
+ function frameScale(frame) {
62
+ const w = frame.clientWidth;
63
+ if (!w) return 1;
64
+ return frame.getBoundingClientRect().width / w || 1;
65
+ }
66
+ __name(frameScale, "frameScale");
67
+ function descendIntoFrame(target, clientX, clientY) {
68
+ const frame = frameForTarget(target);
69
+ if (!frame) return null;
70
+ const doc = contentDocOf(frame);
71
+ if (!doc) return null;
72
+ const r = frame.getBoundingClientRect();
73
+ const s = frameScale(frame);
74
+ const inner = doc.elementFromPoint((clientX - r.left) / s, (clientY - r.top) / s);
75
+ return { el: inner != null ? inner : doc.body, frameBody: doc.body, frame };
76
+ }
77
+ __name(descendIntoFrame, "descendIntoFrame");
78
+ function rectInHost(el) {
79
+ let rect = el.getBoundingClientRect();
80
+ let win = null;
81
+ try {
82
+ win = el.ownerDocument.defaultView;
83
+ } catch {
84
+ return rect;
85
+ }
86
+ let guard = 0;
87
+ while (win && win !== window && guard++ < 8) {
88
+ const frame = win.frameElement;
89
+ if (!(frame instanceof HTMLIFrameElement)) break;
90
+ const fr = frame.getBoundingClientRect();
91
+ const s = frameScale(frame);
92
+ rect = new DOMRect(
93
+ fr.left + rect.left * s,
94
+ fr.top + rect.top * s,
95
+ rect.width * s,
96
+ rect.height * s
97
+ );
98
+ win = frame.ownerDocument.defaultView;
99
+ }
100
+ return rect;
101
+ }
102
+ __name(rectInHost, "rectInHost");
103
+ function closestAcrossFrames(el, selector) {
104
+ let cur = el;
105
+ let guard = 0;
106
+ while (cur && guard++ < 16) {
107
+ const hit = cur.closest(selector);
108
+ if (hit) return hit;
109
+ cur = enclosingFrameOf(cur);
110
+ }
111
+ return null;
112
+ }
113
+ __name(closestAcrossFrames, "closestAcrossFrames");
114
+ const BRAND_NAME = "Carbon Canvas";
115
+ const BRAND = {
116
+ name: BRAND_NAME,
117
+ // Maturity tag shown next to the name (kept from the origin "alpha" pill).
118
+ tag: "alpha",
119
+ tagTitle: "Alpha — APIs and tree behaviour may change without notice",
120
+ credit: "created by Alex Roitch"
121
+ };
122
+ export {
123
+ BRAND as B,
124
+ FRAME_ATTR as F,
125
+ contentDocOf as a,
126
+ BRAND_NAME as b,
127
+ closestAcrossFrames as c,
128
+ descendIntoFrame as d,
129
+ frameBodyOf as f,
130
+ getAncestorScale as g,
131
+ isFrameEl as i,
132
+ rectInHost as r
133
+ };
134
+ //# sourceMappingURL=brand-B9y8WrVi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand-B9y8WrVi.js","sources":["../src/DesignCanvas/getAncestorScale.ts","../src/DesignCanvas/frameInspect.ts","../src/brand.ts"],"sourcesContent":["// Walks up the parent chain from `el` and multiplies any CSS `transform`\n// scale factors. Used by Inspector / XRay / ExplodedView to compensate for\n// the DesignCanvas viewport's `transform: scale(N)` — without this, an\n// element's getBoundingClientRect comes back at *visual* size (post-canvas-\n// zoom) while its computed paddings / font-sizes stay at natural CSS px,\n// and clones look like they were \"drag-resized\" rather than scaled.\n//\n// Assumes uniform scaling (a === d in the matrix), which is true for the\n// canvas. Returns 1 if no transforming ancestor exists.\n//\n// Crosses shadow boundaries (parentElement → host hop): frozen snapshot tiles\n// (src/viewer/FrozenTile) render inside a shadow root, but the canvas zoom\n// transform lives on a light-DOM ancestor of the host — without the hop, every\n// measurement inside a snapshot would ignore the canvas zoom. Light-DOM\n// callers are unaffected (a Document root node ends the walk exactly like the\n// old `parentElement === null` did).\nexport function getAncestorScale(el: Element): number {\n let scale = 1;\n let cur: Element | null = parentAcrossShadow(el);\n while (cur) {\n const t = window.getComputedStyle(cur).transform;\n if (t && t !== 'none') {\n const m = new DOMMatrix(t);\n if (m.a !== 0) scale *= m.a;\n }\n cur = parentAcrossShadow(cur);\n }\n return scale || 1;\n}\n\nfunction parentAcrossShadow(el: Element): Element | null {\n if (el.parentElement) return el.parentElement;\n const root = el.getRootNode();\n return root instanceof ShadowRoot ? root.host : null;\n}\n","// frameInspect — same-origin iframe plumbing for the IN-APP canvas mode\n// (the 2026-06-12 pivot: the user's live routes ARE the artboards, rendered\n// as same-origin iframes of their own running app).\n//\n// This is the iframe sibling of domInspect.ts's shadow-root plumbing, and the\n// two solve the same problem with the same shape: document-level listeners in\n// the HOST page never see events from inside an iframe, so comment/dev modes\n// cover each live frame with a transparent overlay (stamped\n// `data-quilt-frame-overlay`); a click/hover lands on the overlay in HOST\n// coordinates and we \"descend\" — point-map into the frame's content document\n// and resolve the real element under the cursor. Because the frames are the\n// customer's OWN app on their OWN origin, contentDocument is reachable and the\n// elements carry real React fibers — that's what makes the in-app Inspector\n// \"native fiber\" instead of DOM-mode.\n//\n// Coordinate model: inside a frame, rects are natural CSS px (the canvas zoom\n// transform scales the iframe ELEMENT in the host, never the layout inside).\n// `rectInHost` maps an inner rect out to host-viewport px through the frame\n// chain; `descendIntoFrame` maps a host point in. Everything that DRAWS in the\n// host (hover/selection outlines, pins) maps out; everything that MEASURES\n// (computed styles, box model) reads natural px straight off the inner element\n// — which is exactly what the Inspector wants to display.\n//\n// Library-core rules: framework-free, no design-system imports.\n\n/** Stamped on every live route iframe the in-app canvas renders. */\nexport const FRAME_ATTR = 'data-quilt-canvas-frame';\n\n/** Stamped on the transparent event-catching overlay above a live frame\n * (present in comment/dev modes; absent while the tile is being interacted\n * with in cursor mode). */\nexport const FRAME_OVERLAY_ATTR = 'data-quilt-frame-overlay';\n\n/** The frame's content document, or null when unreachable (cross-origin,\n * detached, still loading about:blank with no body yet). */\nexport function contentDocOf(frame: HTMLIFrameElement): Document | null {\n try {\n const doc = frame.contentDocument;\n return doc && doc.body ? doc : null;\n } catch {\n return null; // cross-origin — not a quilt canvas frame's normal state\n }\n}\n\n/** The quilt canvas frame an overlay (or the frame itself) fronts, or null. */\nexport function frameForTarget(target: Element): HTMLIFrameElement | null {\n if (target instanceof HTMLIFrameElement && target.hasAttribute(FRAME_ATTR)) return target;\n if (!target.hasAttribute(FRAME_OVERLAY_ATTR)) return null;\n // The overlay is absolutely positioned over the frame inside the same tile\n // wrapper — find the frame among the wrapper's descendants.\n const wrap = target.parentElement;\n return wrap?.querySelector<HTMLIFrameElement>(`iframe[${FRAME_ATTR}]`) ?? null;\n}\n\n/** Is this element inside a quilt canvas frame's document (any nesting level)? */\nexport function isFrameEl(el: Element): boolean {\n return enclosingFrameOf(el) !== null;\n}\n\n/** The quilt canvas frame ELEMENT (in the parent document) whose document\n * contains `el`, or null for host-document elements. */\nexport function enclosingFrameOf(el: Element): HTMLIFrameElement | null {\n try {\n const win = el.ownerDocument.defaultView;\n if (!win || win === window) return null;\n const frame = win.frameElement;\n return frame instanceof HTMLIFrameElement && frame.hasAttribute(FRAME_ATTR) ? frame : null;\n } catch {\n return null;\n }\n}\n\n/** The frame document's body for an element inside a quilt canvas frame —\n * the selector scope pins are built against (the iframe twin of\n * domInspect's snapshotBodyOf). Null for host elements. */\nexport function frameBodyOf(el: Element): Element | null {\n return enclosingFrameOf(el) ? el.ownerDocument.body : null;\n}\n\n/** Rendered scale of the frame's content on the host screen — the canvas zoom\n * as it applies to this frame element. 1 when unscaled/unmeasurable. */\nexport function frameScale(frame: HTMLIFrameElement): number {\n const w = frame.clientWidth;\n if (!w) return 1;\n return frame.getBoundingClientRect().width / w || 1;\n}\n\n/**\n * Descend through a frame overlay (or the frame element itself) into the live\n * document inside: map the HOST-viewport point to frame-content coordinates\n * and resolve the element under it. Returns the inner element plus the frame\n * body (the pin selector scope) and the frame, or null when `target` doesn't\n * front a readable quilt canvas frame — light-DOM paths stay untouched, same\n * contract as the snapshot descent.\n */\nexport function descendIntoFrame(\n target: Element,\n clientX: number,\n clientY: number,\n): { el: Element; frameBody: Element; frame: HTMLIFrameElement } | null {\n const frame = frameForTarget(target);\n if (!frame) return null;\n const doc = contentDocOf(frame);\n if (!doc) return null;\n const r = frame.getBoundingClientRect();\n const s = frameScale(frame);\n const inner = doc.elementFromPoint((clientX - r.left) / s, (clientY - r.top) / s);\n return { el: inner ?? doc.body, frameBody: doc.body, frame };\n}\n\n/**\n * An element's bounding rect in HOST-viewport coordinates. Identity for host\n * elements; for elements inside (possibly nested) quilt canvas frames, maps\n * each frame's content coords out through the frame element's host rect and\n * rendered scale. Inner-document scroll is already baked into the inner\n * getBoundingClientRect, so no scroll math is needed here.\n */\nexport function rectInHost(el: Element): DOMRect {\n let rect = el.getBoundingClientRect();\n let win: Window | null = null;\n try {\n win = el.ownerDocument.defaultView;\n } catch {\n return rect;\n }\n let guard = 0;\n while (win && win !== window && guard++ < 8) {\n const frame = win.frameElement;\n if (!(frame instanceof HTMLIFrameElement)) break;\n const fr = frame.getBoundingClientRect();\n const s = frameScale(frame);\n rect = new DOMRect(\n fr.left + rect.left * s,\n fr.top + rect.top * s,\n rect.width * s,\n rect.height * s,\n );\n win = frame.ownerDocument.defaultView;\n }\n return rect;\n}\n\n/** `closest()` that keeps walking up through quilt canvas frame boundaries\n * (frame-element hops) — the iframe twin of domInspect's closestAcrossShadow.\n * Lets frame-content elements find their `[data-dc-slot]` artboard. */\nexport function closestAcrossFrames(el: Element, selector: string): Element | null {\n let cur: Element | null = el;\n let guard = 0;\n while (cur && guard++ < 16) {\n const hit = cur.closest(selector);\n if (hit) return hit;\n cur = enclosingFrameOf(cur);\n }\n return null;\n}\n\n/** Resolve `selector` inside every readable quilt canvas frame under `scope`\n * (light-DOM querySelector can't cross the document boundary). First match\n * wins — mirrors domInspect's querySnapshots. */\nexport function queryFrames(scope: ParentNode, selector: string): Element | null {\n for (const frame of Array.from(\n scope.querySelectorAll<HTMLIFrameElement>(`iframe[${FRAME_ATTR}]`),\n )) {\n const doc = contentDocOf(frame);\n const el = doc?.body.querySelector(selector);\n if (el) return el;\n }\n return null;\n}\n","// Single source of truth for the product name. The repo's working codename is\n// \"quilt\"; the public brand is \"Carbon Canvas\" (carboncanvas.design). The rename\n// is a one-line change here, not a find-and-replace across the inspector chrome —\n// the internal codename \"quilt\" stays in dir names and other identifiers.\nexport const BRAND_NAME = 'Carbon Canvas';\n\n// Header chrome shown in the Inspector panel. Tagline/credit live alongside the\n// name so the whole brand surface is editable in one place.\nexport const BRAND = {\n name: BRAND_NAME,\n // Maturity tag shown next to the name (kept from the origin \"alpha\" pill).\n tag: 'alpha',\n tagTitle: 'Alpha — APIs and tree behaviour may change without notice',\n credit: 'created by Alex Roitch',\n} as const;\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,271 @@
1
+ import { CSSProperties } from 'react';
2
+ import { ReactElement } from 'react';
3
+ import { ReactNode } from 'react';
4
+
5
+ export declare type AdapterFiber = {
6
+ return: AdapterFiber | null;
7
+ child: AdapterFiber | null;
8
+ sibling: AdapterFiber | null;
9
+ type: any;
10
+ memoizedProps: Record<string, any> | null;
11
+ stateNode: any;
12
+ [k: string]: any;
13
+ };
14
+
15
+ export declare type AdapterTokens = {
16
+ color: CssVarTokenSpec;
17
+ shadow: CssVarTokenSpec;
18
+ spacing: CssVarTokenSpec;
19
+ radius: CssVarTokenSpec;
20
+ typography: TypographyTokenSpec;
21
+ utilityClassPrefixes: UtilityClassPrefixes;
22
+ };
23
+
24
+ /**
25
+ * Pluggable authentication strategy for the network client. The default
26
+ * (`accountlessAuthProvider`) is account-less: a sign-in is just a name (and an
27
+ * optional email) — no password, no external identity provider, no verification.
28
+ * Swap in your own provider to integrate OAuth / SSO / a real account system.
29
+ */
30
+ export declare interface AuthProvider {
31
+ /** Resolve the current user for a stored bearer token (null = signed out). */
32
+ me(token: string | null): Promise<Me | null>;
33
+ /** Establish a session from the given identity; returns the bearer token + user. */
34
+ signIn(input: {
35
+ name: string;
36
+ email?: string;
37
+ }): Promise<{
38
+ token: string;
39
+ me: Me;
40
+ }>;
41
+ /** Best-effort server-side session teardown for the given token. */
42
+ signOut?(token: string): Promise<void>;
43
+ }
44
+
45
+ /**
46
+ * The canvas interaction mode. One bar, mutually-exclusive modes:
47
+ * • cursor — normal pointer; play with the live prototypes, pan/zoom freely
48
+ * • comment — drop pins + open the comments sidebar
49
+ * • dev — click-to-inspect + the inspector panel
50
+ *
51
+ * Owned by DesignCanvas and threaded into the Inspector / CommentLayer so each
52
+ * derives its `enabled` state from the shared mode rather than its own toggle.
53
+ */
54
+ export declare type CanvasMode = 'cursor' | 'comment' | 'dev';
55
+
56
+ /** Public `comments` prop on DesignCanvas. */
57
+ export declare interface CommentsConfig {
58
+ canvasId: string;
59
+ /** The hosted comments service base URL (required for network mode). */
60
+ serviceUrl?: string;
61
+ /** Use the in-memory mock store instead of the network. */
62
+ devMode?: boolean;
63
+ /** Authentication strategy. Defaults to `accountlessAuthProvider` (name-only sign-in). */
64
+ auth?: AuthProvider;
65
+ /**
66
+ * Multiplayer presence: live cursors + the who's-here facepile (network mode
67
+ * only; needs the realtime socket). Default true — set false to run
68
+ * comments-only with no socket connection.
69
+ */
70
+ presence?: boolean;
71
+ /** Directory backend for @mention typeahead. Defaults to the service's `/api/directory`. */
72
+ directory?: DirectoryProvider;
73
+ /**
74
+ * Hostnames matching any of these patterns are treated as NON-stable (e.g.
75
+ * rotating preview/dev hosts) and fall back to the explicit per-project
76
+ * canvasId instead of auto-scoping by hostname. Default `[]` → every host is
77
+ * its own canvas.
78
+ */
79
+ unstableHostPatterns?: RegExp[];
80
+ /**
81
+ * Auto-provision hint sent on the first write to an unregistered canvas, so the
82
+ * service can resolve the canvas owner deterministically rather than from
83
+ * whoever comments first. Ignored once the canvas exists.
84
+ */
85
+ provisionHint?: string;
86
+ /**
87
+ * Where the comments upsell's "Enable / Renew" CTA points — your dashboard or
88
+ * checkout URL (Workstream B). Shown when the canvas owner isn't entitled
89
+ * (the always-visible comments tab renders the upsell instead of the live
90
+ * runtime). Optional; a missing/placeholder value renders a non-navigating CTA.
91
+ */
92
+ enableUrl?: string;
93
+ }
94
+
95
+ export declare type ComponentKind = 'library' | 'user';
96
+
97
+ export declare type CssVarTokenSpec = {
98
+ cssVarPrefix: string;
99
+ display?: (suffix: string) => string;
100
+ preferOnCollision?: (existing: string, candidate: string, suffix: string) => boolean;
101
+ };
102
+
103
+ export declare function DCArtboard(_props: DCArtboardProps): ReactElement | null;
104
+
105
+ export declare interface DCArtboardProps {
106
+ id?: string;
107
+ label: string;
108
+ width?: number;
109
+ height?: number;
110
+ style?: CSSProperties;
111
+ /**
112
+ * Make the artboard card a containing block for `position:fixed` descendants
113
+ * (adds `contain:layout`), so a host app's fixed portals — modals, drawer
114
+ * sheets, menus — clip to the tile instead of escaping to the transformed
115
+ * canvas. Used by composed live artboards (wave 10). Off by default.
116
+ */
117
+ contain?: boolean;
118
+ children?: ReactNode;
119
+ }
120
+
121
+ export declare function DCPage(_props: DCPageProps): ReactElement | null;
122
+
123
+ export declare interface DCPageProps {
124
+ /** Stable page id; also the `?page=` deep-link value. Defaults to `title`. */
125
+ id?: string;
126
+ title: string;
127
+ children?: ReactNode;
128
+ }
129
+
130
+ export declare function DCPostIt({ children, top, left, right, bottom, rotate, width }: DCPostItProps): ReactElement;
131
+
132
+ export declare interface DCPostItProps {
133
+ children?: ReactNode;
134
+ top?: number | string;
135
+ left?: number | string;
136
+ right?: number | string;
137
+ bottom?: number | string;
138
+ rotate?: number;
139
+ width?: number;
140
+ }
141
+
142
+ export declare function DCSection({ id, title, subtitle, children, gap }: DCSectionProps): ReactElement;
143
+
144
+ export declare interface DCSectionProps {
145
+ id?: string;
146
+ title: string;
147
+ subtitle?: string;
148
+ children?: ReactNode;
149
+ gap?: number;
150
+ }
151
+
152
+ export declare function DesignCanvas({ children, minScale, maxScale, minActiveScale, style, inspector, comments, adapter, }: DesignCanvasProps): ReactElement;
153
+
154
+ /**
155
+ * Props for {@link DesignCanvas}.
156
+ *
157
+ * @remarks
158
+ * **One `<DesignCanvas>` per page.** The canvas chrome (Inspector, ModeBar zoom,
159
+ * comment pin/cursor panning) locates the active viewport via a global
160
+ * `document.querySelector('[data-dc-viewport]')`, so two DesignCanvas instances
161
+ * mounted on the same page cross-wire. Render at most one per route; nested
162
+ * canvases-inside-an-artboard are supported (scoped by nearest-viewport), but two
163
+ * sibling canvases are not.
164
+ */
165
+ export declare interface DesignCanvasProps {
166
+ children?: ReactNode;
167
+ minScale?: number;
168
+ maxScale?: number;
169
+ /**
170
+ * Artboards report `active = false` via `useArtboardActive()` when the
171
+ * viewport scale drops below this threshold (or when the artboard scrolls
172
+ * out of view). Heavy content (videos, canvases) should gate on it.
173
+ * Defaults to 0.35.
174
+ */
175
+ minActiveScale?: number;
176
+ style?: CSSProperties;
177
+ /** Enables the click-to-inspect overlay (library component + props + className). */
178
+ inspector?: boolean;
179
+ /**
180
+ * Connect a design system to the Inspector (token reverse-lookup, library
181
+ * component + icon detection). Defaults to the no-design-system generic
182
+ * adapter. See `../adapters` + `examples/springAdapter.ts`.
183
+ */
184
+ adapter?: DesignSystemAdapter;
185
+ /**
186
+ * Enables the comment layer (drop pins on artboard elements, thread, resolve).
187
+ * Inert when omitted/false. `canvasId` namespaces the data in the
188
+ * comments-service; `devMode` uses an in-memory mock store instead of the network.
189
+ */
190
+ comments?: false | CommentsConfig;
191
+ }
192
+
193
+ export declare interface DesignSystemAdapter {
194
+ libraryLabel: string;
195
+ libraryShortLabel: string;
196
+ isLibraryComponent(type: any): boolean;
197
+ componentName(type: any): string | null;
198
+ classify(type: any): {
199
+ kind: ComponentKind;
200
+ name: string | null;
201
+ };
202
+ findIconName(fiber: AdapterFiber | null): string | null;
203
+ iconWrapperSize?(fiber: AdapterFiber | null): string | null;
204
+ iconImportSnippet(name: string): string;
205
+ iconLabel: string;
206
+ tokens: AdapterTokens;
207
+ themeScopeAttr?: string;
208
+ themeForegroundToken?: string;
209
+ }
210
+
211
+ /** A person from the directory, for the @mention picker (GET /api/directory?q=). */
212
+ export declare interface DirectoryPerson {
213
+ /** Stable directory id for the picked person (the service's user id). */
214
+ personId: string;
215
+ name: string;
216
+ /** Shown to disambiguate same-named people. */
217
+ email: string;
218
+ /**
219
+ * True when this person has already participated on the queried canvas (commented
220
+ * or been @mentioned). Server-set, only present when a `canvasId` is sent with the
221
+ * query; drives the "On this canvas" grouping in the picker. Undefined for the mock
222
+ * client and any query without a canvas → the picker shows a flat, ungrouped list.
223
+ */
224
+ isParticipant?: boolean;
225
+ }
226
+
227
+ /**
228
+ * Optional directory backend for the @mention typeahead. When omitted, the
229
+ * network client falls back to its own `/api/directory` endpoint.
230
+ */
231
+ export declare interface DirectoryProvider {
232
+ search(q: string): Promise<DirectoryPerson[]>;
233
+ }
234
+
235
+ export declare const genericAdapter: DesignSystemAdapter;
236
+
237
+ export declare function getDesignSystemAdapter(): DesignSystemAdapter;
238
+
239
+ /** Signed-in profile (extends PublicUser with the write gate). */
240
+ export declare interface Me extends PublicUser {
241
+ canWrite: boolean;
242
+ }
243
+
244
+ /** Curated public identity — what threads render. Never an email. */
245
+ export declare interface PublicUser {
246
+ id: string;
247
+ username: string;
248
+ displayName: string;
249
+ avatarUrl: string | null;
250
+ }
251
+
252
+ export declare function setDesignSystemAdapter(adapter: DesignSystemAdapter): void;
253
+
254
+ /** Point all DesignCanvas-family portals at `el` (null restores body). */
255
+ export declare function setPortalRoot(el: HTMLElement | null): void;
256
+
257
+ export declare type TypographyTokenSpec = {
258
+ classMatcher: (selectorText: string) => string[];
259
+ signature?: (s: CSSStyleDeclaration) => string;
260
+ };
261
+
262
+ export declare const useArtboardActive: () => boolean;
263
+
264
+ export declare type UtilityClassPrefixes = {
265
+ typography: string;
266
+ text: string;
267
+ bg: string;
268
+ border: string;
269
+ };
270
+
271
+ export { }
package/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { D, h, j, k, m, n, g, s, q, u } from "./lib-CSrTsI4Y.js";
2
+ export {
3
+ D as DCArtboard,
4
+ h as DCPage,
5
+ j as DCPostIt,
6
+ k as DCSection,
7
+ m as DesignCanvas,
8
+ n as genericAdapter,
9
+ g as getDesignSystemAdapter,
10
+ s as setDesignSystemAdapter,
11
+ q as setPortalRoot,
12
+ u as useArtboardActive
13
+ };
14
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}