@openpen/module-api 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js +246 -0
  3. package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js.map +1 -0
  4. package/dist/_chunks/inject-keys-CRXCpN-M.js +196 -0
  5. package/dist/_chunks/inject-keys-CRXCpN-M.js.map +1 -0
  6. package/dist/_chunks/module-context-registry-Cn1IWYu9.js +16 -0
  7. package/dist/_chunks/module-context-registry-Cn1IWYu9.js.map +1 -0
  8. package/dist/_chunks/passthrough-DKU7JYuQ.js +6 -0
  9. package/dist/_chunks/passthrough-DKU7JYuQ.js.map +1 -0
  10. package/dist/cursors.d.ts +83 -0
  11. package/dist/define-module.d.ts +17 -0
  12. package/dist/host/color-utils.d.ts +11 -0
  13. package/{src/host/event-bus.ts → dist/host/event-bus.d.ts} +7 -5
  14. package/dist/host/host-commands.d.ts +1 -0
  15. package/dist/host/index.d.ts +24 -0
  16. package/dist/host/index.js +51 -0
  17. package/dist/host/index.js.map +1 -0
  18. package/{src/host/module-context-registry.ts → dist/host/module-context-registry.d.ts} +5 -19
  19. package/dist/host/passthrough.d.ts +2 -0
  20. package/dist/host/popup-anchor.d.ts +3 -0
  21. package/dist/host/registry.d.ts +39 -0
  22. package/dist/host/registry.js +37 -0
  23. package/dist/host/registry.js.map +1 -0
  24. package/dist/host/slot-entries.d.ts +2 -0
  25. package/dist/host/stroke-store.d.ts +6 -0
  26. package/dist/host/stroke-style.d.ts +3 -0
  27. package/dist/host/types.d.ts +176 -0
  28. package/dist/index.d.ts +28 -0
  29. package/dist/index.js +349 -0
  30. package/dist/index.js.map +1 -0
  31. package/{src/inject-keys.ts → dist/inject-keys.d.ts} +37 -66
  32. package/{src/locale.ts → dist/locale.d.ts} +3 -22
  33. package/dist/slots.d.ts +57 -0
  34. package/dist/types/contributions.d.ts +288 -0
  35. package/dist/types/control-bar-layout.d.ts +227 -0
  36. package/dist/types/module.d.ts +208 -0
  37. package/{src/types/notification.ts → dist/types/notification.d.ts} +12 -14
  38. package/dist/types/tool.d.ts +77 -0
  39. package/dist/uikit/components/AppBanner.vue.d.ts +34 -0
  40. package/dist/uikit/components/AppButton.vue.d.ts +30 -0
  41. package/dist/uikit/components/AppButtonDropdown.vue.d.ts +52 -0
  42. package/dist/uikit/components/AppDialog.vue.d.ts +53 -0
  43. package/dist/uikit/components/AppPopover.vue.d.ts +43 -0
  44. package/dist/uikit/components/AppSegmented.vue.d.ts +19 -0
  45. package/dist/uikit/components/AppSelect.vue.d.ts +26 -0
  46. package/dist/uikit/components/AppSlider.vue.d.ts +43 -0
  47. package/dist/uikit/components/AppTabs.vue.d.ts +35 -0
  48. package/dist/uikit/components/AppToggle.vue.d.ts +15 -0
  49. package/dist/uikit/components/AppTooltip.vue.d.ts +27 -0
  50. package/dist/uikit/components/DialogHost.vue.d.ts +3 -0
  51. package/{src/uikit/composables/dialog-internal-keys.ts → dist/uikit/composables/dialog-internal-keys.d.ts} +3 -5
  52. package/dist/uikit/composables/useDialog.d.ts +399 -0
  53. package/dist/uikit/composables/useDialogPluginComponent.d.ts +9 -0
  54. package/dist/uikit/index.d.ts +42 -0
  55. package/dist/uikit/index.js +898 -0
  56. package/dist/uikit/index.js.map +1 -0
  57. package/{src/uikit/internal.ts → dist/uikit/internal.d.ts} +1 -1
  58. package/dist/uikit/internal.js +157 -0
  59. package/dist/uikit/internal.js.map +1 -0
  60. package/dist/uikit/module-api.css +851 -0
  61. package/dist/uikit/primitives.d.ts +42 -0
  62. package/{src/use-module-context.ts → dist/use-module-context.d.ts} +2 -15
  63. package/{src/validation.ts → dist/validation.d.ts} +2 -6
  64. package/package.json +28 -14
  65. package/src/cursors.ts +0 -369
  66. package/src/define-module.ts +0 -55
  67. package/src/host/color-utils.ts +0 -18
  68. package/src/host/host-commands.ts +0 -9
  69. package/src/host/index.ts +0 -53
  70. package/src/host/passthrough.ts +0 -10
  71. package/src/host/popup-anchor.ts +0 -14
  72. package/src/host/slot-entries.ts +0 -9
  73. package/src/host/stroke-store.ts +0 -20
  74. package/src/host/stroke-style.ts +0 -15
  75. package/src/index.ts +0 -131
  76. package/src/slots.ts +0 -135
  77. package/src/types/contributions.ts +0 -333
  78. package/src/types/control-bar-layout.ts +0 -174
  79. package/src/types/module.ts +0 -225
  80. package/src/types/tool.ts +0 -82
  81. package/src/uikit/components/.gitkeep +0 -0
  82. package/src/uikit/components/AppBanner.vue +0 -131
  83. package/src/uikit/components/AppButton.vue +0 -135
  84. package/src/uikit/components/AppDialog.vue +0 -393
  85. package/src/uikit/components/AppPopover.vue +0 -268
  86. package/src/uikit/components/AppSegmented.vue +0 -128
  87. package/src/uikit/components/AppSelect.vue +0 -235
  88. package/src/uikit/components/AppSlider.vue +0 -204
  89. package/src/uikit/components/AppTabs.vue +0 -163
  90. package/src/uikit/components/AppToggle.vue +0 -96
  91. package/src/uikit/components/AppTooltip.vue +0 -118
  92. package/src/uikit/components/DialogHost.vue +0 -272
  93. package/src/uikit/composables/useDialog.ts +0 -141
  94. package/src/uikit/composables/useDialogPluginComponent.ts +0 -27
  95. package/src/uikit/index.ts +0 -79
  96. package/src/uikit/primitives.ts +0 -150
  97. /package/{src → dist}/uikit/tokens.css +0 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Cursor compilation + sanitisation helpers shared by host runtime and
