@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.
- package/CHANGELOG.md +16 -0
- package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js +246 -0
- package/dist/_chunks/AppButtonDropdown.vue_vue_type_style_index_0_lang-B9dHU-LE.js.map +1 -0
- package/dist/_chunks/inject-keys-CRXCpN-M.js +196 -0
- package/dist/_chunks/inject-keys-CRXCpN-M.js.map +1 -0
- package/dist/_chunks/module-context-registry-Cn1IWYu9.js +16 -0
- package/dist/_chunks/module-context-registry-Cn1IWYu9.js.map +1 -0
- package/dist/_chunks/passthrough-DKU7JYuQ.js +6 -0
- package/dist/_chunks/passthrough-DKU7JYuQ.js.map +1 -0
- package/dist/cursors.d.ts +83 -0
- package/dist/define-module.d.ts +17 -0
- package/dist/host/color-utils.d.ts +11 -0
- package/{src/host/event-bus.ts → dist/host/event-bus.d.ts} +7 -5
- package/dist/host/host-commands.d.ts +1 -0
- package/dist/host/index.d.ts +24 -0
- package/dist/host/index.js +51 -0
- package/dist/host/index.js.map +1 -0
- package/{src/host/module-context-registry.ts → dist/host/module-context-registry.d.ts} +5 -19
- package/dist/host/passthrough.d.ts +2 -0
- package/dist/host/popup-anchor.d.ts +3 -0
- package/dist/host/registry.d.ts +39 -0
- package/dist/host/registry.js +37 -0
- package/dist/host/registry.js.map +1 -0
- package/dist/host/slot-entries.d.ts +2 -0
- package/dist/host/stroke-store.d.ts +6 -0
- package/dist/host/stroke-style.d.ts +3 -0
- package/dist/host/types.d.ts +176 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +349 -0
- package/dist/index.js.map +1 -0
- package/{src/inject-keys.ts → dist/inject-keys.d.ts} +37 -66
- package/{src/locale.ts → dist/locale.d.ts} +3 -22
- package/dist/slots.d.ts +57 -0
- package/dist/types/contributions.d.ts +288 -0
- package/dist/types/control-bar-layout.d.ts +227 -0
- package/dist/types/module.d.ts +208 -0
- package/{src/types/notification.ts → dist/types/notification.d.ts} +12 -14
- package/dist/types/tool.d.ts +77 -0
- package/dist/uikit/components/AppBanner.vue.d.ts +34 -0
- package/dist/uikit/components/AppButton.vue.d.ts +30 -0
- package/dist/uikit/components/AppButtonDropdown.vue.d.ts +52 -0
- package/dist/uikit/components/AppDialog.vue.d.ts +53 -0
- package/dist/uikit/components/AppPopover.vue.d.ts +43 -0
- package/dist/uikit/components/AppSegmented.vue.d.ts +19 -0
- package/dist/uikit/components/AppSelect.vue.d.ts +26 -0
- package/dist/uikit/components/AppSlider.vue.d.ts +43 -0
- package/dist/uikit/components/AppTabs.vue.d.ts +35 -0
- package/dist/uikit/components/AppToggle.vue.d.ts +15 -0
- package/dist/uikit/components/AppTooltip.vue.d.ts +27 -0
- package/dist/uikit/components/DialogHost.vue.d.ts +3 -0
- package/{src/uikit/composables/dialog-internal-keys.ts → dist/uikit/composables/dialog-internal-keys.d.ts} +3 -5
- package/dist/uikit/composables/useDialog.d.ts +399 -0
- package/dist/uikit/composables/useDialogPluginComponent.d.ts +9 -0
- package/dist/uikit/index.d.ts +42 -0
- package/dist/uikit/index.js +898 -0
- package/dist/uikit/index.js.map +1 -0
- package/{src/uikit/internal.ts → dist/uikit/internal.d.ts} +1 -1
- package/dist/uikit/internal.js +157 -0
- package/dist/uikit/internal.js.map +1 -0
- package/dist/uikit/module-api.css +851 -0
- package/dist/uikit/primitives.d.ts +42 -0
- package/{src/use-module-context.ts → dist/use-module-context.d.ts} +2 -15
- package/{src/validation.ts → dist/validation.d.ts} +2 -6
- package/package.json +28 -14
- package/src/cursors.ts +0 -369
- package/src/define-module.ts +0 -55
- package/src/host/color-utils.ts +0 -18
- package/src/host/host-commands.ts +0 -9
- package/src/host/index.ts +0 -53
- package/src/host/passthrough.ts +0 -10
- package/src/host/popup-anchor.ts +0 -14
- package/src/host/slot-entries.ts +0 -9
- package/src/host/stroke-store.ts +0 -20
- package/src/host/stroke-style.ts +0 -15
- package/src/index.ts +0 -131
- package/src/slots.ts +0 -135
- package/src/types/contributions.ts +0 -333
- package/src/types/control-bar-layout.ts +0 -174
- package/src/types/module.ts +0 -225
- package/src/types/tool.ts +0 -82
- package/src/uikit/components/.gitkeep +0 -0
- package/src/uikit/components/AppBanner.vue +0 -131
- package/src/uikit/components/AppButton.vue +0 -135
- package/src/uikit/components/AppDialog.vue +0 -393
- package/src/uikit/components/AppPopover.vue +0 -268
- package/src/uikit/components/AppSegmented.vue +0 -128
- package/src/uikit/components/AppSelect.vue +0 -235
- package/src/uikit/components/AppSlider.vue +0 -204
- package/src/uikit/components/AppTabs.vue +0 -163
- package/src/uikit/components/AppToggle.vue +0 -96
- package/src/uikit/components/AppTooltip.vue +0 -118
- package/src/uikit/components/DialogHost.vue +0 -272
- package/src/uikit/composables/useDialog.ts +0 -141
- package/src/uikit/composables/useDialogPluginComponent.ts +0 -27
- package/src/uikit/index.ts +0 -79
- package/src/uikit/primitives.ts +0 -150
- /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
|
-
*
|
|
5
|
-
* can publish and subscribe without reaching into host
|
|
6
|
-
* The bus is a process-local pub/sub for cross-module
|
|
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
|
|
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,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,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,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 {};
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|