k3-plugin-api 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +75 -10
  2. package/index.ts +131 -1
  3. package/package.json +6 -5
package/README.md CHANGED
@@ -62,6 +62,69 @@ export default plugin;
62
62
 
63
63
  K3 plugins can extend three layers of the configurator.
64
64
 
65
+ ### Runtime Hooks
66
+
67
+ Plugins can access K3 configurator state at runtime using React hooks. These hooks are automatically injected when the plugin is loaded.
68
+
69
+ ```ts
70
+ import { useSettings, useBOM, usePrice } from "k3-plugin-api";
71
+
72
+ const MyComponent = () => {
73
+ // Access plugin-specific settings
74
+ const settings = useSettings("acme.my-plugin");
75
+
76
+ // Access current Bill-of-Materials
77
+ const bom = useBOM();
78
+
79
+ // Access current total price
80
+ const price = usePrice();
81
+
82
+ return (
83
+ <div>
84
+ <p>API Key: {settings.apiKey}</p>
85
+ <p>Total items: {bom.length}</p>
86
+ <p>Price: €{price}</p>
87
+ </div>
88
+ );
89
+ };
90
+ ```
91
+
92
+ Available hooks:
93
+ - `useSettings(pluginId: string)` — Access settings for a specific plugin
94
+ - `useBOM()` — Get current Bill-of-Materials entries
95
+ - `usePrice()` — Get current total price (MOV-adjusted or overall)
96
+
97
+ ### `settings` — Plugin Settings Component
98
+
99
+ Plugins can provide a settings UI component that will be rendered in the K3 admin panel. This allows administrators to configure plugin-specific options that are persisted per plugin instance.
100
+
101
+ ```ts
102
+ import type { K3PluginDescriptor } from "k3-plugin-api";
103
+
104
+ const SettingsComponent = ({ settings, onSave }) => {
105
+ return (
106
+ <div>
107
+ <input
108
+ value={settings.apiKey || ""}
109
+ onChange={(e) => onSave({ ...settings, apiKey: e.target.value })}
110
+ />
111
+ </div>
112
+ );
113
+ };
114
+
115
+ const plugin: K3PluginDescriptor = {
116
+ id: "acme.my-plugin",
117
+ version: "1.0.0",
118
+ settings: SettingsComponent,
119
+ };
120
+ ```
121
+
122
+ The `settings` component receives:
123
+ - `settings: unknown` — The current settings object (initially `{}`)
124
+ - `onSave: (settings: unknown) => void` — Callback to persist updated settings
125
+
126
+ Settings are stored per plugin instance and can be accessed at runtime via the plugin's configuration.
127
+
65
128
  ### `ui` — UI Extensions
66
129
 
67
130
  | Property | Description |