3
+ * plugin SDK.
4
+ *
5
+ * Three closure layers gate every cursor entry before it reaches
6
+ * `v-html`: R1–R12 shape validation here, DOMPurify content sanitisation
7
+ * inside compileCursor, and a deep-clone + freeze in the contribution
8
+ * store. Removing any one of them re-opens an attack surface the others
9
+ * cannot close.
10
+ */
11
+ import type { CursorContribution, CursorSpec, Hotspot } from './types/contributions';
12
+ export interface CompiledCursor {
13
+ /** Sanitised SVG / HTML fragment to mount as `innerHTML` on the cursor element. */
14
+ svgMarkup: string;
15
+ /** Hotspot offset, in CSS pixels relative to the markup's top-left. */
16
+ hotspot: Hotspot;
17
+ /** CSS keyword fallback retained for legacy callers. */
18
+ fallbackCssKeyword: string;
19
+ }
20
+ export interface CursorResolutionContext {
21
+ /**
22
+ * Hostname-encoded plugin id (`@acme/x` → `acme.x`) used to assemble
23
+ * `openpen-plugin://<hostname>/<path>` URLs for relative-path specs.
24
+ * Undefined for built-in modules — path-form specs without a hostname
25
+ * resolve to `null` (host falls back to its default cursor).
26
+ */
27
+ pluginHostname?: string;
28
+ /** Injection point for tests; defaults to global `fetch`. */
29
+ fetchImpl?: typeof fetch;
30
+ }
31
+ /**
32
+ * W3C CSS UI Level 3 cursor keywords. The legacy `cursor: string` form
33
+ * MUST match one of these (case-insensitive after trim); everything else
34
+ * is rejected by R2 / R12.
35
+ */
36
+ export declare const SAFE_CURSOR_KEYWORDS: ReadonlySet<string>;
37
+ export declare function isSafeRelativePath(p: unknown): p is string;
38
+ export declare function pluginHostname(moduleId: string): string;
39
+ export declare function pluginAssetUrl(moduleId: string, relativePath: string): string;
40
+ /**
41
+ * Sanitise SVG markup before it reaches `v-html`. Rationale for each
42
+ * flag: `USE_PROFILES: { svg: true }` covers script tags, on* handlers,
43
+ * and javascript: hrefs; `FORBID_TAGS: ['foreignObject']` closes the
44
+ * mXSS escape hatch the SVG profile alone does not; turning off
45
+ * `ALLOW_UNKNOWN_PROTOCOLS` blocks `<use href="data:...">` and friends.
46
+ */
47
+ export declare function sanitizeSvgMarkup(raw: string): string;
48
+ /**
49
+ * Convert a frozen, shape-validated CursorContribution.cursor into render
50
+ * data ready for `v-html`. Returns `null` for legacy keyword strings
51
+ * (caller falls back to the host default cursor) and for any spec the
52
+ * sanitiser empties out completely.
53
+ *
54
+ * Async because path-form SVG specs are fetched through `openpen-plugin://`
55
+ * at compile time; inline + PNG branches resolve synchronously inside the
56
+ * returned Promise.
57
+ */
58
+ export declare function compileCursor(spec: CursorSpec, context?: CursorResolutionContext): Promise<CompiledCursor | null>;
59
+ export type CursorSanitizeRule = 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12';
60
+ export interface CursorSanitizeDiagnostic {
61
+ /** Contribution id, or `'<unknown>'` if `id` was missing / invalid. */
62
+ id: string;
63
+ rule: CursorSanitizeRule;
64
+ reason: string;
65
+ }
66
+ export interface CursorSanitizeOptions {
67
+ /**
68
+ * Built-in modules (host bundle) cannot use path forms because they
69
+ * have no `openpen-plugin://<hostname>` namespace. R7 / R10 reject
70
+ * such contributions for built-ins.
71
+ */
72
+ isBuiltin?: boolean;
73
+ }
74
+ export interface CursorSanitizeResult {
75
+ cleaned: CursorContribution[];
76
+ diagnostics: CursorSanitizeDiagnostic[];
77
+ }
78
+ /**
79
+ * Shape-validate raw cursor contributions before they reach the
80
+ * contribution-store. This is the R1–R12 gate; SVG markup content is
81
+ * sanitised later inside {@link compileCursor} via DOMPurify.
82
+ */
83
+ export declare function sanitizeCursorContributions(cursors: unknown, options?: CursorSanitizeOptions): CursorSanitizeResult;
@@ -0,0 +1,17 @@
1
+ import type { OpenPenModule } from './types/module';
2
+ /**
3
+ * Identity helper for declaring an OpenPen module with full type
4
+ * inference and runtime sanity checks. Throws `Error` (preflight) when:
5
+ *
6
+ * - `id` is missing or has invalid characters
7
+ * - `contributes` is missing entirely
8
+ * - `contributes` has no contribution fields
9
+ * - `contributes` references a slot key not in the catalogue
10
+ *
11
+ * This is intentionally a small surface — full validation
12
+ * (id collisions across modules, slot quota, settingsSchema parse, etc.)
13
+ * happens later in the host app's `module-validator`. This helper
14
+ * catches the obvious developer mistakes at the module's own boundary
15
+ * so errors surface in the module's repo, not deep inside OpenPen.
16
+ */
17
+ export declare function defineModule<TModule extends OpenPenModule>(spec: TModule): TModule;
@@ -0,0 +1,11 @@
1
+ import type { ModuleHost } from './types';
2
+ type Utils = ModuleHost['colorUtils'];
3
+ export declare const resolveColorStyle: Utils['resolveColorStyle'];
4
+ export declare const hexToRgb: Utils['hexToRgb'];
5
+ export declare const rgbToHex: Utils['rgbToHex'];
6
+ export declare const hsvToRgb: Utils['hsvToRgb'];
7
+ export declare const rgbToHsv: Utils['rgbToHsv'];
8
+ export declare const hexToHsv: Utils['hexToHsv'];
9
+ export declare const hsvToHex: Utils['hsvToHex'];
10
+ export declare const isValidHex: Utils['isValidHex'];
11
+ export {};
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * @openpen/module-api/host — Domain Event Bus
3
3
  *
