@weasel-js/labkit 0.1.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 (217) hide show
  1. package/README.md +72 -0
  2. package/dist/_dts/fitViewToBounds-rWrBI4a6.d.ts +870 -0
  3. package/dist/_dts/index-C6Yze7sQ.d.ts +93 -0
  4. package/dist/_dts/types-Si4Fw-1F.d.ts +48 -0
  5. package/dist/_dts/useExperimentState-eSvpwV_P.d.ts +102 -0
  6. package/dist/canvas/index.d.ts +34 -0
  7. package/dist/canvas/index.js +4 -0
  8. package/dist/canvas/index.js.map +1 -0
  9. package/dist/chunk-2QNYYL3V.js +86 -0
  10. package/dist/chunk-2QNYYL3V.js.map +1 -0
  11. package/dist/chunk-54IQ2DX7.js +222 -0
  12. package/dist/chunk-54IQ2DX7.js.map +1 -0
  13. package/dist/chunk-6FO7G5BB.js +6095 -0
  14. package/dist/chunk-6FO7G5BB.js.map +1 -0
  15. package/dist/chunk-7BKDG73Z.js +54 -0
  16. package/dist/chunk-7BKDG73Z.js.map +1 -0
  17. package/dist/chunk-B5ZUYRKN.js +23330 -0
  18. package/dist/chunk-B5ZUYRKN.js.map +1 -0
  19. package/dist/chunk-HXZHVU4G.js +126 -0
  20. package/dist/chunk-HXZHVU4G.js.map +1 -0
  21. package/dist/chunk-PWC7AQZM.js +8 -0
  22. package/dist/chunk-PWC7AQZM.js.map +1 -0
  23. package/dist/chunk-SD3HDXR7.js +161 -0
  24. package/dist/chunk-SD3HDXR7.js.map +1 -0
  25. package/dist/chunk-SSMHPMDV.js +350 -0
  26. package/dist/chunk-SSMHPMDV.js.map +1 -0
  27. package/dist/chunk-VLAHRJOC.js +201 -0
  28. package/dist/chunk-VLAHRJOC.js.map +1 -0
  29. package/dist/controls/index.d.ts +13 -0
  30. package/dist/controls/index.js +3 -0
  31. package/dist/controls/index.js.map +1 -0
  32. package/dist/dragdrop/index.d.ts +3 -0
  33. package/dist/dragdrop/index.js +3 -0
  34. package/dist/dragdrop/index.js.map +1 -0
  35. package/dist/fonts/oswald-latin-variable.woff2 +0 -0
  36. package/dist/index.d.ts +410 -0
  37. package/dist/index.js +1425 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/layers/index.d.ts +17 -0
  40. package/dist/layers/index.js +3 -0
  41. package/dist/layers/index.js.map +1 -0
  42. package/dist/passthrough/weasel-canvas.d.ts +258 -0
  43. package/dist/passthrough/weasel-canvas.js +3 -0
  44. package/dist/passthrough/weasel-canvas.js.map +1 -0
  45. package/dist/passthrough/weasel-ui.d.ts +2826 -0
  46. package/dist/passthrough/weasel-ui.js +4 -0
  47. package/dist/passthrough/weasel-ui.js.map +1 -0
  48. package/dist/primitives/index.d.ts +57 -0
  49. package/dist/primitives/index.js +4 -0
  50. package/dist/primitives/index.js.map +1 -0
  51. package/dist/state/index.d.ts +33 -0
  52. package/dist/state/index.js +34 -0
  53. package/dist/state/index.js.map +1 -0
  54. package/dist/styles.css +1224 -0
  55. package/dist/theme-interstellar.css +25 -0
  56. package/dist/theme-light.css +13 -0
  57. package/dist/ui/layers/index.d.ts +42 -0
  58. package/dist/ui/layers/index.js +5 -0
  59. package/dist/ui/layers/index.js.map +1 -0
  60. package/dist/undo/index.d.ts +30 -0
  61. package/dist/undo/index.js +3 -0
  62. package/dist/undo/index.js.map +1 -0
  63. package/package.json +99 -0
  64. package/src/canvas/AGENTS.md +72 -0
  65. package/src/canvas/CanvasStack.less +22 -0
  66. package/src/canvas/CanvasStack.stories.tsx +63 -0
  67. package/src/canvas/CanvasStack.test.tsx +82 -0
  68. package/src/canvas/CanvasStack.tsx +108 -0
  69. package/src/canvas/CanvasStackContext.ts +8 -0
  70. package/src/canvas/canvasCoords.test.ts +30 -0
  71. package/src/canvas/canvasCoords.ts +15 -0
  72. package/src/canvas/index.ts +6 -0
  73. package/src/canvas/useLayerScheduler.ts +72 -0
  74. package/src/canvas/usePanZoom.ts +98 -0
  75. package/src/controls/ControlPanel.less +61 -0
  76. package/src/controls/ControlPanel.stories.tsx +77 -0
  77. package/src/controls/ControlPanel.test.tsx +149 -0
  78. package/src/controls/ControlPanel.tsx +249 -0
  79. package/src/controls/index.ts +12 -0
  80. package/src/controls/types.ts +61 -0
  81. package/src/dragdrop/DragDropRuntime.tsx +135 -0
  82. package/src/dragdrop/DragGhost.less +17 -0
  83. package/src/dragdrop/DragGhost.tsx +24 -0
  84. package/src/dragdrop/Palette.less +34 -0
  85. package/src/dragdrop/Palette.tsx +27 -0
  86. package/src/dragdrop/dragDrop.test.tsx +145 -0
  87. package/src/dragdrop/index.ts +1 -0
  88. package/src/fonts/oswald-latin-variable.woff2 +0 -0
  89. package/src/index.ts +70 -0
  90. package/src/instrument/SineWave.smoke.test.tsx +94 -0
  91. package/src/instrument/capabilityDetector.test.ts +64 -0
  92. package/src/instrument/capabilityDetector.ts +17 -0
  93. package/src/instrument/defineInstrument.test.ts +27 -0
  94. package/src/instrument/defineInstrument.ts +5 -0
  95. package/src/instrument/index.ts +19 -0
  96. package/src/instrument/types.ts +65 -0
  97. package/src/instrument/validateConfigSchema.test.ts +125 -0
  98. package/src/instrument/validateConfigSchema.ts +65 -0
  99. package/src/lab/Lab.less +3 -0
  100. package/src/lab/Lab.stories.tsx +51 -0
  101. package/src/lab/Lab.test.tsx +126 -0
  102. package/src/lab/Lab.tsx +196 -0
  103. package/src/lab/LabContext.test.ts +11 -0
  104. package/src/lab/LabContext.ts +28 -0
  105. package/src/lab/LabShell.less +45 -0
  106. package/src/lab/LabShell.stories.tsx +39 -0
  107. package/src/lab/LabShell.test.tsx +59 -0
  108. package/src/lab/LabShell.tsx +33 -0
  109. package/src/lab/WorkspaceGrid.less +8 -0
  110. package/src/lab/WorkspaceGrid.stories.tsx +61 -0
  111. package/src/lab/WorkspaceGrid.test.tsx +40 -0
  112. package/src/lab/WorkspaceGrid.tsx +20 -0
  113. package/src/lab/gridDims.test.ts +35 -0
  114. package/src/lab/gridDims.ts +11 -0
  115. package/src/lab/index.ts +10 -0
  116. package/src/layers/AGENTS.md +65 -0
  117. package/src/layers/LayerList.less +53 -0
  118. package/src/layers/LayerList.test.tsx +49 -0
  119. package/src/layers/LayerList.tsx +105 -0
  120. package/src/layers/index.ts +3 -0
  121. package/src/passthrough/weasel-canvas.test.ts +30 -0
  122. package/src/passthrough/weasel-canvas.ts +23 -0
  123. package/src/passthrough/weasel-ui.test.ts +79 -0
  124. package/src/passthrough/weasel-ui.ts +150 -0
  125. package/src/primitives/FpsMeter.less +14 -0
  126. package/src/primitives/FpsMeter.stories.tsx +14 -0
  127. package/src/primitives/FpsMeter.test.tsx +38 -0
  128. package/src/primitives/FpsMeter.tsx +36 -0
  129. package/src/primitives/ScaleIndicator.less +25 -0
  130. package/src/primitives/ScaleIndicator.stories.tsx +16 -0
  131. package/src/primitives/ScaleIndicator.test.tsx +46 -0
  132. package/src/primitives/ScaleIndicator.tsx +48 -0
  133. package/src/primitives/Sidebar.less +57 -0
  134. package/src/primitives/Sidebar.stories.tsx +38 -0
  135. package/src/primitives/Sidebar.test.tsx +74 -0
  136. package/src/primitives/Sidebar.tsx +32 -0
  137. package/src/primitives/StatusBar.less +21 -0
  138. package/src/primitives/StatusBar.stories.tsx +24 -0
  139. package/src/primitives/StatusBar.test.tsx +25 -0
  140. package/src/primitives/StatusBar.tsx +18 -0
  141. package/src/primitives/Toolbar.less +43 -0
  142. package/src/primitives/Toolbar.stories.tsx +36 -0
  143. package/src/primitives/Toolbar.test.tsx +65 -0
  144. package/src/primitives/Toolbar.tsx +44 -0
  145. package/src/primitives/fpsAverage.test.ts +21 -0
  146. package/src/primitives/fpsAverage.ts +5 -0
  147. package/src/primitives/index.ts +9 -0
  148. package/src/state/SingletonExperiment.test.tsx +101 -0
  149. package/src/state/SingletonExperiment.tsx +52 -0
  150. package/src/state/adapters.test.ts +94 -0
  151. package/src/state/adapters.ts +102 -0
  152. package/src/state/context.tsx +42 -0
  153. package/src/state/helpers.test.ts +89 -0
  154. package/src/state/helpers.ts +61 -0
  155. package/src/state/index.ts +40 -0
  156. package/src/state/store.test.ts +243 -0
  157. package/src/state/store.ts +225 -0
  158. package/src/state/types.ts +53 -0
  159. package/src/state/useExperimentState.test.tsx +102 -0
  160. package/src/state/useExperimentState.ts +28 -0
  161. package/src/styles.less +20 -0
  162. package/src/test-setup.ts +17 -0
  163. package/src/theme/Interstellar.stories.less +92 -0
  164. package/src/theme/Interstellar.stories.tsx +243 -0
  165. package/src/theme/base.less +122 -0
  166. package/src/theme/interstellar.less +34 -0
  167. package/src/theme/light.less +14 -0
  168. package/src/theme/tokens.less +85 -0
  169. package/src/ui/format.ts +19 -0
  170. package/src/ui/layers/LayerStack.less +143 -0
  171. package/src/ui/layers/LayerStack.stories.tsx +45 -0
  172. package/src/ui/layers/LayerStack.test.tsx +170 -0
  173. package/src/ui/layers/LayerStack.tsx +215 -0
  174. package/src/ui/layers/index.ts +1 -0
  175. package/src/ui/properties/CheckboxRow.stories.tsx +27 -0
  176. package/src/ui/properties/ColorRow.stories.tsx +106 -0
  177. package/src/ui/properties/CurveField.less +48 -0
  178. package/src/ui/properties/CurveField.stories.tsx +20 -0
  179. package/src/ui/properties/CurveField.test.tsx +127 -0
  180. package/src/ui/properties/CurveField.tsx +181 -0
  181. package/src/ui/properties/EffectCard.tsx +312 -0
  182. package/src/ui/properties/Gallery.stories.tsx +178 -0
  183. package/src/ui/properties/NumberRow.stories.tsx +65 -0
  184. package/src/ui/properties/PropertyGroup.less +50 -0
  185. package/src/ui/properties/PropertyGroup.stories.tsx +34 -0
  186. package/src/ui/properties/PropertyGroup.test.tsx +33 -0
  187. package/src/ui/properties/PropertyGroup.tsx +44 -0
  188. package/src/ui/properties/PropertyList.stories.tsx +83 -0
  189. package/src/ui/properties/PropertyPanel.less +592 -0
  190. package/src/ui/properties/PropertyPanel.stories.tsx +191 -0
  191. package/src/ui/properties/PropertyPanel.test.tsx +193 -0
  192. package/src/ui/properties/PropertyPanel.tsx +441 -0
  193. package/src/ui/properties/PropertyRow.stories.tsx +53 -0
  194. package/src/ui/properties/SelectRow.stories.tsx +58 -0
  195. package/src/ui/properties/SliderRow.stories.tsx +74 -0
  196. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +396 -0
  197. package/src/ui/properties/TextRow.stories.tsx +62 -0
  198. package/src/ui/properties/ToggleRow.stories.tsx +57 -0
  199. package/src/ui/properties/index.ts +39 -0
  200. package/src/ui/properties/storyLayouts.tsx +36 -0
  201. package/src/undo/eventBus.test.ts +49 -0
  202. package/src/undo/eventBus.ts +32 -0
  203. package/src/undo/index.ts +5 -0
  204. package/src/undo/undoStack.test.ts +70 -0
  205. package/src/undo/undoStack.ts +38 -0
  206. package/src/workspace/DefaultSidebar.tsx +26 -0
  207. package/src/workspace/DefaultStatusBar.tsx +15 -0
  208. package/src/workspace/DefaultToolbar.tsx +88 -0
  209. package/src/workspace/Workspace.less +58 -0
  210. package/src/workspace/Workspace.stories.tsx +78 -0
  211. package/src/workspace/Workspace.test.tsx +186 -0
  212. package/src/workspace/Workspace.tsx +255 -0
  213. package/src/workspace/WorkspaceChrome.tsx +150 -0
  214. package/src/workspace/index.ts +24 -0
  215. package/src/workspace/slotTypes.ts +44 -0
  216. package/src/workspace/workspaceOps.test.ts +142 -0
  217. package/src/workspace/workspaceOps.ts +78 -0