@@ -124,16 +187,18 @@ const plugin: K3PluginDescriptor = {
124
187
 
125
188
  ## Key Types
126
189
 
127
- | Type | Description |
128
- | --------------------------- | -------------------------------------------------------------- |
129
- | `K3PluginDescriptor` | Root descriptor object exported by a plugin |
130
- | `HOC<P>` | Higher-Order Component: `(Default: FC<P>) => FC<P>` |
131
- | `VariableVisualisation` | Descriptor for a custom input visualisation |
132
- | `K3VariableComponentProps` | Props injected into a custom variable renderer |
133
- | `DynamicModel` | 3D model type definition for the viewer |
134
- | `K3Configuration` | A persisted configuration with code, price, and selection JSON |
135
- | `K3ConfigurationSavedEvent` | Event payload dispatched on completed save |
136
- | `VariableType` | Const enum of all variable types |
190
+ | Type | Description |
191
+ | --------------------------- | --------------------------------------------------------------- |
192
+ | `K3PluginDescriptor` | Root descriptor object exported by a plugin |
193
+ | `K3Hooks` | Runtime hooks interface (useSettings, useBOM, usePrice) |
194
+ | `HOC<P>` | Higher-Order Component: `(Default: FC<P>) => FC<P>` |
195
+ | `VariableVisualisation` | Descriptor for a custom input visualisation |
196
+ | `K3VariableComponentProps` | Props injected into a custom variable renderer |
197
+ | `DynamicModel` | 3D model type definition for the viewer |
198
+ | `K3Configuration` | A persisted configuration with code, price, and selection JSON |
199
+ | `K3ConfigurationSavedEvent` | Event payload dispatched on completed save |
200
+ | `K3BomEntry` | Single Bill-of-Materials entry |
201
+ | `VariableType` | Const enum of all variable types |
137
202
 
138
203
  ---
139
204
 
package/index.ts CHANGED
@@ -1,5 +1,62 @@
1
1
  import type React from "react";
2
2
 
3
+ // ─── Plugin Runtime Hooks ───────────────────────────────────────────────────
4
+
5
+ /**
6
+ * K3 runtime hooks available to plugins after initialization.
7
+ * Set via `init()` when the plugin is loaded.
8
+ */
9
+ export interface K3Hooks {
10
+ /**
11
+ * React hook to access plugin-specific settings.
12
+ * @param pluginId - The unique plugin ID (e.g. "acme.my-plugin")
13
+ * @returns The settings object for this plugin, or `{}` if none are configured
14
+ */
15
+ useSettings: (pluginId: string) => unknown;
16
+ /**
17
+ * React hook to access the current Bill-of-Materials.
18
+ * @returns Array of BOM entries derived from the current selection
19
+ */
20
+ useBOM: () => K3BomEntry[];
21
+ /**
22
+ * React hook to access the current total price.
23
+ * @returns The effective total price (MOV-adjusted or overall price)
24
+ */
25
+ usePrice: () => number;
26
+ }
27
+
28
+ /**
29
+ * Hook functions injected by K3 at runtime.
30
+ * Use these in your plugin components to access configurator state.
31
+ */
32
+ export let useSettings: K3Hooks["useSettings"] = () => {
33
+ throw new Error(
34
+ "k3-plugin-api not initialized. Make sure K3 calls init() before using hooks.",
35
+ );
36
+ };
37
+
38
+ export let useBOM: K3Hooks["useBOM"] = () => {
39
+ throw new Error(
40
+ "k3-plugin-api not initialized. Make sure K3 calls init() before using hooks.",
41
+ );
42
+ };
43
+
44
+ export let usePrice: K3Hooks["usePrice"] = () => {
45
+ throw new Error(
46
+ "k3-plugin-api not initialized. Make sure K3 calls init() before using hooks.",
47
+ );
48
+ };
49
+
50
+ /**
51
+ * Called by K3 when the plugin is loaded to inject runtime hooks.
52
+ * @internal This function is called by K3 automatically - plugins should not call it directly.
53
+ */
54
+ export function init(hooks: K3Hooks): void {
55
+ useSettings = hooks.useSettings;
56
+ useBOM = hooks.useBOM;
57
+ usePrice = hooks.usePrice;
58
+ }
59
+
3
60
  // ─── HOC type (mirrors K3 internal, no K3 imports) ──────────────────────────
4
61
 
5
62
  /** A Higher-Order Component: takes the default component, returns a new one. */
@@ -43,6 +100,10 @@ export interface K3PluginDescriptor {
43
100
  viewer?: K3ViewerExtensions;
44
101
  /** Logic/callback hooks: config, camera, core. */
45
102
  logic?: K3LogicExtensions;
103
+ settings?: React.ComponentType<{
104
+ settings: unknown;
105
+ onSave: (settings: unknown) => void;
106
+ }>;
46
107
 
47
108
  /** @deprecated Use viewer.models instead. */
48
109
  dynamicModels?: DynamicModel[];
@@ -627,6 +688,65 @@ export interface K3LogicExtensions {
627
688
  };
628
689
  }
629
690
 
691
+ // ─── Model Slot Types ────────────────────────────────────────────────────────
692
+
693
+ /**
694
+ * Declares a named slot where child models can be placed inside a parent dynamic model.
695
+ * Attach an array of these to `DynamicModel.slotDefinitions`.
696
+ */
697
+ export interface SlotDefinition {
698
+ /** Unique identifier (UUID) — primary key used in rule columns to reference this slot. */
699
+ id: string;
700
+ /** Human-readable name shown in the admin slot picker. */
701
+ name: string;
702
+ /** Optional fallback model ID rendered when no rule assigns a model to this slot. */
703
+ defaultModelId?: string | number;
704
+ }
705
+
706
+ /**
707
+ * A resolved model instance placed into a slot at runtime.
708
+ * Received via `DynamicModelComponentProps.slots[slotId]`.
709
+ */
710
+ export interface SlotModelInstance {
711
+ /** The model data object for this slot instance. */
712
+ model: { id: string | number; [key: string]: unknown };
713
+ /** URL of the model's 3D file, or `undefined` if this is a dynamic model with a component. */
714
+ fileURL: string | undefined;
715
+ /** The React component used to render this model if it is a dynamic model otherwise `undefined`. */
716
+ component: React.ComponentType<any> | undefined;
717
+ /** Evaluated props passed to the model component. */
718
+ props: Record<string, unknown>;
719
+ /** The rule-engine action that placed this model into the scene. */
720
+ modelAction: {
721
+ /** Unique ID of the model action record. */
722
+ id: string;
723
+ /** ID of the model referenced by this action. */
724
+ modelId: string | number;
725
+ /** Props overrides defined on the action. */
726
+ props?: Record<string, unknown>;
727
+ /** ID of the slot this action targets. */
728
+ slotId?: string;
729
+ [key: string]: unknown;
730
+ };
731
+ /** The slot definition this instance was placed into. */
732
+ slotDefinition: SlotDefinition;
733
+ /** Recursively nested slot instances when the slotted model itself has slots. */
734
+ slots?: Record<string, SlotModelInstance[]>;
735
+ }
736
+
737
+ /**
738
+ * Props automatically injected into every `DynamicModel.component` at runtime.
739
+ * Extend this with your custom prop types: `DynamicModelComponentProps & { myProp: string }`.
740
+ */
741
+ export interface DynamicModelComponentProps {
742
+ /**
743
+ * Resolved child model instances keyed by slot ID.
744
+ * Only present when the model has `slotDefinitions` and at least one slot is filled.
745
+ */
746
+ slots?: Record<string, SlotModelInstance[]>;
747
+ [key: string]: unknown;
748
+ }
749
+
630
750
  // ─── Legacy types (unchanged) ────────────────────────────────────────────────
631
751
 
632
752
  export interface DynamicModel {
@@ -652,8 +772,18 @@ export interface DynamicModel {
652
772
  /**
653
773
  * Default prop values used when a new model action is created.
654
774
  * Each key matches a `propsDialog` key; values may be plain data or `{ expression: string }`.
775
+ * Include `slotDefinitions` here to declare named slots for child models.
655
776
  */
656
- defaultProps: Record<string, ModelPropDefault>;
777
+ defaultProps: {
778
+ slotDefinitions?: SlotDefinition[];
779
+ [key: string]:
780
+ | string
781
+ | number
782
+ | boolean
783
+ | ModelPropDefault
784
+ | undefined
785
+ | SlotDefinition[];
786
+ };
657
787
  /** Optional tag used to group or filter models in the admin UI. */
658
788
  tag?: string;
659
789
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k3-plugin-api",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Official TypeScript types and plugin API for the K3 product configurator",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",
@@ -25,14 +25,15 @@
25
25
  ],
26
26
  "author": "ObjectCode GmbH <info@objectcode.de> (https://k3-konfigurator.de/)",
27
27
  "license": "MIT",
28
+ "scripts": {
29
+ "prepublishOnly": "tsc --emitDeclarationOnly",
30
+ "yalc": "pnpm prepublishOnly && yalc publish"
31
+ },
28
32
  "peerDependencies": {
29
33
  "react": ">=19"
30
34
  },
31
35
  "devDependencies": {
32
36
  "typescript": "^5.8.3",
33
37
  "@types/react": "^19.0.0"
34
- },
35
- "scripts": {
36
- "yalc": "pnpm prepublishOnly && yalc publish"
37
38
  }
38
- }
39
+ }