@embedpdf/plugin-commands 3.0.0-next.10 → 3.0.0-next.12

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.
@@ -0,0 +1,137 @@
1
+ import { CapabilityToken, CoreState } from "@embedpdf/core";
2
+ import { KeyStroke } from "@embedpdf/core-ui";
3
+ //#region src/types.d.ts
4
+ /**
5
+ * @embedpdf/plugin-commands — the contract.
6
+ *
7
+ * Commands are the single vocabulary of verbs: toolbars, menus, contextual
8
+ * strips, shortcuts, and the palette are all projections of this registry.
9
+ * The plugin ships ZERO commands (mechanism here, definitions in the product
10
+ * — same split as plugin-i18n's locale packs).
11
+ *
12
+ * Command state is a pure DERIVATION over the store: `resolve()` reads other
13
+ * capabilities' selectors at call time, so any store change is reflected on
14
+ * the next read and the framework binding's one change stream makes every
15
+ * consumer reactive. There is no CommandStateChangedEvent, no diffing, no
16
+ * cache — v2's entire notification apparatus has no v3 equivalent because
17
+ * the reactive store subsumes it.
18
+ *
19
+ * Definitions hold functions, so they live in the plugin's registry (config
20
+ * + `register()`), never in the store; store state is only the serializable
21
+ * `disabledCategories`.
22
+ */
23
+ /** What a derivation or `run` sees: capability resolution bound to the
24
+ * command's target document (explicit, else the active one). */
25
+ interface CommandCtx {
26
+ /** The target document, or null when no document is open. */
27
+ readonly documentId: string | null;
28
+ core(): CoreState;
29
+ /** Resolve a capability; document-scoped tokens bind to the target document. */
30
+ get<T>(token: CapabilityToken<T>): T;
31
+ /** Like `get`, but null when unavailable (no provider / no document). */
32
+ tryGet<T>(token: CapabilityToken<T>): T | null;
33
+ }
34
+ /**
35
+ * Up to two theme colors accompanying a command's icon — typed FACTS any
36
+ * renderer can interpret (tint a glyph's slots, show a swatch), never
37
+ * renderer props. The registry carries it the way it carries `icon`: as
38
+ * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —
39
+ * renderer-specific needs live app-side, joined by command id.
40
+ */
41
+ interface IconAccent {
42
+ /** The mark: stroke / markup / font color. */
43
+ readonly primary?: string;
44
+ /** The fill, when there is one. */
45
+ readonly secondary?: string;
46
+ }
47
+ interface CommandDef {
48
+ /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */
49
+ readonly id: string;
50
+ /** i18n key, resolved through I18nToken when present (else shown verbatim). */
51
+ readonly labelKey: string;
52
+ readonly icon?: string;
53
+ /** Live color accent for the icon (a tool previewing its drawing defaults).
54
+ * A pure derivation over the store, exactly like `active`/`enabled`. */
55
+ readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;
56
+ /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */
57
+ readonly shortcut?: string | readonly string[];
58
+ /** Feature-gating tags: a disabled category hides its commands everywhere. */
59
+ readonly categories?: readonly string[];
60
+ /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */
61
+ readonly menu?: string;
62
+ /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */
63
+ readonly panel?: string | {
64
+ readonly id: string;
65
+ readonly exclusive?: string;
66
+ };
67
+ /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */
68
+ readonly modal?: string;
69
+ readonly enabled?: (ctx: CommandCtx) => boolean;
70
+ readonly active?: (ctx: CommandCtx) => boolean;
71
+ readonly visible?: (ctx: CommandCtx) => boolean;
72
+ /** The verb. Optional for pure surface-target commands. Runs before the
73
+ * default target routing when both are present. */
74
+ readonly run?: (ctx: CommandCtx) => void;
75
+ }
76
+ /** A command as a renderer sees it — everything resolved for the target document. */
77
+ interface ResolvedCommand {
78
+ readonly id: string;
79
+ readonly label: string;
80
+ readonly icon?: string;
81
+ readonly iconAccent?: IconAccent;
82
+ readonly shortcuts: readonly string[];
83
+ readonly menu?: string;
84
+ readonly enabled: boolean;
85
+ readonly active: boolean;
86
+ readonly visible: boolean;
87
+ readonly categories: readonly string[];
88
+ }
89
+ /** Value equality over resolved commands — `resolve()` mints a fresh object
90
+ * per read, so reactive bindings memo by value to re-render on real change. */
91
+ declare const resolvedCommandsEqual: (a: ResolvedCommand | null, b: ResolvedCommand | null) => boolean;
92
+ interface CommandsState {
93
+ readonly disabledCategories: readonly string[];
94
+ }
95
+ type CommandsAction = {
96
+ type: 'COMMANDS/DISABLE_CATEGORY';
97
+ category: string;
98
+ } | {
99
+ type: 'COMMANDS/ENABLE_CATEGORY';
100
+ category: string;
101
+ } | {
102
+ type: 'COMMANDS/SET_DISABLED_CATEGORIES';
103
+ categories: readonly string[];
104
+ };
105
+ interface CommandsConfig {
106
+ /** The app's command definitions (content — the plugin ships none). */
107
+ commands?: readonly CommandDef[];
108
+ /** Categories disabled at startup (host feature-gating). */
109
+ disabledCategories?: readonly string[];
110
+ }
111
+ interface CommandsCapability {
112
+ register(def: CommandDef): void;
113
+ unregister(id: string): void;
114
+ has(id: string): boolean;
115
+ ids(): string[];
116
+ resolve(id: string, documentId?: string): ResolvedCommand | null;
117
+ /** Palette query: visible commands whose resolved label matches. */
118
+ search(query: string, documentId?: string): ResolvedCommand[];
119
+ /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */
120
+ menuTarget(id: string): {
121
+ menu?: string;
122
+ } | null;
123
+ execute(id: string, documentId?: string): void;
124
+ /** Match a keystroke against every registered shortcut → command id or null. */
125
+ matchStroke(stroke: KeyStroke, opts: {
126
+ isMac: boolean;
127
+ }): string | null;
128
+ disabledCategories(): readonly string[];
129
+ isCategoryDisabled(category: string): boolean;
130
+ disableCategory(category: string): void;
131
+ enableCategory(category: string): void;
132
+ setDisabledCategories(categories: readonly string[]): void;
133
+ }
134
+ declare const CommandsToken: CapabilityToken<CommandsCapability>;
135
+ //#endregion
136
+ export { CommandsConfig as a, IconAccent as c, CommandsCapability as i, ResolvedCommand as l, CommandDef as n, CommandsState as o, CommandsAction as r, CommandsToken as s, CommandCtx as t, resolvedCommandsEqual as u };
137
+ //# sourceMappingURL=contract-CSIfF6wC.d.cts.map
@@ -0,0 +1,137 @@
1
+ import { CapabilityToken, CoreState } from "@embedpdf/core";
2
+ import { KeyStroke } from "@embedpdf/core-ui";
3
+ //#region src/types.d.ts
4
+ /**
5
+ * @embedpdf/plugin-commands — the contract.
6
+ *
7
+ * Commands are the single vocabulary of verbs: toolbars, menus, contextual
8
+ * strips, shortcuts, and the palette are all projections of this registry.
9
+ * The plugin ships ZERO commands (mechanism here, definitions in the product
10
+ * — same split as plugin-i18n's locale packs).
11
+ *
12
+ * Command state is a pure DERIVATION over the store: `resolve()` reads other
13
+ * capabilities' selectors at call time, so any store change is reflected on
14
+ * the next read and the framework binding's one change stream makes every
15
+ * consumer reactive. There is no CommandStateChangedEvent, no diffing, no
16
+ * cache — v2's entire notification apparatus has no v3 equivalent because
17
+ * the reactive store subsumes it.
18
+ *
19
+ * Definitions hold functions, so they live in the plugin's registry (config
20
+ * + `register()`), never in the store; store state is only the serializable
21
+ * `disabledCategories`.
22
+ */
23
+ /** What a derivation or `run` sees: capability resolution bound to the
24
+ * command's target document (explicit, else the active one). */
25
+ interface CommandCtx {
26
+ /** The target document, or null when no document is open. */
27
+ readonly documentId: string | null;
28
+ core(): CoreState;
29
+ /** Resolve a capability; document-scoped tokens bind to the target document. */
30
+ get<T>(token: CapabilityToken<T>): T;
31
+ /** Like `get`, but null when unavailable (no provider / no document). */
32
+ tryGet<T>(token: CapabilityToken<T>): T | null;
33
+ }
34
+ /**
35
+ * Up to two theme colors accompanying a command's icon — typed FACTS any
36
+ * renderer can interpret (tint a glyph's slots, show a swatch), never
37
+ * renderer props. The registry carries it the way it carries `icon`: as
38
+ * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —
39
+ * renderer-specific needs live app-side, joined by command id.
40
+ */
41
+ interface IconAccent {
42
+ /** The mark: stroke / markup / font color. */
43
+ readonly primary?: string;
44
+ /** The fill, when there is one. */
45
+ readonly secondary?: string;
46
+ }
47
+ interface CommandDef {
48
+ /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */
49
+ readonly id: string;
50
+ /** i18n key, resolved through I18nToken when present (else shown verbatim). */
51
+ readonly labelKey: string;
52
+ readonly icon?: string;
53
+ /** Live color accent for the icon (a tool previewing its drawing defaults).
54
+ * A pure derivation over the store, exactly like `active`/`enabled`. */
55
+ readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;
56
+ /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */
57
+ readonly shortcut?: string | readonly string[];
58
+ /** Feature-gating tags: a disabled category hides its commands everywhere. */
59
+ readonly categories?: readonly string[];
60
+ /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */
61
+ readonly menu?: string;
62
+ /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */
63
+ readonly panel?: string | {
64
+ readonly id: string;
65
+ readonly exclusive?: string;
66
+ };
67
+ /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */
68
+ readonly modal?: string;
69
+ readonly enabled?: (ctx: CommandCtx) => boolean;
70
+ readonly active?: (ctx: CommandCtx) => boolean;
71
+ readonly visible?: (ctx: CommandCtx) => boolean;
72
+ /** The verb. Optional for pure surface-target commands. Runs before the
73
+ * default target routing when both are present. */
74
+ readonly run?: (ctx: CommandCtx) => void;
75
+ }
76
+ /** A command as a renderer sees it — everything resolved for the target document. */
77
+ interface ResolvedCommand {
78
+ readonly id: string;
79
+ readonly label: string;
80
+ readonly icon?: string;
81
+ readonly iconAccent?: IconAccent;
82
+ readonly shortcuts: readonly string[];
83
+ readonly menu?: string;
84
+ readonly enabled: boolean;
85
+ readonly active: boolean;
86
+ readonly visible: boolean;
87
+ readonly categories: readonly string[];
88
+ }
89
+ /** Value equality over resolved commands — `resolve()` mints a fresh object
90
+ * per read, so reactive bindings memo by value to re-render on real change. */
91
+ declare const resolvedCommandsEqual: (a: ResolvedCommand | null, b: ResolvedCommand | null) => boolean;
92
+ interface CommandsState {
93
+ readonly disabledCategories: readonly string[];
94
+ }
95
+ type CommandsAction = {
96
+ type: 'COMMANDS/DISABLE_CATEGORY';
97
+ category: string;
98
+ } | {
99
+ type: 'COMMANDS/ENABLE_CATEGORY';
100
+ category: string;
101
+ } | {
102
+ type: 'COMMANDS/SET_DISABLED_CATEGORIES';
103
+ categories: readonly string[];
104
+ };
105
+ interface CommandsConfig {
106
+ /** The app's command definitions (content — the plugin ships none). */
107
+ commands?: readonly CommandDef[];
108
+ /** Categories disabled at startup (host feature-gating). */
109
+ disabledCategories?: readonly string[];
110
+ }
111
+ interface CommandsCapability {
112
+ register(def: CommandDef): void;
113
+ unregister(id: string): void;
114
+ has(id: string): boolean;
115
+ ids(): string[];
116
+ resolve(id: string, documentId?: string): ResolvedCommand | null;
117
+ /** Palette query: visible commands whose resolved label matches. */
118
+ search(query: string, documentId?: string): ResolvedCommand[];
119
+ /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */
120
+ menuTarget(id: string): {
121
+ menu?: string;
122
+ } | null;
123
+ execute(id: string, documentId?: string): void;
124
+ /** Match a keystroke against every registered shortcut → command id or null. */
125
+ matchStroke(stroke: KeyStroke, opts: {
126
+ isMac: boolean;
127
+ }): string | null;
128
+ disabledCategories(): readonly string[];
129
+ isCategoryDisabled(category: string): boolean;
130
+ disableCategory(category: string): void;
131
+ enableCategory(category: string): void;
132
+ setDisabledCategories(categories: readonly string[]): void;
133
+ }
134
+ declare const CommandsToken: CapabilityToken<CommandsCapability>;
135
+ //#endregion
136
+ export { CommandsConfig as a, IconAccent as c, CommandsCapability as i, ResolvedCommand as l, CommandDef as n, CommandsState as o, CommandsAction as r, CommandsToken as s, CommandCtx as t, resolvedCommandsEqual as u };
137
+ //# sourceMappingURL=contract-CSIfF6wC.d.ts.map
@@ -0,0 +1,25 @@
1
+ let _embedpdf_core = require("@embedpdf/core");
2
+ //#region src/types.ts
3
+ /** Value equality over resolved commands — `resolve()` mints a fresh object
4
+ * per read, so reactive bindings memo by value to re-render on real change. */
5
+ const resolvedCommandsEqual = (a, b) => {
6
+ if (a === b) return true;
7
+ if (!a || !b) return false;
8
+ return a.id === b.id && a.label === b.label && a.icon === b.icon && a.iconAccent?.primary === b.iconAccent?.primary && a.iconAccent?.secondary === b.iconAccent?.secondary && a.menu === b.menu && a.enabled === b.enabled && a.active === b.active && a.visible === b.visible && a.shortcuts.length === b.shortcuts.length && a.shortcuts.every((s, i) => s === b.shortcuts[i]);
9
+ };
10
+ const CommandsToken = (0, _embedpdf_core.createCapabilityToken)("commands");
11
+ //#endregion
12
+ Object.defineProperty(exports, "CommandsToken", {
13
+ enumerable: true,
14
+ get: function() {
15
+ return CommandsToken;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "resolvedCommandsEqual", {
19
+ enumerable: true,
20
+ get: function() {
21
+ return resolvedCommandsEqual;
22
+ }
23
+ });
24
+
25
+ //# sourceMappingURL=contract-DYlaVyVk.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-DYlaVyVk.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\nimport type { CapabilityToken, CoreState } from '@embedpdf/core';\nimport type { KeyStroke } from '@embedpdf/core-ui';\n\n/**\n * @embedpdf/plugin-commands — the contract.\n *\n * Commands are the single vocabulary of verbs: toolbars, menus, contextual\n * strips, shortcuts, and the palette are all projections of this registry.\n * The plugin ships ZERO commands (mechanism here, definitions in the product\n * — same split as plugin-i18n's locale packs).\n *\n * Command state is a pure DERIVATION over the store: `resolve()` reads other\n * capabilities' selectors at call time, so any store change is reflected on\n * the next read and the framework binding's one change stream makes every\n * consumer reactive. There is no CommandStateChangedEvent, no diffing, no\n * cache — v2's entire notification apparatus has no v3 equivalent because\n * the reactive store subsumes it.\n *\n * Definitions hold functions, so they live in the plugin's registry (config\n * + `register()`), never in the store; store state is only the serializable\n * `disabledCategories`.\n */\n\n/** What a derivation or `run` sees: capability resolution bound to the\n * command's target document (explicit, else the active one). */\nexport interface CommandCtx {\n /** The target document, or null when no document is open. */\n readonly documentId: string | null;\n core(): CoreState;\n /** Resolve a capability; document-scoped tokens bind to the target document. */\n get<T>(token: CapabilityToken<T>): T;\n /** Like `get`, but null when unavailable (no provider / no document). */\n tryGet<T>(token: CapabilityToken<T>): T | null;\n}\n\n/**\n * Up to two theme colors accompanying a command's icon — typed FACTS any\n * renderer can interpret (tint a glyph's slots, show a swatch), never\n * renderer props. The registry carries it the way it carries `icon`: as\n * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —\n * renderer-specific needs live app-side, joined by command id.\n */\nexport interface IconAccent {\n /** The mark: stroke / markup / font color. */\n readonly primary?: string;\n /** The fill, when there is one. */\n readonly secondary?: string;\n}\n\nexport interface CommandDef {\n /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */\n readonly id: string;\n /** i18n key, resolved through I18nToken when present (else shown verbatim). */\n readonly labelKey: string;\n readonly icon?: string;\n /** Live color accent for the icon (a tool previewing its drawing defaults).\n * A pure derivation over the store, exactly like `active`/`enabled`. */\n readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;\n /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */\n readonly shortcut?: string | readonly string[];\n /** Feature-gating tags: a disabled category hides its commands everywhere. */\n readonly categories?: readonly string[];\n\n // ── declarative surface targets ──────────────────────────────────────────\n // A command that opens chrome DECLARES what it opens instead of doing it\n // imperatively. This is load-bearing: buttons render carets/aria-haspopup,\n // `active` derives automatically from the surface's open state, and the\n // overflow projection renders `menu` targets as nested submenus.\n /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */\n readonly menu?: string;\n /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */\n readonly panel?: string | { readonly id: string; readonly exclusive?: string };\n /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */\n readonly modal?: string;\n\n // ── pure derivations over the store ──────────────────────────────────────\n readonly enabled?: (ctx: CommandCtx) => boolean;\n readonly active?: (ctx: CommandCtx) => boolean;\n readonly visible?: (ctx: CommandCtx) => boolean;\n\n /** The verb. Optional for pure surface-target commands. Runs before the\n * default target routing when both are present. */\n readonly run?: (ctx: CommandCtx) => void;\n}\n\n/** A command as a renderer sees it — everything resolved for the target document. */\nexport interface ResolvedCommand {\n readonly id: string;\n readonly label: string;\n readonly icon?: string;\n readonly iconAccent?: IconAccent;\n readonly shortcuts: readonly string[];\n readonly menu?: string;\n readonly enabled: boolean;\n readonly active: boolean;\n readonly visible: boolean;\n readonly categories: readonly string[];\n}\n\n/** Value equality over resolved commands — `resolve()` mints a fresh object\n * per read, so reactive bindings memo by value to re-render on real change. */\nexport const resolvedCommandsEqual = (\n a: ResolvedCommand | null,\n b: ResolvedCommand | null,\n): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.id === b.id &&\n a.label === b.label &&\n a.icon === b.icon &&\n // by value: resolve() mints a fresh accent object each read\n a.iconAccent?.primary === b.iconAccent?.primary &&\n a.iconAccent?.secondary === b.iconAccent?.secondary &&\n a.menu === b.menu &&\n a.enabled === b.enabled &&\n a.active === b.active &&\n a.visible === b.visible &&\n a.shortcuts.length === b.shortcuts.length &&\n a.shortcuts.every((s, i) => s === b.shortcuts[i])\n );\n};\n\nexport interface CommandsState {\n readonly disabledCategories: readonly string[];\n}\n\nexport type CommandsAction =\n | { type: 'COMMANDS/DISABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/ENABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/SET_DISABLED_CATEGORIES'; categories: readonly string[] };\n\nexport interface CommandsConfig {\n /** The app's command definitions (content — the plugin ships none). */\n commands?: readonly CommandDef[];\n /** Categories disabled at startup (host feature-gating). */\n disabledCategories?: readonly string[];\n}\n\nexport interface CommandsCapability {\n // ── registry ──\n register(def: CommandDef): void;\n unregister(id: string): void;\n has(id: string): boolean;\n ids(): string[];\n\n // ── resolution (pure reads; reactive through the store) ──\n resolve(id: string, documentId?: string): ResolvedCommand | null;\n /** Palette query: visible commands whose resolved label matches. */\n search(query: string, documentId?: string): ResolvedCommand[];\n /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */\n menuTarget(id: string): { menu?: string } | null;\n\n // ── execution (the ONLY path; guarded by enabled/visible) ──\n execute(id: string, documentId?: string): void;\n /** Match a keystroke against every registered shortcut → command id or null. */\n matchStroke(stroke: KeyStroke, opts: { isMac: boolean }): string | null;\n\n // ── category gating ──\n disabledCategories(): readonly string[];\n isCategoryDisabled(category: string): boolean;\n disableCategory(category: string): void;\n enableCategory(category: string): void;\n setDisabledCategories(categories: readonly string[]): void;\n}\n\nexport const CommandsToken = createCapabilityToken<CommandsCapability>('commands');\n"],"mappings":";;;;AAsGA,MAAa,yBACX,GACA,MACY;CACZ,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,OACE,EAAE,OAAO,EAAE,MACX,EAAE,UAAU,EAAE,SACd,EAAE,SAAS,EAAE,QAEb,EAAE,YAAY,YAAY,EAAE,YAAY,WACxC,EAAE,YAAY,cAAc,EAAE,YAAY,aAC1C,EAAE,SAAS,EAAE,QACb,EAAE,YAAY,EAAE,WAChB,EAAE,WAAW,EAAE,UACf,EAAE,YAAY,EAAE,WAChB,EAAE,UAAU,WAAW,EAAE,UAAU,UACnC,EAAE,UAAU,OAAO,GAAG,MAAM,MAAM,EAAE,UAAU,EAAE;AAEpD;AA6CA,MAAa,iBAAA,GAAA,eAAA,sBAAA,CAA0D,UAAU"}
@@ -0,0 +1,14 @@
1
+ import { createCapabilityToken } from "@embedpdf/core";
2
+ //#region src/types.ts
3
+ /** Value equality over resolved commands — `resolve()` mints a fresh object
4
+ * per read, so reactive bindings memo by value to re-render on real change. */
5
+ const resolvedCommandsEqual = (a, b) => {
6
+ if (a === b) return true;
7
+ if (!a || !b) return false;
8
+ return a.id === b.id && a.label === b.label && a.icon === b.icon && a.iconAccent?.primary === b.iconAccent?.primary && a.iconAccent?.secondary === b.iconAccent?.secondary && a.menu === b.menu && a.enabled === b.enabled && a.active === b.active && a.visible === b.visible && a.shortcuts.length === b.shortcuts.length && a.shortcuts.every((s, i) => s === b.shortcuts[i]);
9
+ };
10
+ const CommandsToken = createCapabilityToken("commands");
11
+ //#endregion
12
+ export { resolvedCommandsEqual as n, CommandsToken as t };
13
+
14
+ //# sourceMappingURL=contract-UFgXphIQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-UFgXphIQ.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\nimport type { CapabilityToken, CoreState } from '@embedpdf/core';\nimport type { KeyStroke } from '@embedpdf/core-ui';\n\n/**\n * @embedpdf/plugin-commands — the contract.\n *\n * Commands are the single vocabulary of verbs: toolbars, menus, contextual\n * strips, shortcuts, and the palette are all projections of this registry.\n * The plugin ships ZERO commands (mechanism here, definitions in the product\n * — same split as plugin-i18n's locale packs).\n *\n * Command state is a pure DERIVATION over the store: `resolve()` reads other\n * capabilities' selectors at call time, so any store change is reflected on\n * the next read and the framework binding's one change stream makes every\n * consumer reactive. There is no CommandStateChangedEvent, no diffing, no\n * cache — v2's entire notification apparatus has no v3 equivalent because\n * the reactive store subsumes it.\n *\n * Definitions hold functions, so they live in the plugin's registry (config\n * + `register()`), never in the store; store state is only the serializable\n * `disabledCategories`.\n */\n\n/** What a derivation or `run` sees: capability resolution bound to the\n * command's target document (explicit, else the active one). */\nexport interface CommandCtx {\n /** The target document, or null when no document is open. */\n readonly documentId: string | null;\n core(): CoreState;\n /** Resolve a capability; document-scoped tokens bind to the target document. */\n get<T>(token: CapabilityToken<T>): T;\n /** Like `get`, but null when unavailable (no provider / no document). */\n tryGet<T>(token: CapabilityToken<T>): T | null;\n}\n\n/**\n * Up to two theme colors accompanying a command's icon — typed FACTS any\n * renderer can interpret (tint a glyph's slots, show a swatch), never\n * renderer props. The registry carries it the way it carries `icon`: as\n * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —\n * renderer-specific needs live app-side, joined by command id.\n */\nexport interface IconAccent {\n /** The mark: stroke / markup / font color. */\n readonly primary?: string;\n /** The fill, when there is one. */\n readonly secondary?: string;\n}\n\nexport interface CommandDef {\n /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */\n readonly id: string;\n /** i18n key, resolved through I18nToken when present (else shown verbatim). */\n readonly labelKey: string;\n readonly icon?: string;\n /** Live color accent for the icon (a tool previewing its drawing defaults).\n * A pure derivation over the store, exactly like `active`/`enabled`. */\n readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;\n /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */\n readonly shortcut?: string | readonly string[];\n /** Feature-gating tags: a disabled category hides its commands everywhere. */\n readonly categories?: readonly string[];\n\n // ── declarative surface targets ──────────────────────────────────────────\n // A command that opens chrome DECLARES what it opens instead of doing it\n // imperatively. This is load-bearing: buttons render carets/aria-haspopup,\n // `active` derives automatically from the surface's open state, and the\n // overflow projection renders `menu` targets as nested submenus.\n /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */\n readonly menu?: string;\n /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */\n readonly panel?: string | { readonly id: string; readonly exclusive?: string };\n /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */\n readonly modal?: string;\n\n // ── pure derivations over the store ──────────────────────────────────────\n readonly enabled?: (ctx: CommandCtx) => boolean;\n readonly active?: (ctx: CommandCtx) => boolean;\n readonly visible?: (ctx: CommandCtx) => boolean;\n\n /** The verb. Optional for pure surface-target commands. Runs before the\n * default target routing when both are present. */\n readonly run?: (ctx: CommandCtx) => void;\n}\n\n/** A command as a renderer sees it — everything resolved for the target document. */\nexport interface ResolvedCommand {\n readonly id: string;\n readonly label: string;\n readonly icon?: string;\n readonly iconAccent?: IconAccent;\n readonly shortcuts: readonly string[];\n readonly menu?: string;\n readonly enabled: boolean;\n readonly active: boolean;\n readonly visible: boolean;\n readonly categories: readonly string[];\n}\n\n/** Value equality over resolved commands — `resolve()` mints a fresh object\n * per read, so reactive bindings memo by value to re-render on real change. */\nexport const resolvedCommandsEqual = (\n a: ResolvedCommand | null,\n b: ResolvedCommand | null,\n): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.id === b.id &&\n a.label === b.label &&\n a.icon === b.icon &&\n // by value: resolve() mints a fresh accent object each read\n a.iconAccent?.primary === b.iconAccent?.primary &&\n a.iconAccent?.secondary === b.iconAccent?.secondary &&\n a.menu === b.menu &&\n a.enabled === b.enabled &&\n a.active === b.active &&\n a.visible === b.visible &&\n a.shortcuts.length === b.shortcuts.length &&\n a.shortcuts.every((s, i) => s === b.shortcuts[i])\n );\n};\n\nexport interface CommandsState {\n readonly disabledCategories: readonly string[];\n}\n\nexport type CommandsAction =\n | { type: 'COMMANDS/DISABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/ENABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/SET_DISABLED_CATEGORIES'; categories: readonly string[] };\n\nexport interface CommandsConfig {\n /** The app's command definitions (content — the plugin ships none). */\n commands?: readonly CommandDef[];\n /** Categories disabled at startup (host feature-gating). */\n disabledCategories?: readonly string[];\n}\n\nexport interface CommandsCapability {\n // ── registry ──\n register(def: CommandDef): void;\n unregister(id: string): void;\n has(id: string): boolean;\n ids(): string[];\n\n // ── resolution (pure reads; reactive through the store) ──\n resolve(id: string, documentId?: string): ResolvedCommand | null;\n /** Palette query: visible commands whose resolved label matches. */\n search(query: string, documentId?: string): ResolvedCommand[];\n /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */\n menuTarget(id: string): { menu?: string } | null;\n\n // ── execution (the ONLY path; guarded by enabled/visible) ──\n execute(id: string, documentId?: string): void;\n /** Match a keystroke against every registered shortcut → command id or null. */\n matchStroke(stroke: KeyStroke, opts: { isMac: boolean }): string | null;\n\n // ── category gating ──\n disabledCategories(): readonly string[];\n isCategoryDisabled(category: string): boolean;\n disableCategory(category: string): void;\n enableCategory(category: string): void;\n setDisabledCategories(categories: readonly string[]): void;\n}\n\nexport const CommandsToken = createCapabilityToken<CommandsCapability>('commands');\n"],"mappings":";;;;AAsGA,MAAa,yBACX,GACA,MACY;CACZ,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,OACE,EAAE,OAAO,EAAE,MACX,EAAE,UAAU,EAAE,SACd,EAAE,SAAS,EAAE,QAEb,EAAE,YAAY,YAAY,EAAE,YAAY,WACxC,EAAE,YAAY,cAAc,EAAE,YAAY,aAC1C,EAAE,SAAS,EAAE,QACb,EAAE,YAAY,EAAE,WAChB,EAAE,WAAW,EAAE,UACf,EAAE,YAAY,EAAE,WAChB,EAAE,UAAU,WAAW,EAAE,UAAU,UACnC,EAAE,UAAU,OAAO,GAAG,MAAM,MAAM,EAAE,UAAU,EAAE;AAEpD;AA6CA,MAAa,gBAAgB,sBAA0C,UAAU"}
@@ -0,0 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_contract = require("./contract-DYlaVyVk.cjs");
3
+ exports.CommandsToken = require_contract.CommandsToken;
4
+ exports.resolvedCommandsEqual = require_contract.resolvedCommandsEqual;
@@ -0,0 +1,2 @@
1
+ import { a as CommandsConfig, c as IconAccent, i as CommandsCapability, l as ResolvedCommand, n as CommandDef, o as CommandsState, r as CommandsAction, s as CommandsToken, t as CommandCtx, u as resolvedCommandsEqual } from "./contract-CSIfF6wC.cjs";
2
+ export { type CommandCtx, type CommandDef, type CommandsAction, type CommandsCapability, type CommandsConfig, type CommandsState, CommandsToken, type IconAccent, type ResolvedCommand, resolvedCommandsEqual };
@@ -0,0 +1,2 @@
1
+ import { a as CommandsConfig, c as IconAccent, i as CommandsCapability, l as ResolvedCommand, n as CommandDef, o as CommandsState, r as CommandsAction, s as CommandsToken, t as CommandCtx, u as resolvedCommandsEqual } from "./contract-CSIfF6wC.js";
2
+ export { type CommandCtx, type CommandDef, type CommandsAction, type CommandsCapability, type CommandsConfig, type CommandsState, CommandsToken, type IconAccent, type ResolvedCommand, resolvedCommandsEqual };
@@ -0,0 +1,2 @@
1
+ import { n as resolvedCommandsEqual, t as CommandsToken } from "./contract-UFgXphIQ.js";
2
+ export { CommandsToken, resolvedCommandsEqual };
package/dist/index.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_contract = require("./contract-DYlaVyVk.cjs");
2
3
  let _embedpdf_core = require("@embedpdf/core");
3
- let _embedpdf_plugin_i18n = require("@embedpdf/plugin-i18n");
4
- let _embedpdf_plugin_shell = require("@embedpdf/plugin-shell");
4
+ let _embedpdf_plugin_i18n_contract = require("@embedpdf/plugin-i18n/contract");
5
+ let _embedpdf_plugin_shell_contract = require("@embedpdf/plugin-shell/contract");
5
6
  let _embedpdf_core_ui = require("@embedpdf/core-ui");
6
7
  //#region src/capability.ts
7
8
  function registerCommand(registry, def) {
@@ -62,12 +63,12 @@ function createCommandsCapability(ctx, registry) {
62
63
  const c = commandCtx(documentId);
63
64
  const disabled = ctx.getState().disabledCategories;
64
65
  const categoryHidden = (def.categories ?? []).some((cat) => disabled.includes(cat));
65
- const i18n = c.tryGet(_embedpdf_plugin_i18n.I18nToken);
66
+ const i18n = c.tryGet(_embedpdf_plugin_i18n_contract.I18nToken);
66
67
  const label = i18n ? i18n.t(def.labelKey) : def.labelKey;
67
68
  let active;
68
69
  if (def.active) active = derive(def.active, c, false);
69
70
  else {
70
- const shell = c.tryGet(_embedpdf_plugin_shell.ShellToken);
71
+ const shell = c.tryGet(_embedpdf_plugin_shell_contract.ShellToken);
71
72
  const panel = panelTarget(def);
72
73
  active = shell ? def.menu ? shell.isMenuOpen(def.menu) : panel ? shell.isOpen(panel.id) : def.modal ? shell.isOpen(def.modal) : false : false;
73
74
  }
@@ -94,7 +95,7 @@ function createCommandsCapability(ctx, registry) {
94
95
  entry.def.run(c);
95
96
  return;
96
97
  }
97
- const shell = c.tryGet(_embedpdf_plugin_shell.ShellToken);
98
+ const shell = c.tryGet(_embedpdf_plugin_shell_contract.ShellToken);
98
99
  if (!shell) return;
99
100
  const panel = panelTarget(entry.def);
100
101
  if (entry.def.menu) shell.toggleMenu(entry.def.menu);
@@ -154,16 +155,6 @@ function commandsReducer(state, action) {
154
155
  }
155
156
  }
156
157
  //#endregion
157
- //#region src/types.ts
158
- /** Value equality over resolved commands — `resolve()` mints a fresh object
159
- * per read, so reactive bindings memo by value to re-render on real change. */
160
- const resolvedCommandsEqual = (a, b) => {
161
- if (a === b) return true;
162
- if (!a || !b) return false;
163
- return a.id === b.id && a.label === b.label && a.icon === b.icon && a.iconAccent?.primary === b.iconAccent?.primary && a.iconAccent?.secondary === b.iconAccent?.secondary && a.menu === b.menu && a.enabled === b.enabled && a.active === b.active && a.visible === b.visible && a.shortcuts.length === b.shortcuts.length && a.shortcuts.every((s, i) => s === b.shortcuts[i]);
164
- };
165
- const CommandsToken = (0, _embedpdf_core.createCapabilityToken)("commands");
166
- //#endregion
167
158
  //#region src/commands.plugin.ts
168
159
  /**
169
160
  * The commands plugin: workspace-scoped (one vocabulary for the whole
@@ -177,7 +168,7 @@ const commandsPlugin = (config) => {
177
168
  return (0, _embedpdf_core.definePlugin)({
178
169
  id: "commands",
179
170
  scope: "workspace",
180
- token: CommandsToken,
171
+ token: require_contract.CommandsToken,
181
172
  initialState: {
182
173
  ...initialCommandsState,
183
174
  disabledCategories: [...config?.disabledCategories ?? []]
@@ -187,10 +178,10 @@ const commandsPlugin = (config) => {
187
178
  });
188
179
  };
189
180
  //#endregion
190
- exports.CommandsToken = CommandsToken;
181
+ exports.CommandsToken = require_contract.CommandsToken;
191
182
  exports.commandsPlugin = commandsPlugin;
192
183
  exports.commandsReducer = commandsReducer;
193
184
  exports.initialCommandsState = initialCommandsState;
194
- exports.resolvedCommandsEqual = resolvedCommandsEqual;
185
+ exports.resolvedCommandsEqual = require_contract.resolvedCommandsEqual;
195
186
 
196
187
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["parseShortcut","I18nToken","ShellToken"],"sources":["../src/capability.ts","../src/reducer.ts","../src/types.ts","../src/commands.plugin.ts"],"sourcesContent":["import type { CapabilityToken, PluginContext } from '@embedpdf/core';\nimport { I18nToken } from '@embedpdf/plugin-i18n';\nimport { ShellToken } from '@embedpdf/plugin-shell';\nimport { matchShortcut, parseShortcut } from '@embedpdf/core-ui';\nimport type { KeyStroke, ParsedShortcut } from '@embedpdf/core-ui';\nimport type {\n CommandCtx,\n CommandDef,\n CommandsAction,\n CommandsCapability,\n CommandsState,\n IconAccent,\n ResolvedCommand,\n} from './types';\n\n/** A registered command: the definition plus its pre-parsed shortcuts. */\nexport interface RegisteredCommand {\n readonly def: CommandDef;\n readonly shortcuts: readonly string[];\n readonly parsed: readonly ParsedShortcut[];\n}\n\nexport type CommandRegistry = Map<string, RegisteredCommand>;\n\nexport function registerCommand(registry: CommandRegistry, def: CommandDef): void {\n if (registry.has(def.id)) throw new Error(`[commands] duplicate command: ${def.id}`);\n const shortcuts = def.shortcut === undefined ? [] : ([] as string[]).concat(def.shortcut);\n registry.set(def.id, { def, shortcuts, parsed: shortcuts.map(parseShortcut) });\n}\n\nconst panelTarget = (def: CommandDef): { id: string; exclusive?: string } | null =>\n def.panel === undefined ? null : typeof def.panel === 'string' ? { id: def.panel } : def.panel;\n\nexport function createCommandsCapability(\n ctx: PluginContext<CommandsState, CommandsAction>,\n registry: CommandRegistry,\n): CommandsCapability {\n /** Bind capability resolution to the command's target document. The kernel\n * resolves workspace tokens regardless of the document argument, so one\n * code path serves both scopes. */\n const commandCtx = (documentId?: string): CommandCtx => {\n const target = documentId ?? ctx.core().activeId;\n const get = <T>(token: CapabilityToken<T>): T =>\n target ? ctx.forDocument(token, target) : ctx.get(token);\n return {\n documentId: target,\n core: ctx.core,\n get,\n tryGet: <T>(token: CapabilityToken<T>): T | null => {\n try {\n return get(token);\n } catch {\n return null;\n }\n },\n };\n };\n\n /** Derivations run against live state; a derivation that throws (e.g. it\n * needs a document and none is open) falls back to the safe default —\n * the button renders, disabled, exactly like v2's empty state. */\n const derive = (\n fn: ((c: CommandCtx) => boolean) | undefined,\n c: CommandCtx,\n fallback: boolean,\n ): boolean => {\n if (!fn) return fallback;\n try {\n return fn(c);\n } catch {\n return fallback;\n }\n };\n\n /** Same guard as `derive`, for the icon-accent derivation: a throw (no\n * document, provider missing) means \"no accent\" — the icon renders plain. */\n const deriveAccent = (\n fn: ((c: CommandCtx) => IconAccent | null) | undefined,\n c: CommandCtx,\n ): IconAccent | undefined => {\n if (!fn) return undefined;\n try {\n return fn(c) ?? undefined;\n } catch {\n return undefined;\n }\n };\n\n const resolve = (id: string, documentId?: string): ResolvedCommand | null => {\n const entry = registry.get(id);\n if (!entry) return null;\n const { def } = entry;\n const c = commandCtx(documentId);\n\n const disabled = ctx.getState().disabledCategories;\n const categoryHidden = (def.categories ?? []).some((cat) => disabled.includes(cat));\n\n const i18n = c.tryGet(I18nToken);\n const label = i18n ? i18n.t(def.labelKey) : def.labelKey;\n\n // Surface-target commands derive `active` from the surface's open state\n // unless the definition overrides it.\n let active: boolean;\n if (def.active) {\n active = derive(def.active, c, false);\n } else {\n const shell = c.tryGet(ShellToken);\n const panel = panelTarget(def);\n active = shell\n ? def.menu\n ? shell.isMenuOpen(def.menu)\n : panel\n ? shell.isOpen(panel.id)\n : def.modal\n ? shell.isOpen(def.modal)\n : false\n : false;\n }\n\n return {\n id: def.id,\n label,\n icon: def.icon,\n iconAccent: deriveAccent(def.iconAccent, c),\n shortcuts: entry.shortcuts,\n menu: def.menu,\n enabled: derive(def.enabled, c, true) && !categoryHidden,\n active,\n visible: derive(def.visible, c, true) && !categoryHidden,\n categories: def.categories ?? [],\n };\n };\n\n const execute = (id: string, documentId?: string): void => {\n const entry = registry.get(id);\n if (!entry) return;\n const resolved = resolve(id, documentId);\n if (!resolved || !resolved.enabled || !resolved.visible) return;\n const c = commandCtx(documentId);\n\n if (entry.def.run) {\n entry.def.run(c);\n return;\n }\n // Default routing for declarative surface targets.\n const shell = c.tryGet(ShellToken);\n if (!shell) return;\n const panel = panelTarget(entry.def);\n if (entry.def.menu) shell.toggleMenu(entry.def.menu);\n else if (panel) shell.toggle(panel.id, { exclusive: panel.exclusive });\n else if (entry.def.modal) shell.toggle(entry.def.modal, { exclusive: 'modal' });\n };\n\n return {\n register: (def) => registerCommand(registry, def),\n unregister: (id) => void registry.delete(id),\n has: (id) => registry.has(id),\n ids: () => [...registry.keys()],\n\n resolve,\n search: (query, documentId) => {\n const q = query.trim().toLowerCase();\n const hits: ResolvedCommand[] = [];\n for (const id of registry.keys()) {\n const r = resolve(id, documentId);\n if (!r || !r.visible) continue;\n if (q === '' || r.label.toLowerCase().includes(q) || r.id.includes(q)) hits.push(r);\n }\n return hits;\n },\n menuTarget: (id) => {\n const entry = registry.get(id);\n return entry ? { menu: entry.def.menu } : null;\n },\n\n execute,\n matchStroke: (stroke: KeyStroke, opts) => {\n for (const [id, entry] of registry) {\n if (entry.parsed.some((p) => matchShortcut(p, stroke, opts))) return id;\n }\n return null;\n },\n\n disabledCategories: () => ctx.getState().disabledCategories,\n isCategoryDisabled: (category) => ctx.getState().disabledCategories.includes(category),\n disableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/DISABLE_CATEGORY', category }),\n enableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/ENABLE_CATEGORY', category }),\n setDisabledCategories: (categories) =>\n ctx.dispatch({ type: 'COMMANDS/SET_DISABLED_CATEGORIES', categories }),\n };\n}\n","import type { CommandsAction, CommandsState } from './types';\n\nexport const initialCommandsState: CommandsState = {\n disabledCategories: [],\n};\n\nexport function commandsReducer(state: CommandsState, action: CommandsAction): CommandsState {\n switch (action.type) {\n case 'COMMANDS/DISABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? state\n : { disabledCategories: [...state.disabledCategories, action.category] };\n case 'COMMANDS/ENABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? { disabledCategories: state.disabledCategories.filter((c) => c !== action.category) }\n : state;\n case 'COMMANDS/SET_DISABLED_CATEGORIES':\n return { disabledCategories: [...action.categories] };\n default:\n return state;\n }\n}\n","import { createCapabilityToken } from '@embedpdf/core';\nimport type { CapabilityToken, CoreState } from '@embedpdf/core';\nimport type { KeyStroke } from '@embedpdf/core-ui';\n\n/**\n * @embedpdf/plugin-commands — the contract.\n *\n * Commands are the single vocabulary of verbs: toolbars, menus, contextual\n * strips, shortcuts, and the palette are all projections of this registry.\n * The plugin ships ZERO commands (mechanism here, definitions in the product\n * — same split as plugin-i18n's locale packs).\n *\n * Command state is a pure DERIVATION over the store: `resolve()` reads other\n * capabilities' selectors at call time, so any store change is reflected on\n * the next read and the framework binding's one change stream makes every\n * consumer reactive. There is no CommandStateChangedEvent, no diffing, no\n * cache — v2's entire notification apparatus has no v3 equivalent because\n * the reactive store subsumes it.\n *\n * Definitions hold functions, so they live in the plugin's registry (config\n * + `register()`), never in the store; store state is only the serializable\n * `disabledCategories`.\n */\n\n/** What a derivation or `run` sees: capability resolution bound to the\n * command's target document (explicit, else the active one). */\nexport interface CommandCtx {\n /** The target document, or null when no document is open. */\n readonly documentId: string | null;\n core(): CoreState;\n /** Resolve a capability; document-scoped tokens bind to the target document. */\n get<T>(token: CapabilityToken<T>): T;\n /** Like `get`, but null when unavailable (no provider / no document). */\n tryGet<T>(token: CapabilityToken<T>): T | null;\n}\n\n/**\n * Up to two theme colors accompanying a command's icon — typed FACTS any\n * renderer can interpret (tint a glyph's slots, show a swatch), never\n * renderer props. The registry carries it the way it carries `icon`: as\n * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —\n * renderer-specific needs live app-side, joined by command id.\n */\nexport interface IconAccent {\n /** The mark: stroke / markup / font color. */\n readonly primary?: string;\n /** The fill, when there is one. */\n readonly secondary?: string;\n}\n\nexport interface CommandDef {\n /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */\n readonly id: string;\n /** i18n key, resolved through I18nToken when present (else shown verbatim). */\n readonly labelKey: string;\n readonly icon?: string;\n /** Live color accent for the icon (a tool previewing its drawing defaults).\n * A pure derivation over the store, exactly like `active`/`enabled`. */\n readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;\n /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */\n readonly shortcut?: string | readonly string[];\n /** Feature-gating tags: a disabled category hides its commands everywhere. */\n readonly categories?: readonly string[];\n\n // ── declarative surface targets ──────────────────────────────────────────\n // A command that opens chrome DECLARES what it opens instead of doing it\n // imperatively. This is load-bearing: buttons render carets/aria-haspopup,\n // `active` derives automatically from the surface's open state, and the\n // overflow projection renders `menu` targets as nested submenus.\n /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */\n readonly menu?: string;\n /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */\n readonly panel?: string | { readonly id: string; readonly exclusive?: string };\n /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */\n readonly modal?: string;\n\n // ── pure derivations over the store ──────────────────────────────────────\n readonly enabled?: (ctx: CommandCtx) => boolean;\n readonly active?: (ctx: CommandCtx) => boolean;\n readonly visible?: (ctx: CommandCtx) => boolean;\n\n /** The verb. Optional for pure surface-target commands. Runs before the\n * default target routing when both are present. */\n readonly run?: (ctx: CommandCtx) => void;\n}\n\n/** A command as a renderer sees it — everything resolved for the target document. */\nexport interface ResolvedCommand {\n readonly id: string;\n readonly label: string;\n readonly icon?: string;\n readonly iconAccent?: IconAccent;\n readonly shortcuts: readonly string[];\n readonly menu?: string;\n readonly enabled: boolean;\n readonly active: boolean;\n readonly visible: boolean;\n readonly categories: readonly string[];\n}\n\n/** Value equality over resolved commands — `resolve()` mints a fresh object\n * per read, so reactive bindings memo by value to re-render on real change. */\nexport const resolvedCommandsEqual = (\n a: ResolvedCommand | null,\n b: ResolvedCommand | null,\n): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.id === b.id &&\n a.label === b.label &&\n a.icon === b.icon &&\n // by value: resolve() mints a fresh accent object each read\n a.iconAccent?.primary === b.iconAccent?.primary &&\n a.iconAccent?.secondary === b.iconAccent?.secondary &&\n a.menu === b.menu &&\n a.enabled === b.enabled &&\n a.active === b.active &&\n a.visible === b.visible &&\n a.shortcuts.length === b.shortcuts.length &&\n a.shortcuts.every((s, i) => s === b.shortcuts[i])\n );\n};\n\nexport interface CommandsState {\n readonly disabledCategories: readonly string[];\n}\n\nexport type CommandsAction =\n | { type: 'COMMANDS/DISABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/ENABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/SET_DISABLED_CATEGORIES'; categories: readonly string[] };\n\nexport interface CommandsConfig {\n /** The app's command definitions (content — the plugin ships none). */\n commands?: readonly CommandDef[];\n /** Categories disabled at startup (host feature-gating). */\n disabledCategories?: readonly string[];\n}\n\nexport interface CommandsCapability {\n // ── registry ──\n register(def: CommandDef): void;\n unregister(id: string): void;\n has(id: string): boolean;\n ids(): string[];\n\n // ── resolution (pure reads; reactive through the store) ──\n resolve(id: string, documentId?: string): ResolvedCommand | null;\n /** Palette query: visible commands whose resolved label matches. */\n search(query: string, documentId?: string): ResolvedCommand[];\n /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */\n menuTarget(id: string): { menu?: string } | null;\n\n // ── execution (the ONLY path; guarded by enabled/visible) ──\n execute(id: string, documentId?: string): void;\n /** Match a keystroke against every registered shortcut → command id or null. */\n matchStroke(stroke: KeyStroke, opts: { isMac: boolean }): string | null;\n\n // ── category gating ──\n disabledCategories(): readonly string[];\n isCategoryDisabled(category: string): boolean;\n disableCategory(category: string): void;\n enableCategory(category: string): void;\n setDisabledCategories(categories: readonly string[]): void;\n}\n\nexport const CommandsToken = createCapabilityToken<CommandsCapability>('commands');\n","import { definePlugin } from '@embedpdf/core';\nimport { createCommandsCapability, registerCommand } from './capability';\nimport type { CommandRegistry } from './capability';\nimport { commandsReducer, initialCommandsState } from './reducer';\nimport { CommandsToken } from './types';\nimport type { CommandsAction, CommandsCapability, CommandsConfig, CommandsState } from './types';\n\n/**\n * The commands plugin: workspace-scoped (one vocabulary for the whole\n * workspace; resolution/execution bind to a target document per call).\n * Definitions live in this closure — never in the store (they hold\n * functions); the store slice holds only `disabledCategories`.\n */\nexport const commandsPlugin = (config?: CommandsConfig) => {\n const registry: CommandRegistry = new Map();\n for (const def of config?.commands ?? []) registerCommand(registry, def);\n\n return definePlugin<CommandsState, CommandsAction, CommandsCapability>({\n id: 'commands',\n scope: 'workspace',\n token: CommandsToken,\n initialState: {\n ...initialCommandsState,\n disabledCategories: [...(config?.disabledCategories ?? [])],\n },\n reduce: commandsReducer,\n capability: (ctx) => createCommandsCapability(ctx, registry),\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["parseShortcut","I18nToken","ShellToken","CommandsToken"],"sources":["../src/capability.ts","../src/reducer.ts","../src/commands.plugin.ts"],"sourcesContent":["import type { CapabilityToken, PluginContext } from '@embedpdf/core';\nimport { I18nToken } from '@embedpdf/plugin-i18n/contract';\nimport { ShellToken } from '@embedpdf/plugin-shell/contract';\nimport { matchShortcut, parseShortcut } from '@embedpdf/core-ui';\nimport type { KeyStroke, ParsedShortcut } from '@embedpdf/core-ui';\nimport type {\n CommandCtx,\n CommandDef,\n CommandsAction,\n CommandsCapability,\n CommandsState,\n IconAccent,\n ResolvedCommand,\n} from './types';\n\n/** A registered command: the definition plus its pre-parsed shortcuts. */\nexport interface RegisteredCommand {\n readonly def: CommandDef;\n readonly shortcuts: readonly string[];\n readonly parsed: readonly ParsedShortcut[];\n}\n\nexport type CommandRegistry = Map<string, RegisteredCommand>;\n\nexport function registerCommand(registry: CommandRegistry, def: CommandDef): void {\n if (registry.has(def.id)) throw new Error(`[commands] duplicate command: ${def.id}`);\n const shortcuts = def.shortcut === undefined ? [] : ([] as string[]).concat(def.shortcut);\n registry.set(def.id, { def, shortcuts, parsed: shortcuts.map(parseShortcut) });\n}\n\nconst panelTarget = (def: CommandDef): { id: string; exclusive?: string } | null =>\n def.panel === undefined ? null : typeof def.panel === 'string' ? { id: def.panel } : def.panel;\n\nexport function createCommandsCapability(\n ctx: PluginContext<CommandsState, CommandsAction>,\n registry: CommandRegistry,\n): CommandsCapability {\n /** Bind capability resolution to the command's target document. The kernel\n * resolves workspace tokens regardless of the document argument, so one\n * code path serves both scopes. */\n const commandCtx = (documentId?: string): CommandCtx => {\n const target = documentId ?? ctx.core().activeId;\n const get = <T>(token: CapabilityToken<T>): T =>\n target ? ctx.forDocument(token, target) : ctx.get(token);\n return {\n documentId: target,\n core: ctx.core,\n get,\n tryGet: <T>(token: CapabilityToken<T>): T | null => {\n try {\n return get(token);\n } catch {\n return null;\n }\n },\n };\n };\n\n /** Derivations run against live state; a derivation that throws (e.g. it\n * needs a document and none is open) falls back to the safe default —\n * the button renders, disabled, exactly like v2's empty state. */\n const derive = (\n fn: ((c: CommandCtx) => boolean) | undefined,\n c: CommandCtx,\n fallback: boolean,\n ): boolean => {\n if (!fn) return fallback;\n try {\n return fn(c);\n } catch {\n return fallback;\n }\n };\n\n /** Same guard as `derive`, for the icon-accent derivation: a throw (no\n * document, provider missing) means \"no accent\" — the icon renders plain. */\n const deriveAccent = (\n fn: ((c: CommandCtx) => IconAccent | null) | undefined,\n c: CommandCtx,\n ): IconAccent | undefined => {\n if (!fn) return undefined;\n try {\n return fn(c) ?? undefined;\n } catch {\n return undefined;\n }\n };\n\n const resolve = (id: string, documentId?: string): ResolvedCommand | null => {\n const entry = registry.get(id);\n if (!entry) return null;\n const { def } = entry;\n const c = commandCtx(documentId);\n\n const disabled = ctx.getState().disabledCategories;\n const categoryHidden = (def.categories ?? []).some((cat) => disabled.includes(cat));\n\n const i18n = c.tryGet(I18nToken);\n const label = i18n ? i18n.t(def.labelKey) : def.labelKey;\n\n // Surface-target commands derive `active` from the surface's open state\n // unless the definition overrides it.\n let active: boolean;\n if (def.active) {\n active = derive(def.active, c, false);\n } else {\n const shell = c.tryGet(ShellToken);\n const panel = panelTarget(def);\n active = shell\n ? def.menu\n ? shell.isMenuOpen(def.menu)\n : panel\n ? shell.isOpen(panel.id)\n : def.modal\n ? shell.isOpen(def.modal)\n : false\n : false;\n }\n\n return {\n id: def.id,\n label,\n icon: def.icon,\n iconAccent: deriveAccent(def.iconAccent, c),\n shortcuts: entry.shortcuts,\n menu: def.menu,\n enabled: derive(def.enabled, c, true) && !categoryHidden,\n active,\n visible: derive(def.visible, c, true) && !categoryHidden,\n categories: def.categories ?? [],\n };\n };\n\n const execute = (id: string, documentId?: string): void => {\n const entry = registry.get(id);\n if (!entry) return;\n const resolved = resolve(id, documentId);\n if (!resolved || !resolved.enabled || !resolved.visible) return;\n const c = commandCtx(documentId);\n\n if (entry.def.run) {\n entry.def.run(c);\n return;\n }\n // Default routing for declarative surface targets.\n const shell = c.tryGet(ShellToken);\n if (!shell) return;\n const panel = panelTarget(entry.def);\n if (entry.def.menu) shell.toggleMenu(entry.def.menu);\n else if (panel) shell.toggle(panel.id, { exclusive: panel.exclusive });\n else if (entry.def.modal) shell.toggle(entry.def.modal, { exclusive: 'modal' });\n };\n\n return {\n register: (def) => registerCommand(registry, def),\n unregister: (id) => void registry.delete(id),\n has: (id) => registry.has(id),\n ids: () => [...registry.keys()],\n\n resolve,\n search: (query, documentId) => {\n const q = query.trim().toLowerCase();\n const hits: ResolvedCommand[] = [];\n for (const id of registry.keys()) {\n const r = resolve(id, documentId);\n if (!r || !r.visible) continue;\n if (q === '' || r.label.toLowerCase().includes(q) || r.id.includes(q)) hits.push(r);\n }\n return hits;\n },\n menuTarget: (id) => {\n const entry = registry.get(id);\n return entry ? { menu: entry.def.menu } : null;\n },\n\n execute,\n matchStroke: (stroke: KeyStroke, opts) => {\n for (const [id, entry] of registry) {\n if (entry.parsed.some((p) => matchShortcut(p, stroke, opts))) return id;\n }\n return null;\n },\n\n disabledCategories: () => ctx.getState().disabledCategories,\n isCategoryDisabled: (category) => ctx.getState().disabledCategories.includes(category),\n disableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/DISABLE_CATEGORY', category }),\n enableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/ENABLE_CATEGORY', category }),\n setDisabledCategories: (categories) =>\n ctx.dispatch({ type: 'COMMANDS/SET_DISABLED_CATEGORIES', categories }),\n };\n}\n","import type { CommandsAction, CommandsState } from './types';\n\nexport const initialCommandsState: CommandsState = {\n disabledCategories: [],\n};\n\nexport function commandsReducer(state: CommandsState, action: CommandsAction): CommandsState {\n switch (action.type) {\n case 'COMMANDS/DISABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? state\n : { disabledCategories: [...state.disabledCategories, action.category] };\n case 'COMMANDS/ENABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? { disabledCategories: state.disabledCategories.filter((c) => c !== action.category) }\n : state;\n case 'COMMANDS/SET_DISABLED_CATEGORIES':\n return { disabledCategories: [...action.categories] };\n default:\n return state;\n }\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { createCommandsCapability, registerCommand } from './capability';\nimport type { CommandRegistry } from './capability';\nimport { commandsReducer, initialCommandsState } from './reducer';\nimport { CommandsToken } from './types';\nimport type { CommandsAction, CommandsCapability, CommandsConfig, CommandsState } from './types';\n\n/**\n * The commands plugin: workspace-scoped (one vocabulary for the whole\n * workspace; resolution/execution bind to a target document per call).\n * Definitions live in this closure — never in the store (they hold\n * functions); the store slice holds only `disabledCategories`.\n */\nexport const commandsPlugin = (config?: CommandsConfig) => {\n const registry: CommandRegistry = new Map();\n for (const def of config?.commands ?? []) registerCommand(registry, def);\n\n return definePlugin<CommandsState, CommandsAction, CommandsCapability>({\n id: 'commands',\n scope: 'workspace',\n token: CommandsToken,\n initialState: {\n ...initialCommandsState,\n disabledCategories: [...(config?.disabledCategories ?? [])],\n },\n reduce: commandsReducer,\n capability: (ctx) => createCommandsCapability(ctx, registry),\n });\n};\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,138 +1,4 @@
1
- import { CapabilityToken, CoreState } from "@embedpdf/core";
2
- import { KeyStroke } from "@embedpdf/core-ui";
3
- //#region src/types.d.ts
4
- /**
5
- * @embedpdf/plugin-commands — the contract.
6
- *
7
- * Commands are the single vocabulary of verbs: toolbars, menus, contextual
8
- * strips, shortcuts, and the palette are all projections of this registry.
9
- * The plugin ships ZERO commands (mechanism here, definitions in the product
10
- * — same split as plugin-i18n's locale packs).
11
- *
12
- * Command state is a pure DERIVATION over the store: `resolve()` reads other
13
- * capabilities' selectors at call time, so any store change is reflected on
14
- * the next read and the framework binding's one change stream makes every
15
- * consumer reactive. There is no CommandStateChangedEvent, no diffing, no
16
- * cache — v2's entire notification apparatus has no v3 equivalent because
17
- * the reactive store subsumes it.
18
- *
19
- * Definitions hold functions, so they live in the plugin's registry (config
20
- * + `register()`), never in the store; store state is only the serializable
21
- * `disabledCategories`.
22
- */
23
- /** What a derivation or `run` sees: capability resolution bound to the
24
- * command's target document (explicit, else the active one). */
25
- interface CommandCtx {
26
- /** The target document, or null when no document is open. */
27
- readonly documentId: string | null;
28
- core(): CoreState;
29
- /** Resolve a capability; document-scoped tokens bind to the target document. */
30
- get<T>(token: CapabilityToken<T>): T;
31
- /** Like `get`, but null when unavailable (no provider / no document). */
32
- tryGet<T>(token: CapabilityToken<T>): T | null;
33
- }
34
- /**
35
- * Up to two theme colors accompanying a command's icon — typed FACTS any
36
- * renderer can interpret (tint a glyph's slots, show a swatch), never
37
- * renderer props. The registry carries it the way it carries `icon`: as
38
- * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —
39
- * renderer-specific needs live app-side, joined by command id.
40
- */
41
- interface IconAccent {
42
- /** The mark: stroke / markup / font color. */
43
- readonly primary?: string;
44
- /** The fill, when there is one. */
45
- readonly secondary?: string;
46
- }
47
- interface CommandDef {
48
- /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */
49
- readonly id: string;
50
- /** i18n key, resolved through I18nToken when present (else shown verbatim). */
51
- readonly labelKey: string;
52
- readonly icon?: string;
53
- /** Live color accent for the icon (a tool previewing its drawing defaults).
54
- * A pure derivation over the store, exactly like `active`/`enabled`. */
55
- readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;
56
- /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */
57
- readonly shortcut?: string | readonly string[];
58
- /** Feature-gating tags: a disabled category hides its commands everywhere. */
59
- readonly categories?: readonly string[];
60
- /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */
61
- readonly menu?: string;
62
- /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */
63
- readonly panel?: string | {
64
- readonly id: string;
65
- readonly exclusive?: string;
66
- };
67
- /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */
68
- readonly modal?: string;
69
- readonly enabled?: (ctx: CommandCtx) => boolean;
70
- readonly active?: (ctx: CommandCtx) => boolean;
71
- readonly visible?: (ctx: CommandCtx) => boolean;
72
- /** The verb. Optional for pure surface-target commands. Runs before the
73
- * default target routing when both are present. */
74
- readonly run?: (ctx: CommandCtx) => void;
75
- }
76
- /** A command as a renderer sees it — everything resolved for the target document. */
77
- interface ResolvedCommand {
78
- readonly id: string;
79
- readonly label: string;
80
- readonly icon?: string;
81
- readonly iconAccent?: IconAccent;
82
- readonly shortcuts: readonly string[];
83
- readonly menu?: string;
84
- readonly enabled: boolean;
85
- readonly active: boolean;
86
- readonly visible: boolean;
87
- readonly categories: readonly string[];
88
- }
89
- /** Value equality over resolved commands — `resolve()` mints a fresh object
90
- * per read, so reactive bindings memo by value to re-render on real change. */
91
- declare const resolvedCommandsEqual: (a: ResolvedCommand | null, b: ResolvedCommand | null) => boolean;
92
- interface CommandsState {
93
- readonly disabledCategories: readonly string[];
94
- }
95
- type CommandsAction = {
96
- type: 'COMMANDS/DISABLE_CATEGORY';
97
- category: string;
98
- } | {
99
- type: 'COMMANDS/ENABLE_CATEGORY';
100
- category: string;
101
- } | {
102
- type: 'COMMANDS/SET_DISABLED_CATEGORIES';
103
- categories: readonly string[];
104
- };
105
- interface CommandsConfig {
106
- /** The app's command definitions (content — the plugin ships none). */
107
- commands?: readonly CommandDef[];
108
- /** Categories disabled at startup (host feature-gating). */
109
- disabledCategories?: readonly string[];
110
- }
111
- interface CommandsCapability {
112
- register(def: CommandDef): void;
113
- unregister(id: string): void;
114
- has(id: string): boolean;
115
- ids(): string[];
116
- resolve(id: string, documentId?: string): ResolvedCommand | null;
117
- /** Palette query: visible commands whose resolved label matches. */
118
- search(query: string, documentId?: string): ResolvedCommand[];
119
- /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */
120
- menuTarget(id: string): {
121
- menu?: string;
122
- } | null;
123
- execute(id: string, documentId?: string): void;
124
- /** Match a keystroke against every registered shortcut → command id or null. */
125
- matchStroke(stroke: KeyStroke, opts: {
126
- isMac: boolean;
127
- }): string | null;
128
- disabledCategories(): readonly string[];
129
- isCategoryDisabled(category: string): boolean;
130
- disableCategory(category: string): void;
131
- enableCategory(category: string): void;
132
- setDisabledCategories(categories: readonly string[]): void;
133
- }
134
- declare const CommandsToken: CapabilityToken<CommandsCapability>;
135
- //#endregion
1
+ import { a as CommandsConfig, c as IconAccent, i as CommandsCapability, l as ResolvedCommand, n as CommandDef, o as CommandsState, r as CommandsAction, s as CommandsToken, t as CommandCtx, u as resolvedCommandsEqual } from "./contract-CSIfF6wC.cjs";
136
2
  //#region src/commands.plugin.d.ts
137
3
  /**
138
4
  * The commands plugin: workspace-scoped (one vocabulary for the whole
package/dist/index.d.ts CHANGED
@@ -1,138 +1,4 @@
1
- import { CapabilityToken, CoreState } from "@embedpdf/core";
2
- import { KeyStroke } from "@embedpdf/core-ui";
3
- //#region src/types.d.ts
4
- /**
5
- * @embedpdf/plugin-commands — the contract.
6
- *
7
- * Commands are the single vocabulary of verbs: toolbars, menus, contextual
8
- * strips, shortcuts, and the palette are all projections of this registry.
9
- * The plugin ships ZERO commands (mechanism here, definitions in the product
10
- * — same split as plugin-i18n's locale packs).
11
- *
12
- * Command state is a pure DERIVATION over the store: `resolve()` reads other
13
- * capabilities' selectors at call time, so any store change is reflected on
14
- * the next read and the framework binding's one change stream makes every
15
- * consumer reactive. There is no CommandStateChangedEvent, no diffing, no
16
- * cache — v2's entire notification apparatus has no v3 equivalent because
17
- * the reactive store subsumes it.
18
- *
19
- * Definitions hold functions, so they live in the plugin's registry (config
20
- * + `register()`), never in the store; store state is only the serializable
21
- * `disabledCategories`.
22
- */
23
- /** What a derivation or `run` sees: capability resolution bound to the
24
- * command's target document (explicit, else the active one). */
25
- interface CommandCtx {
26
- /** The target document, or null when no document is open. */
27
- readonly documentId: string | null;
28
- core(): CoreState;
29
- /** Resolve a capability; document-scoped tokens bind to the target document. */
30
- get<T>(token: CapabilityToken<T>): T;
31
- /** Like `get`, but null when unavailable (no provider / no document). */
32
- tryGet<T>(token: CapabilityToken<T>): T | null;
33
- }
34
- /**
35
- * Up to two theme colors accompanying a command's icon — typed FACTS any
36
- * renderer can interpret (tint a glyph's slots, show a swatch), never
37
- * renderer props. The registry carries it the way it carries `icon`: as
38
- * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —
39
- * renderer-specific needs live app-side, joined by command id.
40
- */
41
- interface IconAccent {
42
- /** The mark: stroke / markup / font color. */
43
- readonly primary?: string;
44
- /** The fill, when there is one. */
45
- readonly secondary?: string;
46
- }
47
- interface CommandDef {
48
- /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */
49
- readonly id: string;
50
- /** i18n key, resolved through I18nToken when present (else shown verbatim). */
51
- readonly labelKey: string;
52
- readonly icon?: string;
53
- /** Live color accent for the icon (a tool previewing its drawing defaults).
54
- * A pure derivation over the store, exactly like `active`/`enabled`. */
55
- readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;
56
- /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */
57
- readonly shortcut?: string | readonly string[];
58
- /** Feature-gating tags: a disabled category hides its commands everywhere. */
59
- readonly categories?: readonly string[];
60
- /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */
61
- readonly menu?: string;
62
- /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */
63
- readonly panel?: string | {
64
- readonly id: string;
65
- readonly exclusive?: string;
66
- };
67
- /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */
68
- readonly modal?: string;
69
- readonly enabled?: (ctx: CommandCtx) => boolean;
70
- readonly active?: (ctx: CommandCtx) => boolean;
71
- readonly visible?: (ctx: CommandCtx) => boolean;
72
- /** The verb. Optional for pure surface-target commands. Runs before the
73
- * default target routing when both are present. */
74
- readonly run?: (ctx: CommandCtx) => void;
75
- }
76
- /** A command as a renderer sees it — everything resolved for the target document. */
77
- interface ResolvedCommand {
78
- readonly id: string;
79
- readonly label: string;
80
- readonly icon?: string;
81
- readonly iconAccent?: IconAccent;
82
- readonly shortcuts: readonly string[];
83
- readonly menu?: string;
84
- readonly enabled: boolean;
85
- readonly active: boolean;
86
- readonly visible: boolean;
87
- readonly categories: readonly string[];
88
- }
89
- /** Value equality over resolved commands — `resolve()` mints a fresh object
90
- * per read, so reactive bindings memo by value to re-render on real change. */
91
- declare const resolvedCommandsEqual: (a: ResolvedCommand | null, b: ResolvedCommand | null) => boolean;
92
- interface CommandsState {
93
- readonly disabledCategories: readonly string[];
94
- }
95
- type CommandsAction = {
96
- type: 'COMMANDS/DISABLE_CATEGORY';
97
- category: string;
98
- } | {
99
- type: 'COMMANDS/ENABLE_CATEGORY';
100
- category: string;
101
- } | {
102
- type: 'COMMANDS/SET_DISABLED_CATEGORIES';
103
- categories: readonly string[];
104
- };
105
- interface CommandsConfig {
106
- /** The app's command definitions (content — the plugin ships none). */
107
- commands?: readonly CommandDef[];
108
- /** Categories disabled at startup (host feature-gating). */
109
- disabledCategories?: readonly string[];
110
- }
111
- interface CommandsCapability {
112
- register(def: CommandDef): void;
113
- unregister(id: string): void;
114
- has(id: string): boolean;
115
- ids(): string[];
116
- resolve(id: string, documentId?: string): ResolvedCommand | null;
117
- /** Palette query: visible commands whose resolved label matches. */
118
- search(query: string, documentId?: string): ResolvedCommand[];
119
- /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */
120
- menuTarget(id: string): {
121
- menu?: string;
122
- } | null;
123
- execute(id: string, documentId?: string): void;
124
- /** Match a keystroke against every registered shortcut → command id or null. */
125
- matchStroke(stroke: KeyStroke, opts: {
126
- isMac: boolean;
127
- }): string | null;
128
- disabledCategories(): readonly string[];
129
- isCategoryDisabled(category: string): boolean;
130
- disableCategory(category: string): void;
131
- enableCategory(category: string): void;
132
- setDisabledCategories(categories: readonly string[]): void;
133
- }
134
- declare const CommandsToken: CapabilityToken<CommandsCapability>;
135
- //#endregion
1
+ import { a as CommandsConfig, c as IconAccent, i as CommandsCapability, l as ResolvedCommand, n as CommandDef, o as CommandsState, r as CommandsAction, s as CommandsToken, t as CommandCtx, u as resolvedCommandsEqual } from "./contract-CSIfF6wC.js";
136
2
  //#region src/commands.plugin.d.ts
137
3
  /**
138
4
  * The commands plugin: workspace-scoped (one vocabulary for the whole
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { createCapabilityToken, definePlugin } from "@embedpdf/core";
2
- import { I18nToken } from "@embedpdf/plugin-i18n";
3
- import { ShellToken } from "@embedpdf/plugin-shell";
1
+ import { n as resolvedCommandsEqual, t as CommandsToken } from "./contract-UFgXphIQ.js";
2
+ import { definePlugin } from "@embedpdf/core";
3
+ import { I18nToken } from "@embedpdf/plugin-i18n/contract";
4
+ import { ShellToken } from "@embedpdf/plugin-shell/contract";
4
5
  import { matchShortcut, parseShortcut } from "@embedpdf/core-ui";
5
6
  //#region src/capability.ts
6
7
  function registerCommand(registry, def) {
@@ -153,16 +154,6 @@ function commandsReducer(state, action) {
153
154
  }
154
155
  }
155
156
  //#endregion
156
- //#region src/types.ts
157
- /** Value equality over resolved commands — `resolve()` mints a fresh object
158
- * per read, so reactive bindings memo by value to re-render on real change. */
159
- const resolvedCommandsEqual = (a, b) => {
160
- if (a === b) return true;
161
- if (!a || !b) return false;
162
- return a.id === b.id && a.label === b.label && a.icon === b.icon && a.iconAccent?.primary === b.iconAccent?.primary && a.iconAccent?.secondary === b.iconAccent?.secondary && a.menu === b.menu && a.enabled === b.enabled && a.active === b.active && a.visible === b.visible && a.shortcuts.length === b.shortcuts.length && a.shortcuts.every((s, i) => s === b.shortcuts[i]);
163
- };
164
- const CommandsToken = createCapabilityToken("commands");
165
- //#endregion
166
157
  //#region src/commands.plugin.ts
167
158
  /**
168
159
  * The commands plugin: workspace-scoped (one vocabulary for the whole
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/reducer.ts","../src/types.ts","../src/commands.plugin.ts"],"sourcesContent":["import type { CapabilityToken, PluginContext } from '@embedpdf/core';\nimport { I18nToken } from '@embedpdf/plugin-i18n';\nimport { ShellToken } from '@embedpdf/plugin-shell';\nimport { matchShortcut, parseShortcut } from '@embedpdf/core-ui';\nimport type { KeyStroke, ParsedShortcut } from '@embedpdf/core-ui';\nimport type {\n CommandCtx,\n CommandDef,\n CommandsAction,\n CommandsCapability,\n CommandsState,\n IconAccent,\n ResolvedCommand,\n} from './types';\n\n/** A registered command: the definition plus its pre-parsed shortcuts. */\nexport interface RegisteredCommand {\n readonly def: CommandDef;\n readonly shortcuts: readonly string[];\n readonly parsed: readonly ParsedShortcut[];\n}\n\nexport type CommandRegistry = Map<string, RegisteredCommand>;\n\nexport function registerCommand(registry: CommandRegistry, def: CommandDef): void {\n if (registry.has(def.id)) throw new Error(`[commands] duplicate command: ${def.id}`);\n const shortcuts = def.shortcut === undefined ? [] : ([] as string[]).concat(def.shortcut);\n registry.set(def.id, { def, shortcuts, parsed: shortcuts.map(parseShortcut) });\n}\n\nconst panelTarget = (def: CommandDef): { id: string; exclusive?: string } | null =>\n def.panel === undefined ? null : typeof def.panel === 'string' ? { id: def.panel } : def.panel;\n\nexport function createCommandsCapability(\n ctx: PluginContext<CommandsState, CommandsAction>,\n registry: CommandRegistry,\n): CommandsCapability {\n /** Bind capability resolution to the command's target document. The kernel\n * resolves workspace tokens regardless of the document argument, so one\n * code path serves both scopes. */\n const commandCtx = (documentId?: string): CommandCtx => {\n const target = documentId ?? ctx.core().activeId;\n const get = <T>(token: CapabilityToken<T>): T =>\n target ? ctx.forDocument(token, target) : ctx.get(token);\n return {\n documentId: target,\n core: ctx.core,\n get,\n tryGet: <T>(token: CapabilityToken<T>): T | null => {\n try {\n return get(token);\n } catch {\n return null;\n }\n },\n };\n };\n\n /** Derivations run against live state; a derivation that throws (e.g. it\n * needs a document and none is open) falls back to the safe default —\n * the button renders, disabled, exactly like v2's empty state. */\n const derive = (\n fn: ((c: CommandCtx) => boolean) | undefined,\n c: CommandCtx,\n fallback: boolean,\n ): boolean => {\n if (!fn) return fallback;\n try {\n return fn(c);\n } catch {\n return fallback;\n }\n };\n\n /** Same guard as `derive`, for the icon-accent derivation: a throw (no\n * document, provider missing) means \"no accent\" — the icon renders plain. */\n const deriveAccent = (\n fn: ((c: CommandCtx) => IconAccent | null) | undefined,\n c: CommandCtx,\n ): IconAccent | undefined => {\n if (!fn) return undefined;\n try {\n return fn(c) ?? undefined;\n } catch {\n return undefined;\n }\n };\n\n const resolve = (id: string, documentId?: string): ResolvedCommand | null => {\n const entry = registry.get(id);\n if (!entry) return null;\n const { def } = entry;\n const c = commandCtx(documentId);\n\n const disabled = ctx.getState().disabledCategories;\n const categoryHidden = (def.categories ?? []).some((cat) => disabled.includes(cat));\n\n const i18n = c.tryGet(I18nToken);\n const label = i18n ? i18n.t(def.labelKey) : def.labelKey;\n\n // Surface-target commands derive `active` from the surface's open state\n // unless the definition overrides it.\n let active: boolean;\n if (def.active) {\n active = derive(def.active, c, false);\n } else {\n const shell = c.tryGet(ShellToken);\n const panel = panelTarget(def);\n active = shell\n ? def.menu\n ? shell.isMenuOpen(def.menu)\n : panel\n ? shell.isOpen(panel.id)\n : def.modal\n ? shell.isOpen(def.modal)\n : false\n : false;\n }\n\n return {\n id: def.id,\n label,\n icon: def.icon,\n iconAccent: deriveAccent(def.iconAccent, c),\n shortcuts: entry.shortcuts,\n menu: def.menu,\n enabled: derive(def.enabled, c, true) && !categoryHidden,\n active,\n visible: derive(def.visible, c, true) && !categoryHidden,\n categories: def.categories ?? [],\n };\n };\n\n const execute = (id: string, documentId?: string): void => {\n const entry = registry.get(id);\n if (!entry) return;\n const resolved = resolve(id, documentId);\n if (!resolved || !resolved.enabled || !resolved.visible) return;\n const c = commandCtx(documentId);\n\n if (entry.def.run) {\n entry.def.run(c);\n return;\n }\n // Default routing for declarative surface targets.\n const shell = c.tryGet(ShellToken);\n if (!shell) return;\n const panel = panelTarget(entry.def);\n if (entry.def.menu) shell.toggleMenu(entry.def.menu);\n else if (panel) shell.toggle(panel.id, { exclusive: panel.exclusive });\n else if (entry.def.modal) shell.toggle(entry.def.modal, { exclusive: 'modal' });\n };\n\n return {\n register: (def) => registerCommand(registry, def),\n unregister: (id) => void registry.delete(id),\n has: (id) => registry.has(id),\n ids: () => [...registry.keys()],\n\n resolve,\n search: (query, documentId) => {\n const q = query.trim().toLowerCase();\n const hits: ResolvedCommand[] = [];\n for (const id of registry.keys()) {\n const r = resolve(id, documentId);\n if (!r || !r.visible) continue;\n if (q === '' || r.label.toLowerCase().includes(q) || r.id.includes(q)) hits.push(r);\n }\n return hits;\n },\n menuTarget: (id) => {\n const entry = registry.get(id);\n return entry ? { menu: entry.def.menu } : null;\n },\n\n execute,\n matchStroke: (stroke: KeyStroke, opts) => {\n for (const [id, entry] of registry) {\n if (entry.parsed.some((p) => matchShortcut(p, stroke, opts))) return id;\n }\n return null;\n },\n\n disabledCategories: () => ctx.getState().disabledCategories,\n isCategoryDisabled: (category) => ctx.getState().disabledCategories.includes(category),\n disableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/DISABLE_CATEGORY', category }),\n enableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/ENABLE_CATEGORY', category }),\n setDisabledCategories: (categories) =>\n ctx.dispatch({ type: 'COMMANDS/SET_DISABLED_CATEGORIES', categories }),\n };\n}\n","import type { CommandsAction, CommandsState } from './types';\n\nexport const initialCommandsState: CommandsState = {\n disabledCategories: [],\n};\n\nexport function commandsReducer(state: CommandsState, action: CommandsAction): CommandsState {\n switch (action.type) {\n case 'COMMANDS/DISABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? state\n : { disabledCategories: [...state.disabledCategories, action.category] };\n case 'COMMANDS/ENABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? { disabledCategories: state.disabledCategories.filter((c) => c !== action.category) }\n : state;\n case 'COMMANDS/SET_DISABLED_CATEGORIES':\n return { disabledCategories: [...action.categories] };\n default:\n return state;\n }\n}\n","import { createCapabilityToken } from '@embedpdf/core';\nimport type { CapabilityToken, CoreState } from '@embedpdf/core';\nimport type { KeyStroke } from '@embedpdf/core-ui';\n\n/**\n * @embedpdf/plugin-commands — the contract.\n *\n * Commands are the single vocabulary of verbs: toolbars, menus, contextual\n * strips, shortcuts, and the palette are all projections of this registry.\n * The plugin ships ZERO commands (mechanism here, definitions in the product\n * — same split as plugin-i18n's locale packs).\n *\n * Command state is a pure DERIVATION over the store: `resolve()` reads other\n * capabilities' selectors at call time, so any store change is reflected on\n * the next read and the framework binding's one change stream makes every\n * consumer reactive. There is no CommandStateChangedEvent, no diffing, no\n * cache — v2's entire notification apparatus has no v3 equivalent because\n * the reactive store subsumes it.\n *\n * Definitions hold functions, so they live in the plugin's registry (config\n * + `register()`), never in the store; store state is only the serializable\n * `disabledCategories`.\n */\n\n/** What a derivation or `run` sees: capability resolution bound to the\n * command's target document (explicit, else the active one). */\nexport interface CommandCtx {\n /** The target document, or null when no document is open. */\n readonly documentId: string | null;\n core(): CoreState;\n /** Resolve a capability; document-scoped tokens bind to the target document. */\n get<T>(token: CapabilityToken<T>): T;\n /** Like `get`, but null when unavailable (no provider / no document). */\n tryGet<T>(token: CapabilityToken<T>): T | null;\n}\n\n/**\n * Up to two theme colors accompanying a command's icon — typed FACTS any\n * renderer can interpret (tint a glyph's slots, show a swatch), never\n * renderer props. The registry carries it the way it carries `icon`: as\n * data it doesn't interpret. Deliberately NOT v2's `iconProps` bag —\n * renderer-specific needs live app-side, joined by command id.\n */\nexport interface IconAccent {\n /** The mark: stroke / markup / font color. */\n readonly primary?: string;\n /** The fill, when there is one. */\n readonly secondary?: string;\n}\n\nexport interface CommandDef {\n /** Convention: 'domain:verb' — 'zoom:in', 'mode:annotate', 'panel:search'. */\n readonly id: string;\n /** i18n key, resolved through I18nToken when present (else shown verbatim). */\n readonly labelKey: string;\n readonly icon?: string;\n /** Live color accent for the icon (a tool previewing its drawing defaults).\n * A pure derivation over the store, exactly like `active`/`enabled`. */\n readonly iconAccent?: (ctx: CommandCtx) => IconAccent | null;\n /** 'Mod+K' style (ui-core grammar). Multiple bindings allowed. */\n readonly shortcut?: string | readonly string[];\n /** Feature-gating tags: a disabled category hides its commands everywhere. */\n readonly categories?: readonly string[];\n\n // ── declarative surface targets ──────────────────────────────────────────\n // A command that opens chrome DECLARES what it opens instead of doing it\n // imperatively. This is load-bearing: buttons render carets/aria-haspopup,\n // `active` derives automatically from the surface's open state, and the\n // overflow projection renders `menu` targets as nested submenus.\n /** Toggles a named dropdown menu (a MenuSchema id in the app's chrome). */\n readonly menu?: string;\n /** Toggles a named shell surface, optionally exclusive within a tag ('left'…). */\n readonly panel?: string | { readonly id: string; readonly exclusive?: string };\n /** Toggles a modal surface (exclusive within the built-in 'modal' tag). */\n readonly modal?: string;\n\n // ── pure derivations over the store ──────────────────────────────────────\n readonly enabled?: (ctx: CommandCtx) => boolean;\n readonly active?: (ctx: CommandCtx) => boolean;\n readonly visible?: (ctx: CommandCtx) => boolean;\n\n /** The verb. Optional for pure surface-target commands. Runs before the\n * default target routing when both are present. */\n readonly run?: (ctx: CommandCtx) => void;\n}\n\n/** A command as a renderer sees it — everything resolved for the target document. */\nexport interface ResolvedCommand {\n readonly id: string;\n readonly label: string;\n readonly icon?: string;\n readonly iconAccent?: IconAccent;\n readonly shortcuts: readonly string[];\n readonly menu?: string;\n readonly enabled: boolean;\n readonly active: boolean;\n readonly visible: boolean;\n readonly categories: readonly string[];\n}\n\n/** Value equality over resolved commands — `resolve()` mints a fresh object\n * per read, so reactive bindings memo by value to re-render on real change. */\nexport const resolvedCommandsEqual = (\n a: ResolvedCommand | null,\n b: ResolvedCommand | null,\n): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.id === b.id &&\n a.label === b.label &&\n a.icon === b.icon &&\n // by value: resolve() mints a fresh accent object each read\n a.iconAccent?.primary === b.iconAccent?.primary &&\n a.iconAccent?.secondary === b.iconAccent?.secondary &&\n a.menu === b.menu &&\n a.enabled === b.enabled &&\n a.active === b.active &&\n a.visible === b.visible &&\n a.shortcuts.length === b.shortcuts.length &&\n a.shortcuts.every((s, i) => s === b.shortcuts[i])\n );\n};\n\nexport interface CommandsState {\n readonly disabledCategories: readonly string[];\n}\n\nexport type CommandsAction =\n | { type: 'COMMANDS/DISABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/ENABLE_CATEGORY'; category: string }\n | { type: 'COMMANDS/SET_DISABLED_CATEGORIES'; categories: readonly string[] };\n\nexport interface CommandsConfig {\n /** The app's command definitions (content — the plugin ships none). */\n commands?: readonly CommandDef[];\n /** Categories disabled at startup (host feature-gating). */\n disabledCategories?: readonly string[];\n}\n\nexport interface CommandsCapability {\n // ── registry ──\n register(def: CommandDef): void;\n unregister(id: string): void;\n has(id: string): boolean;\n ids(): string[];\n\n // ── resolution (pure reads; reactive through the store) ──\n resolve(id: string, documentId?: string): ResolvedCommand | null;\n /** Palette query: visible commands whose resolved label matches. */\n search(query: string, documentId?: string): ResolvedCommand[];\n /** The one fact the overflow projection needs (ResolveMenuTarget-shaped). */\n menuTarget(id: string): { menu?: string } | null;\n\n // ── execution (the ONLY path; guarded by enabled/visible) ──\n execute(id: string, documentId?: string): void;\n /** Match a keystroke against every registered shortcut → command id or null. */\n matchStroke(stroke: KeyStroke, opts: { isMac: boolean }): string | null;\n\n // ── category gating ──\n disabledCategories(): readonly string[];\n isCategoryDisabled(category: string): boolean;\n disableCategory(category: string): void;\n enableCategory(category: string): void;\n setDisabledCategories(categories: readonly string[]): void;\n}\n\nexport const CommandsToken = createCapabilityToken<CommandsCapability>('commands');\n","import { definePlugin } from '@embedpdf/core';\nimport { createCommandsCapability, registerCommand } from './capability';\nimport type { CommandRegistry } from './capability';\nimport { commandsReducer, initialCommandsState } from './reducer';\nimport { CommandsToken } from './types';\nimport type { CommandsAction, CommandsCapability, CommandsConfig, CommandsState } from './types';\n\n/**\n * The commands plugin: workspace-scoped (one vocabulary for the whole\n * workspace; resolution/execution bind to a target document per call).\n * Definitions live in this closure — never in the store (they hold\n * functions); the store slice holds only `disabledCategories`.\n */\nexport const commandsPlugin = (config?: CommandsConfig) => {\n const registry: CommandRegistry = new Map();\n for (const def of config?.commands ?? []) registerCommand(registry, def);\n\n return definePlugin<CommandsState, CommandsAction, CommandsCapability>({\n id: 'commands',\n scope: 'workspace',\n token: CommandsToken,\n initialState: {\n ...initialCommandsState,\n disabledCategories: [...(config?.disabledCategories ?? [])],\n },\n reduce: commandsReducer,\n capability: (ctx) => createCommandsCapability(ctx, registry),\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/reducer.ts","../src/commands.plugin.ts"],"sourcesContent":["import type { CapabilityToken, PluginContext } from '@embedpdf/core';\nimport { I18nToken } from '@embedpdf/plugin-i18n/contract';\nimport { ShellToken } from '@embedpdf/plugin-shell/contract';\nimport { matchShortcut, parseShortcut } from '@embedpdf/core-ui';\nimport type { KeyStroke, ParsedShortcut } from '@embedpdf/core-ui';\nimport type {\n CommandCtx,\n CommandDef,\n CommandsAction,\n CommandsCapability,\n CommandsState,\n IconAccent,\n ResolvedCommand,\n} from './types';\n\n/** A registered command: the definition plus its pre-parsed shortcuts. */\nexport interface RegisteredCommand {\n readonly def: CommandDef;\n readonly shortcuts: readonly string[];\n readonly parsed: readonly ParsedShortcut[];\n}\n\nexport type CommandRegistry = Map<string, RegisteredCommand>;\n\nexport function registerCommand(registry: CommandRegistry, def: CommandDef): void {\n if (registry.has(def.id)) throw new Error(`[commands] duplicate command: ${def.id}`);\n const shortcuts = def.shortcut === undefined ? [] : ([] as string[]).concat(def.shortcut);\n registry.set(def.id, { def, shortcuts, parsed: shortcuts.map(parseShortcut) });\n}\n\nconst panelTarget = (def: CommandDef): { id: string; exclusive?: string } | null =>\n def.panel === undefined ? null : typeof def.panel === 'string' ? { id: def.panel } : def.panel;\n\nexport function createCommandsCapability(\n ctx: PluginContext<CommandsState, CommandsAction>,\n registry: CommandRegistry,\n): CommandsCapability {\n /** Bind capability resolution to the command's target document. The kernel\n * resolves workspace tokens regardless of the document argument, so one\n * code path serves both scopes. */\n const commandCtx = (documentId?: string): CommandCtx => {\n const target = documentId ?? ctx.core().activeId;\n const get = <T>(token: CapabilityToken<T>): T =>\n target ? ctx.forDocument(token, target) : ctx.get(token);\n return {\n documentId: target,\n core: ctx.core,\n get,\n tryGet: <T>(token: CapabilityToken<T>): T | null => {\n try {\n return get(token);\n } catch {\n return null;\n }\n },\n };\n };\n\n /** Derivations run against live state; a derivation that throws (e.g. it\n * needs a document and none is open) falls back to the safe default —\n * the button renders, disabled, exactly like v2's empty state. */\n const derive = (\n fn: ((c: CommandCtx) => boolean) | undefined,\n c: CommandCtx,\n fallback: boolean,\n ): boolean => {\n if (!fn) return fallback;\n try {\n return fn(c);\n } catch {\n return fallback;\n }\n };\n\n /** Same guard as `derive`, for the icon-accent derivation: a throw (no\n * document, provider missing) means \"no accent\" — the icon renders plain. */\n const deriveAccent = (\n fn: ((c: CommandCtx) => IconAccent | null) | undefined,\n c: CommandCtx,\n ): IconAccent | undefined => {\n if (!fn) return undefined;\n try {\n return fn(c) ?? undefined;\n } catch {\n return undefined;\n }\n };\n\n const resolve = (id: string, documentId?: string): ResolvedCommand | null => {\n const entry = registry.get(id);\n if (!entry) return null;\n const { def } = entry;\n const c = commandCtx(documentId);\n\n const disabled = ctx.getState().disabledCategories;\n const categoryHidden = (def.categories ?? []).some((cat) => disabled.includes(cat));\n\n const i18n = c.tryGet(I18nToken);\n const label = i18n ? i18n.t(def.labelKey) : def.labelKey;\n\n // Surface-target commands derive `active` from the surface's open state\n // unless the definition overrides it.\n let active: boolean;\n if (def.active) {\n active = derive(def.active, c, false);\n } else {\n const shell = c.tryGet(ShellToken);\n const panel = panelTarget(def);\n active = shell\n ? def.menu\n ? shell.isMenuOpen(def.menu)\n : panel\n ? shell.isOpen(panel.id)\n : def.modal\n ? shell.isOpen(def.modal)\n : false\n : false;\n }\n\n return {\n id: def.id,\n label,\n icon: def.icon,\n iconAccent: deriveAccent(def.iconAccent, c),\n shortcuts: entry.shortcuts,\n menu: def.menu,\n enabled: derive(def.enabled, c, true) && !categoryHidden,\n active,\n visible: derive(def.visible, c, true) && !categoryHidden,\n categories: def.categories ?? [],\n };\n };\n\n const execute = (id: string, documentId?: string): void => {\n const entry = registry.get(id);\n if (!entry) return;\n const resolved = resolve(id, documentId);\n if (!resolved || !resolved.enabled || !resolved.visible) return;\n const c = commandCtx(documentId);\n\n if (entry.def.run) {\n entry.def.run(c);\n return;\n }\n // Default routing for declarative surface targets.\n const shell = c.tryGet(ShellToken);\n if (!shell) return;\n const panel = panelTarget(entry.def);\n if (entry.def.menu) shell.toggleMenu(entry.def.menu);\n else if (panel) shell.toggle(panel.id, { exclusive: panel.exclusive });\n else if (entry.def.modal) shell.toggle(entry.def.modal, { exclusive: 'modal' });\n };\n\n return {\n register: (def) => registerCommand(registry, def),\n unregister: (id) => void registry.delete(id),\n has: (id) => registry.has(id),\n ids: () => [...registry.keys()],\n\n resolve,\n search: (query, documentId) => {\n const q = query.trim().toLowerCase();\n const hits: ResolvedCommand[] = [];\n for (const id of registry.keys()) {\n const r = resolve(id, documentId);\n if (!r || !r.visible) continue;\n if (q === '' || r.label.toLowerCase().includes(q) || r.id.includes(q)) hits.push(r);\n }\n return hits;\n },\n menuTarget: (id) => {\n const entry = registry.get(id);\n return entry ? { menu: entry.def.menu } : null;\n },\n\n execute,\n matchStroke: (stroke: KeyStroke, opts) => {\n for (const [id, entry] of registry) {\n if (entry.parsed.some((p) => matchShortcut(p, stroke, opts))) return id;\n }\n return null;\n },\n\n disabledCategories: () => ctx.getState().disabledCategories,\n isCategoryDisabled: (category) => ctx.getState().disabledCategories.includes(category),\n disableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/DISABLE_CATEGORY', category }),\n enableCategory: (category) => ctx.dispatch({ type: 'COMMANDS/ENABLE_CATEGORY', category }),\n setDisabledCategories: (categories) =>\n ctx.dispatch({ type: 'COMMANDS/SET_DISABLED_CATEGORIES', categories }),\n };\n}\n","import type { CommandsAction, CommandsState } from './types';\n\nexport const initialCommandsState: CommandsState = {\n disabledCategories: [],\n};\n\nexport function commandsReducer(state: CommandsState, action: CommandsAction): CommandsState {\n switch (action.type) {\n case 'COMMANDS/DISABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? state\n : { disabledCategories: [...state.disabledCategories, action.category] };\n case 'COMMANDS/ENABLE_CATEGORY':\n return state.disabledCategories.includes(action.category)\n ? { disabledCategories: state.disabledCategories.filter((c) => c !== action.category) }\n : state;\n case 'COMMANDS/SET_DISABLED_CATEGORIES':\n return { disabledCategories: [...action.categories] };\n default:\n return state;\n }\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { createCommandsCapability, registerCommand } from './capability';\nimport type { CommandRegistry } from './capability';\nimport { commandsReducer, initialCommandsState } from './reducer';\nimport { CommandsToken } from './types';\nimport type { CommandsAction, CommandsCapability, CommandsConfig, CommandsState } from './types';\n\n/**\n * The commands plugin: workspace-scoped (one vocabulary for the whole\n * workspace; resolution/execution bind to a target document per call).\n * Definitions live in this closure — never in the store (they hold\n * functions); the store slice holds only `disabledCategories`.\n */\nexport const commandsPlugin = (config?: CommandsConfig) => {\n const registry: CommandRegistry = new Map();\n for (const def of config?.commands ?? []) registerCommand(registry, def);\n\n return definePlugin<CommandsState, CommandsAction, CommandsCapability>({\n id: 'commands',\n scope: 'workspace',\n token: CommandsToken,\n initialState: {\n ...initialCommandsState,\n disabledCategories: [...(config?.disabledCategories ?? [])],\n },\n reduce: commandsReducer,\n capability: (ctx) => createCommandsCapability(ctx, registry),\n });\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-commands",
3
- "version": "3.0.0-next.10",
3
+ "version": "3.0.0-next.12",
4
4
  "private": false,
5
5
  "description": "The command registry: the single vocabulary of verbs. Definitions are app content (mechanism/content split); state (label · icon · enabled · active · visible) derives purely from other capabilities, so every consumer is reactive through the kernel's one change stream — no events, no diffing.",
6
6
  "type": "module",
@@ -12,16 +12,20 @@
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
+ "./contract": {
16
+ "import": "./dist/contract.js",
17
+ "require": "./dist/contract.cjs"
18
+ },
15
19
  "./package.json": "./package.json"
16
20
  },
17
21
  "files": [
18
22
  "dist"
19
23
  ],
20
24
  "dependencies": {
21
- "@embedpdf/plugin-i18n": "3.0.0-next.10",
22
- "@embedpdf/core-ui": "3.0.0-next.10",
23
- "@embedpdf/plugin-shell": "3.0.0-next.10",
24
- "@embedpdf/core": "3.0.0-next.10"
25
+ "@embedpdf/core": "3.0.0-next.12",
26
+ "@embedpdf/plugin-i18n": "3.0.0-next.12",
27
+ "@embedpdf/core-ui": "3.0.0-next.12",
28
+ "@embedpdf/plugin-shell": "3.0.0-next.12"
25
29
  },
26
30
  "devDependencies": {
27
31
  "typescript": "^5.0.0",