@@ -0,0 +1,350 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { useStore } from 'zustand/react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { createStore } from 'zustand/vanilla';
5
+
6
+ // src/state/helpers.ts
7
+ function labStorageKey(storageKey, bucket) {
8
+ return `lk:${storageKey}:${bucket}`;
9
+ }
10
+ function encodeUrlHash(value) {
11
+ return btoa(encodeURIComponent(value));
12
+ }
13
+ function decodeUrlHash(hash) {
14
+ if (!hash) return null;
15
+ try {
16
+ return decodeURIComponent(atob(hash));
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ function emptyUndoStack() {
22
+ return { past: [], future: [] };
23
+ }
24
+ function serializeWorkspaces(workspaces, serializers) {
25
+ const records = workspaces.map(({ undoStack: _undo, ...w }) => {
26
+ const s = serializers[w.instrumentName];
27
+ return {
28
+ ...w,
29
+ state: s?.serialize ? s.serialize(w.state) : w.state
30
+ };
31
+ });
32
+ return JSON.stringify(records);
33
+ }
34
+ function deserializeWorkspaces(raw, deserializers) {
35
+ try {
36
+ const records = JSON.parse(raw);
37
+ return records.map((r) => {
38
+ const d = deserializers[r.instrumentName];
39
+ return {
40
+ ...r,
41
+ state: d?.deserialize ? d.deserialize(r.state) : r.state,
42
+ undoStack: emptyUndoStack()
43
+ };
44
+ });
45
+ } catch {
46
+ console.warn("[labkit] deserializeWorkspaces: failed to parse, returning empty list");
47
+ return [];
48
+ }
49
+ }
50
+
51
+ // src/state/adapters.ts
52
+ var localStorageAdapter = {
53
+ read: (key) => {
54
+ try {
55
+ return localStorage.getItem(key);
56
+ } catch {
57
+ return null;
58
+ }
59
+ },
60
+ write: (key, value) => {
61
+ try {
62
+ localStorage.setItem(key, value);
63
+ } catch (e) {
64
+ console.warn("[labkit] localStorage write failed:", e);
65
+ }
66
+ },
67
+ delete: (key) => {
68
+ try {
69
+ localStorage.removeItem(key);
70
+ } catch {
71
+ }
72
+ }
73
+ };
74
+ var sessionStorageAdapter = {
75
+ read: (key) => {
76
+ try {
77
+ return sessionStorage.getItem(key);
78
+ } catch {
79
+ return null;
80
+ }
81
+ },
82
+ write: (key, value) => {
83
+ try {
84
+ sessionStorage.setItem(key, value);
85
+ } catch (e) {
86
+ console.warn("[labkit] sessionStorage write failed:", e);
87
+ }
88
+ },
89
+ delete: (key) => {
90
+ try {
91
+ sessionStorage.removeItem(key);
92
+ } catch {
93
+ }
94
+ }
95
+ };
96
+ var URL_HASH_GUARD = typeof window !== "undefined";
97
+ function readHashMap() {
98
+ if (!URL_HASH_GUARD) return {};
99
+ const raw = decodeUrlHash(window.location.hash.replace(/^#/, ""));
100
+ if (!raw) return {};
101
+ try {
102
+ return JSON.parse(raw);
103
+ } catch {
104
+ return {};
105
+ }
106
+ }
107
+ function writeHashMap(map) {
108
+ if (!URL_HASH_GUARD) return;
109
+ const encoded = encodeUrlHash(JSON.stringify(map));
110
+ window.history.replaceState(null, "", `#${encoded}`);
111
+ }
112
+ var urlHashAdapter = {
113
+ read: (key) => readHashMap()[key] ?? null,
114
+ write: (key, value) => {
115
+ const map = readHashMap();
116
+ map[key] = value;
117
+ writeHashMap(map);
118
+ },
119
+ delete: (key) => {
120
+ const map = readHashMap();
121
+ delete map[key];
122
+ writeHashMap(map);
123
+ }
124
+ };
125
+ function createMemoryAdapter() {
126
+ const store = /* @__PURE__ */ new Map();
127
+ return {
128
+ read: (key) => store.get(key) ?? null,
129
+ write: (key, value) => {
130
+ store.set(key, value);
131
+ },
132
+ delete: (key) => {
133
+ store.delete(key);
134
+ }
135
+ };
136
+ }
137
+ var noneAdapter = {
138
+ read: () => null,
139
+ write: () => {
140
+ },
141
+ delete: () => {
142
+ }
143
+ };
144
+ var LabStoreContext = createContext(null);
145
+ function LabStoreProvider({
146
+ store,
147
+ children
148
+ }) {
149
+ return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children });
150
+ }
151
+ function useLabStore() {
152
+ const ctx = useContext(LabStoreContext);
153
+ if (!ctx) throw new Error("[labkit] useLabStore must be used inside <LabStoreProvider>");
154
+ return useStore(ctx.store);
155
+ }
156
+ var WorkspaceIdContext = createContext(null);
157
+ function WorkspaceIdProvider({
158
+ workspaceId,
159
+ children
160
+ }) {
161
+ return /* @__PURE__ */ jsx(WorkspaceIdContext.Provider, { value: workspaceId, children });
162
+ }
163
+ function useWorkspaceId() {
164
+ const id = useContext(WorkspaceIdContext);
165
+ if (!id) throw new Error("[labkit] useWorkspaceId must be used inside <WorkspaceIdProvider>");
166
+ return id;
167
+ }
168
+ function createLabStore(options) {
169
+ let serializers = {};
170
+ let flushTimer = null;
171
+ const workspacesRaw = options.storage.read(labStorageKey(options.storageKey, "workspaces"));
172
+ let hydratedWorkspaces = [];
173
+ if (workspacesRaw) {
174
+ hydratedWorkspaces = deserializeWorkspaces(workspacesRaw, serializers);
175
+ }
176
+ const savesRaw = options.storage.read(labStorageKey(options.storageKey, "saves"));
177
+ let hydratedSnapshots = [];
178
+ if (savesRaw) {
179
+ try {
180
+ hydratedSnapshots = JSON.parse(savesRaw);
181
+ } catch {
182
+ console.warn("[labkit] failed to parse saved snapshots, starting empty");
183
+ hydratedSnapshots = [];
184
+ }
185
+ }
186
+ const themeRaw = options.storage.read(labStorageKey(options.storageKey, "theme"));
187
+ let hydratedTheme;
188
+ if (themeRaw === "light" || themeRaw === "interstellar" || themeRaw === "auto") {
189
+ hydratedTheme = themeRaw;
190
+ } else {
191
+ hydratedTheme = options.initialTheme ?? "auto";
192
+ }
193
+ const store = createStore()((set, get) => ({
194
+ workspaces: hydratedWorkspaces,
195
+ savedSnapshots: hydratedSnapshots,
196
+ theme: hydratedTheme,
197
+ addWorkspace: (record) => {
198
+ set((s) => ({
199
+ workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }]
200
+ }));
201
+ scheduleFlush();
202
+ },
203
+ removeWorkspace: (id) => {
204
+ set((s) => ({ workspaces: s.workspaces.filter((w) => w.id !== id) }));
205
+ scheduleFlush();
206
+ },
207
+ updateWorkspaceState: (id, next) => {
208
+ set((s) => ({
209
+ workspaces: s.workspaces.map((w) => {
210
+ if (w.id !== id) return w;
211
+ const nextState = typeof next === "function" ? next(w.state) : next;
212
+ return { ...w, state: nextState };
213
+ })
214
+ }));
215
+ scheduleFlush();
216
+ },
217
+ updateWorkspaceConfig: (id, key, value) => {
218
+ set((s) => ({
219
+ workspaces: s.workspaces.map((w) => {
220
+ if (w.id !== id) return w;
221
+ return {
222
+ ...w,
223
+ config: { ...w.config, [key]: value }
224
+ };
225
+ })
226
+ }));
227
+ scheduleFlush();
228
+ },
229
+ updateWorkspaceView: (id, view) => {
230
+ set((s) => ({
231
+ workspaces: s.workspaces.map((w) => w.id === id ? { ...w, view } : w)
232
+ }));
233
+ scheduleFlush();
234
+ },
235
+ updateWorkspaceUndoStack: (id, next) => {
236
+ set((s) => ({
237
+ workspaces: s.workspaces.map((w) => {
238
+ if (w.id !== id) return w;
239
+ const undoStack = typeof next === "function" ? next(
240
+ w.undoStack
241
+ ) : next;
242
+ return { ...w, undoStack };
243
+ })
244
+ }));
245
+ },
246
+ setWorkspaceInstrument: (id, instrumentName) => {
247
+ set((s) => ({
248
+ workspaces: s.workspaces.map((w) => w.id === id ? { ...w, instrumentName } : w)
249
+ }));
250
+ scheduleFlush();
251
+ },
252
+ saveSnapshot: (workspaceId, name) => {
253
+ const workspace = get().workspaces.find((w) => w.id === workspaceId);
254
+ if (!workspace) return;
255
+ const reg = serializers[workspace.instrumentName];
256
+ const serializedState = reg?.serialize ? reg.serialize(workspace.state) : structuredClone(workspace.state);
257
+ const clonedConfig = structuredClone(workspace.config);
258
+ const lastAt = get().savedSnapshots.reduce((m, sn) => sn.savedAt > m ? sn.savedAt : m, 0);
259
+ const savedAt = Math.max(Date.now(), lastAt + 1);
260
+ const snapshot = {
261
+ id: crypto.randomUUID(),
262
+ name,
263
+ workspaceId,
264
+ instrumentName: workspace.instrumentName,
265
+ config: clonedConfig,
266
+ state: serializedState,
267
+ savedAt
268
+ };
269
+ set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));
270
+ scheduleFlush();
271
+ },
272
+ loadSnapshot: (snapshotId, workspaceId) => {
273
+ const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);
274
+ if (!snapshot) return;
275
+ const workspace = get().workspaces.find((w) => w.id === workspaceId);
276
+ if (!workspace) return;
277
+ if (snapshot.instrumentName !== workspace.instrumentName) {
278
+ console.warn(
279
+ `[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, workspace=${workspace.instrumentName}); refusing to load`
280
+ );
281
+ return;
282
+ }
283
+ const reg = serializers[snapshot.instrumentName];
284
+ const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;
285
+ set((s) => ({
286
+ workspaces: s.workspaces.map(
287
+ (w) => w.id === workspaceId ? { ...w, state: restoredState, config: snapshot.config } : w
288
+ )
289
+ }));
290
+ scheduleFlush();
291
+ },
292
+ deleteSnapshot: (snapshotId) => {
293
+ set((s) => ({
294
+ savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId)
295
+ }));
296
+ scheduleFlush();
297
+ },
298
+ listSnapshots: (workspaceId) => {
299
+ const all = get().savedSnapshots;
300
+ const filtered = workspaceId ? all.filter((sn) => sn.workspaceId === workspaceId) : all;
301
+ return [...filtered].sort((a, b) => b.savedAt - a.savedAt);
302
+ },
303
+ setTheme: (theme) => {
304
+ set({ theme });
305
+ scheduleFlush();
306
+ }
307
+ }));
308
+ function scheduleFlush() {
309
+ if (flushTimer) clearTimeout(flushTimer);
310
+ flushTimer = setTimeout(() => {
311
+ const s = store.getState();
312
+ options.storage.write(
313
+ labStorageKey(options.storageKey, "workspaces"),
314
+ serializeWorkspaces(s.workspaces, serializers)
315
+ );
316
+ options.storage.write(
317
+ labStorageKey(options.storageKey, "saves"),
318
+ JSON.stringify(s.savedSnapshots)
319
+ );
320
+ options.storage.write(labStorageKey(options.storageKey, "theme"), s.theme);
321
+ flushTimer = null;
322
+ }, 300);
323
+ }
324
+ return Object.assign(store, {
325
+ registerSerializers(s) {
326
+ serializers = s;
327
+ }
328
+ });
329
+ }
330
+ function useExperimentState() {
331
+ const ctx = useContext(LabStoreContext);
332
+ if (!ctx) throw new Error("[labkit] useExperimentState must be used inside <LabStoreProvider>");
333
+ const workspaceId = useContext(WorkspaceIdContext);
334
+ if (!workspaceId)
335
+ throw new Error("[labkit] useExperimentState must be used inside <WorkspaceIdProvider>");
336
+ const record = useStore(ctx.store, (s) => s.workspaces.find((w) => w.id === workspaceId));
337
+ if (!record) throw new Error(`[labkit] No workspace found with id "${workspaceId}"`);
338
+ const updateWorkspaceState = useStore(ctx.store, (s) => s.updateWorkspaceState);
339
+ const updateWorkspaceConfig = useStore(ctx.store, (s) => s.updateWorkspaceConfig);
340
+ return {
341
+ state: record.state,
342
+ config: record.config,
343
+ setState: (next) => updateWorkspaceState(workspaceId, next),
344
+ setConfig: (key, value) => updateWorkspaceConfig(workspaceId, key, value)
345
+ };
346
+ }
347
+
348
+ export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createLabStore, createMemoryAdapter, decodeUrlHash, deserializeWorkspaces, emptyUndoStack, encodeUrlHash, labStorageKey, localStorageAdapter, noneAdapter, serializeWorkspaces, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId };
349
+ //# sourceMappingURL=chunk-SSMHPMDV.js.map
350
+ //# sourceMappingURL=chunk-SSMHPMDV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/state/helpers.ts","../src/state/adapters.ts","../src/state/context.tsx","../src/state/store.ts","../src/state/useExperimentState.ts"],"names":["useContext","useStore"],"mappings":"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","file":"chunk-SSMHPMDV.js","sourcesContent":["import type { InstrumentSerializers, UndoStack, WorkspaceRecord } from './types';\n\nexport function labStorageKey(\n storageKey: string,\n bucket: 'workspaces' | 'saves' | 'theme',\n): string {\n return `lk:${storageKey}:${bucket}`;\n}\n\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\nexport function decodeUrlHash(hash: string): string | null {\n if (!hash) return null;\n try {\n return decodeURIComponent(atob(hash));\n } catch {\n return null;\n }\n}\n\nexport function emptyUndoStack(): UndoStack {\n return { past: [], future: [] };\n}\n\ntype SerializedRecord = Omit<WorkspaceRecord, 'undoStack'>;\n\nexport function serializeWorkspaces(\n workspaces: WorkspaceRecord[],\n serializers: InstrumentSerializers,\n): string {\n const records: SerializedRecord[] = workspaces.map(({ undoStack: _undo, ...w }) => {\n const s = serializers[w.instrumentName];\n return {\n ...w,\n state: s?.serialize ? s.serialize(w.state) : w.state,\n };\n });\n return JSON.stringify(records);\n}\n\nexport function deserializeWorkspaces(\n raw: string,\n deserializers: InstrumentSerializers,\n): WorkspaceRecord[] {\n try {\n const records = JSON.parse(raw) as SerializedRecord[];\n return records.map((r) => {\n const d = deserializers[r.instrumentName];\n return {\n ...r,\n state: d?.deserialize ? d.deserialize(r.state) : r.state,\n undoStack: emptyUndoStack(),\n };\n });\n } catch {\n console.warn('[labkit] deserializeWorkspaces: failed to parse, returning empty list');\n return [];\n }\n}\n","import { decodeUrlHash, encodeUrlHash } from './helpers';\nimport type { StorageAdapter } from './types';\n\nexport const localStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n localStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] localStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n localStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\nexport const sessionStorageAdapter: StorageAdapter = {\n read: (key) => {\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n },\n write: (key, value) => {\n try {\n sessionStorage.setItem(key, value);\n } catch (e) {\n console.warn('[labkit] sessionStorage write failed:', e);\n }\n },\n delete: (key) => {\n try {\n sessionStorage.removeItem(key);\n } catch {\n /* ignore */\n }\n },\n};\n\nconst URL_HASH_GUARD = typeof window !== 'undefined';\n\nfunction readHashMap(): Record<string, string> {\n if (!URL_HASH_GUARD) return {};\n const raw = decodeUrlHash(window.location.hash.replace(/^#/, ''));\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, string>;\n } catch {\n return {};\n }\n}\n\nfunction writeHashMap(map: Record<string, string>): void {\n if (!URL_HASH_GUARD) return;\n const encoded = encodeUrlHash(JSON.stringify(map));\n window.history.replaceState(null, '', `#${encoded}`);\n}\n\nexport const urlHashAdapter: StorageAdapter = {\n read: (key) => readHashMap()[key] ?? null,\n write: (key, value) => {\n const map = readHashMap();\n map[key] = value;\n writeHashMap(map);\n },\n delete: (key) => {\n const map = readHashMap();\n delete map[key];\n writeHashMap(map);\n },\n};\n\nexport function createMemoryAdapter(): StorageAdapter {\n const store = new Map<string, string>();\n return {\n read: (key) => store.get(key) ?? null,\n write: (key, value) => {\n store.set(key, value);\n },\n delete: (key) => {\n store.delete(key);\n },\n };\n}\n\nexport const noneAdapter: StorageAdapter = {\n read: () => null,\n write: () => {},\n delete: () => {},\n};\n","import { createContext, type ReactElement, type ReactNode, useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport type { LabStore } from './store';\nimport type { LabStoreState } from './types';\n\ntype LabStoreCtx = { store: LabStore } | null;\n\nexport const LabStoreContext = createContext<LabStoreCtx>(null);\n\nexport function LabStoreProvider({\n store,\n children,\n}: {\n store: LabStore;\n children: ReactNode;\n}): ReactElement {\n return <LabStoreContext.Provider value={{ store }}>{children}</LabStoreContext.Provider>;\n}\n\nexport function useLabStore(): LabStoreState & ReturnType<LabStore['getState']> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useLabStore must be used inside <LabStoreProvider>');\n return useStore(ctx.store);\n}\n\nexport const WorkspaceIdContext = createContext<string | null>(null);\n\nexport function WorkspaceIdProvider({\n workspaceId,\n children,\n}: {\n workspaceId: string;\n children: ReactNode;\n}): ReactElement {\n return <WorkspaceIdContext.Provider value={workspaceId}>{children}</WorkspaceIdContext.Provider>;\n}\n\nexport function useWorkspaceId(): string {\n const id = useContext(WorkspaceIdContext);\n if (!id) throw new Error('[labkit] useWorkspaceId must be used inside <WorkspaceIdProvider>');\n return id;\n}\n","import { createStore, type StoreApi } from 'zustand/vanilla';\nimport {\n deserializeWorkspaces,\n emptyUndoStack,\n labStorageKey,\n serializeWorkspaces,\n} from './helpers';\nimport type {\n CreateLabStoreOptions,\n InstrumentSerializers,\n LabStoreState,\n SavedSnapshot,\n WorkspaceRecord,\n} from './types';\n\nexport interface LabStoreActions {\n addWorkspace: (record: Omit<WorkspaceRecord, 'undoStack'>) => void;\n removeWorkspace: (id: string) => void;\n updateWorkspaceState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;\n updateWorkspaceConfig: <TC>(id: string, key: keyof TC, value: TC[keyof TC]) => void;\n updateWorkspaceView: (id: string, view: WorkspaceRecord['view']) => void;\n updateWorkspaceUndoStack: (\n id: string,\n next:\n | WorkspaceRecord['undoStack']\n | ((prev: WorkspaceRecord['undoStack']) => WorkspaceRecord['undoStack']),\n ) => void;\n setWorkspaceInstrument: (id: string, instrumentName: string) => void;\n saveSnapshot: (workspaceId: string, name: string) => void;\n loadSnapshot: (snapshotId: string, workspaceId: string) => void;\n deleteSnapshot: (snapshotId: string) => void;\n listSnapshots: (workspaceId?: string) => SavedSnapshot[];\n setTheme: (theme: 'light' | 'interstellar' | 'auto') => void;\n}\n\nexport type LabStore = StoreApi<LabStoreState & LabStoreActions> & {\n registerSerializers: (s: InstrumentSerializers) => void;\n};\n\nexport function createLabStore(options: CreateLabStoreOptions): LabStore {\n let serializers: InstrumentSerializers = {};\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const workspacesRaw = options.storage.read(labStorageKey(options.storageKey, 'workspaces'));\n let hydratedWorkspaces: WorkspaceRecord[] = [];\n if (workspacesRaw) {\n hydratedWorkspaces = deserializeWorkspaces(workspacesRaw, serializers);\n }\n\n const savesRaw = options.storage.read(labStorageKey(options.storageKey, 'saves'));\n let hydratedSnapshots: SavedSnapshot[] = [];\n if (savesRaw) {\n try {\n hydratedSnapshots = JSON.parse(savesRaw) as SavedSnapshot[];\n } catch {\n console.warn('[labkit] failed to parse saved snapshots, starting empty');\n hydratedSnapshots = [];\n }\n }\n\n const themeRaw = options.storage.read(labStorageKey(options.storageKey, 'theme'));\n let hydratedTheme: 'light' | 'interstellar' | 'auto';\n if (themeRaw === 'light' || themeRaw === 'interstellar' || themeRaw === 'auto') {\n hydratedTheme = themeRaw;\n } else {\n hydratedTheme = options.initialTheme ?? 'auto';\n }\n\n const store = createStore<LabStoreState & LabStoreActions>()((set, get) => ({\n workspaces: hydratedWorkspaces,\n savedSnapshots: hydratedSnapshots,\n theme: hydratedTheme,\n\n addWorkspace: (record) => {\n set((s) => ({\n workspaces: [...s.workspaces, { ...record, undoStack: emptyUndoStack() }],\n }));\n scheduleFlush();\n },\n\n removeWorkspace: (id) => {\n set((s) => ({ workspaces: s.workspaces.filter((w) => w.id !== id) }));\n scheduleFlush();\n },\n\n updateWorkspaceState: (id, next) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => {\n if (w.id !== id) return w;\n const nextState =\n typeof next === 'function' ? (next as (prev: unknown) => unknown)(w.state) : next;\n return { ...w, state: nextState };\n }),\n }));\n scheduleFlush();\n },\n\n updateWorkspaceConfig: (id, key, value) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => {\n if (w.id !== id) return w;\n return {\n ...w,\n config: { ...(w.config as Record<string, unknown>), [key as string]: value },\n };\n }),\n }));\n scheduleFlush();\n },\n\n updateWorkspaceView: (id, view) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => (w.id === id ? { ...w, view } : w)),\n }));\n scheduleFlush();\n },\n\n updateWorkspaceUndoStack: (id, next) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => {\n if (w.id !== id) return w;\n const undoStack =\n typeof next === 'function'\n ? (next as (prev: WorkspaceRecord['undoStack']) => WorkspaceRecord['undoStack'])(\n w.undoStack,\n )\n : next;\n return { ...w, undoStack };\n }),\n }));\n },\n\n setWorkspaceInstrument: (id, instrumentName) => {\n set((s) => ({\n workspaces: s.workspaces.map((w) => (w.id === id ? { ...w, instrumentName } : w)),\n }));\n scheduleFlush();\n },\n\n saveSnapshot: (workspaceId, name) => {\n const workspace = get().workspaces.find((w) => w.id === workspaceId);\n if (!workspace) return;\n const reg = serializers[workspace.instrumentName];\n const serializedState = reg?.serialize\n ? reg.serialize(workspace.state)\n : structuredClone(workspace.state);\n const clonedConfig = structuredClone(workspace.config);\n const lastAt = get().savedSnapshots.reduce((m, sn) => (sn.savedAt > m ? sn.savedAt : m), 0);\n const savedAt = Math.max(Date.now(), lastAt + 1);\n const snapshot: SavedSnapshot = {\n id: crypto.randomUUID(),\n name,\n workspaceId,\n instrumentName: workspace.instrumentName,\n config: clonedConfig,\n state: serializedState,\n savedAt,\n };\n set((s) => ({ savedSnapshots: [...s.savedSnapshots, snapshot] }));\n scheduleFlush();\n },\n\n loadSnapshot: (snapshotId, workspaceId) => {\n const snapshot = get().savedSnapshots.find((sn) => sn.id === snapshotId);\n if (!snapshot) return;\n const workspace = get().workspaces.find((w) => w.id === workspaceId);\n if (!workspace) return;\n if (snapshot.instrumentName !== workspace.instrumentName) {\n console.warn(\n `[labkit] loadSnapshot: instrument mismatch (snapshot=${snapshot.instrumentName}, workspace=${workspace.instrumentName}); refusing to load`,\n );\n return;\n }\n const reg = serializers[snapshot.instrumentName];\n const restoredState = reg?.deserialize ? reg.deserialize(snapshot.state) : snapshot.state;\n set((s) => ({\n workspaces: s.workspaces.map((w) =>\n w.id === workspaceId ? { ...w, state: restoredState, config: snapshot.config } : w,\n ),\n }));\n scheduleFlush();\n },\n\n deleteSnapshot: (snapshotId) => {\n set((s) => ({\n savedSnapshots: s.savedSnapshots.filter((sn) => sn.id !== snapshotId),\n }));\n scheduleFlush();\n },\n\n listSnapshots: (workspaceId) => {\n const all = get().savedSnapshots;\n const filtered = workspaceId ? all.filter((sn) => sn.workspaceId === workspaceId) : all;\n return [...filtered].sort((a, b) => b.savedAt - a.savedAt);\n },\n\n setTheme: (theme) => {\n set({ theme });\n scheduleFlush();\n },\n }));\n\n function scheduleFlush(): void {\n if (flushTimer) clearTimeout(flushTimer);\n flushTimer = setTimeout(() => {\n const s = store.getState();\n options.storage.write(\n labStorageKey(options.storageKey, 'workspaces'),\n serializeWorkspaces(s.workspaces, serializers),\n );\n options.storage.write(\n labStorageKey(options.storageKey, 'saves'),\n JSON.stringify(s.savedSnapshots),\n );\n options.storage.write(labStorageKey(options.storageKey, 'theme'), s.theme);\n flushTimer = null;\n }, 300);\n }\n\n return Object.assign(store, {\n registerSerializers(s: InstrumentSerializers) {\n serializers = s;\n },\n });\n}\n","import { useContext } from 'react';\nimport { useStore } from 'zustand/react';\nimport { LabStoreContext, WorkspaceIdContext } from './context';\nimport type { ExperimentStateHandle } from './types';\n\nexport function useExperimentState<TS = unknown, TC = unknown>(): ExperimentStateHandle<TS, TC> {\n const ctx = useContext(LabStoreContext);\n if (!ctx) throw new Error('[labkit] useExperimentState must be used inside <LabStoreProvider>');\n\n const workspaceId = useContext(WorkspaceIdContext);\n if (!workspaceId)\n throw new Error('[labkit] useExperimentState must be used inside <WorkspaceIdProvider>');\n\n const record = useStore(ctx.store, (s) => s.workspaces.find((w) => w.id === workspaceId));\n\n if (!record) throw new Error(`[labkit] No workspace found with id \"${workspaceId}\"`);\n\n const updateWorkspaceState = useStore(ctx.store, (s) => s.updateWorkspaceState);\n const updateWorkspaceConfig = useStore(ctx.store, (s) => s.updateWorkspaceConfig);\n\n return {\n state: record.state as TS,\n config: record.config as TC,\n setState: (next) =>\n updateWorkspaceState(workspaceId, next as Parameters<typeof updateWorkspaceState>[1]),\n setConfig: (key, value) => updateWorkspaceConfig(workspaceId, key as never, value as never),\n };\n}\n"]}
@@ -0,0 +1,201 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+
4
+ // src/controls/ControlPanel.tsx
5
+ function ControlPanel({
6
+ fields,
7
+ config,
8
+ setConfig,
9
+ className
10
+ }) {
11
+ return /* @__PURE__ */ jsx("div", { className: className ? `lk-control-panel ${className}` : "lk-control-panel", children: fields.map((field) => /* @__PURE__ */ jsx(ControlRow, { field, config, setConfig }, field.key)) });
12
+ }
13
+ function ControlRow({
14
+ field,
15
+ config,
16
+ setConfig
17
+ }) {
18
+ switch (field.type) {
19
+ case "slider":
20
+ return /* @__PURE__ */ jsx(SliderRow, { field, config, setConfig });
21
+ case "checkbox":
22
+ return /* @__PURE__ */ jsx(CheckboxRow, { field, config, setConfig });
23
+ case "select":
24
+ return /* @__PURE__ */ jsx(SelectRow, { field, config, setConfig });
25
+ case "number":
26
+ return /* @__PURE__ */ jsx(NumberRow, { field, config, setConfig });
27
+ case "text":
28
+ return /* @__PURE__ */ jsx(TextRow, { field, config, setConfig });
29
+ case "color":
30
+ return /* @__PURE__ */ jsx(ColorRow, { field, config, setConfig });
31
+ default:
32
+ return null;
33
+ }
34
+ }
35
+ function SliderRow({
36
+ field,
37
+ config,
38
+ setConfig
39
+ }) {
40
+ const value = config[field.key] ?? field.default;
41
+ return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
42
+ /* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
43
+ /* @__PURE__ */ jsxs("div", { className: "lk-control-slider-row", children: [
44
+ /* @__PURE__ */ jsx(
45
+ "input",
46
+ {
47
+ id: `lk-ctrl-${field.key}`,
48
+ className: "lk-control-input lk-control-input--range",
49
+ type: "range",
50
+ min: field.min,
51
+ max: field.max,
52
+ step: field.step ?? 1,
53
+ value,
54
+ onChange: (e) => setConfig(field.key, Number(e.target.value))
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx("span", { className: "lk-control-slider-value", children: value })
58
+ ] })
59
+ ] });
60
+ }
61
+ function CheckboxRow({
62
+ field,
63
+ config,
64
+ setConfig
65
+ }) {
66
+ const value = config[field.key] ?? field.default;
67
+ return /* @__PURE__ */ jsxs("div", { className: "lk-control-row lk-control-row--inline", children: [
68
+ /* @__PURE__ */ jsx(
69
+ "input",
70
+ {
71
+ id: `lk-ctrl-${field.key}`,
72
+ className: "lk-control-input lk-control-input--checkbox",
73
+ type: "checkbox",
74
+ checked: value,
75
+ onChange: (e) => setConfig(field.key, e.target.checked)
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label })
79
+ ] });
80
+ }
81
+ function SelectRow({
82
+ field,
83
+ config,
84
+ setConfig
85
+ }) {
86
+ const value = config[field.key] ?? field.default;
87
+ return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
88
+ /* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
89
+ /* @__PURE__ */ jsx(
90
+ "select",
91
+ {
92
+ id: `lk-ctrl-${field.key}`,
93
+ className: "lk-control-input",
94
+ value,
95
+ onChange: (e) => setConfig(field.key, e.target.value),
96
+ children: field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
97
+ }
98
+ )
99
+ ] });
100
+ }
101
+ function NumberRow({
102
+ field,
103
+ config,
104
+ setConfig
105
+ }) {
106
+ const value = config[field.key] ?? field.default;
107
+ return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
108
+ /* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
109
+ /* @__PURE__ */ jsx(
110
+ "input",
111
+ {
112
+ id: `lk-ctrl-${field.key}`,
113
+ className: "lk-control-input",
114
+ type: "number",
115
+ value,
116
+ min: field.min,
117
+ max: field.max,
118
+ step: field.step,
119
+ onChange: (e) => setConfig(field.key, Number(e.target.value)),
120
+ onBlur: (e) => {
121
+ const n = Number(e.target.value);
122
+ if (!Number.isFinite(n)) return;
123
+ let clamped = n;
124
+ if (field.min !== void 0 && clamped < field.min) clamped = field.min;
125
+ if (field.max !== void 0 && clamped > field.max) clamped = field.max;
126
+ if (clamped !== n) setConfig(field.key, clamped);
127
+ }
128
+ }
129
+ )
130
+ ] });
131
+ }
132
+ function TextRow({
133
+ field,
134
+ config,
135
+ setConfig
136
+ }) {
137
+ const debounceMs = field.debounceMs ?? 150;
138
+ const initial = config[field.key] ?? field.default;
139
+ const [local, setLocal] = useState(initial);
140
+ const timer = useRef(null);
141
+ const lastExternal = useRef(initial);
142
+ useEffect(() => {
143
+ const ext = config[field.key] ?? field.default;
144
+ if (ext !== lastExternal.current) {
145
+ lastExternal.current = ext;
146
+ setLocal(ext);
147
+ }
148
+ }, [config, field.key, field.default]);
149
+ return /* @__PURE__ */ jsxs("div", { className: "lk-control-row", children: [
150
+ /* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
151
+ /* @__PURE__ */ jsx(
152
+ "input",
153
+ {
154
+ id: `lk-ctrl-${field.key}`,
155
+ className: "lk-control-input",
156
+ type: "text",
157
+ value: local,
158
+ placeholder: field.placeholder,
159
+ maxLength: field.maxLength,
160
+ onChange: (e) => {
161
+ const next = e.target.value;
162
+ setLocal(next);
163
+ if (timer.current) clearTimeout(timer.current);
164
+ if (debounceMs === 0) {
165
+ lastExternal.current = next;
166
+ setConfig(field.key, next);
167
+ } else {
168
+ timer.current = setTimeout(() => {
169
+ lastExternal.current = next;
170
+ setConfig(field.key, next);
171
+ }, debounceMs);
172
+ }
173
+ }
174
+ }
175
+ )
176
+ ] });
177
+ }
178
+ function ColorRow({
179
+ field,
180
+ config,
181
+ setConfig
182
+ }) {
183
+ const value = config[field.key] ?? field.default;
184
+ return /* @__PURE__ */ jsxs("div", { className: "lk-control-row lk-control-row--inline", children: [
185
+ /* @__PURE__ */ jsx("label", { className: "lk-control-label", htmlFor: `lk-ctrl-${field.key}`, children: field.label }),
186
+ /* @__PURE__ */ jsx(
187
+ "input",
188
+ {
189
+ id: `lk-ctrl-${field.key}`,
190
+ className: "lk-control-input lk-control-input--color",
191
+ type: "color",
192
+ value,
193
+ onChange: (e) => setConfig(field.key, e.target.value)
194
+ }
195
+ )
196
+ ] });
197
+ }
198
+
199
+ export { ControlPanel };
200
+ //# sourceMappingURL=chunk-VLAHRJOC.js.map
201
+ //# sourceMappingURL=chunk-VLAHRJOC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/controls/ControlPanel.tsx"],"names":[],"mappings":"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","file":"chunk-VLAHRJOC.js","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type {\n CheckboxField,\n ColorField,\n ConfigField,\n NumberField,\n SelectField,\n SliderField,\n TextField,\n} from './types';\n\nexport interface ControlPanelProps<TC extends Record<string, unknown>> {\n fields: ConfigField[];\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n className?: string;\n}\n\nexport function ControlPanel<TC extends Record<string, unknown>>({\n fields,\n config,\n setConfig,\n className,\n}: ControlPanelProps<TC>) {\n return (\n <div className={className ? `lk-control-panel ${className}` : 'lk-control-panel'}>\n {fields.map((field) => (\n <ControlRow key={field.key} field={field} config={config} setConfig={setConfig} />\n ))}\n </div>\n );\n}\n\ninterface ControlRowProps<TC extends Record<string, unknown>> {\n field: ConfigField;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n}\n\nfunction ControlRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: ControlRowProps<TC>) {\n switch (field.type) {\n case 'slider':\n return <SliderRow field={field} config={config} setConfig={setConfig} />;\n case 'checkbox':\n return <CheckboxRow field={field} config={config} setConfig={setConfig} />;\n case 'select':\n return <SelectRow field={field} config={config} setConfig={setConfig} />;\n case 'number':\n return <NumberRow field={field} config={config} setConfig={setConfig} />;\n case 'text':\n return <TextRow field={field} config={config} setConfig={setConfig} />;\n case 'color':\n return <ColorRow field={field} config={config} setConfig={setConfig} />;\n default:\n return null;\n }\n}\n\ntype RowProps<F extends ConfigField, TC extends Record<string, unknown>> = {\n field: F;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n};\n\nfunction SliderRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SliderField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <div className=\"lk-control-slider-row\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--range\"\n type=\"range\"\n min={field.min}\n max={field.max}\n step={field.step ?? 1}\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n />\n <span className=\"lk-control-slider-value\">{value}</span>\n </div>\n </div>\n );\n}\n\nfunction CheckboxRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<CheckboxField, TC>) {\n const value = (config[field.key] as boolean | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--checkbox\"\n type=\"checkbox\"\n checked={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.checked)}\n />\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n </div>\n );\n}\n\nfunction SelectRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SelectField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <select\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n >\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\nfunction NumberRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<NumberField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"number\"\n value={value}\n min={field.min}\n max={field.max}\n step={field.step}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n onBlur={(e) => {\n const n = Number(e.target.value);\n if (!Number.isFinite(n)) return;\n let clamped = n;\n if (field.min !== undefined && clamped < field.min) clamped = field.min;\n if (field.max !== undefined && clamped > field.max) clamped = field.max;\n if (clamped !== n) setConfig(field.key as keyof TC, clamped);\n }}\n />\n </div>\n );\n}\n\nfunction TextRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<TextField, TC>) {\n const debounceMs = field.debounceMs ?? 150;\n const initial = (config[field.key] as string | undefined) ?? field.default;\n const [local, setLocal] = useState(initial);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastExternal = useRef(initial);\n\n useEffect(() => {\n const ext = (config[field.key] as string | undefined) ?? field.default;\n if (ext !== lastExternal.current) {\n lastExternal.current = ext;\n setLocal(ext);\n }\n }, [config, field.key, field.default]);\n\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"text\"\n value={local}\n placeholder={field.placeholder}\n maxLength={field.maxLength}\n onChange={(e) => {\n const next = e.target.value;\n setLocal(next);\n if (timer.current) clearTimeout(timer.current);\n if (debounceMs === 0) {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n } else {\n timer.current = setTimeout(() => {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n }, debounceMs);\n }\n }}\n />\n </div>\n );\n}\n\nfunction ColorRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<ColorField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--color\"\n type=\"color\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n />\n </div>\n );\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { C as ConfigField } from '../_dts/types-Si4Fw-1F.js';
3
+ export { a as CheckboxField, b as ColorField, d as ConfigFieldType, N as NumberField, S as SelectField, e as SelectOption, f as SliderField, T as TextField } from '../_dts/types-Si4Fw-1F.js';
4
+
5
+ interface ControlPanelProps<TC extends Record<string, unknown>> {
6
+ fields: ConfigField[];
7
+ config: TC;
8
+ setConfig: (key: keyof TC, value: unknown) => void;
9
+ className?: string;
10
+ }
11
+ declare function ControlPanel<TC extends Record<string, unknown>>({ fields, config, setConfig, className, }: ControlPanelProps<TC>): react_jsx_runtime.JSX.Element;
12
+
13
+ export { ConfigField, ControlPanel };
@@ -0,0 +1,3 @@
1
+ export { ControlPanel } from '../chunk-VLAHRJOC.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,3 @@
1
+ export { D as DragDropCapability, b as DragFeedback, P as PaletteItem } from '../_dts/index-C6Yze7sQ.js';
2
+ import 'react';
3
+ import '../_dts/types-Si4Fw-1F.js';