@bubblydoo/uxp-toolkit-react 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +239 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @bubblydoo/uxp-toolkit-react
|
|
2
|
+
|
|
3
|
+
React hooks for Photoshop UXP plugins. Generic, non–app-specific utilities built on `@bubblydoo/uxp-toolkit`.
|
|
4
|
+
|
|
5
|
+
## Peer dependencies
|
|
6
|
+
|
|
7
|
+
- `react` (^18 or ^19)
|
|
8
|
+
- `@tanstack/react-query` (^5)
|
|
9
|
+
- `@bubblydoo/uxp-toolkit` (workspace)
|
|
10
|
+
- `zod` (^3 or ^4)
|
|
11
|
+
|
|
12
|
+
## Exports
|
|
13
|
+
|
|
14
|
+
- **useEventListenerSkippable** – Subscribe to events with optional skip/filter so triggers can be queued or ignored
|
|
15
|
+
- **useApplicationInfoQuery** – React Query for Photoshop application info (e.g. panel list)
|
|
16
|
+
- **useIsPluginPanelVisible** – Whether the plugin panel is visible (optionally for a given `panelId`)
|
|
17
|
+
- **useOnDocumentEdited** – Run a callback when the given document is edited (select, delete, make, set, move, close, show, hide, etc.)
|
|
18
|
+
- **useActiveDocument** – Sync external store for the current active document
|
|
19
|
+
- **useOnDocumentLayersEdited** – Run a callback when layers change (delete, make, set, move, close)
|
|
20
|
+
- **useOnDocumentLayersSelection** – Run a callback when layer selection changes (select, deselect)
|
|
21
|
+
- **useOnEvent** – Run a callback for arbitrary Photoshop action events on a given document
|
|
22
|
+
- **useOpenDocuments** – Sync external store for the list of open documents
|
|
23
|
+
|
|
24
|
+
All document/event hooks that take a `document` only fire when that document is the active document and (where applicable) when the plugin panel is visible.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { Document } from 'photoshop/dom/Document';
|
|
3
|
+
|
|
4
|
+
declare function useEventListenerSkippable({ subscribe, trigger, skip, filter, }: {
|
|
5
|
+
subscribe: (boundTrigger: () => void) => (() => void);
|
|
6
|
+
trigger: () => void;
|
|
7
|
+
/** If true, the trigger will be queued until the skip is false */
|
|
8
|
+
skip: boolean;
|
|
9
|
+
/** If false, the trigger will be skipped altogether */
|
|
10
|
+
filter: boolean;
|
|
11
|
+
}): void;
|
|
12
|
+
|
|
13
|
+
declare function useApplicationInfoQuery(refetchInterval?: number): _tanstack_react_query.UseQueryResult<{
|
|
14
|
+
active: boolean;
|
|
15
|
+
autoShowHomeScreen: boolean;
|
|
16
|
+
available: number;
|
|
17
|
+
buildNumber: string;
|
|
18
|
+
documentArea: {
|
|
19
|
+
left: number;
|
|
20
|
+
top: number;
|
|
21
|
+
right: number;
|
|
22
|
+
bottom: number;
|
|
23
|
+
};
|
|
24
|
+
hostName: string;
|
|
25
|
+
hostVersion: {
|
|
26
|
+
versionMajor: number;
|
|
27
|
+
versionMinor: number;
|
|
28
|
+
versionFix: number;
|
|
29
|
+
};
|
|
30
|
+
localeInfo: {
|
|
31
|
+
decimalPoint: string;
|
|
32
|
+
};
|
|
33
|
+
osVersion: string;
|
|
34
|
+
panelList: {
|
|
35
|
+
ID: string;
|
|
36
|
+
name: string;
|
|
37
|
+
obscured: boolean;
|
|
38
|
+
visible: boolean;
|
|
39
|
+
}[];
|
|
40
|
+
}, Error>;
|
|
41
|
+
declare function useIsPluginPanelVisible(panelId: string): boolean | null;
|
|
42
|
+
|
|
43
|
+
declare function useOnDocumentEdited(document: Document, trigger: () => void): void;
|
|
44
|
+
declare function useActiveDocument(): Document | null;
|
|
45
|
+
|
|
46
|
+
declare function useOnDocumentLayersEdited(document: Document, trigger: () => void): void;
|
|
47
|
+
|
|
48
|
+
declare function useOnDocumentLayersSelection(document: Document, trigger: () => void): void;
|
|
49
|
+
|
|
50
|
+
declare function useOnEvent(document: Document, events: string[], trigger: () => void): void;
|
|
51
|
+
|
|
52
|
+
declare function useOpenDocuments(): Document[];
|
|
53
|
+
|
|
54
|
+
export { useActiveDocument, useApplicationInfoQuery, useEventListenerSkippable, useIsPluginPanelVisible, useOnDocumentEdited, useOnDocumentLayersEdited, useOnDocumentLayersSelection, useOnEvent, useOpenDocuments };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/useEventListenerSkippable.ts
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
function useEventListenerSkippable({
|
|
4
|
+
subscribe,
|
|
5
|
+
trigger,
|
|
6
|
+
skip,
|
|
7
|
+
filter
|
|
8
|
+
}) {
|
|
9
|
+
const [queuedWhileSkipped, setQueuedWhileSkipped] = useState(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const unsubscribe = subscribe(() => {
|
|
12
|
+
if (filter) {
|
|
13
|
+
if (skip) {
|
|
14
|
+
setQueuedWhileSkipped(true);
|
|
15
|
+
} else {
|
|
16
|
+
trigger();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return unsubscribe;
|
|
21
|
+
}, [subscribe, trigger, skip, filter]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (queuedWhileSkipped && !skip) {
|
|
24
|
+
trigger();
|
|
25
|
+
setQueuedWhileSkipped(false);
|
|
26
|
+
}
|
|
27
|
+
}, [queuedWhileSkipped, trigger, skip]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/useIsPluginVisible.tsx
|
|
31
|
+
import { useQuery } from "@tanstack/react-query";
|
|
32
|
+
import { entrypoints } from "uxp";
|
|
33
|
+
import { photoshopGetApplicationInfo, uxpEntrypointsSchema } from "@bubblydoo/uxp-toolkit";
|
|
34
|
+
var pluginInfo = uxpEntrypointsSchema.parse(entrypoints)._pluginInfo;
|
|
35
|
+
function useApplicationInfoQuery(refetchInterval = 1e3) {
|
|
36
|
+
return useQuery({
|
|
37
|
+
queryKey: ["application-info"],
|
|
38
|
+
queryFn: async () => {
|
|
39
|
+
const appInfo = await photoshopGetApplicationInfo();
|
|
40
|
+
return appInfo;
|
|
41
|
+
},
|
|
42
|
+
refetchInterval
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function useIsPluginPanelVisible(panelId) {
|
|
46
|
+
const appInfoQuery = useApplicationInfoQuery(1e3);
|
|
47
|
+
if (!appInfoQuery.data) return null;
|
|
48
|
+
const pluginPanel = appInfoQuery.data.panelList.find((panel) => {
|
|
49
|
+
const idParts = panel.ID.split("/");
|
|
50
|
+
return idParts.includes(pluginInfo.id) && idParts.includes(panelId);
|
|
51
|
+
});
|
|
52
|
+
if (!pluginPanel) return false;
|
|
53
|
+
return pluginPanel.visible;
|
|
54
|
+
}
|
|
55
|
+
function useIsAnyPluginPanelVisible() {
|
|
56
|
+
const appInfoQuery = useApplicationInfoQuery(1e3);
|
|
57
|
+
if (!appInfoQuery.data) return null;
|
|
58
|
+
const pluginPanel = appInfoQuery.data.panelList.find((panel) => {
|
|
59
|
+
const idParts = panel.ID.split("/");
|
|
60
|
+
return idParts.includes(pluginInfo.id);
|
|
61
|
+
});
|
|
62
|
+
if (!pluginPanel) return false;
|
|
63
|
+
return pluginPanel.visible;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/useOnDocumentEdited.tsx
|
|
67
|
+
import { action, app } from "photoshop";
|
|
68
|
+
import { useSyncExternalStore } from "react";
|
|
69
|
+
var EVENTS = [
|
|
70
|
+
"select",
|
|
71
|
+
"delete",
|
|
72
|
+
"make",
|
|
73
|
+
"set",
|
|
74
|
+
"move",
|
|
75
|
+
"close",
|
|
76
|
+
"show",
|
|
77
|
+
"hide",
|
|
78
|
+
"convertToProfile",
|
|
79
|
+
"selectNoLayers",
|
|
80
|
+
"historyStateChanged"
|
|
81
|
+
// this might have changed the document
|
|
82
|
+
];
|
|
83
|
+
function useOnDocumentEdited(document, trigger) {
|
|
84
|
+
const isPluginPanelVisible = useIsAnyPluginPanelVisible() ?? true;
|
|
85
|
+
useEventListenerSkippable({
|
|
86
|
+
trigger,
|
|
87
|
+
subscribe: (boundTrigger) => {
|
|
88
|
+
action.addNotificationListener(EVENTS, boundTrigger);
|
|
89
|
+
return () => {
|
|
90
|
+
action.removeNotificationListener(EVENTS, boundTrigger);
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
skip: !isPluginPanelVisible,
|
|
94
|
+
filter: document === app.activeDocument
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
var DOCUMENT_CHANGE_EVENTS = [
|
|
98
|
+
"select",
|
|
99
|
+
"open",
|
|
100
|
+
"close",
|
|
101
|
+
"smartBrushWorkspace",
|
|
102
|
+
"layersFiltered"
|
|
103
|
+
];
|
|
104
|
+
var activeDocumentExternalStore = {
|
|
105
|
+
subscribe: (fn) => {
|
|
106
|
+
action.addNotificationListener(DOCUMENT_CHANGE_EVENTS, fn);
|
|
107
|
+
return () => {
|
|
108
|
+
action.removeNotificationListener(DOCUMENT_CHANGE_EVENTS, fn);
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
getSnapshot: () => app.activeDocument
|
|
112
|
+
};
|
|
113
|
+
function useActiveDocument() {
|
|
114
|
+
return useSyncExternalStore(
|
|
115
|
+
activeDocumentExternalStore.subscribe,
|
|
116
|
+
activeDocumentExternalStore.getSnapshot
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/useOnDocumentLayersEdited.tsx
|
|
121
|
+
import { action as action2, app as app2 } from "photoshop";
|
|
122
|
+
var EVENTS2 = [
|
|
123
|
+
// "select",
|
|
124
|
+
"delete",
|
|
125
|
+
"make",
|
|
126
|
+
"set",
|
|
127
|
+
"move",
|
|
128
|
+
"close",
|
|
129
|
+
// "show",
|
|
130
|
+
// "hide",
|
|
131
|
+
// "convertToProfile",
|
|
132
|
+
// "selectNoLayers",
|
|
133
|
+
"historyStateChanged"
|
|
134
|
+
// this might have changed the layers
|
|
135
|
+
];
|
|
136
|
+
function useOnDocumentLayersEdited(document, trigger) {
|
|
137
|
+
const isPluginPanelVisible = useIsAnyPluginPanelVisible() ?? true;
|
|
138
|
+
useEventListenerSkippable({
|
|
139
|
+
trigger,
|
|
140
|
+
subscribe: (boundTrigger) => {
|
|
141
|
+
action2.addNotificationListener(EVENTS2, boundTrigger);
|
|
142
|
+
return () => {
|
|
143
|
+
action2.removeNotificationListener(EVENTS2, boundTrigger);
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
skip: !isPluginPanelVisible,
|
|
147
|
+
filter: document === app2.activeDocument
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/useOnDocumentLayersSelection.tsx
|
|
152
|
+
import { action as action3, app as app3 } from "photoshop";
|
|
153
|
+
var EVENTS3 = [
|
|
154
|
+
"select",
|
|
155
|
+
"deselect",
|
|
156
|
+
// "delete",
|
|
157
|
+
// "make",
|
|
158
|
+
// "set",
|
|
159
|
+
// "move",
|
|
160
|
+
// "close",
|
|
161
|
+
// "show",
|
|
162
|
+
// "hide",
|
|
163
|
+
// "convertToProfile",
|
|
164
|
+
// "selectNoLayers",
|
|
165
|
+
"historyStateChanged"
|
|
166
|
+
// this might have changed the layers selection, e.g. when deleting an undo
|
|
167
|
+
];
|
|
168
|
+
function useOnDocumentLayersSelection(document, trigger) {
|
|
169
|
+
const isPluginPanelVisible = useIsAnyPluginPanelVisible() ?? true;
|
|
170
|
+
useEventListenerSkippable({
|
|
171
|
+
trigger,
|
|
172
|
+
subscribe: (boundTrigger) => {
|
|
173
|
+
action3.addNotificationListener(EVENTS3, boundTrigger);
|
|
174
|
+
return () => {
|
|
175
|
+
action3.removeNotificationListener(EVENTS3, boundTrigger);
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
skip: !isPluginPanelVisible,
|
|
179
|
+
filter: document === app3.activeDocument
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/useOnEvent.tsx
|
|
184
|
+
import { action as action4, app as app4 } from "photoshop";
|
|
185
|
+
function useOnEvent(document, events, trigger) {
|
|
186
|
+
const isPluginPanelVisible = useIsAnyPluginPanelVisible() ?? true;
|
|
187
|
+
useEventListenerSkippable({
|
|
188
|
+
trigger,
|
|
189
|
+
subscribe: (boundTrigger) => {
|
|
190
|
+
action4.addNotificationListener(events, boundTrigger);
|
|
191
|
+
return () => {
|
|
192
|
+
action4.removeNotificationListener(events, boundTrigger);
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
skip: !isPluginPanelVisible,
|
|
196
|
+
filter: document === app4.activeDocument
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/useOpenDocuments.tsx
|
|
201
|
+
import { action as action5, app as app5 } from "photoshop";
|
|
202
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
203
|
+
var OPEN_DOCUMENTS_EVENTS = ["open", "close"];
|
|
204
|
+
var cachedDocuments = null;
|
|
205
|
+
var cachedDocumentsSnapshot = null;
|
|
206
|
+
var openDocumentsExternalStore = {
|
|
207
|
+
subscribe: (fn) => {
|
|
208
|
+
action5.addNotificationListener(OPEN_DOCUMENTS_EVENTS, fn);
|
|
209
|
+
return () => {
|
|
210
|
+
action5.removeNotificationListener(OPEN_DOCUMENTS_EVENTS, fn);
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
getSnapshot: () => {
|
|
214
|
+
const currentDocuments = Array.from(app5.documents);
|
|
215
|
+
const currentSnapshot = currentDocuments.map((doc) => doc.id || doc.name).join(",");
|
|
216
|
+
if (currentSnapshot !== cachedDocumentsSnapshot) {
|
|
217
|
+
cachedDocuments = currentDocuments;
|
|
218
|
+
cachedDocumentsSnapshot = currentSnapshot;
|
|
219
|
+
}
|
|
220
|
+
return cachedDocuments || [];
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function useOpenDocuments() {
|
|
224
|
+
return useSyncExternalStore2(
|
|
225
|
+
openDocumentsExternalStore.subscribe,
|
|
226
|
+
openDocumentsExternalStore.getSnapshot
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
useActiveDocument,
|
|
231
|
+
useApplicationInfoQuery,
|
|
232
|
+
useEventListenerSkippable,
|
|
233
|
+
useIsPluginPanelVisible,
|
|
234
|
+
useOnDocumentEdited,
|
|
235
|
+
useOnDocumentLayersEdited,
|
|
236
|
+
useOnDocumentLayersSelection,
|
|
237
|
+
useOnEvent,
|
|
238
|
+
useOpenDocuments
|
|
239
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bubblydoo/uxp-toolkit-react",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@tanstack/react-query": "^5.0.0",
|
|
21
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
22
|
+
"zod": "^3.0.0 || ^4.0.0",
|
|
23
|
+
"@bubblydoo/uxp-toolkit": "0.0.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@adobe-uxp-types/uxp": "^0.0.9",
|
|
27
|
+
"@tanstack/react-query": "^5.0.0",
|
|
28
|
+
"@types/photoshop": "^25.0.2",
|
|
29
|
+
"@types/react": "^19.0.0",
|
|
30
|
+
"@types/node": "^20.8.7",
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"typescript": "^5.8.3",
|
|
33
|
+
"tsup": "^8.5.1",
|
|
34
|
+
"zod": "^4.3.6",
|
|
35
|
+
"@bubblydoo/tsconfig": "0.0.1",
|
|
36
|
+
"@bubblydoo/uxp-toolkit": "0.0.2"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup"
|
|
40
|
+
}
|
|
41
|
+
}
|