@openpen/module-api 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 +21 -0
- package/README.md +99 -0
- package/package.json +53 -0
- package/src/define-module.ts +55 -0
- package/src/host/color-utils.ts +18 -0
- package/src/host/event-bus.ts +37 -0
- package/src/host/host-commands.ts +9 -0
- package/src/host/index.ts +53 -0
- package/src/host/module-context-registry.ts +34 -0
- package/src/host/passthrough.ts +10 -0
- package/src/host/popup-anchor.ts +14 -0
- package/src/host/slot-entries.ts +9 -0
- package/src/host/stroke-store.ts +20 -0
- package/src/host/stroke-style.ts +15 -0
- package/src/index.ts +106 -0
- package/src/inject-keys.ts +143 -0
- package/src/locale.ts +52 -0
- package/src/slots.ts +135 -0
- package/src/types/contributions.ts +298 -0
- package/src/types/control-bar-layout.ts +174 -0
- package/src/types/module.ts +225 -0
- package/src/types/notification.ts +40 -0
- package/src/types/tool.ts +82 -0
- package/src/uikit/components/.gitkeep +0 -0
- package/src/uikit/components/AppBanner.vue +131 -0
- package/src/uikit/components/AppButton.vue +135 -0
- package/src/uikit/components/AppDialog.vue +393 -0
- package/src/uikit/components/AppPopover.vue +268 -0
- package/src/uikit/components/AppSegmented.vue +126 -0
- package/src/uikit/components/AppSelect.vue +235 -0
- package/src/uikit/components/AppSlider.vue +204 -0
- package/src/uikit/components/AppTabs.vue +163 -0
- package/src/uikit/components/AppToggle.vue +96 -0
- package/src/uikit/components/AppTooltip.vue +118 -0
- package/src/uikit/components/DialogHost.vue +260 -0
- package/src/uikit/composables/dialog-internal-keys.ts +17 -0
- package/src/uikit/composables/useDialog.ts +141 -0
- package/src/uikit/composables/useDialogPluginComponent.ts +27 -0
- package/src/uikit/index.ts +79 -0
- package/src/uikit/internal.ts +12 -0
- package/src/uikit/primitives.ts +150 -0
- package/src/uikit/tokens.css +205 -0
- package/src/use-module-context.ts +50 -0
- package/src/validation.ts +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 navishachiku
|
|
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,99 @@
|
|
|
1
|
+
# @openpen/module-api
|
|
2
|
+
|
|
3
|
+
The public contract surface for **OpenPen modules** — both first-party (`src/core/modules/*`) and third-party plugins (`~/.openpen/plugins/*`).
|
|
4
|
+
|
|
5
|
+
This is the **only** import path that built-in modules and plugins are allowed to use (apart from relative imports, `node:*`, and third-party npm packages). The boundary is enforced by `tests/unit/boundaries/*` in the OpenPen main repo.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @openpen/module-api zod
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Import `z` from `@openpen/module-api`, not from `'zod'` directly.**
|
|
14
|
+
> The build externalises `'zod'` — a direct `import { z } from 'zod'` in a
|
|
15
|
+
> plugin file will fail at build time with an unresolved-import error. Use
|
|
16
|
+
> `import { z } from '@openpen/module-api'` everywhere in your plugin.
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { defineModule, z } from '@openpen/module-api'
|
|
22
|
+
|
|
23
|
+
export default defineModule({
|
|
24
|
+
id: 'my-module',
|
|
25
|
+
name: { en: 'My Module', 'zh-TW': '我的模組' },
|
|
26
|
+
settingsSchema: z.object({
|
|
27
|
+
enabled: z.boolean().default(true),
|
|
28
|
+
}),
|
|
29
|
+
contributes: {
|
|
30
|
+
tools: [
|
|
31
|
+
{
|
|
32
|
+
id: 'my-tool',
|
|
33
|
+
// ...
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `setup(ctx)` — Module initialisation
|
|
41
|
+
|
|
42
|
+
`defineModule` supports an optional `setup(ctx: ModuleSetupContext)` hook, called once when the module loads. `ctx` provides:
|
|
43
|
+
|
|
44
|
+
| Method | Description |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `ctx.getSettings<T>()` | Read this module's parsed settings (requires `settingsSchema` to be declared) |
|
|
47
|
+
| `ctx.callMain(action, payload?)` | Call this module's main-side handler |
|
|
48
|
+
| `ctx.onDispose(fn)` | Register a cleanup callback (called in reverse order when the module is unloaded) |
|
|
49
|
+
| `ctx.notify(payload)` | Show a short-lived toast in the overlay window; returns `NotifyHandle` for early `dismiss()` |
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { defineModule } from '@openpen/module-api'
|
|
53
|
+
import type { NotifyPayload } from '@openpen/module-api'
|
|
54
|
+
|
|
55
|
+
export default defineModule({
|
|
56
|
+
id: 'my-module',
|
|
57
|
+
name: { en: 'My Module' },
|
|
58
|
+
contributes: { /* ... */ },
|
|
59
|
+
|
|
60
|
+
setup(ctx) {
|
|
61
|
+
// Show a toast when the module finishes loading.
|
|
62
|
+
ctx.notify({
|
|
63
|
+
message: { en: 'My Module ready' },
|
|
64
|
+
variant: 'success',
|
|
65
|
+
duration: 1500,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Release resources (timers, listeners, etc.) on unload.
|
|
69
|
+
const timer = setInterval(() => { /* ... */ }, 5000)
|
|
70
|
+
ctx.onDispose(() => clearInterval(timer))
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
See [docs/reference/notify-api.md](../../docs/reference/notify-api.md).
|
|
76
|
+
|
|
77
|
+
See the OpenPen [docs](https://github.com/openpen/openpen/blob/main/docs/README.md) for guides, slot catalogue, and UIKit reference.
|
|
78
|
+
|
|
79
|
+
## Long-term stability of the UIKit
|
|
80
|
+
|
|
81
|
+
The UIKit (`@openpen/module-api/uikit`) wraps a headless behaviour library
|
|
82
|
+
internally. That library is an implementation detail — it is not part of the
|
|
83
|
+
public API surface.
|
|
84
|
+
|
|
85
|
+
If the underlying library is ever replaced, the host will follow a documented
|
|
86
|
+
fallback order: Headless UI Vue → Ark UI → self-written primitives. The wrapper
|
|
87
|
+
props, events, and slots documented here will remain stable across any such
|
|
88
|
+
swap. Plugin authors who import only from `@openpen/module-api/uikit` will be
|
|
89
|
+
unaffected. Authors who drop to the primitive layer (named primitive exports)
|
|
90
|
+
will see a major-version bump and a small targeted port. Authors who bypass the
|
|
91
|
+
UIKit entirely and import a headless library directly in their plugin's
|
|
92
|
+
`package.json` are responsible for porting that surface themselves.
|
|
93
|
+
|
|
94
|
+
This guarantee is why the wrapper layer is the recommended starting point for
|
|
95
|
+
all plugin UI.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openpen/module-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenPen Module API — public contract for built-in modules and third-party plugins",
|
|
5
|
+
"author": "navishachiku <navishachiku@gmail.com>",
|
|
6
|
+
"homepage": "https://github.com/openpen-platform/openpen#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/openpen-platform/openpen.git",
|
|
10
|
+
"directory": "packages/module-api"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/openpen-platform/openpen/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./src/index.ts",
|
|
21
|
+
"types": "./src/index.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./src/index.ts",
|
|
25
|
+
"import": "./src/index.ts"
|
|
26
|
+
},
|
|
27
|
+
"./uikit": {
|
|
28
|
+
"types": "./src/uikit/index.ts",
|
|
29
|
+
"import": "./src/uikit/index.ts"
|
|
30
|
+
},
|
|
31
|
+
"./uikit/tokens.css": "./src/uikit/tokens.css",
|
|
32
|
+
"./uikit/internal": {
|
|
33
|
+
"types": "./src/uikit/internal.ts",
|
|
34
|
+
"import": "./src/uikit/internal.ts"
|
|
35
|
+
},
|
|
36
|
+
"./host": {
|
|
37
|
+
"types": "./src/host/index.ts",
|
|
38
|
+
"import": "./src/host/index.ts"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20",
|
|
43
|
+
"npm": ">=9"
|
|
44
|
+
},
|
|
45
|
+
"keywords": ["openpen", "plugin", "module", "sdk"],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"reka-ui": "~2.9.6"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"vue": "^3.5.0",
|
|
51
|
+
"zod": "^3.23.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isValidModuleId } from './validation'
|
|
2
|
+
import { CONTRIBUTION_KEY_TO_SLOT_ID } from './slots'
|
|
3
|
+
import type { OpenPenModule } from './types/module'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Identity helper for declaring an OpenPen module with full type
|
|
7
|
+
* inference and runtime sanity checks. Throws `Error` (preflight) when:
|
|
8
|
+
*
|
|
9
|
+
* - `id` is missing or has invalid characters
|
|
10
|
+
* - `contributes` is missing entirely
|
|
11
|
+
* - `contributes` has no contribution fields
|
|
12
|
+
* - `contributes` references a slot key not in the catalogue
|
|
13
|
+
*
|
|
14
|
+
* This is intentionally a small surface — full validation
|
|
15
|
+
* (id collisions across modules, slot quota, settingsSchema parse, etc.)
|
|
16
|
+
* happens later in the host app's `module-validator`. This helper
|
|
17
|
+
* catches the obvious developer mistakes at the module's own boundary
|
|
18
|
+
* so errors surface in the module's repo, not deep inside OpenPen.
|
|
19
|
+
*/
|
|
20
|
+
export function defineModule<TModule extends OpenPenModule>(
|
|
21
|
+
spec: TModule
|
|
22
|
+
): TModule {
|
|
23
|
+
if (!isValidModuleId(spec.id)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`[@openpen/module-api] Invalid module id: ${JSON.stringify(spec.id)}. ` +
|
|
26
|
+
`Module ids must use \`@scope/name\` format with lowercase ASCII alphanumeric + hyphen, ` +
|
|
27
|
+
`max 39 chars per segment (e.g. \`@alice/my-plugin\`).`
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!spec.contributes || typeof spec.contributes !== 'object') {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`[@openpen/module-api] Module "${spec.id}" must declare a non-empty 'contributes' object.`
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const keys = Object.keys(spec.contributes)
|
|
38
|
+
if (keys.length === 0) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`[@openpen/module-api] Module "${spec.id}" must contribute to at least one slot. ` +
|
|
41
|
+
`Add at least one field to 'contributes' (e.g. 'tools', 'settingsTabs', 'shortcuts').`
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const key of keys) {
|
|
46
|
+
if (!(key in CONTRIBUTION_KEY_TO_SLOT_ID)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`[@openpen/module-api] Module "${spec.id}" contributes to unknown slot key '${key}'. ` +
|
|
49
|
+
`Known keys: ${Object.keys(CONTRIBUTION_KEY_TO_SLOT_ID).sort().join(', ')}.`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return spec
|
|
55
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Color Utilities
|
|
3
|
+
*
|
|
4
|
+
* Re-exports color helpers that module components need for rendering.
|
|
5
|
+
* The canvas-rendering helper resolveColorStyle and the color manipulation
|
|
6
|
+
* utilities are exposed here so modules don't need cross-layer imports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
resolveColorStyle,
|
|
11
|
+
hexToRgb,
|
|
12
|
+
rgbToHex,
|
|
13
|
+
hsvToRgb,
|
|
14
|
+
rgbToHsv,
|
|
15
|
+
hexToHsv,
|
|
16
|
+
hsvToHex,
|
|
17
|
+
isValidHex,
|
|
18
|
+
} from '../../../../src/services/color-utils'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Domain Event Bus
|
|
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.
|
|
7
|
+
*
|
|
8
|
+
* **Host-emitted events (consume via `on(name, cb)`):**
|
|
9
|
+
*
|
|
10
|
+
* | Event name | Payload shape | Fires when |
|
|
11
|
+
* |---------------------------|-------------------------------------------------|----------------------------------------------------------------------|
|
|
12
|
+
* | `'tool-changed'` | `{ tool: string } & Partial<ToolConfig>` | The active drawing tool changes (user click, IPC, or shortcut) |
|
|
13
|
+
* | `'stroke-style-changed'` | `{ color?: StrokeColor; lineWidth?: number }` | Color or stroke width changes |
|
|
14
|
+
* | `'canvas-redraw-requested'` | (no payload) | A module wants the canvas to repaint (eraser, transformer, etc.) |
|
|
15
|
+
*
|
|
16
|
+
* **Module-emitted events:** modules MAY emit any event name they
|
|
17
|
+
* choose; the bus is namespace-free. Use a prefix (e.g. `'my-plugin:'`)
|
|
18
|
+
* to avoid colliding with another module's events.
|
|
19
|
+
*
|
|
20
|
+
* `on(name, cb)` returns an unsubscribe function — register it via
|
|
21
|
+
* `ctx.onDispose(unsubscribe)` so the listener is cleaned up when the
|
|
22
|
+
* module unloads.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { on } from '@openpen/module-api/host'
|
|
27
|
+
*
|
|
28
|
+
* setup(ctx) {
|
|
29
|
+
* const off = on('tool-changed', (payload) => {
|
|
30
|
+
* console.log('tool now', payload.tool)
|
|
31
|
+
* })
|
|
32
|
+
* ctx.onDispose(off)
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export { emit, on } from '../../../../src/core/runtime/event-bus'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Host Commands
|
|
3
|
+
*
|
|
4
|
+
* Re-exports hostCommands for module components that need to trigger
|
|
5
|
+
* host infrastructure actions (pin toggle, canvas clear, undo/redo,
|
|
6
|
+
* drawing mode toggle).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { hostCommands } from '../../../../src/services/host-commands'
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
|
|
15
|
+
// ── Stroke Style ──────────────────────────────────────────────────────────────
|
|
16
|
+
export { useStrokeStyle } from './stroke-style'
|
|
17
|
+
export type { StrokeColor } from './stroke-style'
|
|
18
|
+
|
|
19
|
+
// ── Color Utilities ───────────────────────────────────────────────────────────
|
|
20
|
+
export {
|
|
21
|
+
resolveColorStyle,
|
|
22
|
+
hexToRgb,
|
|
23
|
+
rgbToHex,
|
|
24
|
+
hsvToRgb,
|
|
25
|
+
rgbToHsv,
|
|
26
|
+
hexToHsv,
|
|
27
|
+
hsvToHex,
|
|
28
|
+
isValidHex,
|
|
29
|
+
} from './color-utils'
|
|
30
|
+
|
|
31
|
+
// ── Stroke Store ──────────────────────────────────────────────────────────────
|
|
32
|
+
export { getAllStrokes, removeStrokeById, pushCommand } from './stroke-store'
|
|
33
|
+
export type { Stroke } from './stroke-store'
|
|
34
|
+
|
|
35
|
+
// ── Popup Anchor ──────────────────────────────────────────────────────────────
|
|
36
|
+
export { usePopupAnchor, calculatePopupAnchor } from './popup-anchor'
|
|
37
|
+
|
|
38
|
+
// ── Passthrough Guard ─────────────────────────────────────────────────────────
|
|
39
|
+
export { usePassthroughGuard } from './passthrough'
|
|
40
|
+
|
|
41
|
+
// ── Host Commands ─────────────────────────────────────────────────────────────
|
|
42
|
+
export { hostCommands } from './host-commands'
|
|
43
|
+
|
|
44
|
+
// ── Event Bus ─────────────────────────────────────────────────────────────────
|
|
45
|
+
export { emit, on } from './event-bus'
|
|
46
|
+
|
|
47
|
+
// ── Slot Entries ──────────────────────────────────────────────────────────────
|
|
48
|
+
export { getSlotEntries } from './slot-entries'
|
|
49
|
+
|
|
50
|
+
// ── Module Context Registry ───────────────────────────────────────────────────
|
|
51
|
+
// Used by the renderer host (module-loader.ts) to populate/clear the registry
|
|
52
|
+
// that backs `useModuleContext`. Plugin code MUST NOT import these directly.
|
|
53
|
+
export { setModuleContext, clearModuleContext } from './module-context-registry'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side registry that backs `useModuleContext`.
|
|
3
|
+
*
|
|
4
|
+
* The renderer host (module-loader.ts) writes to this registry via
|
|
5
|
+
* `setModuleContext` / `clearModuleContext` immediately around each
|
|
6
|
+
* module's `setup()` call. Plugin-authored Vue components then read
|
|
7
|
+
* from it via the public `useModuleContext` composable.
|
|
8
|
+
*
|
|
9
|
+
* Module code MUST NOT import from this file directly — use
|
|
10
|
+
* `useModuleContext` from `@openpen/module-api` instead.
|
|
11
|
+
*/
|
|
12
|
+
import type { ModuleSetupContext } from '../types/module'
|
|
13
|
+
|
|
14
|
+
const registry = new Map<string, ModuleSetupContext>()
|
|
15
|
+
|
|
16
|
+
/** 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
|
+
|
|
21
|
+
/** 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
|
+
|
|
26
|
+
/** 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
|
+
|
|
31
|
+
/** Resets the entire registry. Test-only — do not call in production code. */
|
|
32
|
+
export function resetModuleContextRegistryForTest(): void {
|
|
33
|
+
registry.clear()
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Passthrough Guard
|
|
3
|
+
*
|
|
4
|
+
* Re-exports usePassthroughGuard so module components (and UIKit wrappers)
|
|
5
|
+
* can import from @openpen/module-api/host without cross-layer imports.
|
|
6
|
+
*
|
|
7
|
+
* Exposed here so module code can import without cross-layer relative paths.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { usePassthroughGuard } from '../../../../src/composables/usePassthroughGuard'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Popup Anchor
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the usePopupAnchor composable and its pure helper
|
|
5
|
+
* calculatePopupAnchor for module components that need anchor-relative
|
|
6
|
+
* popup positioning.
|
|
7
|
+
*
|
|
8
|
+
* Exposed here so module code can import without cross-layer relative paths.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
usePopupAnchor,
|
|
13
|
+
calculatePopupAnchor,
|
|
14
|
+
} from '../../../../src/composables/usePopupAnchor'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Slot Entries Reader
|
|
3
|
+
*
|
|
4
|
+
* Re-exports getSlotEntries so module components can render UI driven
|
|
5
|
+
* by other modules' contributions to the same slot (for example,
|
|
6
|
+
* the shape-picker rendering all canvas.shapes contributions).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { getSlotEntries } from '../../../../src/core/runtime/contribution-store'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Stroke Store
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the stroke store APIs that module tools need.
|
|
5
|
+
* Only the minimum surface required by stroke-eraser (and similar tools)
|
|
6
|
+
* is exposed: getAllStrokes, removeStrokeById, pushCommand.
|
|
7
|
+
*
|
|
8
|
+
* Host-internal state mutation helpers (clearAll, addStroke, undo, redo)
|
|
9
|
+
* are intentionally NOT exported — modules must use hostCommands for
|
|
10
|
+
* undo/redo and canvas clear.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
getAllStrokes,
|
|
15
|
+
removeStrokeById,
|
|
16
|
+
pushCommand,
|
|
17
|
+
} from '../../../../src/services/stroke-store'
|
|
18
|
+
|
|
19
|
+
// Re-export Stroke type via the public SDK types, not host internals.
|
|
20
|
+
export type { Stroke } from '../types/tool'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openpen/module-api/host — Stroke Style
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the public API of the host useStrokeStyle composable.
|
|
5
|
+
* Module components MUST import from here, not from src/composables directly.
|
|
6
|
+
*
|
|
7
|
+
* Exposed here so module components can access it without cross-layer imports.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Re-export the public API functions that modules need.
|
|
11
|
+
// initStrokeStyleFromSettings is host-boot-only and intentionally NOT exported here.
|
|
12
|
+
export { useStrokeStyle } from '../../../../src/composables/useStrokeStyle'
|
|
13
|
+
|
|
14
|
+
// Re-export the StrokeColor type via the public SDK types (not host internals).
|
|
15
|
+
export type { StrokeColor } from '../types/tool'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
|
|
10
|
+
// Core helpers
|
|
11
|
+
export { defineModule } from './define-module'
|
|
12
|
+
export { useModuleContext } from './use-module-context'
|
|
13
|
+
export { isValidModuleId, MODULE_ID_RE } from './validation'
|
|
14
|
+
export { resolveLabel, sanitizeIdForI18n } from './locale'
|
|
15
|
+
export type { LocaleMap } from './locale'
|
|
16
|
+
|
|
17
|
+
// Slot catalogue
|
|
18
|
+
export {
|
|
19
|
+
ALL_SLOTS,
|
|
20
|
+
V1_ACTIVE_SLOTS,
|
|
21
|
+
V1_RESERVED_SLOTS,
|
|
22
|
+
CONTRIBUTION_KEY_TO_SLOT_ID,
|
|
23
|
+
isKnownSlot,
|
|
24
|
+
getSlot,
|
|
25
|
+
} from './slots'
|
|
26
|
+
export type { SlotDefinition, SlotCategory, SlotStatus } from './slots'
|
|
27
|
+
|
|
28
|
+
// Tool primitives (Point, Stroke, StrokeStyle, Tool, …)
|
|
29
|
+
export type {
|
|
30
|
+
Point,
|
|
31
|
+
StrokeColor,
|
|
32
|
+
StrokeStyle,
|
|
33
|
+
Stroke,
|
|
34
|
+
PointerModifiers,
|
|
35
|
+
Tool,
|
|
36
|
+
} from './types/tool'
|
|
37
|
+
|
|
38
|
+
// Control bar layout types + helpers
|
|
39
|
+
export type { ControlBarLayout, LayoutGroup, GroupInset, ControlBarContribution } from './types/control-bar-layout'
|
|
40
|
+
export {
|
|
41
|
+
ControlBarLayoutSchema,
|
|
42
|
+
LayoutGroupSchema,
|
|
43
|
+
GroupInsetSchema,
|
|
44
|
+
DEFAULT_CONTROL_BAR_LAYOUT,
|
|
45
|
+
repairLayoutL3a,
|
|
46
|
+
repairLayoutL3b,
|
|
47
|
+
} from './types/control-bar-layout'
|
|
48
|
+
|
|
49
|
+
// Notification types
|
|
50
|
+
export type { NotifyPayload, NotifyHandle } from './types/notification'
|
|
51
|
+
|
|
52
|
+
// Module + Contribution types
|
|
53
|
+
export type { OpenPenModule, ModuleSetupContext } from './types/module'
|
|
54
|
+
export type {
|
|
55
|
+
ModuleContributions,
|
|
56
|
+
ToolContribution,
|
|
57
|
+
ShapeContribution,
|
|
58
|
+
StrokeStyleContribution,
|
|
59
|
+
HistoryCommandContribution,
|
|
60
|
+
CanvasLayerContribution,
|
|
61
|
+
HtmlOverlayContribution,
|
|
62
|
+
StrokeTransformerContribution,
|
|
63
|
+
SettingsTabContribution,
|
|
64
|
+
SettingsPanelContribution,
|
|
65
|
+
CursorContribution,
|
|
66
|
+
StatusContribution,
|
|
67
|
+
ModalContribution,
|
|
68
|
+
TrayMenuContribution,
|
|
69
|
+
ContextMenuContribution,
|
|
70
|
+
ThemeTokenContribution,
|
|
71
|
+
ShortcutContribution,
|
|
72
|
+
WindowBehaviorContribution,
|
|
73
|
+
LocaleContribution,
|
|
74
|
+
EventSubscriptionContribution,
|
|
75
|
+
LifecycleContribution,
|
|
76
|
+
StorageContribution,
|
|
77
|
+
MainHandlerContribution,
|
|
78
|
+
FileDropContribution,
|
|
79
|
+
} from './types/contributions'
|
|
80
|
+
|
|
81
|
+
// Host ↔ module inject keys (Vue provide/inject bridge)
|
|
82
|
+
export {
|
|
83
|
+
STROKE_STYLE_CONTEXT_KEY,
|
|
84
|
+
SNAP_EDGE_KEY,
|
|
85
|
+
IS_VERTICAL_KEY,
|
|
86
|
+
MODAL_MANAGER_KEY,
|
|
87
|
+
WRAPPER_EL_KEY,
|
|
88
|
+
ANCHOR_EL_KEY,
|
|
89
|
+
CONTROL_BAR_ANIMATING_KEY,
|
|
90
|
+
HOST_DIALOG_OPEN_COUNT_KEY,
|
|
91
|
+
POPOVER_PLACEMENT_HINT_KEY,
|
|
92
|
+
} from './inject-keys'
|
|
93
|
+
export type { StrokeStyleContext, SnapEdge, ModalManager, PopoverPlacementHint } from './inject-keys'
|
|
94
|
+
|
|
95
|
+
// Re-export zod so modules don't need a separate import path.
|
|
96
|
+
export { z } from 'zod'
|
|
97
|
+
|
|
98
|
+
// Host-internal registry writers — re-exported here so the host's module
|
|
99
|
+
// loader pulls them through the SAME externalised entry point
|
|
100
|
+
// (`@openpen/module-api`, runtime bundle: openpen-runtime/module-api.js)
|
|
101
|
+
// that `useModuleContext` reads from. Importing them via
|
|
102
|
+
// `@openpen/module-api/host` instead would inline a duplicate
|
|
103
|
+
// module-context-registry into the host bundle, breaking state-sharing
|
|
104
|
+
// between writers and readers in production builds (see
|
|
105
|
+
// project_dev_prod_parity.md). Plugin authors MUST NOT use these.
|
|
106
|
+
export { setModuleContext, clearModuleContext } from './host/module-context-registry'
|