4
- * Re-exports the renderer-side event-bus emit/on so module components
5
- * can publish and subscribe without reaching into host runtime internals.
6
- * The bus is a process-local pub/sub for cross-module coordination.
4
+ * Proxies the renderer-side event-bus emit/on through the host registry so
5
+ * module components can publish and subscribe without reaching into host
6
+ * runtime internals. The bus is a process-local pub/sub for cross-module
7
+ * coordination.
7
8
  *
8
9
  * **Host-emitted events (consume via `on(name, cb)`):**
9
10
  *
@@ -33,5 +34,6 @@
33
34
  * }
34
35
  * ```
35
36
  */
36
-
37
- export { emit, on } from '../../../../src/core/runtime/event-bus'
37
+ import type { ModuleHost } from './types';
38
+ export declare const emit: ModuleHost['emit'];
39
+ export declare const on: ModuleHost['on'];
@@ -0,0 +1 @@
1
+ export declare const hostCommands: import("./types").HostCommandsAPI;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @openpen/module-api/host — Host Services Sub-module
3
+ *
4
+ * Exposes host shared services to module components via a clean sub-path
5
+ * export. Module code MUST import from here instead of using cross-layer
6
+ * '../../../' relative imports into src/composables or src/services.
7
+ *
8
+ * Module code MUST import from `@openpen/module-api/host` instead of
9
+ * using cross-layer relative imports into src/.
10
+ *
11
+ * Design principle: this sub-module MUST NOT leak host internal
12
+ * implementation types. All types are OpenPen SDK types (from ../types/).
13
+ */
14
+ export { useStrokeStyle } from './stroke-style';
15
+ export type { StrokeColor } from './stroke-style';
16
+ export { resolveColorStyle, hexToRgb, rgbToHex, hsvToRgb, rgbToHsv, hexToHsv, hsvToHex, isValidHex, } from './color-utils';
17
+ export { getAllStrokes, removeStrokeById, pushCommand, commitStroke } from './stroke-store';
18
+ export type { Stroke } from './stroke-store';
19
+ export { usePopupAnchor, calculatePopupAnchor } from './popup-anchor';
20
+ export { usePassthroughGuard } from './passthrough';
21
+ export { hostCommands } from './host-commands';
22
+ export { emit, on } from './event-bus';
23
+ export { getSlotEntries } from './slot-entries';
24
+ export { setModuleContext, clearModuleContext } from './module-context-registry';
@@ -0,0 +1,51 @@
1
+ import { _useHost } from "./registry.js";
2
+ import { u } from "../_chunks/passthrough-DKU7JYuQ.js";
3
+ import { c, s } from "../_chunks/module-context-registry-Cn1IWYu9.js";
4
+ const useStrokeStyle = () => _useHost().useStrokeStyle();
5
+ const resolveColorStyle = (ctx, color, startPoint, endPoint) => _useHost().colorUtils.resolveColorStyle(ctx, color, startPoint, endPoint);
6
+ const hexToRgb = (hex) => _useHost().colorUtils.hexToRgb(hex);
7
+ const rgbToHex = (r, g, b) => _useHost().colorUtils.rgbToHex(r, g, b);
8
+ const hsvToRgb = (h, s2, v) => _useHost().colorUtils.hsvToRgb(h, s2, v);
9
+ const rgbToHsv = (r, g, b) => _useHost().colorUtils.rgbToHsv(r, g, b);
10
+ const hexToHsv = (hex) => _useHost().colorUtils.hexToHsv(hex);
11
+ const hsvToHex = (h, s2, v) => _useHost().colorUtils.hsvToHex(h, s2, v);
12
+ const isValidHex = (hex) => _useHost().colorUtils.isValidHex(hex);
13
+ const getAllStrokes = () => _useHost().getAllStrokes();
14
+ const removeStrokeById = (id) => _useHost().removeStrokeById(id);
15
+ const pushCommand = (command) => _useHost().pushCommand(command);
16
+ const commitStroke = (stroke) => _useHost().commitStroke(stroke);
17
+ const usePopupAnchor = (options) => _useHost().usePopupAnchor(options);
18
+ const calculatePopupAnchor = (options) => _useHost().calculatePopupAnchor(options);
19
+ const hostCommands = new Proxy({}, {
20
+ get(_target, prop) {
21
+ return Reflect.get(_useHost().hostCommands, prop);
22
+ }
23
+ });
24
+ const emit = (event, payload) => _useHost().emit(event, payload);
25
+ const on = (event, handler) => _useHost().on(event, handler);
26
+ const getSlotEntries = (slotKey) => _useHost().getSlotEntries(slotKey);
27
+ export {
28
+ calculatePopupAnchor,
29
+ c as clearModuleContext,
30
+ commitStroke,
31
+ emit,
32
+ getAllStrokes,
33
+ getSlotEntries,
34
+ hexToHsv,
35
+ hexToRgb,
36
+ hostCommands,
37
+ hsvToHex,
38
+ hsvToRgb,
39
+ isValidHex,
40
+ on,
41
+ pushCommand,
42
+ removeStrokeById,
43
+ resolveColorStyle,
44
+ rgbToHex,
45
+ rgbToHsv,
46
+ s as setModuleContext,
47
+ u as usePassthroughGuard,
48
+ usePopupAnchor,
49
+ useStrokeStyle
50
+ };
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/host/stroke-style.ts","../../src/host/color-utils.ts","../../src/host/stroke-store.ts","../../src/host/popup-anchor.ts","../../src/host/host-commands.ts","../../src/host/event-bus.ts","../../src/host/slot-entries.ts"],"sourcesContent":["/**\n * @openpen/module-api/host — Stroke Style\n *\n * Proxies the useStrokeStyle composable through the host registry so\n * module components can read + write the global stroke style without\n * reaching past the package boundary. The reactive refs returned by\n * useStrokeStyle stay live across the proxy because the host registry\n * returns the singleton refs the host owns.\n *\n * initStrokeStyleFromSettings is host-boot-only and intentionally\n * NOT exposed here.\n */\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\nexport const useStrokeStyle: ModuleHost['useStrokeStyle'] = () =>\n _useHost().useStrokeStyle()\n\n// Re-export the StrokeColor type via the public SDK types (not host internals).\nexport type { StrokeColor } from '../types/tool'\n","/**\n * @openpen/module-api/host — Color Utilities\n *\n * Proxies color manipulation helpers through the host registry. These\n * helpers are pure functions (no host state), so a future refactor MAY\n * lift the implementations into this package itself; for now they flow\n * through injection for consistency with the rest of the host surface.\n */\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\ntype Utils = ModuleHost['colorUtils']\n\nexport const resolveColorStyle: Utils['resolveColorStyle'] = (ctx, color, startPoint, endPoint) =>\n _useHost().colorUtils.resolveColorStyle(ctx, color, startPoint, endPoint)\n\nexport const hexToRgb: Utils['hexToRgb'] = (hex) =>\n _useHost().colorUtils.hexToRgb(hex)\n\nexport const rgbToHex: Utils['rgbToHex'] = (r, g, b) =>\n _useHost().colorUtils.rgbToHex(r, g, b)\n\nexport const hsvToRgb: Utils['hsvToRgb'] = (h, s, v) =>\n _useHost().colorUtils.hsvToRgb(h, s, v)\n\nexport const rgbToHsv: Utils['rgbToHsv'] = (r, g, b) =>\n _useHost().colorUtils.rgbToHsv(r, g, b)\n\nexport const hexToHsv: Utils['hexToHsv'] = (hex) =>\n _useHost().colorUtils.hexToHsv(hex)\n\nexport const hsvToHex: Utils['hsvToHex'] = (h, s, v) =>\n _useHost().colorUtils.hsvToHex(h, s, v)\n\nexport const isValidHex: Utils['isValidHex'] = (hex) =>\n _useHost().colorUtils.isValidHex(hex)\n","/**\n * @openpen/module-api/host — Stroke Store\n *\n * Proxies the stroke store read + history APIs through the host registry.\n * The exposed surface:\n * - getAllStrokes / removeStrokeById / pushCommand: read + history access\n * required by stroke-eraser (and similar tools).\n * - commitStroke: atomic add + ADD_STROKE history + redraw for async-placement\n * tools (e.g. text) that finalise a stroke outside the synchronous\n * onPointerUp return. Not idempotent — calling it twice appends twice.\n *\n * Host-internal state mutation helpers (clearAll, addStroke, undo, redo)\n * are intentionally NOT exposed here — modules must use hostCommands for\n * undo/redo and canvas clear.\n */\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\nexport const getAllStrokes: ModuleHost['getAllStrokes'] = () =>\n _useHost().getAllStrokes()\n\nexport const removeStrokeById: ModuleHost['removeStrokeById'] = (id) =>\n _useHost().removeStrokeById(id)\n\nexport const pushCommand: ModuleHost['pushCommand'] = (command) =>\n _useHost().pushCommand(command)\n\nexport const commitStroke: ModuleHost['commitStroke'] = (stroke) =>\n _useHost().commitStroke(stroke)\n\n// Re-export Stroke type via the public SDK types, not host internals.\nexport type { Stroke } from '../types/tool'\n","/**\n * @openpen/module-api/host — Popup Anchor\n *\n * Proxies the `usePopupAnchor` composable and the `calculatePopupAnchor`\n * pure helper through the host registry so module components can position\n * popups relative to anchor elements without reaching past the package\n * boundary.\n */\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\nexport const usePopupAnchor: ModuleHost['usePopupAnchor'] = (options) =>\n _useHost().usePopupAnchor(options)\n\nexport const calculatePopupAnchor: ModuleHost['calculatePopupAnchor'] = (options) =>\n _useHost().calculatePopupAnchor(options)\n","/**\n * @openpen/module-api/host — Host Commands\n *\n * Proxies hostCommands through the host registry so module components\n * can trigger host infrastructure actions (pin toggle, canvas clear,\n * undo/redo, drawing mode toggle) without reaching past the package\n * boundary. The exported object is a Proxy so every property access\n * fetches the live `hostCommands` from the registered host.\n */\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\nexport const hostCommands = new Proxy({} as ModuleHost['hostCommands'], {\n get(_target, prop) {\n return Reflect.get(_useHost().hostCommands as object, prop)\n },\n})\n","/**\n * @openpen/module-api/host — Domain Event Bus\n *\n * Proxies the renderer-side event-bus emit/on through the host registry so\n * module components can publish and subscribe without reaching into host\n * runtime internals. The bus is a process-local pub/sub for cross-module\n * coordination.\n *\n * **Host-emitted events (consume via `on(name, cb)`):**\n *\n * | Event name | Payload shape | Fires when |\n * |---------------------------|-------------------------------------------------|----------------------------------------------------------------------|\n * | `'tool-changed'` | `{ tool: string } & Partial<ToolConfig>` | The active drawing tool changes (user click, IPC, or shortcut) |\n * | `'stroke-style-changed'` | `{ color?: StrokeColor; lineWidth?: number }` | Color or stroke width changes |\n * | `'canvas-redraw-requested'` | (no payload) | A module wants the canvas to repaint (eraser, transformer, etc.) |\n *\n * **Module-emitted events:** modules MAY emit any event name they\n * choose; the bus is namespace-free. Use a prefix (e.g. `'my-plugin:'`)\n * to avoid colliding with another module's events.\n *\n * `on(name, cb)` returns an unsubscribe function — register it via\n * `ctx.onDispose(unsubscribe)` so the listener is cleaned up when the\n * module unloads.\n *\n * @example\n * ```ts\n * import { on } from '@openpen/module-api/host'\n *\n * setup(ctx) {\n * const off = on('tool-changed', (payload) => {\n * console.log('tool now', payload.tool)\n * })\n * ctx.onDispose(off)\n * }\n * ```\n */\n\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\nexport const emit: ModuleHost['emit'] = (event, payload) =>\n _useHost().emit(event, payload)\n\nexport const on: ModuleHost['on'] = (event, handler) =>\n _useHost().on(event, handler)\n","/**\n * @openpen/module-api/host — Slot Entries Reader\n *\n * Proxies getSlotEntries through the host registry so module components\n * can render UI driven by other modules' contributions to the same slot\n * (for example, the shape-picker rendering all canvas.shapes contributions).\n */\nimport { _useHost } from './registry'\nimport type { ModuleHost } from './types'\n\nexport const getSlotEntries: ModuleHost['getSlotEntries'] = <T = unknown>(slotKey: string) =>\n _useHost().getSlotEntries<T>(slotKey)\n"],"names":["s"],"mappings":";;;AAeO,MAAM,iBAA+C,MAC1D,SAAA,EAAW,eAAA;ACHN,MAAM,oBAAgD,CAAC,KAAK,OAAO,YAAY,aACpF,WAAW,WAAW,kBAAkB,KAAK,OAAO,YAAY,QAAQ;AAEnE,MAAM,WAA8B,CAAC,QAC1C,WAAW,WAAW,SAAS,GAAG;AAE7B,MAAM,WAA8B,CAAC,GAAG,GAAG,MAChD,WAAW,WAAW,SAAS,GAAG,GAAG,CAAC;AAEjC,MAAM,WAA8B,CAAC,GAAGA,IAAG,MAChD,WAAW,WAAW,SAAS,GAAGA,IAAG,CAAC;AAEjC,MAAM,WAA8B,CAAC,GAAG,GAAG,MAChD,WAAW,WAAW,SAAS,GAAG,GAAG,CAAC;AAEjC,MAAM,WAA8B,CAAC,QAC1C,WAAW,WAAW,SAAS,GAAG;AAE7B,MAAM,WAA8B,CAAC,GAAGA,IAAG,MAChD,WAAW,WAAW,SAAS,GAAGA,IAAG,CAAC;AAEjC,MAAM,aAAkC,CAAC,QAC9C,WAAW,WAAW,WAAW,GAAG;ACjB/B,MAAM,gBAA6C,MACxD,SAAA,EAAW,cAAA;AAEN,MAAM,mBAAmD,CAAC,OAC/D,SAAA,EAAW,iBAAiB,EAAE;AAEzB,MAAM,cAAyC,CAAC,YACrD,SAAA,EAAW,YAAY,OAAO;AAEzB,MAAM,eAA2C,CAAC,WACvD,SAAA,EAAW,aAAa,MAAM;ACjBzB,MAAM,iBAA+C,CAAC,YAC3D,SAAA,EAAW,eAAe,OAAO;AAE5B,MAAM,uBAA2D,CAAC,YACvE,SAAA,EAAW,qBAAqB,OAAO;ACHlC,MAAM,eAAe,IAAI,MAAM,IAAkC;AAAA,EACtE,IAAI,SAAS,MAAM;AACjB,WAAO,QAAQ,IAAI,SAAA,EAAW,cAAwB,IAAI;AAAA,EAC5D;AACF,CAAC;ACwBM,MAAM,OAA2B,CAAC,OAAO,YAC9C,WAAW,KAAK,OAAO,OAAO;AAEzB,MAAM,KAAuB,CAAC,OAAO,YAC1C,WAAW,GAAG,OAAO,OAAO;AClCvB,MAAM,iBAA+C,CAAc,YACxE,SAAA,EAAW,eAAkB,OAAO;"}
@@ -9,26 +9,12 @@
9
9
  * Module code MUST NOT import from this file directly — use
10
10
  * `useModuleContext` from `@openpen/module-api` instead.
11
11
  */
12
- import type { ModuleSetupContext } from '../types/module'
13
-
14
- const registry = new Map<string, ModuleSetupContext>()
15
-
12
+ import type { ModuleSetupContext } from '../types/module';
16
13
  /** Register (or replace) the context for a module. Called by module-loader before setup(). */
17
- export function setModuleContext(moduleId: string, ctx: ModuleSetupContext): void {
18
- registry.set(moduleId, ctx)
19
- }
20
-
14
+ export declare function setModuleContext(moduleId: string, ctx: ModuleSetupContext): void;
21
15
  /** Remove the context for a module. Called by module-loader in rollbackModule(). */
22
- export function clearModuleContext(moduleId: string): void {
23
- registry.delete(moduleId)
24
- }
25
-
16
+ export declare function clearModuleContext(moduleId: string): void;
26
17
  /** Look up the context for a module. Returns undefined if not registered. */
27
- export function getModuleContext(moduleId: string): ModuleSetupContext | undefined {
28
- return registry.get(moduleId)
29
- }
30
-
18
+ export declare function getModuleContext(moduleId: string): ModuleSetupContext | undefined;
31
19
  /** Resets the entire registry. Test-only — do not call in production code. */
32
- export function resetModuleContextRegistryForTest(): void {
33
- registry.clear()
34
- }
20
+ export declare function resetModuleContextRegistryForTest(): void;
@@ -0,0 +1,2 @@
1
+ import type { ModuleHost } from './types';
2
+ export declare const usePassthroughGuard: ModuleHost['usePassthroughGuard'];
@@ -0,0 +1,3 @@
1
+ import type { ModuleHost } from './types';
2
+ export declare const usePopupAnchor: ModuleHost['usePopupAnchor'];
3
+ export declare const calculatePopupAnchor: ModuleHost['calculatePopupAnchor'];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @openpen/module-api/host — Host runtime registry
3
+ *
4
+ * The host (OpenPen renderer process) calls `registerHost(impl)` once at
5
+ * boot to inject its concrete implementation of the `ModuleHost` contract.
6
+ * Module / plugin code consumes the surface through the proxy exports in
7
+ * `./index.ts`, which fetch the registered implementation via `_useHost()`.
8
+ *
9
+ * This file is intentionally **not** re-exported from the package's `.`
10
+ * entry. The renderer host imports it via the `@openpen/module-api/host/registry`
11
+ * subpath; plugin code MUST NOT import from this path.
12
+ *
13
+ * The pattern follows the same dependency direction as VS Code's extension
14
+ * host, Figma's plugin sandbox, Obsidian's plugin API, and Penpot's plugin
15
+ * types: the SDK package owns the contract, the host injects the runtime.
16
+ */
17
+ import type { ModuleHost } from './types';
18
+ /**
19
+ * Inject the host's concrete `ModuleHost` implementation. **MUST** be called
20
+ * exactly once, at app boot in `src/main.ts`, before any module/plugin code
21
+ * imports from `@openpen/module-api/host`.
22
+ *
23
+ * @throws if called more than once.
24
+ */
25
+ export declare function registerHost(impl: ModuleHost): void;
26
+ /**
27
+ * Internal: fetch the registered host implementation. Called by every proxy
28
+ * in `./index.ts`. Plugin / module code MUST NOT import this directly —
29
+ * use the proxies instead.
30
+ *
31
+ * @throws if `registerHost()` has not been called yet (fail-fast: catches
32
+ * boot-order bugs before silent state corruption).
33
+ */
34
+ export declare function _useHost(): ModuleHost;
35
+ /**
36
+ * Test-only: clears the registered host so a fresh `registerHost()` call
37
+ * can be made in the next test. MUST NOT be called from production code.
38
+ */
39
+ export declare function _resetHostRegistryForTest(): void;
@@ -0,0 +1,37 @@
1
+ const HOST_STORE_KEY = Symbol.for("openpen.module-api.host-store");
2
+ function getStore() {
3
+ const g = globalThis;
4
+ let store = g[HOST_STORE_KEY];
5
+ if (!store) {
6
+ store = { registered: null };
7
+ g[HOST_STORE_KEY] = store;
8
+ }
9
+ return store;
10
+ }
11
+ function registerHost(impl) {
12
+ const store = getStore();
13
+ if (store.registered !== null) {
14
+ throw new Error(
15
+ "@openpen/module-api/host: registerHost called more than once. The host MUST register exactly once at boot."
16
+ );
17
+ }
18
+ store.registered = impl;
19
+ }
20
+ function _useHost() {
21
+ const store = getStore();
22
+ if (store.registered === null) {
23
+ throw new Error(
24
+ "@openpen/module-api/host: host not registered. Call registerHost() at app boot (src/main.ts) before any module or plugin code imports from this subpath."
25
+ );
26
+ }
27
+ return store.registered;
28
+ }
29
+ function _resetHostRegistryForTest() {
30
+ getStore().registered = null;
31
+ }
32
+ export {
33
+ _resetHostRegistryForTest,
34
+ _useHost,
35
+ registerHost
36
+ };
37
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sources":["../../src/host/registry.ts"],"sourcesContent":["/**\n * @openpen/module-api/host — Host runtime registry\n *\n * The host (OpenPen renderer process) calls `registerHost(impl)` once at\n * boot to inject its concrete implementation of the `ModuleHost` contract.\n * Module / plugin code consumes the surface through the proxy exports in\n * `./index.ts`, which fetch the registered implementation via `_useHost()`.\n *\n * This file is intentionally **not** re-exported from the package's `.`\n * entry. The renderer host imports it via the `@openpen/module-api/host/registry`\n * subpath; plugin code MUST NOT import from this path.\n *\n * The pattern follows the same dependency direction as VS Code's extension\n * host, Figma's plugin sandbox, Obsidian's plugin API, and Penpot's plugin\n * types: the SDK package owns the contract, the host injects the runtime.\n */\nimport type { ModuleHost } from './types'\n\n/**\n * Module-scope state cannot live as a top-level `let` because production\n * bundles can load this file more than once (main app bundle + runtime\n * uikit bundle each carry their own copy when the host subpath is not\n * externalized in the import-map). Multiple copies of the module each get\n * their own `let registered`, so `registerHost()` writes one slot and\n * `_useHost()` reads the other — both raise the unregistered-host error.\n *\n * Stash the slot on globalThis under a unique Symbol so every copy of this\n * file resolves to the same store. Vue uses the same trick for shared\n * globals across bundlers (see VUE_OPTIONS_API symbol in @vue/runtime-core).\n */\ninterface HostStore {\n registered: ModuleHost | null\n}\n\nconst HOST_STORE_KEY = Symbol.for('openpen.module-api.host-store') as unknown as PropertyKey\n\nfunction getStore(): HostStore {\n type GlobalWithStore = typeof globalThis & { [k: PropertyKey]: HostStore | undefined }\n const g = globalThis as GlobalWithStore\n let store = g[HOST_STORE_KEY]\n if (!store) {\n store = { registered: null }\n g[HOST_STORE_KEY] = store\n }\n return store\n}\n\n/**\n * Inject the host's concrete `ModuleHost` implementation. **MUST** be called\n * exactly once, at app boot in `src/main.ts`, before any module/plugin code\n * imports from `@openpen/module-api/host`.\n *\n * @throws if called more than once.\n */\nexport function registerHost(impl: ModuleHost): void {\n const store = getStore()\n if (store.registered !== null) {\n throw new Error(\n '@openpen/module-api/host: registerHost called more than once. ' +\n 'The host MUST register exactly once at boot.',\n )\n }\n store.registered = impl\n}\n\n/**\n * Internal: fetch the registered host implementation. Called by every proxy\n * in `./index.ts`. Plugin / module code MUST NOT import this directly —\n * use the proxies instead.\n *\n * @throws if `registerHost()` has not been called yet (fail-fast: catches\n * boot-order bugs before silent state corruption).\n */\nexport function _useHost(): ModuleHost {\n const store = getStore()\n if (store.registered === null) {\n throw new Error(\n '@openpen/module-api/host: host not registered. ' +\n 'Call registerHost() at app boot (src/main.ts) before any module ' +\n 'or plugin code imports from this subpath.',\n )\n }\n return store.registered\n}\n\n/**\n * Test-only: clears the registered host so a fresh `registerHost()` call\n * can be made in the next test. MUST NOT be called from production code.\n */\nexport function _resetHostRegistryForTest(): void {\n getStore().registered = null\n}\n"],"names":[],"mappings":"AAkCA,MAAM,iBAAiB,OAAO,IAAI,+BAA+B;AAEjE,SAAS,WAAsB;AAE7B,QAAM,IAAI;AACV,MAAI,QAAQ,EAAE,cAAc;AAC5B,MAAI,CAAC,OAAO;AACV,YAAQ,EAAE,YAAY,KAAA;AACtB,MAAE,cAAc,IAAI;AAAA,EACtB;AACA,SAAO;AACT;AASO,SAAS,aAAa,MAAwB;AACnD,QAAM,QAAQ,SAAA;AACd,MAAI,MAAM,eAAe,MAAM;AAC7B,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAGJ;AACA,QAAM,aAAa;AACrB;AAUO,SAAS,WAAuB;AACrC,QAAM,QAAQ,SAAA;AACd,MAAI,MAAM,eAAe,MAAM;AAC7B,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAIJ;AACA,SAAO,MAAM;AACf;AAMO,SAAS,4BAAkC;AAChD,WAAA,EAAW,aAAa;AAC1B;"}
@@ -0,0 +1,2 @@
1
+ import type { ModuleHost } from './types';
2
+ export declare const getSlotEntries: ModuleHost['getSlotEntries'];
@@ -0,0 +1,6 @@
1
+ import type { ModuleHost } from './types';
2
+ export declare const getAllStrokes: ModuleHost['getAllStrokes'];
3
+ export declare const removeStrokeById: ModuleHost['removeStrokeById'];
4
+ export declare const pushCommand: ModuleHost['pushCommand'];
5
+ export declare const commitStroke: ModuleHost['commitStroke'];
6
+ export type { Stroke } from '../types/tool';
@@ -0,0 +1,3 @@
1
+ import type { ModuleHost } from './types';
2
+ export declare const useStrokeStyle: ModuleHost['useStrokeStyle'];
3
+ export type { StrokeColor } from '../types/tool';
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @openpen/module-api/host — Host interface contract
3
+ *
4
+ * Defines the surface the host (OpenPen renderer process) MUST implement
5
+ * and inject via `registerHost()`. Module / plugin code consumes this surface
6
+ * through the proxy exports in `./index.ts`, never importing this file
7
+ * directly.
8
+ *
9
+ * This file MUST NOT import from outside `packages/module-api/src/`; the
10
+ * point of the contract is that the SDK package owns the shape independent
11
+ * of host implementation paths. Cross-boundary imports here would prevent
12
+ * clean .d.ts emission (TS6059) when the package is built for publishing.
13
+ */
14
+ import type { Ref, ComputedRef } from 'vue';
15
+ import type { Stroke, StrokeColor } from '../types/tool';
16
+ import type { SnapEdge } from '../inject-keys';
17
+ /**
18
+ * Discriminated union for `pushCommand`. Modules wrap their state changes in
19
+ * one of these so the host can undo / redo them through `hostCommands.history`.
20
+ */
21
+ type StrokeAddCommand = {
22
+ type: 'ADD_STROKE';
23
+ stroke: Stroke;
24
+ };
25
+ type StrokeRemoveCommand = {
26
+ type: 'REMOVE_STROKE';
27
+ stroke: Stroke;
28
+ };
29
+ type StrokeClearAllCommand = {
30
+ type: 'CLEAR_ALL';
31
+ strokes: Stroke[];
32
+ };
33
+ export type HistoryCommand = StrokeAddCommand | StrokeRemoveCommand | StrokeClearAllCommand;
34
+ /** Slot-entry shape returned by `getSlotEntries`. */
35
+ export interface ContributionEntry<T = unknown> {
36
+ /** Module that registered this contribution. */
37
+ moduleId: string;
38
+ /** Slot-specific contribution payload (typed by consumer). */
39
+ contribution: T;
40
+ }
41
+ /**
42
+ * Color manipulation helpers. Pure functions, no host state.
43
+ *
44
+ * NOTE: These pure helpers are exposed via injection for symmetry with the
45
+ * rest of the host surface; a later refactor MAY lift them into the package
46
+ * itself once the host-inversion is settled.
47
+ */
48
+ export interface ColorUtilsAPI {
49
+ /**
50
+ * Resolve a `StrokeColor` to a canvas-compatible style value, expanding
51
+ * linear-gradient descriptors via the provided context + endpoints.
52
+ */
53
+ resolveColorStyle: (ctx: CanvasRenderingContext2D, color: StrokeColor, startPoint: {
54
+ x: number;
55
+ y: number;
56
+ }, endPoint: {
57
+ x: number;
58
+ y: number;
59
+ }) => string | CanvasGradient;
60
+ hexToRgb: (hex: string) => [number, number, number];
61
+ rgbToHex: (r: number, g: number, b: number) => string;
62
+ hsvToRgb: (h: number, s: number, v: number) => [number, number, number];
63
+ rgbToHsv: (r: number, g: number, b: number) => [number, number, number];
64
+ hexToHsv: (hex: string) => [number, number, number];
65
+ hsvToHex: (h: number, s: number, v: number) => string;
66
+ isValidHex: (hex: string) => boolean;
67
+ }
68
+ /** Imperative actions modules invoke against host infrastructure. */
69
+ export interface HostCommandsAPI {
70
+ controlBar: {
71
+ togglePin: () => void;
72
+ };
73
+ canvas: {
74
+ clear: () => void;
75
+ };
76
+ history: {
77
+ undo: () => void;
78
+ redo: () => void;
79
+ };
80
+ app: {
81
+ toggleDrawingMode: () => void;
82
+ setDrawingMode: (enabled: boolean) => void;
83
+ };
84
+ }
85
+ /** Return shape of `useStrokeStyle()`. */
86
+ export interface StrokeStyleAPI {
87
+ color: Readonly<Ref<StrokeColor>>;
88
+ lineWidth: Readonly<Ref<number>>;
89
+ setColor: (color: StrokeColor) => void;
90
+ setLineWidth: (w: number) => void;
91
+ }
92
+ export type PopupPlacement = 'below' | 'above' | 'left' | 'right';
93
+ export type PopupArrowDir = 'up' | 'down' | 'left' | 'right';
94
+ type RefOrComputed<T> = Ref<T> | ComputedRef<T>;
95
+ export interface UsePopupAnchorOptions {
96
+ triggerEl: RefOrComputed<HTMLElement | null>;
97
+ anchorEl?: RefOrComputed<HTMLElement | null>;
98
+ wrapperEl: RefOrComputed<HTMLElement | null>;
99
+ snapEdge: RefOrComputed<SnapEdge>;
100
+ popupEl?: RefOrComputed<HTMLElement | null>;
101
+ popupHeight: number | RefOrComputed<number>;
102
+ popupWidth: number | RefOrComputed<number>;
103
+ gap?: number;
104
+ safeMargin?: number;
105
+ arrowInset?: number;
106
+ }
107
+ export interface UsePopupAnchorAPI {
108
+ popupStyle: Readonly<Ref<Record<string, string>>>;
109
+ arrowDir: Readonly<Ref<PopupArrowDir>>;
110
+ arrowOffset: Readonly<Ref<string>>;
111
+ placement: Readonly<Ref<PopupPlacement>>;
112
+ updatePosition: () => void;
113
+ }
114
+ export interface PopupRect {
115
+ top: number;
116
+ left: number;
117
+ right: number;
118
+ bottom: number;
119
+ width: number;
120
+ height: number;
121
+ }
122
+ export interface CalculatePopupAnchorOptions {
123
+ triggerRect: PopupRect;
124
+ anchorRect?: PopupRect;
125
+ wrapperRect: PopupRect;
126
+ snapEdge: SnapEdge;
127
+ popupHeight?: number;
128
+ popupWidth?: number;
129
+ gap?: number;
130
+ arrowInset?: number;
131
+ safeMargin?: number;
132
+ availableSpace?: {
133
+ above: number;
134
+ below: number;
135
+ left: number;
136
+ right: number;
137
+ };
138
+ }
139
+ export interface CalculatePopupAnchorResult {
140
+ placement: PopupPlacement;
141
+ arrowDir: PopupArrowDir;
142
+ arrowOffset: string;
143
+ popupStyle: Record<string, string>;
144
+ }
145
+ /**
146
+ * The shape OpenPen's renderer process implements and registers at boot via
147
+ * `registerHost(impl)`. All `@openpen/module-api/host` exports proxy through
148
+ * `_useHost()` to fetch the registered implementation lazily.
149
+ */
150
+ export interface ModuleHost {
151
+ emit: (event: string, payload?: unknown) => void;
152
+ on: (event: string, handler: (payload: unknown) => void) => () => void;
153
+ getAllStrokes: () => Stroke[];
154
+ removeStrokeById: (id: string) => boolean;
155
+ pushCommand: (command: HistoryCommand) => void;
156
+ /**
157
+ * Atomically commit a finished stroke: append it to the store, record an
158
+ * ADD_STROKE history command, and request a canvas redraw. The commit path
159
+ * for tools that produce strokes outside the synchronous pointer-up return
160
+ * (e.g. async-placement tools that finalise after a deferred interaction).
161
+ *
162
+ * Semantically equivalent to the host's synchronous onPointerUp commit and
163
+ * fully participates in undo/redo. NOT idempotent: calling it twice with the
164
+ * same stroke appends two strokes and two history entries. The stroke shape
165
+ * is the caller's responsibility — v1 is full-trust and performs no sanitize.
166
+ */
167
+ commitStroke: (stroke: Stroke) => void;
168
+ hostCommands: HostCommandsAPI;
169
+ colorUtils: ColorUtilsAPI;
170
+ useStrokeStyle: () => StrokeStyleAPI;
171
+ usePopupAnchor: (options: UsePopupAnchorOptions) => UsePopupAnchorAPI;
172
+ calculatePopupAnchor: (options: CalculatePopupAnchorOptions) => CalculatePopupAnchorResult;
173
+ usePassthroughGuard: (target?: string | Ref<HTMLElement | null>) => void;
174
+ getSlotEntries: <T = unknown>(slotKey: string) => Readonly<Ref<readonly ContributionEntry<T>[]>>;
175
+ }
176
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @openpen/module-api — public contract surface for OpenPen modules.
3
+ *
4
+ * This is the **only** import path that built-in modules and plugins
5
+ * are allowed to use, alongside relative imports, `node:*`, and
6
+ * third-party npm packages. Boundary tests in the OpenPen main repo
7
+ * enforce this rule on `src/core/modules/**` and on plugin code.
8
+ */
9
+ export { defineModule } from './define-module';
10
+ export { useModuleContext } from './use-module-context';
11
+ export { isValidModuleId, MODULE_ID_RE } from './validation';
12
+ export { resolveLabel, sanitizeIdForI18n } from './locale';
13
+ export type { LocaleMap } from './locale';
14
+ export { ALL_SLOTS, V1_ACTIVE_SLOTS, V1_RESERVED_SLOTS, CONTRIBUTION_KEY_TO_SLOT_ID, isKnownSlot, getSlot, } from './slots';
15
+ export type { SlotDefinition, SlotCategory, SlotStatus } from './slots';
16
+ export type { Point, StrokeColor, StrokeStyle, Stroke, PointerModifiers, Tool, } from './types/tool';
17
+ export { resolveStrokeColor } from './types/tool';
18
+ export type { ControlBarLayout, LayoutGroup, GroupInset, ControlBarContribution } from './types/control-bar-layout';
19
+ export { ControlBarLayoutSchema, LayoutGroupSchema, GroupInsetSchema, DEFAULT_CONTROL_BAR_LAYOUT, repairLayoutL3a, repairLayoutL3b, } from './types/control-bar-layout';
20
+ export type { NotifyPayload, NotifyHandle } from './types/notification';
21
+ export type { OpenPenModule, ModuleSetupContext } from './types/module';
22
+ export type { ModuleContributions, ToolContribution, ShapeContribution, StrokeStyleContribution, HistoryCommandContribution, CanvasLayerContribution, HtmlOverlayContribution, StrokeTransformerContribution, SettingsTabContribution, SettingsPanelContribution, CursorContribution, CursorSpec, SvgCursorSpec, PngCursorSpec, Hotspot, StatusContribution, ModalContribution, TrayMenuContribution, ContextMenuContribution, ThemeTokenContribution, ShortcutContribution, WindowBehaviorContribution, LocaleContribution, EventSubscriptionContribution, LifecycleContribution, StorageContribution, MainHandlerContribution, FileDropContribution, } from './types/contributions';
23
+ export { STROKE_STYLE_CONTEXT_KEY, SNAP_EDGE_KEY, IS_VERTICAL_KEY, MODAL_MANAGER_KEY, WRAPPER_EL_KEY, ANCHOR_EL_KEY, CONTROL_BAR_ANIMATING_KEY, TOOLTIP_FLIP_DOWN_KEY, HOST_DIALOG_OPEN_COUNT_KEY, POPOVER_PLACEMENT_HINT_KEY, ACTIVE_TOOL_KEY, } from './inject-keys';
24
+ export type { StrokeStyleContext, SnapEdge, ModalManager, PopoverPlacementHint } from './inject-keys';
25
+ export { compileCursor, sanitizeSvgMarkup, sanitizeCursorContributions, isSafeRelativePath, pluginHostname, pluginAssetUrl, SAFE_CURSOR_KEYWORDS, } from './cursors';
26
+ export type { CompiledCursor, CursorResolutionContext, CursorSanitizeRule, CursorSanitizeDiagnostic, CursorSanitizeOptions, CursorSanitizeResult, } from './cursors';
27
+ export { z } from 'zod';
28
+ export { setModuleContext, clearModuleContext } from './host/module-context-registry';