@rozenite/controls-plugin 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/devtools/assets/panel-EMOeI6i6.css +1 -0
  3. package/dist/devtools/assets/panel-mjW0t-tQ.js +18 -0
  4. package/dist/devtools/panel.html +2 -2
  5. package/dist/react-native/cjs/package.json +3 -0
  6. package/dist/react-native/cjs/react-native.js +14 -0
  7. package/dist/react-native/cjs/src/react-native/controlsRegistry.js +70 -0
  8. package/dist/react-native/cjs/src/react-native/useControlsAgentTools.js +175 -0
  9. package/dist/react-native/cjs/src/react-native/useRozeniteControlsPlugin.js +177 -0
  10. package/dist/react-native/cjs/src/shared/agent-tools.js +51 -0
  11. package/dist/react-native/cjs/src/shared/messaging.js +2 -0
  12. package/dist/react-native/cjs/src/shared/serialization.js +102 -0
  13. package/dist/react-native/cjs/src/shared/types.js +5 -0
  14. package/dist/react-native/package.json +3 -0
  15. package/dist/react-native/react-native.d.ts +2 -0
  16. package/dist/react-native/react-native.js +11 -0
  17. package/dist/react-native/src/react-native/controlsRegistry.d.ts +23 -0
  18. package/dist/react-native/src/react-native/controlsRegistry.js +66 -0
  19. package/dist/react-native/src/react-native/useControlsAgentTools.d.ts +2 -0
  20. package/dist/react-native/src/react-native/useControlsAgentTools.js +171 -0
  21. package/dist/react-native/src/react-native/useRozeniteControlsPlugin.d.ts +3 -0
  22. package/dist/react-native/src/react-native/useRozeniteControlsPlugin.js +173 -0
  23. package/dist/react-native/src/shared/agent-tools.d.ts +48 -0
  24. package/dist/react-native/src/shared/agent-tools.js +48 -0
  25. package/dist/react-native/src/shared/messaging.d.ts +35 -0
  26. package/dist/react-native/src/shared/messaging.js +1 -0
  27. package/dist/react-native/src/shared/serialization.d.ts +22 -0
  28. package/dist/react-native/src/shared/serialization.js +96 -0
  29. package/dist/react-native/src/shared/types.d.ts +69 -0
  30. package/dist/react-native/src/shared/types.js +1 -0
  31. package/dist/rozenite.json +1 -1
  32. package/dist/sdk/package.json +3 -0
  33. package/dist/sdk/sdk.d.ts +10 -0
  34. package/dist/sdk/sdk.js +8 -0
  35. package/dist/sdk/src/shared/agent-tools.d.ts +48 -0
  36. package/dist/sdk/src/shared/agent-tools.js +51 -0
  37. package/dist/sdk/src/shared/messaging.d.ts +35 -0
  38. package/dist/sdk/src/shared/messaging.js +2 -0
  39. package/dist/sdk/src/shared/types.d.ts +69 -0
  40. package/dist/sdk/src/shared/types.js +5 -0
  41. package/package.json +17 -18
  42. package/react-native.ts +8 -1
  43. package/rozenite.config.ts +1 -0
  44. package/src/__tests__/release-bundle.test.ts +32 -0
  45. package/dist/devtools/assets/panel-BGnH7fJp.js +0 -18
  46. package/dist/devtools/assets/panel-DFvY5duB.css +0 -1
  47. package/dist/react-native/chunks/useRozeniteControlsPlugin.require.cjs +0 -1
  48. package/dist/react-native/chunks/useRozeniteControlsPlugin.require.js +0 -466
  49. package/dist/react-native/index.cjs +0 -1
  50. package/dist/react-native/index.d.ts +0 -138
  51. package/dist/react-native/index.js +0 -8
  52. package/dist/sdk/index.cjs +0 -1
  53. package/dist/sdk/index.d.ts +0 -150
  54. package/dist/sdk/index.js +0 -55
