@popcraft/plugins 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PopCraft
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,72 @@
1
+ # @popcraft/plugins
2
+
3
+ The [PopCraft](https://popcraft.app) plugin SDK — and the example plugins that use it.
4
+
5
+ ```bash
6
+ npm i -D @popcraft/plugins
7
+ ```
8
+
9
+ ```js
10
+ /// <reference types="@popcraft/plugins" />
11
+
12
+ const selection = await popcraft.getSelection()
13
+ await popcraft.notify(`${selection.length} selected`)
14
+ await popcraft.closePlugin()
15
+ ```
16
+
17
+ Full developer guide: <https://popcraft.app/docs/plugins>.
18
+
19
+ ## What is in here
20
+
21
+ | | |
22
+ | --- | --- |
23
+ | `src/runtime/` | The `popcraft` global itself — the RPC client, the main-script API, the UI shim. Compiled to a self-contained IIFE and injected into the plugin sandbox by the editor. |
24
+ | `src/host.ts` | `buildMainDocument` / `buildUIDocument`: the sandboxed documents, with their CSP. |
25
+ | `src/types.ts`, `src/widget.ts` | The manifest, node and widget types. |
26
+ | `examples/` | Four working plugins. Not published — clone or copy them. |
27
+
28
+ **The types are generated, not written.** `dist/generated/global.d.ts` declares the ambient
29
+ `popcraft` global as `PopCraftApi`, which is `ReturnType<typeof createMainApi>` — the type of the
30
+ object the runtime actually builds. Change a method and its published type changes with it; there is
31
+ no second description to keep in step.
32
+
33
+ ## The examples
34
+
35
+ - **[`random-colors/`](examples/random-colors)** — commands, no UI.
36
+ - **[`rename-layers/`](examples/rename-layers)** — UI panel, events, storage.
37
+ - **[`vote-counter/`](examples/vote-counter)** — a canvas widget.
38
+ - **[`comic-themes/`](examples/comic-themes)** — a data-only theme pack (no `main`, no permissions).
39
+
40
+ Install one from **Main menu → Manage plugins… → Install from file…**, with either a `.zip` of the
41
+ folder (`manifest.json` at the root) or a single-file `.json` bundle:
42
+ `{ "manifest": { … }, "main": "…code…", "ui": "<html>…" }`.
43
+
44
+ ## Sandbox
45
+
46
+ Each plugin runs in an opaque-origin iframe (`sandbox="allow-scripts"`), talks to the editor only
47
+ over a private `MessagePort`, and is blocked from the network by CSP unless its manifest asks for
48
+ `network`. Users approve the manifest's permissions (`read`, `write`, `export`, `network`,
49
+ `comments`) on first run; calls outside the granted set are rejected. Plugins are stored in
50
+ IndexedDB and work offline.
51
+
52
+ ## Widgets
53
+
54
+ `vote-counter/` is a canvas widget. Call `popcraft.widget.register({ initialState, render })` at the
55
+ top of `main.js`, then `popcraft.widget.insert()` from a command. `render(state)` returns a tree of
56
+ `AutoLayout`, `Text`, `Rectangle` and `Ellipse` elements; an element's `onClick` mutates (or returns)
57
+ the next state.
58
+
59
+ The tree and state are stored on the `WIDGET` node, so the widget renders and syncs for every
60
+ collaborator, even without the plugin. Clicking needs the plugin installed (it runs in the background
61
+ with command `"widget"`), and only file editors can click. Undo reverts widget clicks like any other
62
+ edit.
63
+
64
+ ## Developing
65
+
66
+ ```bash
67
+ pnpm install
68
+ pnpm build # compile the runtime, emit dist/ + the ambient global
69
+ pnpm typecheck # the package, then the examples against the generated global
70
+ ```
71
+
72
+ Releases: see [RELEASING.md](RELEASING.md).
@@ -0,0 +1,19 @@
1
+ // Generated by scripts/build.mjs from src/runtime — do not edit.
2
+
3
+ import type { PopCraftApi } from '../runtime/main.js'
4
+ import type { PopCraftUiApi } from '../runtime/ui.js'
5
+
6
+ declare global {
7
+ /**
8
+ * The PopCraft plugin API.
9
+ *
10
+ * In a plugin's main script this is the full API (nodes, selection, styles, variables, export,
11
+ * storage, widgets, events). In a plugin's UI document it is the message shim
12
+ * (`postMessage`, `onmessage`, `close`).
13
+ */
14
+ const popcraft: PopCraftApi & PopCraftUiApi
15
+ /** @deprecated Pre-rename alias for `popcraft`. */
16
+ const vizpops: PopCraftApi & PopCraftUiApi
17
+ }
18
+
19
+ export {}
@@ -0,0 +1,5 @@
1
+ /** The `popcraft` global for a plugin main script, as injectable inline script. */
2
+ export declare const MAIN_RUNTIME = "\"use strict\";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: \"rpc\", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener(\"message\", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== \"popcraft-plugin-port\" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === \"result\" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === \"event\" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/main.ts\n function clone(v) {\n return v === void 0 ? {} : JSON.parse(JSON.stringify(v));\n }\n function createMainApi(rpc = createRpcClient()) {\n const uiHandlers = [];\n let uiOnMessage = null;\n const uiQueue = [];\n function deliverUI(msg) {\n for (const cb of uiHandlers) {\n try {\n cb(msg);\n } catch (e) {\n console.error(e);\n }\n }\n if (typeof uiOnMessage === \"function\") {\n try {\n uiOnMessage(msg);\n } catch (e) {\n console.error(e);\n }\n }\n }\n function flushUI() {\n if (uiHandlers.length || typeof uiOnMessage === \"function\") uiQueue.splice(0).forEach(deliverUI);\n }\n rpc.on(\"uimessage\", (msg) => {\n if (!uiHandlers.length && typeof uiOnMessage !== \"function\") uiQueue.push(msg);\n else deliverUI(msg);\n });\n let widgetDef = null;\n const widgetQueue = [];\n function renderWidget(state) {\n const handlers = {};\n function walk(el, path) {\n if (!el || typeof el !== \"object\") return el;\n const out = {};\n for (const k of Object.keys(el)) {\n const v = el[k];\n if (k === \"children\") {\n out.children = (Array.isArray(v) ? v : [v]).filter((c) => c !== null && c !== void 0 && c !== false).map((c, i) => walk(c, path + \".\" + i));\n } else if (k === \"onClick\" && typeof v === \"function\") {\n handlers[path] = v;\n out.onClick = path;\n } else {\n out[k] = v;\n }\n }\n return out;\n }\n return { tree: walk(widgetDef.render(state), \"root\"), handlers };\n }\n function initialWidgetState() {\n const s = widgetDef && widgetDef.initialState;\n return clone(typeof s === \"function\" ? s() : s);\n }\n function commitWidget(nodeId, state) {\n return rpc.call(\"widgetUpdate\", { id: nodeId, state, tree: renderWidget(clone(state)).tree });\n }\n function handleWidgetClick(e) {\n Promise.resolve().then(() => {\n const state = clone(e.state);\n const rendered = renderWidget(state);\n const h = rendered.handlers[e.handler] || widgetDef.handlers && widgetDef.handlers[e.handler];\n if (typeof h !== \"function\") return;\n return Promise.resolve(h(state, { nodeId: e.nodeId })).then((ret) => commitWidget(e.nodeId, ret === void 0 ? state : ret));\n }).catch((err) => rpc.send({ kind: \"error\", message: String((err == null ? void 0 : err.message) || err) }));\n }\n rpc.on(\"widgetclick\", (e) => {\n if (widgetDef) handleWidgetClick(e);\n else widgetQueue.push(e);\n });\n const popcraft = {\n /** Which menu command the user picked, or null when the plugin ran some other way. */\n command: null,\n getSelection: () => rpc.call(\"getSelection\"),\n setSelection: (ids) => rpc.call(\"setSelection\", { ids }),\n getNode: (id) => rpc.call(\"getNode\", { id }),\n findNodes: (query) => rpc.call(\"findNodes\", query || {}),\n getCurrentPage: () => rpc.call(\"getCurrentPage\"),\n createNode: (type, props) => rpc.call(\"createNode\", { type, props: props || {} }),\n updateNode: (id, props) => rpc.call(\"updateNode\", { id, props }),\n deleteNode: (id) => rpc.call(\"deleteNode\", { id }),\n setText: (id, characters) => rpc.call(\"setText\", { id, characters }),\n getStyles: () => rpc.call(\"getStyles\"),\n createPaintStyle: (name, paints) => rpc.call(\"createPaintStyle\", { name, paints }),\n getVariables: () => rpc.call(\"getVariables\"),\n setVariableValue: (variableId, modeId, value) => rpc.call(\"setVariableValue\", { variableId, modeId, value }),\n exportNode: (id, options) => rpc.call(\"exportNode\", { id, options: options || {} }),\n openExternal: (url) => rpc.call(\"openExternal\", { url }),\n notify: (message, options) => rpc.call(\"notify\", { message: String(message), error: !!(options && options.error) }),\n showUI: (options) => rpc.call(\"showUI\", options || {}),\n hideUI: () => rpc.call(\"hideUI\"),\n /** Ends the run. The optional message is shown as a notification. */\n closePlugin: (message) => rpc.call(\"closePlugin\", { message }),\n storage: {\n get: (key) => rpc.call(\"storageGet\", { key }),\n set: (key, value) => rpc.call(\"storageSet\", { key, value })\n },\n ui: {\n get onmessage() {\n return uiOnMessage;\n },\n set onmessage(cb) {\n uiOnMessage = cb;\n flushUI();\n },\n postMessage: (msg) => rpc.send({ kind: \"toUI\", data: msg }),\n on: (cb) => {\n uiHandlers.push(cb);\n flushUI();\n }\n },\n widget: {\n register(def) {\n if (!def || typeof def.render !== \"function\") throw new Error(\"popcraft.widget.register needs a render(state) function\");\n widgetDef = def;\n widgetQueue.splice(0).forEach(handleWidgetClick);\n },\n insert(options) {\n if (!widgetDef) return Promise.reject(new Error(\"Call popcraft.widget.register first\"));\n const o = options || {};\n const state = o.state !== void 0 ? clone(o.state) : initialWidgetState();\n return rpc.call(\"widgetInsert\", { state, tree: renderWidget(state).tree, x: o.x, y: o.y });\n },\n getState: (nodeId) => rpc.call(\"widgetGet\", { id: nodeId }),\n setState(nodeId, state) {\n if (!widgetDef) return Promise.reject(new Error(\"Call popcraft.widget.register first\"));\n return commitWidget(nodeId, clone(state));\n }\n },\n on: (event, cb) => rpc.on(event, cb),\n off: (event, cb) => rpc.off(event, cb)\n };\n rpc.on(\"init\", (data) => {\n popcraft.command = data.command;\n });\n return popcraft;\n }\n function installMainApi() {\n const rpc = createRpcClient();\n const api = createMainApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n window.addEventListener(\"error\", (e) => rpc.send({ kind: \"error\", message: String(e.message || e) }));\n window.addEventListener(\"unhandledrejection\", (e) => rpc.send({ kind: \"error\", message: String(e.reason && e.reason.message || e.reason) }));\n return api;\n }\n\n // src/runtime/main.entry.ts\n installMainApi();\n})();";
3
+ /** The `popcraft` shim for a plugin UI document, as injectable inline script. */
4
+ export declare const UI_RUNTIME = "\"use strict\";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: \"rpc\", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener(\"message\", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== \"popcraft-plugin-port\" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === \"result\" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === \"event\" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/ui.ts\n function createUiApi(rpc = createRpcClient()) {\n const popcraft = {\n onmessage: null,\n postMessage: (msg) => rpc.send({ kind: \"fromUI\", data: msg }),\n close: () => rpc.send({ kind: \"closeUI\" })\n };\n rpc.on(\"message\", (msg) => {\n if (typeof popcraft.onmessage === \"function\") popcraft.onmessage(msg);\n window.dispatchEvent(new MessageEvent(\"message\", { data: { pluginMessage: msg } }));\n });\n return popcraft;\n }\n function installUiApi() {\n const rpc = createRpcClient();\n const api = createUiApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n return api;\n }\n\n // src/runtime/ui.entry.ts\n installUiApi();\n})();";
5
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/generated/runtime.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,eAAO,MAAM,YAAY,8iQAA8iQ,CAAA;AAEvkQ,iFAAiF;AACjF,eAAO,MAAM,UAAU,o7EAAo7E,CAAA"}
@@ -0,0 +1,6 @@
1
+ // Generated by scripts/build.mjs from src/runtime — do not edit.
2
+ /** The `popcraft` global for a plugin main script, as injectable inline script. */
3
+ export const MAIN_RUNTIME = "\"use strict\";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: \"rpc\", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener(\"message\", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== \"popcraft-plugin-port\" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === \"result\" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === \"event\" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/main.ts\n function clone(v) {\n return v === void 0 ? {} : JSON.parse(JSON.stringify(v));\n }\n function createMainApi(rpc = createRpcClient()) {\n const uiHandlers = [];\n let uiOnMessage = null;\n const uiQueue = [];\n function deliverUI(msg) {\n for (const cb of uiHandlers) {\n try {\n cb(msg);\n } catch (e) {\n console.error(e);\n }\n }\n if (typeof uiOnMessage === \"function\") {\n try {\n uiOnMessage(msg);\n } catch (e) {\n console.error(e);\n }\n }\n }\n function flushUI() {\n if (uiHandlers.length || typeof uiOnMessage === \"function\") uiQueue.splice(0).forEach(deliverUI);\n }\n rpc.on(\"uimessage\", (msg) => {\n if (!uiHandlers.length && typeof uiOnMessage !== \"function\") uiQueue.push(msg);\n else deliverUI(msg);\n });\n let widgetDef = null;\n const widgetQueue = [];\n function renderWidget(state) {\n const handlers = {};\n function walk(el, path) {\n if (!el || typeof el !== \"object\") return el;\n const out = {};\n for (const k of Object.keys(el)) {\n const v = el[k];\n if (k === \"children\") {\n out.children = (Array.isArray(v) ? v : [v]).filter((c) => c !== null && c !== void 0 && c !== false).map((c, i) => walk(c, path + \".\" + i));\n } else if (k === \"onClick\" && typeof v === \"function\") {\n handlers[path] = v;\n out.onClick = path;\n } else {\n out[k] = v;\n }\n }\n return out;\n }\n return { tree: walk(widgetDef.render(state), \"root\"), handlers };\n }\n function initialWidgetState() {\n const s = widgetDef && widgetDef.initialState;\n return clone(typeof s === \"function\" ? s() : s);\n }\n function commitWidget(nodeId, state) {\n return rpc.call(\"widgetUpdate\", { id: nodeId, state, tree: renderWidget(clone(state)).tree });\n }\n function handleWidgetClick(e) {\n Promise.resolve().then(() => {\n const state = clone(e.state);\n const rendered = renderWidget(state);\n const h = rendered.handlers[e.handler] || widgetDef.handlers && widgetDef.handlers[e.handler];\n if (typeof h !== \"function\") return;\n return Promise.resolve(h(state, { nodeId: e.nodeId })).then((ret) => commitWidget(e.nodeId, ret === void 0 ? state : ret));\n }).catch((err) => rpc.send({ kind: \"error\", message: String((err == null ? void 0 : err.message) || err) }));\n }\n rpc.on(\"widgetclick\", (e) => {\n if (widgetDef) handleWidgetClick(e);\n else widgetQueue.push(e);\n });\n const popcraft = {\n /** Which menu command the user picked, or null when the plugin ran some other way. */\n command: null,\n getSelection: () => rpc.call(\"getSelection\"),\n setSelection: (ids) => rpc.call(\"setSelection\", { ids }),\n getNode: (id) => rpc.call(\"getNode\", { id }),\n findNodes: (query) => rpc.call(\"findNodes\", query || {}),\n getCurrentPage: () => rpc.call(\"getCurrentPage\"),\n createNode: (type, props) => rpc.call(\"createNode\", { type, props: props || {} }),\n updateNode: (id, props) => rpc.call(\"updateNode\", { id, props }),\n deleteNode: (id) => rpc.call(\"deleteNode\", { id }),\n setText: (id, characters) => rpc.call(\"setText\", { id, characters }),\n getStyles: () => rpc.call(\"getStyles\"),\n createPaintStyle: (name, paints) => rpc.call(\"createPaintStyle\", { name, paints }),\n getVariables: () => rpc.call(\"getVariables\"),\n setVariableValue: (variableId, modeId, value) => rpc.call(\"setVariableValue\", { variableId, modeId, value }),\n exportNode: (id, options) => rpc.call(\"exportNode\", { id, options: options || {} }),\n openExternal: (url) => rpc.call(\"openExternal\", { url }),\n notify: (message, options) => rpc.call(\"notify\", { message: String(message), error: !!(options && options.error) }),\n showUI: (options) => rpc.call(\"showUI\", options || {}),\n hideUI: () => rpc.call(\"hideUI\"),\n /** Ends the run. The optional message is shown as a notification. */\n closePlugin: (message) => rpc.call(\"closePlugin\", { message }),\n storage: {\n get: (key) => rpc.call(\"storageGet\", { key }),\n set: (key, value) => rpc.call(\"storageSet\", { key, value })\n },\n ui: {\n get onmessage() {\n return uiOnMessage;\n },\n set onmessage(cb) {\n uiOnMessage = cb;\n flushUI();\n },\n postMessage: (msg) => rpc.send({ kind: \"toUI\", data: msg }),\n on: (cb) => {\n uiHandlers.push(cb);\n flushUI();\n }\n },\n widget: {\n register(def) {\n if (!def || typeof def.render !== \"function\") throw new Error(\"popcraft.widget.register needs a render(state) function\");\n widgetDef = def;\n widgetQueue.splice(0).forEach(handleWidgetClick);\n },\n insert(options) {\n if (!widgetDef) return Promise.reject(new Error(\"Call popcraft.widget.register first\"));\n const o = options || {};\n const state = o.state !== void 0 ? clone(o.state) : initialWidgetState();\n return rpc.call(\"widgetInsert\", { state, tree: renderWidget(state).tree, x: o.x, y: o.y });\n },\n getState: (nodeId) => rpc.call(\"widgetGet\", { id: nodeId }),\n setState(nodeId, state) {\n if (!widgetDef) return Promise.reject(new Error(\"Call popcraft.widget.register first\"));\n return commitWidget(nodeId, clone(state));\n }\n },\n on: (event, cb) => rpc.on(event, cb),\n off: (event, cb) => rpc.off(event, cb)\n };\n rpc.on(\"init\", (data) => {\n popcraft.command = data.command;\n });\n return popcraft;\n }\n function installMainApi() {\n const rpc = createRpcClient();\n const api = createMainApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n window.addEventListener(\"error\", (e) => rpc.send({ kind: \"error\", message: String(e.message || e) }));\n window.addEventListener(\"unhandledrejection\", (e) => rpc.send({ kind: \"error\", message: String(e.reason && e.reason.message || e.reason) }));\n return api;\n }\n\n // src/runtime/main.entry.ts\n installMainApi();\n})();";
4
+ /** The `popcraft` shim for a plugin UI document, as injectable inline script. */
5
+ export const UI_RUNTIME = "\"use strict\";\n(() => {\n // src/runtime/rpc.ts\n function createRpcClient() {\n let port = null;\n const queue = [];\n const pending = {};\n const listeners = {};\n let nextId = 1;\n function send(msg) {\n if (port) port.postMessage(msg);\n else queue.push(msg);\n }\n function call(method, params) {\n return new Promise((resolve, reject) => {\n const id = nextId++;\n pending[id] = { resolve, reject };\n send({ kind: \"rpc\", id, method, params });\n });\n }\n function emit(event, data) {\n for (const cb of (listeners[event] || []).slice()) {\n try {\n cb(data);\n } catch (e) {\n console.error(e);\n }\n }\n }\n window.addEventListener(\"message\", (e) => {\n const data = e.data;\n if (e.source !== window.parent || !data || data.type !== \"popcraft-plugin-port\" || port) return;\n port = e.ports[0];\n port.onmessage = (ev) => {\n const m = ev.data || {};\n if (m.kind === \"result\" && m.id !== void 0 && pending[m.id]) {\n const p = pending[m.id];\n delete pending[m.id];\n if (m.error) p.reject(new Error(m.error));\n else p.resolve(m.result);\n } else if (m.kind === \"event\" && m.event) {\n emit(m.event, m.data);\n }\n };\n for (const msg of queue.splice(0)) port.postMessage(msg);\n });\n return {\n call,\n send,\n on(event, cb) {\n (listeners[event] = listeners[event] || []).push(cb);\n },\n off(event, cb) {\n listeners[event] = (listeners[event] || []).filter((x) => x !== cb);\n }\n };\n }\n\n // src/runtime/ui.ts\n function createUiApi(rpc = createRpcClient()) {\n const popcraft = {\n onmessage: null,\n postMessage: (msg) => rpc.send({ kind: \"fromUI\", data: msg }),\n close: () => rpc.send({ kind: \"closeUI\" })\n };\n rpc.on(\"message\", (msg) => {\n if (typeof popcraft.onmessage === \"function\") popcraft.onmessage(msg);\n window.dispatchEvent(new MessageEvent(\"message\", { data: { pluginMessage: msg } }));\n });\n return popcraft;\n }\n function installUiApi() {\n const rpc = createRpcClient();\n const api = createUiApi(rpc);\n const w = window;\n w.__popcraftRpc = rpc;\n w.popcraft = api;\n w.vizpops = api;\n return api;\n }\n\n // src/runtime/ui.entry.ts\n installUiApi();\n})();";
6
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/generated/runtime.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,mFAAmF;AACnF,MAAM,CAAC,MAAM,YAAY,GAAG,2iQAA2iQ,CAAA;AAEvkQ,iFAAiF;AACjF,MAAM,CAAC,MAAM,UAAU,GAAG,i7EAAi7E,CAAA"}
package/dist/host.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export declare function csp(network: boolean): string;
2
+ export declare function buildMainDocument(mainCode: string, network: boolean): string;
3
+ export declare function buildUIDocument(uiHtml: string, network: boolean): string;
4
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAYA,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAO5C;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAY5E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAKxE"}
package/dist/host.js ADDED
@@ -0,0 +1,38 @@
1
+ // Sandboxed iframe documents for plugins. Both the main (headless) context and the UI context get an
2
+ // opaque origin (`sandbox="allow-scripts"`, no allow-same-origin) and talk to the editor ONLY over a
3
+ // MessagePort handed to them once at load. A CSP blocks all network access unless the plugin was
4
+ // granted `network`.
5
+ //
6
+ // The runtime injected here is compiled from ./runtime — the same source the published types are
7
+ // generated from, so what a plugin is typed against is exactly what runs.
8
+ import { MAIN_RUNTIME, UI_RUNTIME } from './generated/runtime.js';
9
+ const escapeScript = (code) => code.replace(/<\/script/gi, '<\\/script');
10
+ export function csp(network) {
11
+ const connect = network ? "connect-src * data: blob:;" : "connect-src 'none';";
12
+ const img = network ? "img-src * data: blob:;" : "img-src data: blob:;";
13
+ const font = network ? "font-src * data:;" : "font-src data:;";
14
+ // form-action/base-uri 'none': a form post or <base> rewrite would otherwise leak document data to any
15
+ // origin, network permission or not.
16
+ return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; form-action 'none'; base-uri 'none'; ${img} ${font} ${connect}">`;
17
+ }
18
+ export function buildMainDocument(mainCode, network) {
19
+ return `<!doctype html><html><head><meta charset="utf-8">${csp(network)}<script>${MAIN_RUNTIME}</script></head><body><script>
20
+ (function waitForInit() {
21
+ var started = false;
22
+ window.__popcraftRpc.on('init', function () {
23
+ if (started) return; started = true;
24
+ try {
25
+ ${escapeScript(mainCode)}
26
+ } catch (e) { window.__popcraftRpc.send({ kind: 'error', message: String(e && e.message || e) }); }
27
+ });
28
+ })();
29
+ </script></body></html>`;
30
+ }
31
+ export function buildUIDocument(uiHtml, network) {
32
+ const head = `<meta charset="utf-8">${csp(network)}<script>${UI_RUNTIME}</script>`;
33
+ // Inject the shim at the start of <head> (or prepend when the author wrote a fragment).
34
+ if (/<head[^>]*>/i.test(uiHtml))
35
+ return uiHtml.replace(/<head[^>]*>/i, m => m + head);
36
+ return `<!doctype html><html><head>${head}</head><body>${uiHtml}</body></html>`;
37
+ }
38
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,qGAAqG;AACrG,iGAAiG;AACjG,qBAAqB;AACrB,EAAE;AACF,iGAAiG;AACjG,0EAA0E;AAE1E,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAEjE,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,YAAY,CAAC,CAAA;AAEhF,MAAM,UAAU,GAAG,CAAC,OAAgB;IAClC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,qBAAqB,CAAA;IAC9E,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,sBAAsB,CAAA;IACvE,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,iBAAiB,CAAA;IAC9D,uGAAuG;IACvG,qCAAqC;IACrC,OAAO,uKAAuK,GAAG,IAAI,IAAI,IAAI,OAAO,IAAI,CAAA;AAC1M,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,OAAgB;IAClE,OAAO,oDAAoD,GAAG,CAAC,OAAO,CAAC,WAAW,YAAY;;;;;;EAM9F,YAAY,CAAC,QAAQ,CAAC;;;;wBAIA,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,OAAgB;IAC9D,MAAM,IAAI,GAAG,yBAAyB,GAAG,CAAC,OAAO,CAAC,WAAW,UAAU,WAAW,CAAA;IAClF,wFAAwF;IACxF,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACrF,OAAO,8BAA8B,IAAI,gBAAgB,MAAM,gBAAgB,CAAA;AACjF,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { buildMainDocument, buildUIDocument, csp } from './host.js';
2
+ export { mainApiMethods } from './runtime/main.js';
3
+ export { MAIN_RUNTIME, UI_RUNTIME } from './generated/runtime.js';
4
+ export type { PopCraftApi } from './runtime/main.js';
5
+ export type { PopCraftUiApi } from './runtime/ui.js';
6
+ export * from './types.js';
7
+ export * from './widget.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAEjE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /// <reference path="./generated/global.d.ts" />
2
+ // @popcraft/plugins — the PopCraft plugin SDK.
3
+ //
4
+ // Plugin authors want the types: the ambient `popcraft` global comes with the package, so
5
+ // `/// <reference types="@popcraft/plugins" />` at the top of a main script is all it takes.
6
+ //
7
+ // The editor imports the rest: the compiled sandbox runtime and the document builders that inline
8
+ // it under a CSP.
9
+ export { buildMainDocument, buildUIDocument, csp } from './host.js';
10
+ export { mainApiMethods } from './runtime/main.js';
11
+ export { MAIN_RUNTIME, UI_RUNTIME } from './generated/runtime.js';
12
+ export * from './types.js';
13
+ export * from './widget.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAEhD,+CAA+C;AAC/C,EAAE;AACF,0FAA0F;AAC1F,6FAA6F;AAC7F,EAAE;AACF,kGAAkG;AAClG,kBAAkB;AAElB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAKjE,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
@@ -0,0 +1,62 @@
1
+ import { type RpcClient } from './rpc.js';
2
+ import type { PopCraftEvents, PopCraftExportOptions, PopCraftFindQuery, PopCraftNode, PopCraftNodeType, PopCraftPage, PopCraftPaint, PopCraftShowUIOptions, PopCraftVariables } from '../types.js';
3
+ import type { PopCraftWidgetDefinition } from '../widget.js';
4
+ export declare function createMainApi(rpc?: RpcClient): {
5
+ /** Which menu command the user picked, or null when the plugin ran some other way. */
6
+ command: string | null;
7
+ getSelection: () => Promise<PopCraftNode[]>;
8
+ setSelection: (ids: string[]) => Promise<void>;
9
+ getNode: (id: string) => Promise<PopCraftNode | null>;
10
+ findNodes: (query?: PopCraftFindQuery) => Promise<PopCraftNode[]>;
11
+ getCurrentPage: () => Promise<PopCraftPage | null>;
12
+ createNode: (type: PopCraftNodeType, props?: Partial<PopCraftNode>) => Promise<string>;
13
+ updateNode: (id: string, props: Partial<PopCraftNode>) => Promise<void>;
14
+ deleteNode: (id: string) => Promise<void>;
15
+ setText: (id: string, characters: string) => Promise<void>;
16
+ getStyles: () => Promise<unknown[]>;
17
+ createPaintStyle: (name: string, paints: PopCraftPaint[]) => Promise<string>;
18
+ getVariables: () => Promise<PopCraftVariables>;
19
+ setVariableValue: (variableId: string, modeId: string, value: unknown) => Promise<void>;
20
+ exportNode: (id: string, options?: PopCraftExportOptions) => Promise<string | Uint8Array<ArrayBufferLike>>;
21
+ openExternal: (url: string) => Promise<void>;
22
+ notify: (message: string, options?: {
23
+ error?: boolean;
24
+ }) => Promise<void>;
25
+ showUI: (options?: PopCraftShowUIOptions) => Promise<void>;
26
+ hideUI: () => Promise<void>;
27
+ /** Ends the run. The optional message is shown as a notification. */
28
+ closePlugin: (message?: string) => Promise<void>;
29
+ storage: {
30
+ get: <T = unknown>(key: string) => Promise<T | undefined>;
31
+ set: (key: string, value: unknown) => Promise<void>;
32
+ };
33
+ ui: {
34
+ get onmessage(): ((msg: any) => void) | null;
35
+ set onmessage(cb: ((msg: any) => void) | null);
36
+ postMessage: (msg: unknown) => void;
37
+ on: (cb: (msg: any) => void) => void;
38
+ };
39
+ widget: {
40
+ register<State extends object>(def: PopCraftWidgetDefinition<State>): void;
41
+ insert<State extends object>(options?: {
42
+ state?: State;
43
+ x?: number;
44
+ y?: number;
45
+ }): Promise<string>;
46
+ getState: <State extends object = any>(nodeId: string) => Promise<State>;
47
+ setState<State extends object>(nodeId: string, state: State): Promise<void>;
48
+ };
49
+ on: <E extends keyof PopCraftEvents>(event: E, cb: (data: PopCraftEvents[E]) => void) => void;
50
+ off: <E extends keyof PopCraftEvents>(event: E, cb: (data: PopCraftEvents[E]) => void) => void;
51
+ };
52
+ /** Everything a plugin's main script can reach through the `popcraft` global. */
53
+ export type PopCraftApi = ReturnType<typeof createMainApi>;
54
+ /** Called by the generated sandbox document — not part of the authoring surface. */
55
+ export declare function installMainApi(): PopCraftApi;
56
+ /**
57
+ * Every RPC method name the `popcraft` global exposes, read off a real instance rather than a list
58
+ * anyone has to remember to update. The editor asserts this matches the methods its command registry
59
+ * exposes to plugins, so the two halves of the wire contract cannot drift apart.
60
+ */
61
+ export declare function mainApiMethods(): string[];
62
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/runtime/main.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAC1D,OAAO,KAAK,EACV,cAAc,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EACxF,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,iBAAiB,EACtE,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACgB,wBAAwB,EACnD,MAAM,cAAc,CAAA;AAMrB,wBAAgB,aAAa,CAAC,GAAG,GAAE,SAA6B;IA0E5D,sFAAsF;aACrE,MAAM,GAAG,IAAI;;wBAGV,MAAM,EAAE;kBACd,MAAM;wBACA,iBAAiB;;uBAElB,gBAAgB,UAAU,OAAO,CAAC,YAAY,CAAC;qBACjD,MAAM,SAAS,OAAO,CAAC,YAAY,CAAC;qBACpC,MAAM;kBACT,MAAM,cAAc,MAAM;;6BAGf,MAAM,UAAU,aAAa,EAAE;;mCAEzB,MAAM,UAAU,MAAM,SAAS,OAAO;qBAEpD,MAAM,YAAY,qBAAqB;wBACpC,MAAM;sBACR,MAAM,YAAY;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;uBAEpC,qBAAqB;;IAExC,qEAAqE;4BAC7C,MAAM;;cAGtB,CAAC,iBAAiB,MAAM;mBACnB,MAAM,SAAS,OAAO;;;yBAIhB,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI;0BAC1B,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI;2BAC1B,OAAO;iBACjB,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI;;;iBAIlB,KAAK,SAAS,MAAM,OAAO,wBAAwB,CAAC,KAAK,CAAC,GAAG,IAAI;eAKnE,KAAK,SAAS,MAAM,YAAY;YAAE,KAAK,CAAC,EAAE,KAAK,CAAC;YAAC,CAAC,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,MAAM,CAAC;mBAMvF,KAAK,SAAS,MAAM,gBAAgB,MAAM;iBAC5C,KAAK,SAAS,MAAM,UAAU,MAAM,SAAS,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;;SAMxE,CAAC,SAAS,MAAM,cAAc,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI;UAC9E,CAAC,SAAS,MAAM,cAAc,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI;EAKxF;AAQD,iFAAiF;AACjF,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA;AAE1D,oFAAoF;AACpF,wBAAgB,cAAc,IAAI,WAAW,CAU5C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAQzC"}
@@ -0,0 +1,183 @@
1
+ // The `popcraft` global for a plugin's main script. Every method is async: it is an RPC over the
2
+ // MessagePort. The ambient global declaration published to plugin authors is generated from
3
+ // `PopCraftApi` below, so the types cannot drift from what this actually implements.
4
+ import { createRpcClient } from './rpc.js';
5
+ function clone(v) {
6
+ return (v === undefined ? {} : JSON.parse(JSON.stringify(v)));
7
+ }
8
+ export function createMainApi(rpc = createRpcClient()) {
9
+ // UI messages that arrive before the plugin registers a handler (the UI often loads first) are
10
+ // queued and delivered as soon as one is set.
11
+ const uiHandlers = [];
12
+ let uiOnMessage = null;
13
+ const uiQueue = [];
14
+ function deliverUI(msg) {
15
+ for (const cb of uiHandlers) {
16
+ try {
17
+ cb(msg);
18
+ }
19
+ catch (e) {
20
+ console.error(e);
21
+ }
22
+ }
23
+ if (typeof uiOnMessage === 'function') {
24
+ try {
25
+ uiOnMessage(msg);
26
+ }
27
+ catch (e) {
28
+ console.error(e);
29
+ }
30
+ }
31
+ }
32
+ function flushUI() {
33
+ if (uiHandlers.length || typeof uiOnMessage === 'function')
34
+ uiQueue.splice(0).forEach(deliverUI);
35
+ }
36
+ rpc.on('uimessage', (msg) => {
37
+ if (!uiHandlers.length && typeof uiOnMessage !== 'function')
38
+ uiQueue.push(msg);
39
+ else
40
+ deliverUI(msg);
41
+ });
42
+ // Widgets: `render(state)` returns a UI tree; onClick handlers are functions (keyed by their
43
+ // position in the tree) or names looked up in `handlers`. A handler may mutate the state it is
44
+ // given or return a new one.
45
+ let widgetDef = null;
46
+ const widgetQueue = [];
47
+ function renderWidget(state) {
48
+ const handlers = {};
49
+ function walk(el, path) {
50
+ if (!el || typeof el !== 'object')
51
+ return el;
52
+ const out = {};
53
+ for (const k of Object.keys(el)) {
54
+ const v = el[k];
55
+ if (k === 'children') {
56
+ out.children = (Array.isArray(v) ? v : [v])
57
+ .filter((c) => c !== null && c !== undefined && c !== false)
58
+ .map((c, i) => walk(c, path + '.' + i));
59
+ }
60
+ else if (k === 'onClick' && typeof v === 'function') {
61
+ handlers[path] = v;
62
+ out.onClick = path;
63
+ }
64
+ else {
65
+ out[k] = v;
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ return { tree: walk(widgetDef.render(state), 'root'), handlers };
71
+ }
72
+ function initialWidgetState() {
73
+ const s = widgetDef && widgetDef.initialState;
74
+ return clone(typeof s === 'function' ? s() : s);
75
+ }
76
+ function commitWidget(nodeId, state) {
77
+ return rpc.call('widgetUpdate', { id: nodeId, state, tree: renderWidget(clone(state)).tree });
78
+ }
79
+ function handleWidgetClick(e) {
80
+ Promise.resolve()
81
+ .then(() => {
82
+ const state = clone(e.state);
83
+ const rendered = renderWidget(state);
84
+ const h = rendered.handlers[e.handler] || (widgetDef.handlers && widgetDef.handlers[e.handler]);
85
+ if (typeof h !== 'function')
86
+ return;
87
+ return Promise.resolve(h(state, { nodeId: e.nodeId })).then(ret => commitWidget(e.nodeId, ret === undefined ? state : ret));
88
+ })
89
+ .catch((err) => rpc.send({ kind: 'error', message: String(err?.message || err) }));
90
+ }
91
+ rpc.on('widgetclick', (e) => {
92
+ if (widgetDef)
93
+ handleWidgetClick(e);
94
+ else
95
+ widgetQueue.push(e);
96
+ });
97
+ const popcraft = {
98
+ /** Which menu command the user picked, or null when the plugin ran some other way. */
99
+ command: null,
100
+ getSelection: () => rpc.call('getSelection'),
101
+ setSelection: (ids) => rpc.call('setSelection', { ids }),
102
+ getNode: (id) => rpc.call('getNode', { id }),
103
+ findNodes: (query) => rpc.call('findNodes', query || {}),
104
+ getCurrentPage: () => rpc.call('getCurrentPage'),
105
+ createNode: (type, props) => rpc.call('createNode', { type, props: props || {} }),
106
+ updateNode: (id, props) => rpc.call('updateNode', { id, props }),
107
+ deleteNode: (id) => rpc.call('deleteNode', { id }),
108
+ setText: (id, characters) => rpc.call('setText', { id, characters }),
109
+ getStyles: () => rpc.call('getStyles'),
110
+ createPaintStyle: (name, paints) => rpc.call('createPaintStyle', { name, paints }),
111
+ getVariables: () => rpc.call('getVariables'),
112
+ setVariableValue: (variableId, modeId, value) => rpc.call('setVariableValue', { variableId, modeId, value }),
113
+ exportNode: (id, options) => rpc.call('exportNode', { id, options: options || {} }),
114
+ openExternal: (url) => rpc.call('openExternal', { url }),
115
+ notify: (message, options) => rpc.call('notify', { message: String(message), error: !!(options && options.error) }),
116
+ showUI: (options) => rpc.call('showUI', options || {}),
117
+ hideUI: () => rpc.call('hideUI'),
118
+ /** Ends the run. The optional message is shown as a notification. */
119
+ closePlugin: (message) => rpc.call('closePlugin', { message }),
120
+ storage: {
121
+ get: (key) => rpc.call('storageGet', { key }),
122
+ set: (key, value) => rpc.call('storageSet', { key, value }),
123
+ },
124
+ ui: {
125
+ get onmessage() { return uiOnMessage; },
126
+ set onmessage(cb) { uiOnMessage = cb; flushUI(); },
127
+ postMessage: (msg) => rpc.send({ kind: 'toUI', data: msg }),
128
+ on: (cb) => { uiHandlers.push(cb); flushUI(); },
129
+ },
130
+ widget: {
131
+ register(def) {
132
+ if (!def || typeof def.render !== 'function')
133
+ throw new Error('popcraft.widget.register needs a render(state) function');
134
+ widgetDef = def;
135
+ widgetQueue.splice(0).forEach(handleWidgetClick);
136
+ },
137
+ insert(options) {
138
+ if (!widgetDef)
139
+ return Promise.reject(new Error('Call popcraft.widget.register first'));
140
+ const o = options || {};
141
+ const state = o.state !== undefined ? clone(o.state) : initialWidgetState();
142
+ return rpc.call('widgetInsert', { state, tree: renderWidget(state).tree, x: o.x, y: o.y });
143
+ },
144
+ getState: (nodeId) => rpc.call('widgetGet', { id: nodeId }),
145
+ setState(nodeId, state) {
146
+ if (!widgetDef)
147
+ return Promise.reject(new Error('Call popcraft.widget.register first'));
148
+ return commitWidget(nodeId, clone(state));
149
+ },
150
+ },
151
+ on: (event, cb) => rpc.on(event, cb),
152
+ off: (event, cb) => rpc.off(event, cb),
153
+ };
154
+ rpc.on('init', (data) => { popcraft.command = data.command; });
155
+ return popcraft;
156
+ }
157
+ /** Called by the generated sandbox document — not part of the authoring surface. */
158
+ export function installMainApi() {
159
+ const rpc = createRpcClient();
160
+ const api = createMainApi(rpc);
161
+ const w = window;
162
+ w.__popcraftRpc = rpc;
163
+ w.popcraft = api;
164
+ w.vizpops = api; // plugins written before the rename
165
+ window.addEventListener('error', e => rpc.send({ kind: 'error', message: String(e.message || e) }));
166
+ window.addEventListener('unhandledrejection', e => rpc.send({ kind: 'error', message: String((e.reason && e.reason.message) || e.reason) }));
167
+ return api;
168
+ }
169
+ /**
170
+ * Every RPC method name the `popcraft` global exposes, read off a real instance rather than a list
171
+ * anyone has to remember to update. The editor asserts this matches the methods its command registry
172
+ * exposes to plugins, so the two halves of the wire contract cannot drift apart.
173
+ */
174
+ export function mainApiMethods() {
175
+ const noop = () => { };
176
+ const api = createMainApi({ call: () => new Promise(() => { }), send: noop, on: noop, off: noop });
177
+ // `on`/`off` are local event subscriptions, not RPCs.
178
+ const methods = Object.keys(api).filter(k => typeof api[k] === 'function' && k !== 'on' && k !== 'off');
179
+ const storage = Object.keys(api.storage).map(k => 'storage' + k[0].toUpperCase() + k.slice(1));
180
+ const widget = ['widgetInsert', 'widgetUpdate', 'widgetGet'];
181
+ return [...methods, ...storage, ...widget].sort();
182
+ }
183
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/runtime/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface RpcMessage {
2
+ kind: string;
3
+ [key: string]: unknown;
4
+ }
5
+ export interface RpcClient {
6
+ call<T = unknown>(method: string, params?: unknown): Promise<T>;
7
+ send(msg: RpcMessage): void;
8
+ on(event: string, cb: (data: any) => void): void;
9
+ off(event: string, cb: (data: any) => void): void;
10
+ }
11
+ export declare function createRpcClient(): RpcClient;
12
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../src/runtime/rpc.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAID,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC/D,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAA;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAA;IAChD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAA;CAClD;AAED,wBAAgB,eAAe,IAAI,SAAS,CAkD3C"}
@@ -0,0 +1,61 @@
1
+ // Port handshake + RPC client, shared by both iframe kinds. The editor hands the sandbox exactly one
2
+ // MessagePort, once, at load; everything else travels over it.
3
+ export function createRpcClient() {
4
+ let port = null;
5
+ const queue = [];
6
+ const pending = {};
7
+ const listeners = {};
8
+ let nextId = 1;
9
+ function send(msg) {
10
+ if (port)
11
+ port.postMessage(msg);
12
+ else
13
+ queue.push(msg);
14
+ }
15
+ function call(method, params) {
16
+ return new Promise((resolve, reject) => {
17
+ const id = nextId++;
18
+ pending[id] = { resolve, reject };
19
+ send({ kind: 'rpc', id, method, params });
20
+ });
21
+ }
22
+ function emit(event, data) {
23
+ for (const cb of (listeners[event] || []).slice()) {
24
+ try {
25
+ cb(data);
26
+ }
27
+ catch (e) {
28
+ console.error(e);
29
+ }
30
+ }
31
+ }
32
+ window.addEventListener('message', (e) => {
33
+ const data = e.data;
34
+ if (e.source !== window.parent || !data || data.type !== 'popcraft-plugin-port' || port)
35
+ return;
36
+ port = e.ports[0];
37
+ port.onmessage = (ev) => {
38
+ const m = (ev.data || {});
39
+ if (m.kind === 'result' && m.id !== undefined && pending[m.id]) {
40
+ const p = pending[m.id];
41
+ delete pending[m.id];
42
+ if (m.error)
43
+ p.reject(new Error(m.error));
44
+ else
45
+ p.resolve(m.result);
46
+ }
47
+ else if (m.kind === 'event' && m.event) {
48
+ emit(m.event, m.data);
49
+ }
50
+ };
51
+ for (const msg of queue.splice(0))
52
+ port.postMessage(msg);
53
+ });
54
+ return {
55
+ call,
56
+ send,
57
+ on(event, cb) { (listeners[event] = listeners[event] || []).push(cb); },
58
+ off(event, cb) { listeners[event] = (listeners[event] || []).filter(x => x !== cb); },
59
+ };
60
+ }
61
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/runtime/rpc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type RpcClient } from './rpc.js';
2
+ export declare function createUiApi(rpc?: RpcClient): {
3
+ onmessage: ((msg: any) => void) | null;
4
+ postMessage: (msg: unknown) => void;
5
+ close: () => void;
6
+ };
7
+ /** Everything a plugin's UI document can reach through the `popcraft` global. */
8
+ export type PopCraftUiApi = ReturnType<typeof createUiApi>;
9
+ /** Called by the generated sandbox document — not part of the authoring surface. */
10
+ export declare function installUiApi(): PopCraftUiApi;
11
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/runtime/ui.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAE1D,wBAAgB,WAAW,CAAC,GAAG,GAAE,SAA6B;eAEvC,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI;uBAC3B,OAAO;;EAQ7B;AAED,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAA;AAE1D,oFAAoF;AACpF,wBAAgB,YAAY,IAAI,aAAa,CAQ5C"}
@@ -0,0 +1,28 @@
1
+ // The UI iframe shim: `popcraft.postMessage(msg)` talks to the main script, and messages arrive both
2
+ // as `popcraft.onmessage` and as window "message" events with `event.data.pluginMessage`
3
+ // (Figma-style, so existing UI code ports over).
4
+ import { createRpcClient } from './rpc.js';
5
+ export function createUiApi(rpc = createRpcClient()) {
6
+ const popcraft = {
7
+ onmessage: null,
8
+ postMessage: (msg) => rpc.send({ kind: 'fromUI', data: msg }),
9
+ close: () => rpc.send({ kind: 'closeUI' }),
10
+ };
11
+ rpc.on('message', (msg) => {
12
+ if (typeof popcraft.onmessage === 'function')
13
+ popcraft.onmessage(msg);
14
+ window.dispatchEvent(new MessageEvent('message', { data: { pluginMessage: msg } }));
15
+ });
16
+ return popcraft;
17
+ }
18
+ /** Called by the generated sandbox document — not part of the authoring surface. */
19
+ export function installUiApi() {
20
+ const rpc = createRpcClient();
21
+ const api = createUiApi(rpc);
22
+ const w = window;
23
+ w.__popcraftRpc = rpc;
24
+ w.popcraft = api;
25
+ w.vizpops = api; // plugins written before the rename
26
+ return api;
27
+ }
28
+ //# sourceMappingURL=ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/runtime/ui.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,yFAAyF;AACzF,iDAAiD;AAEjD,OAAO,EAAE,eAAe,EAAkB,MAAM,UAAU,CAAA;AAE1D,MAAM,UAAU,WAAW,CAAC,MAAiB,eAAe,EAAE;IAC5D,MAAM,QAAQ,GAAG;QACf,SAAS,EAAE,IAAmC;QAC9C,WAAW,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QACtE,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;KAC3C,CAAA;IACD,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,EAAE;QACjC,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,UAAU;YAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QACrE,MAAM,CAAC,aAAa,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IACF,OAAO,QAAQ,CAAA;AACjB,CAAC;AAKD,oFAAoF;AACpF,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,eAAe,EAAE,CAAA;IAC7B,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,CAAC,GAAG,MAA4C,CAAA;IACtD,CAAC,CAAC,aAAa,GAAG,GAAG,CAAA;IACrB,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAA;IAChB,CAAC,CAAC,OAAO,GAAG,GAAG,CAAA,CAAC,oCAAoC;IACpD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,107 @@
1
+ /** What a plugin may do. `notify`, `showUI` and per-plugin storage are always allowed. */
2
+ export type PopCraftPermission = 'read' | 'write' | 'export' | 'network' | 'comments';
3
+ export declare const ALL_PERMISSIONS: readonly PopCraftPermission[];
4
+ export type PopCraftNodeType = 'RECTANGLE' | 'ELLIPSE' | 'POLYGON' | 'STAR' | 'LINE' | 'ARROW' | 'FRAME' | 'SECTION' | 'TEXT' | 'STICKY' | 'VECTOR' | 'WIDGET';
5
+ export interface PopCraftCommand {
6
+ /** Passed to the plugin as `popcraft.command`. */
7
+ id: string;
8
+ label: string;
9
+ }
10
+ /** A UI theme file referenced from `contributes.themes`. */
11
+ export interface PopCraftTheme {
12
+ type: 'dark' | 'light';
13
+ extends?: string;
14
+ tokens?: Record<string, string>;
15
+ css?: string;
16
+ }
17
+ export interface PopCraftThemeContribution {
18
+ /** Theme id; must start with the plugin id (`my.plugin.dark`). */
19
+ id: string;
20
+ label: string;
21
+ /** Path of the theme JSON inside the package. */
22
+ path: string;
23
+ }
24
+ /** Static contributions that need no code: they load without running the plugin sandbox. */
25
+ export interface PopCraftContributions {
26
+ themes?: PopCraftThemeContribution[];
27
+ }
28
+ export interface PopCraftManifest {
29
+ id: string;
30
+ name: string;
31
+ version: string;
32
+ description?: string;
33
+ author?: string;
34
+ /** Path of the main script inside the package. Optional for data-only plugins (e.g. theme packs). */
35
+ main?: string;
36
+ /** Optional path of the UI HTML inside the package. */
37
+ ui?: string;
38
+ permissions: PopCraftPermission[];
39
+ /** Menu commands. Defaults to a single "Run" command. */
40
+ commands?: PopCraftCommand[];
41
+ contributes?: PopCraftContributions;
42
+ /** Marketplace listing; all optional, editable later by the publisher. */
43
+ icon?: string;
44
+ screenshots?: string[];
45
+ category?: string;
46
+ tags?: string[];
47
+ homepage?: string;
48
+ }
49
+ export interface PopCraftNode {
50
+ id: string;
51
+ type: PopCraftNodeType;
52
+ name: string;
53
+ x: number;
54
+ y: number;
55
+ width: number;
56
+ height: number;
57
+ rotation?: number;
58
+ opacity?: number;
59
+ visible?: boolean;
60
+ locked?: boolean;
61
+ parentId?: string | null;
62
+ characters?: string;
63
+ [key: string]: unknown;
64
+ }
65
+ export interface PopCraftFindQuery {
66
+ type?: PopCraftNodeType;
67
+ name?: string;
68
+ }
69
+ export interface PopCraftPage {
70
+ id: string;
71
+ name: string;
72
+ childIds: string[];
73
+ }
74
+ export interface PopCraftPaint {
75
+ type: 'SOLID' | 'GRADIENT_LINEAR' | 'GRADIENT_RADIAL' | 'IMAGE';
76
+ color?: string;
77
+ opacity?: number;
78
+ [key: string]: unknown;
79
+ }
80
+ /** `getVariables` returns the raw collections and variables of the open document. */
81
+ export interface PopCraftVariables {
82
+ collections: unknown[];
83
+ variables: unknown[];
84
+ }
85
+ export type PopCraftExportFormat = 'PNG' | 'JPG' | 'WEBP' | 'SVG' | 'PDF';
86
+ export interface PopCraftExportOptions {
87
+ format?: PopCraftExportFormat;
88
+ /** Pixel scale for raster formats. */
89
+ scale?: number;
90
+ }
91
+ export interface PopCraftShowUIOptions {
92
+ width?: number;
93
+ height?: number;
94
+ title?: string;
95
+ visible?: boolean;
96
+ }
97
+ /** Events a plugin can subscribe to with `popcraft.on`. */
98
+ export interface PopCraftEvents {
99
+ selectionchange: {
100
+ ids: string[];
101
+ };
102
+ pagechange: {
103
+ id: string;
104
+ };
105
+ documentchange: void;
106
+ }
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ // The plugin contract: what a manifest may declare and what the `popcraft` global hands back.
2
+ // These types are the published API — the runtime in ./runtime implements them, and the ambient
3
+ // `popcraft` global is derived from that implementation, never written by hand.
4
+ export const ALL_PERMISSIONS = ['read', 'write', 'export', 'network', 'comments'];
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,gGAAgG;AAChG,gFAAgF;AAUhF,MAAM,CAAC,MAAM,eAAe,GAAkC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAA"}
@@ -0,0 +1,70 @@
1
+ export type PopCraftWidgetColor = string;
2
+ export type PopCraftWidgetHandler<State> = (state: State, ctx: {
3
+ nodeId: string;
4
+ }) => State | void | Promise<State | void>;
5
+ export interface PopCraftWidgetBase<State> {
6
+ /** A function (keyed by its position in the tree) or the name of one in `handlers`. */
7
+ onClick?: PopCraftWidgetHandler<State> | (() => void) | string;
8
+ /** Tooltip, also used as the accessible name. */
9
+ tooltip?: string;
10
+ }
11
+ export interface PopCraftWidgetAutoLayout<State> extends PopCraftWidgetBase<State> {
12
+ type: 'AutoLayout';
13
+ direction?: 'horizontal' | 'vertical';
14
+ spacing?: number;
15
+ padding?: number | {
16
+ top?: number;
17
+ right?: number;
18
+ bottom?: number;
19
+ left?: number;
20
+ horizontal?: number;
21
+ vertical?: number;
22
+ };
23
+ /** A fixed size, or 'hug' (the default) to fit the children. */
24
+ width?: number | 'hug';
25
+ height?: number | 'hug';
26
+ horizontalAlign?: 'start' | 'center' | 'end';
27
+ verticalAlign?: 'start' | 'center' | 'end';
28
+ fill?: PopCraftWidgetColor;
29
+ stroke?: PopCraftWidgetColor;
30
+ strokeWidth?: number;
31
+ cornerRadius?: number;
32
+ /** Falsy children are dropped, so `cond && {…}` works. */
33
+ children?: (PopCraftWidgetElement<State> | null | false | undefined)[];
34
+ }
35
+ export interface PopCraftWidgetText<State> extends PopCraftWidgetBase<State> {
36
+ type: 'Text';
37
+ characters: string;
38
+ fontSize?: number;
39
+ fontWeight?: number;
40
+ fill?: PopCraftWidgetColor;
41
+ width?: number;
42
+ }
43
+ export interface PopCraftWidgetShape<State> extends PopCraftWidgetBase<State> {
44
+ type: 'Rectangle' | 'Ellipse';
45
+ width: number;
46
+ height: number;
47
+ fill?: PopCraftWidgetColor;
48
+ stroke?: PopCraftWidgetColor;
49
+ strokeWidth?: number;
50
+ cornerRadius?: number;
51
+ }
52
+ export type PopCraftWidgetElement<State = any> = PopCraftWidgetAutoLayout<State> | PopCraftWidgetText<State> | PopCraftWidgetShape<State>;
53
+ export interface PopCraftWidgetDefinition<State extends object = any> {
54
+ /** Starting state for a newly inserted widget. A function is called per insert. */
55
+ initialState?: State | (() => State);
56
+ render(state: State): PopCraftWidgetElement<State>;
57
+ /** Named handlers, for elements whose `onClick` is a string. */
58
+ handlers?: Record<string, PopCraftWidgetHandler<State>>;
59
+ }
60
+ /**
61
+ * The serialised tree as it is stored on the node: `onClick` functions have been replaced by the
62
+ * handler key the host sends back on a click. This is what the editor renders.
63
+ */
64
+ export interface PopCraftSerialisedWidget {
65
+ type: 'AutoLayout' | 'Text' | 'Rectangle' | 'Ellipse';
66
+ onClick?: string;
67
+ children?: PopCraftSerialisedWidget[];
68
+ [key: string]: unknown;
69
+ }
70
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"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"}
package/dist/widget.js ADDED
@@ -0,0 +1,8 @@
1
+ // Widget element tree. A widget is an interactive canvas object: `render(state)` returns this tree,
2
+ // and both the tree and the state are stored on the WIDGET node, so everyone in the file sees the
3
+ // widget even without the plugin installed. Clicking an element with `onClick` runs the plugin (in
4
+ // the background if needed); the handler mutates or returns the next state and the widget re-renders.
5
+ //
6
+ // Colours are hex strings: '#rgb', '#rrggbb' or '#rrggbbaa'.
7
+ export {};
8
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,kGAAkG;AAClG,mGAAmG;AACnG,sGAAsG;AACtG,EAAE;AACF,6DAA6D"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@popcraft/plugins",
3
+ "version": "1.0.0",
4
+ "description": "The PopCraft plugin SDK: the sandbox runtime the editor injects, and the types plugin authors build against.",
5
+ "license": "MIT",
6
+ "devDependencies": {
7
+ "esbuild": "^0.25.0",
8
+ "typescript": "^5.6.0",
9
+ "@popcraft/plugins": "link:."
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "LICENSE",
24
+ "README.md"
25
+ ],
26
+ "keywords": [
27
+ "popcraft",
28
+ "plugin",
29
+ "sdk",
30
+ "design",
31
+ "editor"
32
+ ],
33
+ "homepage": "https://popcraft.app/docs/plugins",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/getpopcraft/plugins.git"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "node scripts/build.mjs && tsc -p tsconfig.json && node scripts/copy-global.mjs",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.examples.json"
44
+ }
45
+ }