@@ -0,0 +1,66 @@
1
+ const EMPTY_OPTIONS = {
2
+ sections: [],
3
+ };
4
+ const resolveOptionsInput = (previousOptions, input) => {
5
+ if (typeof input === 'function') {
6
+ return input(previousOptions);
7
+ }
8
+ return {
9
+ ...previousOptions,
10
+ ...input,
11
+ sections: [...previousOptions.sections, ...input.sections],
12
+ };
13
+ };
14
+ export const createControlsRegistry = () => {
15
+ const registrations = new Map();
16
+ const listeners = new Set();
17
+ let snapshot = 0;
18
+ const getOwnerId = () => registrations.keys().next().value;
19
+ const notify = () => {
20
+ snapshot += 1;
21
+ listeners.forEach((listener) => listener());
22
+ };
23
+ const getRegistrationEntries = (override) => {
24
+ const entries = Array.from(registrations.entries());
25
+ if (!override) {
26
+ return entries;
27
+ }
28
+ const overrideIndex = entries.findIndex(([id]) => id === override.id);
29
+ if (overrideIndex === -1) {
30
+ return [...entries, [override.id, override.input]];
31
+ }
32
+ return entries.map(([id, input]) => id === override.id ? [id, override.input] : [id, input]);
33
+ };
34
+ return {
35
+ set(id, input) {
36
+ const previousOwnerId = getOwnerId();
37
+ registrations.set(id, input);
38
+ if (previousOwnerId !== getOwnerId()) {
39
+ notify();
40
+ }
41
+ },
42
+ delete(id) {
43
+ registrations.delete(id);
44
+ notify();
45
+ },
46
+ getOptions(override) {
47
+ return getRegistrationEntries(override)
48
+ .map(([, input]) => input)
49
+ .reduce(resolveOptionsInput, EMPTY_OPTIONS);
50
+ },
51
+ isOwner(id, override) {
52
+ const ownerEntry = getRegistrationEntries(override)[0];
53
+ return ownerEntry?.[0] === id;
54
+ },
55
+ getSnapshot() {
56
+ return snapshot;
57
+ },
58
+ subscribe(listener) {
59
+ listeners.add(listener);
60
+ return () => {
61
+ listeners.delete(listener);
62
+ };
63
+ },
64
+ };
65
+ };
66
+ export const controlsRegistry = createControlsRegistry();
@@ -0,0 +1,2 @@
1
+ import type { ControlsSection } from '../shared/types';
2
+ export declare const useControlsAgentTools: (getSections: () => ControlsSection[], enabled?: boolean) => void;
@@ -0,0 +1,171 @@
1
+ import { useRozenitePluginAgentTool } from '@rozenite/agent-bridge';
2
+ import { CONTROLS_AGENT_PLUGIN_ID, controlsToolDefinitions } from '../shared/agent-tools';
3
+ const resolveItem = (sections, sectionId, itemId) => {
4
+ const section = sections.find((s) => s.id === sectionId);
5
+ if (!section) {
6
+ const available = sections.map((s) => s.id).join(', ');
7
+ throw new Error(`Section "${sectionId}" not found. Available: ${available || '(none)'}`);
8
+ }
9
+ const item = section.items.find((i) => i.id === itemId);
10
+ if (!item) {
11
+ const available = section.items.map((i) => i.id).join(', ');
12
+ throw new Error(`Item "${itemId}" not found in section "${sectionId}". Available: ${available || '(none)'}`);
13
+ }
14
+ return { section, item };
15
+ };
16
+ export const useControlsAgentTools = (getSections, enabled = true) => {
17
+ useRozenitePluginAgentTool({
18
+ pluginId: CONTROLS_AGENT_PLUGIN_ID,
19
+ tool: controlsToolDefinitions.listSections,
20
+ enabled,
21
+ handler: () => ({
22
+ sections: getSections().map((section) => ({
23
+ id: section.id,
24
+ title: section.title,
25
+ description: section.description,
26
+ items: section.items.map((item) => ({
27
+ id: item.id,
28
+ type: item.type,
29
+ title: item.title,
30
+ disabled: 'disabled' in item ? item.disabled : undefined,
31
+ })),
32
+ })),
33
+ }),
34
+ });
35
+ useRozenitePluginAgentTool({
36
+ pluginId: CONTROLS_AGENT_PLUGIN_ID,
37
+ tool: controlsToolDefinitions.getItem,
38
+ enabled,
39
+ handler: ({ sectionId, itemId }) => {
40
+ const { item } = resolveItem(getSections(), sectionId, itemId);
41
+ if (item.type === 'text') {
42
+ return {
43
+ sectionId,
44
+ item: {
45
+ id: item.id,
46
+ type: item.type,
47
+ title: item.title,
48
+ value: item.value,
49
+ description: item.description,
50
+ },
51
+ };
52
+ }
53
+ if (item.type === 'toggle') {
54
+ return {
55
+ sectionId,
56
+ item: {
57
+ id: item.id,
58
+ type: item.type,
59
+ title: item.title,
60
+ value: item.value,
61
+ description: item.description,
62
+ disabled: item.disabled,
63
+ },
64
+ };
65
+ }
66
+ if (item.type === 'button') {
67
+ return {
68
+ sectionId,
69
+ item: {
70
+ id: item.id,
71
+ type: item.type,
72
+ title: item.title,
73
+ actionLabel: item.actionLabel,
74
+ description: item.description,
75
+ disabled: item.disabled,
76
+ },
77
+ };
78
+ }
79
+ if (item.type === 'select') {
80
+ return {
81
+ sectionId,
82
+ item: {
83
+ id: item.id,
84
+ type: item.type,
85
+ title: item.title,
86
+ value: item.value,
87
+ options: item.options,
88
+ description: item.description,
89
+ disabled: item.disabled,
90
+ },
91
+ };
92
+ }
93
+ return {
94
+ sectionId,
95
+ item: {
96
+ id: item.id,
97
+ type: item.type,
98
+ title: item.title,
99
+ value: item.value,
100
+ placeholder: item.placeholder,
101
+ applyLabel: item.applyLabel,
102
+ description: item.description,
103
+ disabled: item.disabled,
104
+ },
105
+ };
106
+ },
107
+ });
108
+ useRozenitePluginAgentTool({
109
+ pluginId: CONTROLS_AGENT_PLUGIN_ID,
110
+ tool: controlsToolDefinitions.setValue,
111
+ enabled,
112
+ handler: async ({ sectionId, itemId, value }) => {
113
+ const { item } = resolveItem(getSections(), sectionId, itemId);
114
+ if (item.type === 'text') {
115
+ throw new Error(`Item "${itemId}" is a read-only text item and cannot be updated.`);
116
+ }
117
+ if (item.type === 'button') {
118
+ throw new Error(`Item "${itemId}" is a button. Use press-button to trigger its action.`);
119
+ }
120
+ if (item.disabled) {
121
+ throw new Error(`Item "${itemId}" is disabled.`);
122
+ }
123
+ if (item.type === 'toggle') {
124
+ if (typeof value !== 'boolean') {
125
+ throw new Error(`Expected boolean value for toggle item "${itemId}".`);
126
+ }
127
+ if (item.validate) {
128
+ const result = item.validate(value);
129
+ if (!result.valid) {
130
+ throw new Error(result.message);
131
+ }
132
+ }
133
+ await item.onUpdate(value);
134
+ return { applied: true, sectionId, itemId };
135
+ }
136
+ if (typeof value !== 'string') {
137
+ throw new Error(`Expected string value for ${item.type} item "${itemId}".`);
138
+ }
139
+ if (item.type === 'select') {
140
+ const validOptions = item.options.map((o) => o.value);
141
+ if (!validOptions.includes(value)) {
142
+ throw new Error(`Invalid option "${value}" for item "${itemId}". Valid options: ${validOptions.join(', ')}`);
143
+ }
144
+ }
145
+ if (item.validate) {
146
+ const result = item.validate(value);
147
+ if (!result.valid) {
148
+ throw new Error(result.message);
149
+ }
150
+ }
151
+ await item.onUpdate(value);
152
+ return { applied: true, sectionId, itemId };
153
+ },
154
+ });
155
+ useRozenitePluginAgentTool({
156
+ pluginId: CONTROLS_AGENT_PLUGIN_ID,
157
+ tool: controlsToolDefinitions.pressButton,
158
+ enabled,
159
+ handler: async ({ sectionId, itemId }) => {
160
+ const { item } = resolveItem(getSections(), sectionId, itemId);
161
+ if (item.type !== 'button') {
162
+ throw new Error(`Item "${itemId}" is not a button (type: ${item.type}). Use set-value to update its value.`);
163
+ }
164
+ if (item.disabled) {
165
+ throw new Error(`Button "${itemId}" is disabled.`);
166
+ }
167
+ await item.onPress();
168
+ return { pressed: true, sectionId, itemId };
169
+ },
170
+ });
171
+ };
@@ -0,0 +1,3 @@
1
+ import type { ControlsEventMap } from '../shared/messaging';
2
+ import type { RozeniteControlsPluginOptionsInput } from '../shared/types';
3
+ export declare const useRozeniteControlsPlugin: (optionsInput: RozeniteControlsPluginOptionsInput) => import("@rozenite/plugin-bridge").RozeniteDevToolsRequestClient<ControlsEventMap> | null;
@@ -0,0 +1,173 @@
1
+ import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
2
+ import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
3
+ import { buildActionRegistry, getActionRegistryKey, serializeSections, validateValue, } from '../shared/serialization';
4
+ import { useControlsAgentTools } from './useControlsAgentTools';
5
+ import { controlsRegistry } from './controlsRegistry';
6
+ export const useRozeniteControlsPlugin = (optionsInput) => {
7
+ const client = useRozeniteDevToolsClient({
8
+ pluginId: '@rozenite/controls-plugin',
9
+ });
10
+ const registrationIdRef = useRef(Symbol('rozenite-controls'));
11
+ const registrationId = registrationIdRef.current;
12
+ const registrySnapshot = useSyncExternalStore(controlsRegistry.subscribe, controlsRegistry.getSnapshot, controlsRegistry.getSnapshot);
13
+ useEffect(() => {
14
+ return () => {
15
+ controlsRegistry.delete(registrationId);
16
+ };
17
+ }, [registrationId]);
18
+ useEffect(() => {
19
+ controlsRegistry.set(registrationId, optionsInput);
20
+ }, [optionsInput, registrationId]);
21
+ const isRegistryOwner = useMemo(() => controlsRegistry.isOwner(registrationId, {
22
+ id: registrationId,
23
+ input: optionsInput,
24
+ }), [optionsInput, registrySnapshot, registrationId]);
25
+ useControlsAgentTools(() => controlsRegistry.getOptions().sections, isRegistryOwner);
26
+ useEffect(() => {
27
+ if (!client) {
28
+ return;
29
+ }
30
+ client.send('snapshot', {
31
+ type: 'snapshot',
32
+ sections: serializeSections(controlsRegistry.getOptions().sections),
33
+ });
34
+ }, [client, optionsInput, registrySnapshot]);
35
+ useEffect(() => {
36
+ if (!client || !isRegistryOwner) {
37
+ return;
38
+ }
39
+ const handleUpdateRequest = async ({ requestId, sectionId, itemId, value, }) => {
40
+ const key = getActionRegistryKey(sectionId, itemId);
41
+ const entry = buildActionRegistry(controlsRegistry.getOptions().sections).get(key);
42
+ if (!entry || entry.type === 'button') {
43
+ client.send('update-result', {
44
+ type: 'update-result',
45
+ requestId,
46
+ sectionId,
47
+ itemId,
48
+ status: 'error',
49
+ message: 'Update target not found.',
50
+ });
51
+ return;
52
+ }
53
+ try {
54
+ if (entry.type === 'toggle') {
55
+ if (typeof value !== 'boolean') {
56
+ client.send('update-result', {
57
+ type: 'update-result',
58
+ requestId,
59
+ sectionId,
60
+ itemId,
61
+ status: 'error',
62
+ message: 'Invalid toggle value.',
63
+ });
64
+ return;
65
+ }
66
+ const result = validateValue(entry.validate, value);
67
+ if (!result.valid) {
68
+ client.send('update-result', {
69
+ type: 'update-result',
70
+ requestId,
71
+ sectionId,
72
+ itemId,
73
+ status: 'error',
74
+ message: result.message,
75
+ });
76
+ return;
77
+ }
78
+ await entry.onUpdate(value);
79
+ client.send('update-result', {
80
+ type: 'update-result',
81
+ requestId,
82
+ sectionId,
83
+ itemId,
84
+ status: 'ok',
85
+ });
86
+ return;
87
+ }
88
+ if (typeof value !== 'string') {
89
+ client.send('update-result', {
90
+ type: 'update-result',
91
+ requestId,
92
+ sectionId,
93
+ itemId,
94
+ status: 'error',
95
+ message: `Invalid ${entry.type} value.`,
96
+ });
97
+ return;
98
+ }
99
+ const result = validateValue(entry.validate, value);
100
+ if (!result.valid) {
101
+ client.send('update-result', {
102
+ type: 'update-result',
103
+ requestId,
104
+ sectionId,
105
+ itemId,
106
+ status: 'error',
107
+ message: result.message,
108
+ });
109
+ return;
110
+ }
111
+ await entry.onUpdate(value);
112
+ client.send('update-result', {
113
+ type: 'update-result',
114
+ requestId,
115
+ sectionId,
116
+ itemId,
117
+ status: 'ok',
118
+ });
119
+ }
120
+ catch (error) {
121
+ console.warn(`[Rozenite] Controls Plugin: Update failed for ${sectionId}/${itemId}.`, error);
122
+ client.send('update-result', {
123
+ type: 'update-result',
124
+ requestId,
125
+ sectionId,
126
+ itemId,
127
+ status: 'error',
128
+ message: 'Update failed on the device.',
129
+ });
130
+ }
131
+ };
132
+ const handleInvokeAction = async ({ sectionId, itemId, action }) => {
133
+ if (action !== 'press') {
134
+ console.warn(`[Rozenite] Controls Plugin: Unsupported action "${action}" for ${sectionId}/${itemId}.`);
135
+ return;
136
+ }
137
+ const key = getActionRegistryKey(sectionId, itemId);
138
+ const entry = buildActionRegistry(controlsRegistry.getOptions().sections).get(key);
139
+ if (!entry) {
140
+ console.warn(`[Rozenite] Controls Plugin: Action target not found for ${sectionId}/${itemId}.`);
141
+ return;
142
+ }
143
+ try {
144
+ if (entry.type !== 'button') {
145
+ console.warn(`[Rozenite] Controls Plugin: Invalid press action payload for ${sectionId}/${itemId}.`);
146
+ return;
147
+ }
148
+ await entry.onPress();
149
+ }
150
+ catch (error) {
151
+ console.warn(`[Rozenite] Controls Plugin: Action failed for ${sectionId}/${itemId}.`, error);
152
+ }
153
+ };
154
+ const subscriptions = [
155
+ client.onMessage('get-snapshot', () => {
156
+ client.send('snapshot', {
157
+ type: 'snapshot',
158
+ sections: serializeSections(controlsRegistry.getOptions().sections),
159
+ });
160
+ }),
161
+ client.onMessage('update-request', (event) => {
162
+ void handleUpdateRequest(event);
163
+ }),
164
+ client.onMessage('invoke-action', (event) => {
165
+ void handleInvokeAction(event);
166
+ }),
167
+ ];
168
+ return () => {
169
+ subscriptions.forEach((subscription) => subscription.remove());
170
+ };
171
+ }, [client, isRegistryOwner]);
172
+ return client;
173
+ };
@@ -0,0 +1,48 @@
1
+ import { type AgentToolContract } from '@rozenite/agent-shared';
2
+ import type { ControlsItem, ControlsItemSnapshot } from './types';
3
+ export declare const CONTROLS_AGENT_PLUGIN_ID = "@rozenite/controls-plugin";
4
+ export type ControlsSectionItemArgs = {
5
+ sectionId: string;
6
+ itemId: string;
7
+ };
8
+ export type ControlsListSectionsArgs = undefined;
9
+ export type ControlsListSectionItemSummary = {
10
+ id: string;
11
+ type: ControlsItem['type'];
12
+ title: string;
13
+ disabled?: boolean;
14
+ };
15
+ export type ControlsListSectionSummary = {
16
+ id: string;
17
+ title: string;
18
+ description?: string;
19
+ items: ControlsListSectionItemSummary[];
20
+ };
21
+ export type ControlsListSectionsResult = {
22
+ sections: ControlsListSectionSummary[];
23
+ };
24
+ export type ControlsGetItemArgs = ControlsSectionItemArgs;
25
+ export type ControlsGetItemResult = {
26
+ sectionId: string;
27
+ item: ControlsItemSnapshot;
28
+ };
29
+ export type ControlsSetValueArgs = ControlsSectionItemArgs & {
30
+ value: boolean | string;
31
+ };
32
+ export type ControlsSetValueResult = {
33
+ applied: true;
34
+ sectionId: string;
35
+ itemId: string;
36
+ };
37
+ export type ControlsPressButtonArgs = ControlsSectionItemArgs;
38
+ export type ControlsPressButtonResult = {
39
+ pressed: true;
40
+ sectionId: string;
41
+ itemId: string;
42
+ };
43
+ export declare const controlsToolDefinitions: {
44
+ readonly listSections: AgentToolContract<undefined, ControlsListSectionsResult>;
45
+ readonly getItem: AgentToolContract<ControlsSectionItemArgs, ControlsGetItemResult>;
46
+ readonly setValue: AgentToolContract<ControlsSetValueArgs, ControlsSetValueResult>;
47
+ readonly pressButton: AgentToolContract<ControlsSectionItemArgs, ControlsPressButtonResult>;
48
+ };
@@ -0,0 +1,48 @@
1
+ import { defineAgentToolContract } from '@rozenite/agent-shared';
2
+ export const CONTROLS_AGENT_PLUGIN_ID = '@rozenite/controls-plugin';
3
+ export const controlsToolDefinitions = {
4
+ listSections: defineAgentToolContract({
5
+ name: 'list-sections',
6
+ description: 'List all controls sections with their item IDs, types, and titles. Does not include values — call get-item for that.',
7
+ inputSchema: { type: 'object', properties: {} },
8
+ }),
9
+ getItem: defineAgentToolContract({
10
+ name: 'get-item',
11
+ description: 'Get full details of a single controls item including its current value. For select items this includes available options.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ sectionId: { type: 'string', description: 'Section ID.' },
16
+ itemId: { type: 'string', description: 'Item ID.' },
17
+ },
18
+ required: ['sectionId', 'itemId'],
19
+ },
20
+ }),
21
+ setValue: defineAgentToolContract({
22
+ name: 'set-value',
23
+ description: 'Update the value of a toggle, select, or input item. Runs the validate callback when present. Fails for text (read-only) and button items.',
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ sectionId: { type: 'string', description: 'Section ID.' },
28
+ itemId: { type: 'string', description: 'Item ID.' },
29
+ value: {
30
+ description: 'New value. Boolean for toggle items, string for select/input items.',
31
+ },
32
+ },
33
+ required: ['sectionId', 'itemId', 'value'],
34
+ },
35
+ }),
36
+ pressButton: defineAgentToolContract({
37
+ name: 'press-button',
38
+ description: "Trigger a button item's action. Fails if the item is not a button or is disabled.",
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ sectionId: { type: 'string', description: 'Section ID.' },
43
+ itemId: { type: 'string', description: 'Item ID.' },
44
+ },
45
+ required: ['sectionId', 'itemId'],
46
+ },
47
+ }),
48
+ };
@@ -0,0 +1,35 @@
1
+ import type { ControlsSectionSnapshot } from './types';
2
+ export type ControlsSnapshotEvent = {
3
+ type: 'snapshot';
4
+ sections: ControlsSectionSnapshot[];
5
+ };
6
+ export type ControlsGetSnapshotEvent = {
7
+ type: 'get-snapshot';
8
+ };
9
+ export type ControlsUpdateRequestEvent = {
10
+ type: 'update-request';
11
+ requestId: string;
12
+ sectionId: string;
13
+ itemId: string;
14
+ value: boolean | string;
15
+ };
16
+ export type ControlsUpdateResultEvent = {
17
+ type: 'update-result';
18
+ requestId: string;
19
+ sectionId: string;
20
+ itemId: string;
21
+ status: 'ok' | 'error';
22
+ message?: string;
23
+ };
24
+ export type ControlsInvokeActionEvent = {
25
+ type: 'invoke-action';
26
+ sectionId: string;
27
+ itemId: string;
28
+ action: 'press';
29
+ };
30
+ export type ControlsEvent = ControlsSnapshotEvent | ControlsGetSnapshotEvent | ControlsUpdateRequestEvent | ControlsUpdateResultEvent | ControlsInvokeActionEvent;
31
+ export type ControlsEventMap = {
32
+ [K in ControlsEvent['type']]: Extract<ControlsEvent, {
33
+ type: K;
34
+ }>;
35
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { ControlsButtonItem, ControlsInputItem, ControlsMutableItemBase, ControlsSelectItem, ControlsSection, ControlsSectionSnapshot, ControlsToggleItem, ControlsValidationResult } from './types';
2
+ export type ActionRegistryEntry = {
3
+ type: 'toggle';
4
+ validate?: ControlsToggleItem['validate'];
5
+ onUpdate: ControlsToggleItem['onUpdate'];
6
+ } | {
7
+ type: 'button';
8
+ onPress: ControlsButtonItem['onPress'];
9
+ } | {
10
+ type: 'select';
11
+ validate?: ControlsSelectItem['validate'];
12
+ onUpdate: ControlsSelectItem['onUpdate'];
13
+ } | {
14
+ type: 'input';
15
+ validate?: ControlsInputItem['validate'];
16
+ onUpdate: ControlsInputItem['onUpdate'];
17
+ };
18
+ declare const validateValue: <TValue>(validate: ControlsMutableItemBase<TValue>["validate"], value: TValue) => ControlsValidationResult;
19
+ export declare const serializeSections: (sections: ControlsSection[]) => ControlsSectionSnapshot[];
20
+ export declare const buildActionRegistry: (sections: ControlsSection[]) => Map<string, ActionRegistryEntry>;
21
+ export declare const getActionRegistryKey: (sectionId: string, itemId: string) => string;
22
+ export { validateValue };
@@ -0,0 +1,96 @@
1
+ const validateValue = (validate, value) => {
2
+ if (!validate) {
3
+ return { valid: true };
4
+ }
5
+ return validate(value);
6
+ };
7
+ const toSnapshotItem = (item) => {
8
+ if (item.type === 'text') {
9
+ return item;
10
+ }
11
+ if (item.type === 'toggle') {
12
+ return {
13
+ id: item.id,
14
+ type: item.type,
15
+ title: item.title,
16
+ value: item.value,
17
+ description: item.description,
18
+ disabled: item.disabled,
19
+ };
20
+ }
21
+ if (item.type === 'button') {
22
+ return {
23
+ id: item.id,
24
+ type: item.type,
25
+ title: item.title,
26
+ actionLabel: item.actionLabel,
27
+ description: item.description,
28
+ disabled: item.disabled,
29
+ };
30
+ }
31
+ if (item.type === 'select') {
32
+ return {
33
+ id: item.id,
34
+ type: item.type,
35
+ title: item.title,
36
+ value: item.value,
37
+ options: item.options,
38
+ description: item.description,
39
+ disabled: item.disabled,
40
+ };
41
+ }
42
+ return {
43
+ id: item.id,
44
+ type: item.type,
45
+ title: item.title,
46
+ value: item.value,
47
+ placeholder: item.placeholder,
48
+ applyLabel: item.applyLabel,
49
+ description: item.description,
50
+ disabled: item.disabled,
51
+ };
52
+ };
53
+ export const serializeSections = (sections) => sections.map((section) => ({
54
+ id: section.id,
55
+ title: section.title,
56
+ description: section.description,
57
+ items: section.items.map(toSnapshotItem),
58
+ }));
59
+ export const buildActionRegistry = (sections) => {
60
+ const registry = new Map();
61
+ sections.forEach((section) => {
62
+ section.items.forEach((item) => {
63
+ const key = `${section.id}:${item.id}`;
64
+ if (item.type === 'toggle') {
65
+ registry.set(key, {
66
+ type: 'toggle',
67
+ validate: item.validate,
68
+ onUpdate: item.onUpdate,
69
+ });
70
+ }
71
+ if (item.type === 'button') {
72
+ registry.set(key, {
73
+ type: 'button',
74
+ onPress: item.onPress,
75
+ });
76
+ }
77
+ if (item.type === 'select') {
78
+ registry.set(key, {
79
+ type: 'select',
80
+ validate: item.validate,
81
+ onUpdate: item.onUpdate,
82
+ });
83
+ }
84
+ if (item.type === 'input') {
85
+ registry.set(key, {
86
+ type: 'input',
87
+ validate: item.validate,
88
+ onUpdate: item.onUpdate,
89
+ });
90
+ }
91
+ });
92
+ });
93
+ return registry;
94
+ };
95
+ export const getActionRegistryKey = (sectionId, itemId) => `${sectionId}:${itemId}`;
96
+ export { validateValue };