@toriistudio/v0-playground 0.5.3 → 0.6.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.
- package/README.md +1 -20
- package/dist/index.d.mts +103 -30
- package/dist/index.d.ts +103 -30
- package/dist/index.js +1132 -342
- package/dist/index.mjs +1132 -338
- package/dist/preset.d.mts +5 -0
- package/dist/preset.d.ts +5 -0
- package/package.json +4 -33
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/Playground/Playground.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect5, useMemo as useMemo4, useState as useState5 } from "react";
|
|
3
3
|
import { Check as Check3, Copy as Copy2 } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
// src/context/ResizableLayout.tsx
|
|
@@ -116,7 +116,8 @@ import {
|
|
|
116
116
|
useState as useState2,
|
|
117
117
|
useMemo,
|
|
118
118
|
useEffect as useEffect2,
|
|
119
|
-
useCallback
|
|
119
|
+
useCallback,
|
|
120
|
+
useRef as useRef2
|
|
120
121
|
} from "react";
|
|
121
122
|
|
|
122
123
|
// src/utils/getUrlParams.ts
|
|
@@ -130,6 +131,274 @@ var getUrlParams = () => {
|
|
|
130
131
|
return entries;
|
|
131
132
|
};
|
|
132
133
|
|
|
134
|
+
// src/constants/urlParams.ts
|
|
135
|
+
var NO_CONTROLS_PARAM = "nocontrols";
|
|
136
|
+
var PRESENTATION_PARAM = "presentation";
|
|
137
|
+
var CONTROLS_ONLY_PARAM = "controlsonly";
|
|
138
|
+
|
|
139
|
+
// src/utils/getControlsChannelName.ts
|
|
140
|
+
var EXCLUDED_KEYS = /* @__PURE__ */ new Set([
|
|
141
|
+
NO_CONTROLS_PARAM,
|
|
142
|
+
PRESENTATION_PARAM,
|
|
143
|
+
CONTROLS_ONLY_PARAM
|
|
144
|
+
]);
|
|
145
|
+
var getControlsChannelName = () => {
|
|
146
|
+
if (typeof window === "undefined") return null;
|
|
147
|
+
const params = new URLSearchParams(window.location.search);
|
|
148
|
+
for (const key of EXCLUDED_KEYS) {
|
|
149
|
+
params.delete(key);
|
|
150
|
+
}
|
|
151
|
+
const query = params.toString();
|
|
152
|
+
const base = window.location.pathname || "/";
|
|
153
|
+
return `v0-controls:${base}${query ? `?${query}` : ""}`;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/lib/advancedPalette.ts
|
|
157
|
+
var CHANNEL_KEYS = ["r", "g", "b"];
|
|
158
|
+
var DEFAULT_CHANNEL_LABELS = {
|
|
159
|
+
r: "Red",
|
|
160
|
+
g: "Green",
|
|
161
|
+
b: "Blue"
|
|
162
|
+
};
|
|
163
|
+
var DEFAULT_SECTIONS = [
|
|
164
|
+
{ key: "A", label: "Vector A", helper: "Base offset" },
|
|
165
|
+
{ key: "B", label: "Vector B", helper: "Amplitude" },
|
|
166
|
+
{ key: "C", label: "Vector C", helper: "Frequency" },
|
|
167
|
+
{ key: "D", label: "Vector D", helper: "Phase shift" }
|
|
168
|
+
];
|
|
169
|
+
var DEFAULT_RANGES = {
|
|
170
|
+
A: { min: 0, max: 1, step: 0.01 },
|
|
171
|
+
B: { min: -1, max: 1, step: 0.01 },
|
|
172
|
+
C: { min: 0, max: 2, step: 0.01 },
|
|
173
|
+
D: { min: 0, max: 1, step: 0.01 }
|
|
174
|
+
};
|
|
175
|
+
var DEFAULT_HIDDEN_KEY_PREFIX = "palette";
|
|
176
|
+
var DEFAULT_GRADIENT_STEPS = 12;
|
|
177
|
+
var DEFAULT_HEX_PALETTE = {
|
|
178
|
+
A: { r: 0.5, g: 0.5, b: 0.5 },
|
|
179
|
+
B: { r: 0.5, g: 0.5, b: 0.5 },
|
|
180
|
+
C: { r: 1, g: 1, b: 1 },
|
|
181
|
+
D: { r: 0, g: 0.1, b: 0.2 }
|
|
182
|
+
};
|
|
183
|
+
var createPaletteControlKey = (prefix, section, channel) => `${prefix}${section}${channel}`;
|
|
184
|
+
var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
185
|
+
var clamp01 = (value) => clamp(value, 0, 1);
|
|
186
|
+
var toNumberOr = (value, fallback) => {
|
|
187
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
188
|
+
if (typeof value === "string") {
|
|
189
|
+
const parsed = parseFloat(value);
|
|
190
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
191
|
+
}
|
|
192
|
+
return fallback;
|
|
193
|
+
};
|
|
194
|
+
var paletteColorAt = (palette, t) => {
|
|
195
|
+
const twoPi = Math.PI * 2;
|
|
196
|
+
const computeChannel = (a, b, c, d) => {
|
|
197
|
+
const value = a + b * Math.cos(twoPi * (c * t + d));
|
|
198
|
+
return clamp(value, 0, 1);
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
r: computeChannel(
|
|
202
|
+
palette.A?.r ?? 0,
|
|
203
|
+
palette.B?.r ?? 0,
|
|
204
|
+
palette.C?.r ?? 0,
|
|
205
|
+
palette.D?.r ?? 0
|
|
206
|
+
),
|
|
207
|
+
g: computeChannel(
|
|
208
|
+
palette.A?.g ?? 0,
|
|
209
|
+
palette.B?.g ?? 0,
|
|
210
|
+
palette.C?.g ?? 0,
|
|
211
|
+
palette.D?.g ?? 0
|
|
212
|
+
),
|
|
213
|
+
b: computeChannel(
|
|
214
|
+
palette.A?.b ?? 0,
|
|
215
|
+
palette.B?.b ?? 0,
|
|
216
|
+
palette.C?.b ?? 0,
|
|
217
|
+
palette.D?.b ?? 0
|
|
218
|
+
)
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
var toRgba = ({ r, g, b }, alpha = 0.5) => `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(
|
|
222
|
+
b * 255
|
|
223
|
+
)}, ${alpha})`;
|
|
224
|
+
var computePaletteGradient = (palette, steps = DEFAULT_GRADIENT_STEPS) => {
|
|
225
|
+
const stops = Array.from({ length: steps }, (_, index) => {
|
|
226
|
+
const t = index / (steps - 1);
|
|
227
|
+
const color = paletteColorAt(palette, t);
|
|
228
|
+
const stop = (t * 100).toFixed(1);
|
|
229
|
+
return `${toRgba(color)} ${stop}%`;
|
|
230
|
+
});
|
|
231
|
+
return `linear-gradient(to right, ${stops.join(", ")})`;
|
|
232
|
+
};
|
|
233
|
+
var createPaletteSignature = (palette) => Object.entries(palette).sort(([aKey], [bKey]) => aKey.localeCompare(bKey)).flatMap(
|
|
234
|
+
([, channels]) => CHANNEL_KEYS.map((channel) => (channels?.[channel] ?? 0).toFixed(3))
|
|
235
|
+
).join("-");
|
|
236
|
+
var isAdvancedPaletteValue = (value) => Boolean(
|
|
237
|
+
value && typeof value === "object" && CHANNEL_KEYS.every((channel) => {
|
|
238
|
+
const channelValue = value[channel];
|
|
239
|
+
return typeof channelValue === "number" && Number.isFinite(channelValue);
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
var isAdvancedPalette = (value) => Boolean(
|
|
243
|
+
value && typeof value === "object" && Object.values(value).every(
|
|
244
|
+
(entry) => isAdvancedPaletteValue(entry) || typeof entry === "object"
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
var normalizePaletteValue = (source) => {
|
|
248
|
+
if (typeof source === "string") {
|
|
249
|
+
return hexToPaletteValue(source);
|
|
250
|
+
}
|
|
251
|
+
const channelSource = source ?? {};
|
|
252
|
+
const toChannel = (channel) => clamp01(
|
|
253
|
+
toNumberOr(
|
|
254
|
+
channelSource[channel],
|
|
255
|
+
0
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
return {
|
|
259
|
+
r: toChannel("r"),
|
|
260
|
+
g: toChannel("g"),
|
|
261
|
+
b: toChannel("b")
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
var createPaletteFromRecord = (record) => Object.entries(record).reduce((acc, [key, value]) => {
|
|
265
|
+
acc[key] = normalizePaletteValue(value);
|
|
266
|
+
return acc;
|
|
267
|
+
}, {});
|
|
268
|
+
var clonePalette = (palette) => Object.fromEntries(
|
|
269
|
+
Object.entries(palette).map(([sectionKey, channels]) => [
|
|
270
|
+
sectionKey,
|
|
271
|
+
{ ...channels }
|
|
272
|
+
])
|
|
273
|
+
);
|
|
274
|
+
var hexComponentToNormalized = (component) => clamp01(parseInt(component, 16) / 255 || 0);
|
|
275
|
+
var normalizedChannelToHex = (value) => Math.round(clamp01(value) * 255).toString(16).padStart(2, "0");
|
|
276
|
+
var sanitizeHex = (hex) => {
|
|
277
|
+
let sanitized = hex.trim();
|
|
278
|
+
if (sanitized.startsWith("#")) {
|
|
279
|
+
sanitized = sanitized.slice(1);
|
|
280
|
+
}
|
|
281
|
+
if (sanitized.length === 3) {
|
|
282
|
+
sanitized = sanitized.split("").map((char) => char + char).join("");
|
|
283
|
+
}
|
|
284
|
+
return sanitized.length === 6 ? sanitized : null;
|
|
285
|
+
};
|
|
286
|
+
var hexToPaletteValue = (hex) => {
|
|
287
|
+
const sanitized = sanitizeHex(hex);
|
|
288
|
+
if (!sanitized) {
|
|
289
|
+
return { r: 0, g: 0, b: 0 };
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
r: hexComponentToNormalized(sanitized.slice(0, 2)),
|
|
293
|
+
g: hexComponentToNormalized(sanitized.slice(2, 4)),
|
|
294
|
+
b: hexComponentToNormalized(sanitized.slice(4, 6))
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
var paletteValueToHex = (value) => `#${normalizedChannelToHex(value.r)}${normalizedChannelToHex(
|
|
298
|
+
value.g
|
|
299
|
+
)}${normalizedChannelToHex(value.b)}`;
|
|
300
|
+
var createAdvancedPalette = (source = DEFAULT_HEX_PALETTE, options) => {
|
|
301
|
+
if (Array.isArray(source)) {
|
|
302
|
+
const order = options?.sectionOrder ?? DEFAULT_SECTIONS.map((section) => section.key);
|
|
303
|
+
const record = {};
|
|
304
|
+
source.forEach((value, index) => {
|
|
305
|
+
const preferredKey = order[index];
|
|
306
|
+
const fallbackKey = `Color${index + 1}`;
|
|
307
|
+
const key = preferredKey && !(preferredKey in record) ? preferredKey : fallbackKey;
|
|
308
|
+
record[key] = value;
|
|
309
|
+
});
|
|
310
|
+
return createPaletteFromRecord(record);
|
|
311
|
+
}
|
|
312
|
+
if (isAdvancedPalette(source)) {
|
|
313
|
+
return clonePalette(
|
|
314
|
+
Object.entries(source ?? {}).reduce(
|
|
315
|
+
(acc, [key, value]) => {
|
|
316
|
+
acc[key] = normalizePaletteValue(value);
|
|
317
|
+
return acc;
|
|
318
|
+
},
|
|
319
|
+
{}
|
|
320
|
+
)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (source && typeof source === "object") {
|
|
324
|
+
return createPaletteFromRecord(source);
|
|
325
|
+
}
|
|
326
|
+
return createPaletteFromRecord(DEFAULT_HEX_PALETTE);
|
|
327
|
+
};
|
|
328
|
+
var DEFAULT_ADVANCED_PALETTE = createAdvancedPalette(
|
|
329
|
+
DEFAULT_HEX_PALETTE
|
|
330
|
+
);
|
|
331
|
+
var advancedPaletteToHexColors = (palette, options) => {
|
|
332
|
+
const fallbackPalette = options?.fallbackPalette ?? DEFAULT_ADVANCED_PALETTE;
|
|
333
|
+
const orderedKeys = options?.sectionOrder ?? (Object.keys(palette).length > 0 ? Object.keys(palette) : Object.keys(fallbackPalette));
|
|
334
|
+
const uniqueKeys = Array.from(new Set(orderedKeys));
|
|
335
|
+
if (uniqueKeys.length === 0) {
|
|
336
|
+
uniqueKeys.push(...Object.keys(DEFAULT_ADVANCED_PALETTE));
|
|
337
|
+
}
|
|
338
|
+
const defaultColor = options?.defaultColor ?? "#000000";
|
|
339
|
+
return uniqueKeys.map((key) => {
|
|
340
|
+
const paletteValue = palette[key] ?? fallbackPalette[key];
|
|
341
|
+
if (!paletteValue) return defaultColor;
|
|
342
|
+
return paletteValueToHex(paletteValue);
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
var createDefaultSectionsFromPalette = (palette) => {
|
|
346
|
+
const sectionKeys = Object.keys(palette);
|
|
347
|
+
if (sectionKeys.length === 0) return DEFAULT_SECTIONS;
|
|
348
|
+
return sectionKeys.map((key, index) => ({
|
|
349
|
+
key,
|
|
350
|
+
label: `Vector ${key}`,
|
|
351
|
+
helper: DEFAULT_SECTIONS[index]?.helper ?? "Palette parameter"
|
|
352
|
+
}));
|
|
353
|
+
};
|
|
354
|
+
var resolveAdvancedPaletteConfig = (config) => {
|
|
355
|
+
const defaultPalette = createAdvancedPalette(config.defaultPalette);
|
|
356
|
+
const sections = config.sections ?? createDefaultSectionsFromPalette(defaultPalette);
|
|
357
|
+
const ranges = {};
|
|
358
|
+
sections.forEach((section) => {
|
|
359
|
+
ranges[section.key] = config.ranges?.[section.key] ?? DEFAULT_RANGES[section.key] ?? {
|
|
360
|
+
min: 0,
|
|
361
|
+
max: 1,
|
|
362
|
+
step: 0.01
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
const channelLabels = {
|
|
366
|
+
...DEFAULT_CHANNEL_LABELS,
|
|
367
|
+
...config.channelLabels ?? {}
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
...config,
|
|
371
|
+
defaultPalette,
|
|
372
|
+
sections,
|
|
373
|
+
ranges,
|
|
374
|
+
channelLabels,
|
|
375
|
+
hiddenKeyPrefix: config.hiddenKeyPrefix ?? DEFAULT_HIDDEN_KEY_PREFIX,
|
|
376
|
+
controlKey: config.controlKey ?? "advancedPaletteControl",
|
|
377
|
+
gradientSteps: config.gradientSteps ?? DEFAULT_GRADIENT_STEPS
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
var createAdvancedPaletteSchemaEntries = (schema, resolvedConfig) => {
|
|
381
|
+
const { sections, hiddenKeyPrefix, defaultPalette } = resolvedConfig;
|
|
382
|
+
const updatedSchema = { ...schema };
|
|
383
|
+
sections.forEach((section) => {
|
|
384
|
+
CHANNEL_KEYS.forEach((channel) => {
|
|
385
|
+
const key = createPaletteControlKey(
|
|
386
|
+
hiddenKeyPrefix,
|
|
387
|
+
section.key,
|
|
388
|
+
channel
|
|
389
|
+
);
|
|
390
|
+
if (!(key in updatedSchema)) {
|
|
391
|
+
updatedSchema[key] = {
|
|
392
|
+
type: "number",
|
|
393
|
+
value: defaultPalette?.[section.key]?.[channel] ?? DEFAULT_RANGES[section.key]?.min ?? 0,
|
|
394
|
+
hidden: true
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
return updatedSchema;
|
|
400
|
+
};
|
|
401
|
+
|
|
133
402
|
// src/context/ControlsContext.tsx
|
|
134
403
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
135
404
|
var ControlsContext = createContext2(null);
|
|
@@ -145,6 +414,18 @@ var ControlsProvider = ({ children }) => {
|
|
|
145
414
|
showCopyButton: true
|
|
146
415
|
});
|
|
147
416
|
const [componentName, setComponentName] = useState2();
|
|
417
|
+
const [channelName, setChannelName] = useState2(null);
|
|
418
|
+
const channelRef = useRef2(null);
|
|
419
|
+
const instanceIdRef = useRef2(null);
|
|
420
|
+
const skipBroadcastRef = useRef2(false);
|
|
421
|
+
const latestValuesRef = useRef2(values);
|
|
422
|
+
useEffect2(() => {
|
|
423
|
+
latestValuesRef.current = values;
|
|
424
|
+
}, [values]);
|
|
425
|
+
useEffect2(() => {
|
|
426
|
+
if (typeof window === "undefined") return;
|
|
427
|
+
setChannelName(getControlsChannelName());
|
|
428
|
+
}, []);
|
|
148
429
|
const setValue = (key, value) => {
|
|
149
430
|
setValues((prev) => ({ ...prev, [key]: value }));
|
|
150
431
|
};
|
|
@@ -153,9 +434,16 @@ var ControlsProvider = ({ children }) => {
|
|
|
153
434
|
setComponentName(opts.componentName);
|
|
154
435
|
}
|
|
155
436
|
if (opts?.config) {
|
|
437
|
+
const { addAdvancedPaletteControl, ...otherConfig } = opts.config;
|
|
156
438
|
setConfig((prev) => ({
|
|
157
439
|
...prev,
|
|
158
|
-
...
|
|
440
|
+
...otherConfig,
|
|
441
|
+
...Object.prototype.hasOwnProperty.call(
|
|
442
|
+
opts.config,
|
|
443
|
+
"addAdvancedPaletteControl"
|
|
444
|
+
) ? {
|
|
445
|
+
addAdvancedPaletteControl: addAdvancedPaletteControl ? resolveAdvancedPaletteConfig(addAdvancedPaletteControl) : void 0
|
|
446
|
+
} : {}
|
|
159
447
|
}));
|
|
160
448
|
}
|
|
161
449
|
setSchema((prevSchema) => ({ ...prevSchema, ...newSchema }));
|
|
@@ -172,6 +460,66 @@ var ControlsProvider = ({ children }) => {
|
|
|
172
460
|
return updated;
|
|
173
461
|
});
|
|
174
462
|
};
|
|
463
|
+
useEffect2(() => {
|
|
464
|
+
if (!channelName) return;
|
|
465
|
+
if (typeof window === "undefined") return;
|
|
466
|
+
if (typeof window.BroadcastChannel === "undefined") return;
|
|
467
|
+
const instanceId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
|
|
468
|
+
instanceIdRef.current = instanceId;
|
|
469
|
+
const channel = new BroadcastChannel(channelName);
|
|
470
|
+
channelRef.current = channel;
|
|
471
|
+
const sendValues = () => {
|
|
472
|
+
if (!instanceIdRef.current) return;
|
|
473
|
+
channel.postMessage({
|
|
474
|
+
type: "controls-sync-values",
|
|
475
|
+
source: instanceIdRef.current,
|
|
476
|
+
values: latestValuesRef.current
|
|
477
|
+
});
|
|
478
|
+
};
|
|
479
|
+
const handleMessage = (event) => {
|
|
480
|
+
const data = event.data;
|
|
481
|
+
if (!data || data.source === instanceIdRef.current) return;
|
|
482
|
+
if (data.type === "controls-sync-request") {
|
|
483
|
+
sendValues();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (data.type === "controls-sync-values" && data.values) {
|
|
487
|
+
const incoming = data.values;
|
|
488
|
+
setValues((prev) => {
|
|
489
|
+
const prevKeys = Object.keys(prev);
|
|
490
|
+
const incomingKeys = Object.keys(incoming);
|
|
491
|
+
const sameLength = prevKeys.length === incomingKeys.length;
|
|
492
|
+
const sameValues = sameLength && incomingKeys.every((key) => prev[key] === incoming[key]);
|
|
493
|
+
if (sameValues) return prev;
|
|
494
|
+
skipBroadcastRef.current = true;
|
|
495
|
+
return { ...incoming };
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
channel.addEventListener("message", handleMessage);
|
|
500
|
+
channel.postMessage({
|
|
501
|
+
type: "controls-sync-request",
|
|
502
|
+
source: instanceId
|
|
503
|
+
});
|
|
504
|
+
return () => {
|
|
505
|
+
channel.removeEventListener("message", handleMessage);
|
|
506
|
+
channel.close();
|
|
507
|
+
channelRef.current = null;
|
|
508
|
+
instanceIdRef.current = null;
|
|
509
|
+
};
|
|
510
|
+
}, [channelName]);
|
|
511
|
+
useEffect2(() => {
|
|
512
|
+
if (!channelRef.current || !instanceIdRef.current) return;
|
|
513
|
+
if (skipBroadcastRef.current) {
|
|
514
|
+
skipBroadcastRef.current = false;
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
channelRef.current.postMessage({
|
|
518
|
+
type: "controls-sync-values",
|
|
519
|
+
source: instanceIdRef.current,
|
|
520
|
+
values
|
|
521
|
+
});
|
|
522
|
+
}, [values]);
|
|
175
523
|
const contextValue = useMemo(
|
|
176
524
|
() => ({
|
|
177
525
|
schema,
|
|
@@ -188,28 +536,41 @@ var ControlsProvider = ({ children }) => {
|
|
|
188
536
|
var useControls = (schema, options) => {
|
|
189
537
|
const ctx = useContext2(ControlsContext);
|
|
190
538
|
if (!ctx) throw new Error("useControls must be used within ControlsProvider");
|
|
539
|
+
const lastAdvancedPaletteSignature = useRef2(null);
|
|
191
540
|
const urlParams = getUrlParams();
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
key
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
541
|
+
const resolvedAdvancedConfig = options?.config?.addAdvancedPaletteControl ? resolveAdvancedPaletteConfig(options.config.addAdvancedPaletteControl) : void 0;
|
|
542
|
+
const schemaWithAdvanced = useMemo(() => {
|
|
543
|
+
const baseSchema = { ...schema };
|
|
544
|
+
if (!resolvedAdvancedConfig) return baseSchema;
|
|
545
|
+
return createAdvancedPaletteSchemaEntries(
|
|
546
|
+
baseSchema,
|
|
547
|
+
resolvedAdvancedConfig
|
|
548
|
+
);
|
|
549
|
+
}, [schema, resolvedAdvancedConfig]);
|
|
550
|
+
const urlParamsKey = useMemo(() => JSON.stringify(urlParams), [urlParams]);
|
|
551
|
+
const mergedSchema = useMemo(() => {
|
|
552
|
+
return Object.fromEntries(
|
|
553
|
+
Object.entries(schemaWithAdvanced).map(([key, control]) => {
|
|
554
|
+
const urlValue = urlParams[key];
|
|
555
|
+
if (!urlValue || !("value" in control)) return [key, control];
|
|
556
|
+
const defaultValue = control.value;
|
|
557
|
+
let parsed = urlValue;
|
|
558
|
+
if (typeof defaultValue === "number") {
|
|
559
|
+
parsed = parseFloat(urlValue);
|
|
560
|
+
if (isNaN(parsed)) parsed = defaultValue;
|
|
561
|
+
} else if (typeof defaultValue === "boolean") {
|
|
562
|
+
parsed = urlValue === "true";
|
|
209
563
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
564
|
+
return [
|
|
565
|
+
key,
|
|
566
|
+
{
|
|
567
|
+
...control,
|
|
568
|
+
value: parsed
|
|
569
|
+
}
|
|
570
|
+
];
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
}, [schemaWithAdvanced, urlParams, urlParamsKey]);
|
|
213
574
|
useEffect2(() => {
|
|
214
575
|
ctx.registerSchema(mergedSchema, options);
|
|
215
576
|
}, [JSON.stringify(mergedSchema), JSON.stringify(options)]);
|
|
@@ -220,8 +581,35 @@ var useControls = (schema, options) => {
|
|
|
220
581
|
}
|
|
221
582
|
}
|
|
222
583
|
}, [JSON.stringify(mergedSchema), JSON.stringify(ctx.values)]);
|
|
584
|
+
useEffect2(() => {
|
|
585
|
+
if (!resolvedAdvancedConfig?.onPaletteChange) return;
|
|
586
|
+
const palette = resolvedAdvancedConfig.sections.reduce(
|
|
587
|
+
(acc, section) => {
|
|
588
|
+
const channels = CHANNEL_KEYS.reduce(
|
|
589
|
+
(channelAcc, channel) => {
|
|
590
|
+
const key = createPaletteControlKey(
|
|
591
|
+
resolvedAdvancedConfig.hiddenKeyPrefix,
|
|
592
|
+
section.key,
|
|
593
|
+
channel
|
|
594
|
+
);
|
|
595
|
+
const fallback = resolvedAdvancedConfig.defaultPalette?.[section.key]?.[channel] ?? 0;
|
|
596
|
+
channelAcc[channel] = toNumberOr(ctx.values[key], fallback);
|
|
597
|
+
return channelAcc;
|
|
598
|
+
},
|
|
599
|
+
{}
|
|
600
|
+
);
|
|
601
|
+
acc[section.key] = channels;
|
|
602
|
+
return acc;
|
|
603
|
+
},
|
|
604
|
+
{}
|
|
605
|
+
);
|
|
606
|
+
const signature = createPaletteSignature(palette);
|
|
607
|
+
if (lastAdvancedPaletteSignature.current === signature) return;
|
|
608
|
+
lastAdvancedPaletteSignature.current = signature;
|
|
609
|
+
resolvedAdvancedConfig.onPaletteChange(clonePalette(palette));
|
|
610
|
+
}, [ctx.values, resolvedAdvancedConfig]);
|
|
223
611
|
const typedValues = ctx.values;
|
|
224
|
-
const
|
|
612
|
+
const jsx14 = useCallback(() => {
|
|
225
613
|
if (!options?.componentName) return "";
|
|
226
614
|
const props = Object.entries(typedValues).map(([key, val]) => {
|
|
227
615
|
if (typeof val === "string") return `${key}="${val}"`;
|
|
@@ -235,14 +623,20 @@ var useControls = (schema, options) => {
|
|
|
235
623
|
controls: ctx.values,
|
|
236
624
|
schema: ctx.schema,
|
|
237
625
|
setValue: ctx.setValue,
|
|
238
|
-
jsx:
|
|
626
|
+
jsx: jsx14
|
|
239
627
|
};
|
|
240
628
|
};
|
|
241
629
|
var useUrlSyncedControls = useControls;
|
|
242
630
|
|
|
243
631
|
// src/components/ControlPanel/ControlPanel.tsx
|
|
244
|
-
import { useState as useState4, useMemo as
|
|
245
|
-
import {
|
|
632
|
+
import { useState as useState4, useMemo as useMemo3, useCallback as useCallback3 } from "react";
|
|
633
|
+
import {
|
|
634
|
+
Check as Check2,
|
|
635
|
+
Copy,
|
|
636
|
+
SquareArrowOutUpRight,
|
|
637
|
+
ChevronDown as ChevronDown2,
|
|
638
|
+
Presentation
|
|
639
|
+
} from "lucide-react";
|
|
246
640
|
|
|
247
641
|
// src/hooks/usePreviewUrl.ts
|
|
248
642
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
@@ -251,7 +645,7 @@ var usePreviewUrl = (values, basePath = "") => {
|
|
|
251
645
|
useEffect3(() => {
|
|
252
646
|
if (typeof window === "undefined") return;
|
|
253
647
|
const params = new URLSearchParams();
|
|
254
|
-
params.set(
|
|
648
|
+
params.set(NO_CONTROLS_PARAM, "true");
|
|
255
649
|
for (const [key, value] of Object.entries(values)) {
|
|
256
650
|
if (value !== void 0 && value !== null) {
|
|
257
651
|
params.set(key, value.toString());
|
|
@@ -514,20 +908,223 @@ var Button = React8.forwardRef(
|
|
|
514
908
|
);
|
|
515
909
|
Button.displayName = "Button";
|
|
516
910
|
|
|
911
|
+
// src/constants/layout.ts
|
|
912
|
+
var MOBILE_CONTROL_PANEL_PEEK = 112;
|
|
913
|
+
|
|
914
|
+
// src/components/AdvancedPaletteControl/AdvancedPaletteControl.tsx
|
|
915
|
+
import {
|
|
916
|
+
useCallback as useCallback2,
|
|
917
|
+
useEffect as useEffect4,
|
|
918
|
+
useMemo as useMemo2,
|
|
919
|
+
useRef as useRef3
|
|
920
|
+
} from "react";
|
|
921
|
+
import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
922
|
+
var AdvancedPaletteControl = ({
|
|
923
|
+
config
|
|
924
|
+
}) => {
|
|
925
|
+
const { values, setValue } = useControlsContext();
|
|
926
|
+
const palette = useMemo2(() => {
|
|
927
|
+
const result = {};
|
|
928
|
+
config.sections.forEach((section) => {
|
|
929
|
+
result[section.key] = CHANNEL_KEYS.reduce((acc, channel) => {
|
|
930
|
+
const key = createPaletteControlKey(
|
|
931
|
+
config.hiddenKeyPrefix,
|
|
932
|
+
section.key,
|
|
933
|
+
channel
|
|
934
|
+
);
|
|
935
|
+
const defaultValue = config.defaultPalette?.[section.key]?.[channel] ?? DEFAULT_RANGES[section.key]?.min ?? 0;
|
|
936
|
+
acc[channel] = toNumberOr(values?.[key], defaultValue);
|
|
937
|
+
return acc;
|
|
938
|
+
}, {});
|
|
939
|
+
});
|
|
940
|
+
return result;
|
|
941
|
+
}, [config.defaultPalette, config.hiddenKeyPrefix, config.sections, values]);
|
|
942
|
+
const paletteGradient = useMemo2(
|
|
943
|
+
() => computePaletteGradient(palette, config.gradientSteps),
|
|
944
|
+
[palette, config.gradientSteps]
|
|
945
|
+
);
|
|
946
|
+
const paletteSignature = useMemo2(
|
|
947
|
+
() => createPaletteSignature(palette),
|
|
948
|
+
[palette]
|
|
949
|
+
);
|
|
950
|
+
const lastSignatureRef = useRef3(null);
|
|
951
|
+
useEffect4(() => {
|
|
952
|
+
if (!config.onPaletteChange) return;
|
|
953
|
+
if (lastSignatureRef.current === paletteSignature) return;
|
|
954
|
+
lastSignatureRef.current = paletteSignature;
|
|
955
|
+
config.onPaletteChange(palette);
|
|
956
|
+
}, [config, palette, paletteSignature]);
|
|
957
|
+
const updatePaletteValue = useCallback2(
|
|
958
|
+
(sectionKey, channel, nextValue) => {
|
|
959
|
+
const range = config.ranges[sectionKey] ?? DEFAULT_RANGES[sectionKey] ?? {
|
|
960
|
+
min: 0,
|
|
961
|
+
max: 1,
|
|
962
|
+
step: 0.01
|
|
963
|
+
};
|
|
964
|
+
const clamped = Math.min(Math.max(nextValue, range.min), range.max);
|
|
965
|
+
config.onInteraction?.();
|
|
966
|
+
const controlKey = createPaletteControlKey(
|
|
967
|
+
config.hiddenKeyPrefix,
|
|
968
|
+
sectionKey,
|
|
969
|
+
channel
|
|
970
|
+
);
|
|
971
|
+
setValue(controlKey, clamped);
|
|
972
|
+
},
|
|
973
|
+
[config, setValue]
|
|
974
|
+
);
|
|
975
|
+
const handleResetPalette = useCallback2(() => {
|
|
976
|
+
config.onInteraction?.();
|
|
977
|
+
config.sections.forEach((section) => {
|
|
978
|
+
CHANNEL_KEYS.forEach((channel) => {
|
|
979
|
+
const controlKey = createPaletteControlKey(
|
|
980
|
+
config.hiddenKeyPrefix,
|
|
981
|
+
section.key,
|
|
982
|
+
channel
|
|
983
|
+
);
|
|
984
|
+
const defaultValue = config.defaultPalette?.[section.key]?.[channel] ?? DEFAULT_RANGES[section.key]?.min ?? 0;
|
|
985
|
+
setValue(controlKey, defaultValue);
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
}, [config, setValue]);
|
|
989
|
+
return /* @__PURE__ */ jsx9("div", { className: "flex w-full flex-col gap-6", children: /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-col gap-4", children: [
|
|
990
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
|
|
991
|
+
/* @__PURE__ */ jsx9("span", { className: "text-xs font-semibold uppercase tracking-wide text-stone-200", children: "Palette" }),
|
|
992
|
+
/* @__PURE__ */ jsx9(
|
|
993
|
+
"button",
|
|
994
|
+
{
|
|
995
|
+
type: "button",
|
|
996
|
+
onClick: handleResetPalette,
|
|
997
|
+
className: "rounded border border-stone-700 px-3 py-1 text-[10px] font-semibold uppercase tracking-widest text-stone-200 transition hover:border-stone-500",
|
|
998
|
+
children: "Reset Palette"
|
|
999
|
+
}
|
|
1000
|
+
)
|
|
1001
|
+
] }),
|
|
1002
|
+
/* @__PURE__ */ jsx9(
|
|
1003
|
+
"div",
|
|
1004
|
+
{
|
|
1005
|
+
className: "h-4 w-full rounded border border-stone-700",
|
|
1006
|
+
style: { background: paletteGradient }
|
|
1007
|
+
}
|
|
1008
|
+
),
|
|
1009
|
+
/* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-4", children: config.sections.map((section) => {
|
|
1010
|
+
const range = config.ranges[section.key];
|
|
1011
|
+
return /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
|
|
1012
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between text-[11px] uppercase tracking-widest text-stone-300", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx9("span", { children: section.label }),
|
|
1014
|
+
section.helper && /* @__PURE__ */ jsx9("span", { className: "text-stone-500", children: section.helper })
|
|
1015
|
+
] }),
|
|
1016
|
+
/* @__PURE__ */ jsx9("div", { className: "grid grid-cols-3 gap-3", children: CHANNEL_KEYS.map((channel) => {
|
|
1017
|
+
const value = palette[section.key][channel];
|
|
1018
|
+
const channelLabel = config.channelLabels?.[channel] ?? DEFAULT_CHANNEL_LABELS[channel];
|
|
1019
|
+
return /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
1020
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between text-[10px] uppercase tracking-widest text-stone-400", children: [
|
|
1021
|
+
/* @__PURE__ */ jsx9("span", { children: channelLabel }),
|
|
1022
|
+
/* @__PURE__ */ jsx9("span", { children: value.toFixed(2) })
|
|
1023
|
+
] }),
|
|
1024
|
+
/* @__PURE__ */ jsx9(
|
|
1025
|
+
"input",
|
|
1026
|
+
{
|
|
1027
|
+
type: "range",
|
|
1028
|
+
min: range.min,
|
|
1029
|
+
max: range.max,
|
|
1030
|
+
step: range.step,
|
|
1031
|
+
value,
|
|
1032
|
+
onPointerDown: config.onInteraction,
|
|
1033
|
+
onChange: (event) => updatePaletteValue(
|
|
1034
|
+
section.key,
|
|
1035
|
+
channel,
|
|
1036
|
+
parseFloat(event.target.value)
|
|
1037
|
+
),
|
|
1038
|
+
className: "w-full cursor-pointer accent-stone-300"
|
|
1039
|
+
}
|
|
1040
|
+
),
|
|
1041
|
+
/* @__PURE__ */ jsx9(
|
|
1042
|
+
"input",
|
|
1043
|
+
{
|
|
1044
|
+
type: "number",
|
|
1045
|
+
min: range.min,
|
|
1046
|
+
max: range.max,
|
|
1047
|
+
step: range.step,
|
|
1048
|
+
value: value.toFixed(3),
|
|
1049
|
+
onPointerDown: config.onInteraction,
|
|
1050
|
+
onFocus: config.onInteraction,
|
|
1051
|
+
onChange: (event) => {
|
|
1052
|
+
const parsed = parseFloat(event.target.value);
|
|
1053
|
+
if (Number.isNaN(parsed)) return;
|
|
1054
|
+
updatePaletteValue(section.key, channel, parsed);
|
|
1055
|
+
},
|
|
1056
|
+
className: "w-full rounded border border-stone-700 bg-stone-900 px-2 py-1 text-xs text-stone-200 focus:border-stone-500 focus:outline-none"
|
|
1057
|
+
}
|
|
1058
|
+
)
|
|
1059
|
+
] }, channel);
|
|
1060
|
+
}) })
|
|
1061
|
+
] }, section.key);
|
|
1062
|
+
}) })
|
|
1063
|
+
] }) });
|
|
1064
|
+
};
|
|
1065
|
+
var AdvancedPaletteControl_default = AdvancedPaletteControl;
|
|
1066
|
+
|
|
517
1067
|
// src/components/ControlPanel/ControlPanel.tsx
|
|
518
|
-
import { Fragment, jsx as
|
|
1068
|
+
import { Fragment, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
519
1069
|
var ControlPanel = () => {
|
|
520
1070
|
const [copied, setCopied] = useState4(false);
|
|
1071
|
+
const [folderStates, setFolderStates] = useState4({});
|
|
521
1072
|
const { leftPanelWidth, isDesktop, isHydrated } = useResizableLayout();
|
|
522
1073
|
const { schema, setValue, values, componentName, config } = useControlsContext();
|
|
523
1074
|
const previewUrl = usePreviewUrl(values);
|
|
524
|
-
const
|
|
525
|
-
(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1075
|
+
const buildUrl = useCallback3(
|
|
1076
|
+
(modifier) => {
|
|
1077
|
+
if (!previewUrl) return "";
|
|
1078
|
+
const [path, search = ""] = previewUrl.split("?");
|
|
1079
|
+
const params = new URLSearchParams(search);
|
|
1080
|
+
modifier(params);
|
|
1081
|
+
const query = params.toString();
|
|
1082
|
+
return query ? `${path}?${query}` : path;
|
|
1083
|
+
},
|
|
1084
|
+
[previewUrl]
|
|
529
1085
|
);
|
|
530
|
-
const
|
|
1086
|
+
const presentationUrl = useMemo3(() => {
|
|
1087
|
+
if (!previewUrl) return "";
|
|
1088
|
+
return buildUrl((params) => {
|
|
1089
|
+
params.set(PRESENTATION_PARAM, "true");
|
|
1090
|
+
});
|
|
1091
|
+
}, [buildUrl, previewUrl]);
|
|
1092
|
+
const controlsOnlyUrl = useMemo3(() => {
|
|
1093
|
+
if (!previewUrl) return "";
|
|
1094
|
+
return buildUrl((params) => {
|
|
1095
|
+
params.delete(NO_CONTROLS_PARAM);
|
|
1096
|
+
params.delete(PRESENTATION_PARAM);
|
|
1097
|
+
params.set(CONTROLS_ONLY_PARAM, "true");
|
|
1098
|
+
});
|
|
1099
|
+
}, [buildUrl, previewUrl]);
|
|
1100
|
+
const handlePresentationClick = useCallback3(() => {
|
|
1101
|
+
if (typeof window === "undefined" || !presentationUrl) return;
|
|
1102
|
+
window.open(presentationUrl, "_blank", "noopener,noreferrer");
|
|
1103
|
+
if (controlsOnlyUrl) {
|
|
1104
|
+
const viewportWidth = window.innerWidth || 1200;
|
|
1105
|
+
const viewportHeight = window.innerHeight || 900;
|
|
1106
|
+
const controlsWidth = Math.max(
|
|
1107
|
+
320,
|
|
1108
|
+
Math.min(
|
|
1109
|
+
600,
|
|
1110
|
+
Math.round(viewportWidth * leftPanelWidth / 100)
|
|
1111
|
+
)
|
|
1112
|
+
);
|
|
1113
|
+
const controlsHeight = Math.max(600, viewportHeight);
|
|
1114
|
+
const controlsFeatures = [
|
|
1115
|
+
"noopener",
|
|
1116
|
+
"noreferrer",
|
|
1117
|
+
"toolbar=0",
|
|
1118
|
+
"menubar=0",
|
|
1119
|
+
"resizable=yes",
|
|
1120
|
+
"scrollbars=yes",
|
|
1121
|
+
`width=${controlsWidth}`,
|
|
1122
|
+
`height=${controlsHeight}`
|
|
1123
|
+
].join(",");
|
|
1124
|
+
window.open(controlsOnlyUrl, "v0-controls", controlsFeatures);
|
|
1125
|
+
}
|
|
1126
|
+
}, [controlsOnlyUrl, leftPanelWidth, presentationUrl]);
|
|
1127
|
+
const jsx14 = useMemo3(() => {
|
|
531
1128
|
if (!componentName) return "";
|
|
532
1129
|
const props = Object.entries(values).map(([key, val]) => {
|
|
533
1130
|
if (typeof val === "string") return `${key}="${val}"`;
|
|
@@ -536,182 +1133,367 @@ var ControlPanel = () => {
|
|
|
536
1133
|
}).join(" ");
|
|
537
1134
|
return `<${componentName} ${props} />`;
|
|
538
1135
|
}, [componentName, values]);
|
|
539
|
-
|
|
1136
|
+
const visibleEntries = Object.entries(schema).filter(
|
|
1137
|
+
([, control]) => !control.hidden
|
|
1138
|
+
);
|
|
1139
|
+
const rootControls = [];
|
|
1140
|
+
const folderOrder = [];
|
|
1141
|
+
const folderControls = /* @__PURE__ */ new Map();
|
|
1142
|
+
const folderExtras = /* @__PURE__ */ new Map();
|
|
1143
|
+
const folderPlacement = /* @__PURE__ */ new Map();
|
|
1144
|
+
const seenFolders = /* @__PURE__ */ new Set();
|
|
1145
|
+
const ensureFolder = (folder) => {
|
|
1146
|
+
if (!seenFolders.has(folder)) {
|
|
1147
|
+
seenFolders.add(folder);
|
|
1148
|
+
folderOrder.push(folder);
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
visibleEntries.forEach((entry) => {
|
|
1152
|
+
const [key, control] = entry;
|
|
1153
|
+
const folder = control.folder?.trim();
|
|
1154
|
+
if (folder) {
|
|
1155
|
+
const placement = control.folderPlacement ?? "bottom";
|
|
1156
|
+
ensureFolder(folder);
|
|
1157
|
+
if (!folderControls.has(folder)) {
|
|
1158
|
+
folderControls.set(folder, []);
|
|
1159
|
+
}
|
|
1160
|
+
folderControls.get(folder).push(entry);
|
|
1161
|
+
const existingPlacement = folderPlacement.get(folder);
|
|
1162
|
+
if (!existingPlacement || placement === "top") {
|
|
1163
|
+
folderPlacement.set(folder, placement);
|
|
1164
|
+
}
|
|
1165
|
+
} else {
|
|
1166
|
+
rootControls.push(entry);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
const advancedConfig = config?.addAdvancedPaletteControl;
|
|
1170
|
+
let advancedPaletteControlNode = null;
|
|
1171
|
+
if (advancedConfig) {
|
|
1172
|
+
const advancedNode = /* @__PURE__ */ jsx10(
|
|
1173
|
+
AdvancedPaletteControl_default,
|
|
1174
|
+
{
|
|
1175
|
+
config: advancedConfig
|
|
1176
|
+
},
|
|
1177
|
+
"advancedPaletteControl"
|
|
1178
|
+
);
|
|
1179
|
+
const advancedFolder = advancedConfig.folder?.trim();
|
|
1180
|
+
if (advancedFolder) {
|
|
1181
|
+
const placement = advancedConfig.folderPlacement ?? "bottom";
|
|
1182
|
+
ensureFolder(advancedFolder);
|
|
1183
|
+
if (!folderControls.has(advancedFolder)) {
|
|
1184
|
+
folderControls.set(advancedFolder, []);
|
|
1185
|
+
}
|
|
1186
|
+
const existingPlacement = folderPlacement.get(advancedFolder);
|
|
1187
|
+
if (!existingPlacement || placement === "top") {
|
|
1188
|
+
folderPlacement.set(advancedFolder, placement);
|
|
1189
|
+
}
|
|
1190
|
+
if (!folderExtras.has(advancedFolder)) {
|
|
1191
|
+
folderExtras.set(advancedFolder, []);
|
|
1192
|
+
}
|
|
1193
|
+
folderExtras.get(advancedFolder).push(advancedNode);
|
|
1194
|
+
} else {
|
|
1195
|
+
advancedPaletteControlNode = advancedNode;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const rootButtonControls = rootControls.filter(
|
|
1199
|
+
([, control]) => control.type === "button"
|
|
1200
|
+
);
|
|
1201
|
+
const rootNormalControls = rootControls.filter(
|
|
1202
|
+
([, control]) => control.type !== "button"
|
|
1203
|
+
);
|
|
1204
|
+
const folderGroups = folderOrder.map((folder) => ({
|
|
1205
|
+
folder,
|
|
1206
|
+
entries: folderControls.get(folder) ?? [],
|
|
1207
|
+
extras: folderExtras.get(folder) ?? [],
|
|
1208
|
+
placement: folderPlacement.get(folder) ?? "bottom"
|
|
1209
|
+
})).filter((group) => group.entries.length > 0 || group.extras.length > 0);
|
|
1210
|
+
const hasRootButtonControls = rootButtonControls.length > 0;
|
|
1211
|
+
const hasAnyFolders = folderGroups.length > 0;
|
|
1212
|
+
const jsonToComponentString = useCallback3(
|
|
1213
|
+
({
|
|
1214
|
+
componentName: componentNameOverride,
|
|
1215
|
+
props
|
|
1216
|
+
}) => {
|
|
1217
|
+
const resolvedComponentName = componentNameOverride ?? componentName;
|
|
1218
|
+
if (!resolvedComponentName) return "";
|
|
1219
|
+
const formatProp = (key, value) => {
|
|
1220
|
+
if (value === void 0) return null;
|
|
1221
|
+
if (value === null) return `${key}={null}`;
|
|
1222
|
+
if (typeof value === "string") return `${key}="${value}"`;
|
|
1223
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1224
|
+
return `${key}={${value}}`;
|
|
1225
|
+
}
|
|
1226
|
+
if (typeof value === "bigint") {
|
|
1227
|
+
return `${key}={${value.toString()}n}`;
|
|
1228
|
+
}
|
|
1229
|
+
return `${key}={${JSON.stringify(value)}}`;
|
|
1230
|
+
};
|
|
1231
|
+
const formattedProps = Object.entries(props ?? {}).map(([key, value]) => formatProp(key, value)).filter((prop) => Boolean(prop)).join(" ");
|
|
1232
|
+
if (!formattedProps) {
|
|
1233
|
+
return `<${resolvedComponentName} />`;
|
|
1234
|
+
}
|
|
1235
|
+
return `<${resolvedComponentName} ${formattedProps} />`;
|
|
1236
|
+
},
|
|
1237
|
+
[componentName]
|
|
1238
|
+
);
|
|
1239
|
+
const copyText = config?.showCopyButtonFn?.({
|
|
1240
|
+
componentName,
|
|
1241
|
+
values,
|
|
1242
|
+
schema,
|
|
1243
|
+
jsx: jsx14,
|
|
1244
|
+
jsonToComponentString
|
|
1245
|
+
}) ?? jsx14;
|
|
1246
|
+
const shouldShowCopyButton = config?.showCopyButton !== false && Boolean(copyText);
|
|
1247
|
+
const labelize = (key) => key.replace(/([A-Z])/g, " $1").replace(/[\-_]/g, " ").replace(/\s+/g, " ").trim().replace(/(^|\s)\S/g, (s) => s.toUpperCase());
|
|
1248
|
+
const renderButtonControl = (key, control, variant) => /* @__PURE__ */ jsx10(
|
|
540
1249
|
"div",
|
|
541
1250
|
{
|
|
542
|
-
className:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
/* @__PURE__ */ jsx9(
|
|
571
|
-
Switch,
|
|
572
|
-
{
|
|
573
|
-
id: key,
|
|
574
|
-
checked: value,
|
|
575
|
-
onCheckedChange: (v) => setValue(key, v),
|
|
576
|
-
className: "data-[state=checked]:bg-stone-700 data-[state=unchecked]:bg-stone-700/40"
|
|
577
|
-
}
|
|
578
|
-
),
|
|
579
|
-
/* @__PURE__ */ jsx9(Label, { htmlFor: key, className: "cursor-pointer", children: key })
|
|
580
|
-
]
|
|
581
|
-
},
|
|
582
|
-
key
|
|
583
|
-
);
|
|
584
|
-
case "number":
|
|
585
|
-
return /* @__PURE__ */ jsxs4("div", { className: "space-y-2 w-full", children: [
|
|
586
|
-
/* @__PURE__ */ jsx9("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsxs4(Label, { className: "text-stone-300", htmlFor: key, children: [
|
|
587
|
-
key,
|
|
588
|
-
": ",
|
|
589
|
-
value
|
|
590
|
-
] }) }),
|
|
591
|
-
/* @__PURE__ */ jsx9(
|
|
592
|
-
Slider,
|
|
593
|
-
{
|
|
594
|
-
id: key,
|
|
595
|
-
min: control.min ?? 0,
|
|
596
|
-
max: control.max ?? 100,
|
|
597
|
-
step: control.step ?? 1,
|
|
598
|
-
value: [value],
|
|
599
|
-
onValueChange: ([v]) => setValue(key, v),
|
|
600
|
-
className: "[&>span]:border-none [&_.bg-primary]:bg-stone-800 [&>.bg-background]:bg-stone-500/30"
|
|
601
|
-
}
|
|
602
|
-
)
|
|
603
|
-
] }, key);
|
|
604
|
-
case "string":
|
|
605
|
-
return /* @__PURE__ */ jsx9(
|
|
606
|
-
Input,
|
|
607
|
-
{
|
|
608
|
-
id: key,
|
|
609
|
-
value,
|
|
610
|
-
className: "bg-stone-900",
|
|
611
|
-
placeholder: key,
|
|
612
|
-
onChange: (e) => setValue(key, e.target.value)
|
|
613
|
-
},
|
|
614
|
-
key
|
|
615
|
-
);
|
|
616
|
-
case "color":
|
|
617
|
-
return /* @__PURE__ */ jsxs4("div", { className: "space-y-2 w-full", children: [
|
|
618
|
-
/* @__PURE__ */ jsx9("div", { className: "flex items-center justify-between pb-1", children: /* @__PURE__ */ jsx9(Label, { className: "text-stone-300", htmlFor: key, children: key }) }),
|
|
619
|
-
/* @__PURE__ */ jsx9(
|
|
620
|
-
"input",
|
|
621
|
-
{
|
|
622
|
-
type: "color",
|
|
623
|
-
id: key,
|
|
624
|
-
value,
|
|
625
|
-
onChange: (e) => setValue(key, e.target.value),
|
|
626
|
-
className: "w-full h-10 rounded border border-stone-600 bg-transparent"
|
|
627
|
-
}
|
|
628
|
-
)
|
|
629
|
-
] }, key);
|
|
630
|
-
case "select":
|
|
631
|
-
return /* @__PURE__ */ jsxs4(
|
|
632
|
-
"div",
|
|
633
|
-
{
|
|
634
|
-
className: "space-y-2 border-t border-stone-700 pt-4",
|
|
635
|
-
children: [
|
|
636
|
-
/* @__PURE__ */ jsx9(Label, { className: "text-stone-300", htmlFor: key, children: key }),
|
|
637
|
-
/* @__PURE__ */ jsxs4(
|
|
638
|
-
Select,
|
|
639
|
-
{
|
|
640
|
-
value,
|
|
641
|
-
onValueChange: (val) => setValue(key, val),
|
|
642
|
-
children: [
|
|
643
|
-
/* @__PURE__ */ jsx9(SelectTrigger, { children: /* @__PURE__ */ jsx9(SelectValue, { placeholder: "Select option" }) }),
|
|
644
|
-
/* @__PURE__ */ jsx9(SelectContent, { children: Object.entries(control.options).map(
|
|
645
|
-
([label, _val]) => /* @__PURE__ */ jsx9(SelectItem, { value: label, children: label }, label)
|
|
646
|
-
) })
|
|
647
|
-
]
|
|
648
|
-
}
|
|
649
|
-
)
|
|
650
|
-
]
|
|
651
|
-
},
|
|
652
|
-
key
|
|
653
|
-
);
|
|
654
|
-
default:
|
|
655
|
-
return null;
|
|
1251
|
+
className: variant === "root" ? "flex-1 [&_[data-slot=button]]:w-full" : "[&_[data-slot=button]]:w-full",
|
|
1252
|
+
children: control.render ? control.render() : /* @__PURE__ */ jsx10(
|
|
1253
|
+
"button",
|
|
1254
|
+
{
|
|
1255
|
+
onClick: control.onClick,
|
|
1256
|
+
className: "w-full px-4 py-2 text-sm bg-stone-800 hover:bg-stone-700 text-white rounded-md shadow",
|
|
1257
|
+
children: control.label ?? key
|
|
1258
|
+
}
|
|
1259
|
+
)
|
|
1260
|
+
},
|
|
1261
|
+
`control-panel-custom-${key}`
|
|
1262
|
+
);
|
|
1263
|
+
const renderControl = (key, control, variant) => {
|
|
1264
|
+
if (control.type === "button") {
|
|
1265
|
+
return renderButtonControl(key, control, variant);
|
|
1266
|
+
}
|
|
1267
|
+
const value = values[key];
|
|
1268
|
+
switch (control.type) {
|
|
1269
|
+
case "boolean":
|
|
1270
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
|
|
1271
|
+
/* @__PURE__ */ jsx10(Label, { htmlFor: key, className: "cursor-pointer", children: labelize(key) }),
|
|
1272
|
+
/* @__PURE__ */ jsx10(
|
|
1273
|
+
Switch,
|
|
1274
|
+
{
|
|
1275
|
+
id: key,
|
|
1276
|
+
checked: value,
|
|
1277
|
+
onCheckedChange: (v) => setValue(key, v),
|
|
1278
|
+
className: "cursor-pointer scale-90"
|
|
656
1279
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1280
|
+
)
|
|
1281
|
+
] }, key);
|
|
1282
|
+
case "number":
|
|
1283
|
+
return /* @__PURE__ */ jsxs5("div", { className: "space-y-3 w-full", children: [
|
|
1284
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
|
|
1285
|
+
/* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
|
|
1286
|
+
/* @__PURE__ */ jsx10(
|
|
1287
|
+
Input,
|
|
1288
|
+
{
|
|
1289
|
+
type: "number",
|
|
1290
|
+
value,
|
|
1291
|
+
min: control.min ?? 0,
|
|
1292
|
+
max: control.max ?? 100,
|
|
1293
|
+
step: control.step ?? 1,
|
|
1294
|
+
onChange: (e) => {
|
|
1295
|
+
const v = parseFloat(e.target.value);
|
|
1296
|
+
if (Number.isNaN(v)) return;
|
|
1297
|
+
setValue(key, v);
|
|
1298
|
+
},
|
|
1299
|
+
className: "w-20 text-center cursor-text"
|
|
1300
|
+
}
|
|
1301
|
+
)
|
|
1302
|
+
] }),
|
|
1303
|
+
/* @__PURE__ */ jsx10(
|
|
1304
|
+
Slider,
|
|
1305
|
+
{
|
|
1306
|
+
id: key,
|
|
1307
|
+
min: control.min ?? 0,
|
|
1308
|
+
max: control.max ?? 100,
|
|
1309
|
+
step: control.step ?? 1,
|
|
1310
|
+
value: [value],
|
|
1311
|
+
onValueChange: ([v]) => setValue(key, v),
|
|
1312
|
+
className: "w-full cursor-pointer"
|
|
1313
|
+
}
|
|
1314
|
+
)
|
|
1315
|
+
] }, key);
|
|
1316
|
+
case "string":
|
|
1317
|
+
return /* @__PURE__ */ jsxs5("div", { className: "space-y-2 w-full", children: [
|
|
1318
|
+
/* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
|
|
1319
|
+
/* @__PURE__ */ jsx10(
|
|
1320
|
+
Input,
|
|
660
1321
|
{
|
|
661
|
-
|
|
1322
|
+
id: key,
|
|
1323
|
+
value,
|
|
1324
|
+
placeholder: key,
|
|
1325
|
+
onChange: (e) => setValue(key, e.target.value),
|
|
1326
|
+
className: "bg-stone-900"
|
|
1327
|
+
}
|
|
1328
|
+
)
|
|
1329
|
+
] }, key);
|
|
1330
|
+
case "color":
|
|
1331
|
+
return /* @__PURE__ */ jsxs5("div", { className: "space-y-2 w-full", children: [
|
|
1332
|
+
/* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
|
|
1333
|
+
/* @__PURE__ */ jsx10(
|
|
1334
|
+
"input",
|
|
1335
|
+
{
|
|
1336
|
+
type: "color",
|
|
1337
|
+
id: key,
|
|
1338
|
+
value,
|
|
1339
|
+
onChange: (e) => setValue(key, e.target.value),
|
|
1340
|
+
className: "w-full h-10 rounded border border-stone-600 bg-transparent"
|
|
1341
|
+
}
|
|
1342
|
+
)
|
|
1343
|
+
] }, key);
|
|
1344
|
+
case "select":
|
|
1345
|
+
return /* @__PURE__ */ jsx10("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
|
|
1346
|
+
/* @__PURE__ */ jsx10(Label, { className: "min-w-fit", htmlFor: key, children: labelize(key) }),
|
|
1347
|
+
/* @__PURE__ */ jsxs5(Select, { value, onValueChange: (val) => setValue(key, val), children: [
|
|
1348
|
+
/* @__PURE__ */ jsx10(SelectTrigger, { className: "flex-1 cursor-pointer", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: "Select option" }) }),
|
|
1349
|
+
/* @__PURE__ */ jsx10(SelectContent, { className: "cursor-pointer z-[9999]", children: Object.entries(control.options).map(([label]) => /* @__PURE__ */ jsx10(
|
|
1350
|
+
SelectItem,
|
|
1351
|
+
{
|
|
1352
|
+
value: label,
|
|
1353
|
+
className: "cursor-pointer",
|
|
1354
|
+
children: label
|
|
1355
|
+
},
|
|
1356
|
+
label
|
|
1357
|
+
)) })
|
|
1358
|
+
] })
|
|
1359
|
+
] }) }, key);
|
|
1360
|
+
default:
|
|
1361
|
+
return null;
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
const renderFolder = (folder, entries, extras = []) => {
|
|
1365
|
+
const isOpen = folderStates[folder] ?? true;
|
|
1366
|
+
return /* @__PURE__ */ jsxs5(
|
|
1367
|
+
"div",
|
|
1368
|
+
{
|
|
1369
|
+
className: "border border-stone-700/60 rounded-lg bg-stone-900/70",
|
|
1370
|
+
children: [
|
|
1371
|
+
/* @__PURE__ */ jsxs5(
|
|
1372
|
+
"button",
|
|
1373
|
+
{
|
|
1374
|
+
type: "button",
|
|
1375
|
+
onClick: () => setFolderStates((prev) => ({
|
|
1376
|
+
...prev,
|
|
1377
|
+
[folder]: !isOpen
|
|
1378
|
+
})),
|
|
1379
|
+
className: "w-full flex items-center justify-between px-4 py-3 text-left font-semibold text-stone-200 tracking-wide",
|
|
662
1380
|
children: [
|
|
663
|
-
|
|
664
|
-
|
|
1381
|
+
/* @__PURE__ */ jsx10("span", { children: folder }),
|
|
1382
|
+
/* @__PURE__ */ jsx10(
|
|
1383
|
+
ChevronDown2,
|
|
665
1384
|
{
|
|
666
|
-
|
|
667
|
-
navigator.clipboard.writeText(jsx16);
|
|
668
|
-
setCopied(true);
|
|
669
|
-
setTimeout(() => setCopied(false), 5e3);
|
|
670
|
-
},
|
|
671
|
-
className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded flex items-center justify-center gap-2",
|
|
672
|
-
children: copied ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
673
|
-
/* @__PURE__ */ jsx9(Check2, { className: "w-4 h-4" }),
|
|
674
|
-
"Copied"
|
|
675
|
-
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
676
|
-
/* @__PURE__ */ jsx9(Copy, { className: "w-4 h-4" }),
|
|
677
|
-
"Copy to Clipboard"
|
|
678
|
-
] })
|
|
1385
|
+
className: `w-4 h-4 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
|
|
679
1386
|
}
|
|
680
|
-
)
|
|
681
|
-
buttonControls.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-2 pt-4", children: buttonControls.map(
|
|
682
|
-
([key, control]) => control.type === "button" ? /* @__PURE__ */ jsx9(
|
|
683
|
-
"div",
|
|
684
|
-
{
|
|
685
|
-
className: "flex-1",
|
|
686
|
-
children: control.render ? control.render() : /* @__PURE__ */ jsx9(
|
|
687
|
-
"button",
|
|
688
|
-
{
|
|
689
|
-
onClick: control.onClick,
|
|
690
|
-
className: "w-full px-4 py-2 text-sm bg-stone-700 hover:bg-stone-600 text-white rounded",
|
|
691
|
-
children: control.label ?? key
|
|
692
|
-
}
|
|
693
|
-
)
|
|
694
|
-
},
|
|
695
|
-
`control-panel-custom-${key}`
|
|
696
|
-
) : null
|
|
697
|
-
) })
|
|
1387
|
+
)
|
|
698
1388
|
]
|
|
699
1389
|
}
|
|
700
|
-
)
|
|
1390
|
+
),
|
|
1391
|
+
isOpen && /* @__PURE__ */ jsxs5("div", { className: "px-4 pb-4 pt-0 space-y-5", children: [
|
|
1392
|
+
entries.map(
|
|
1393
|
+
([key, control]) => renderControl(key, control, "folder")
|
|
1394
|
+
),
|
|
1395
|
+
extras.map((extra) => extra)
|
|
1396
|
+
] })
|
|
1397
|
+
]
|
|
1398
|
+
},
|
|
1399
|
+
folder
|
|
1400
|
+
);
|
|
1401
|
+
};
|
|
1402
|
+
const topFolderSections = hasAnyFolders ? folderGroups.filter(({ placement }) => placement === "top").map(
|
|
1403
|
+
({ folder, entries, extras }) => renderFolder(folder, entries, extras)
|
|
1404
|
+
) : null;
|
|
1405
|
+
const bottomFolderSections = hasAnyFolders ? folderGroups.filter(({ placement }) => placement === "bottom").map(
|
|
1406
|
+
({ folder, entries, extras }) => renderFolder(folder, entries, extras)
|
|
1407
|
+
) : null;
|
|
1408
|
+
const panelStyle = {
|
|
1409
|
+
width: "100%",
|
|
1410
|
+
height: "auto",
|
|
1411
|
+
flex: "0 0 auto"
|
|
1412
|
+
};
|
|
1413
|
+
if (isHydrated) {
|
|
1414
|
+
if (isDesktop) {
|
|
1415
|
+
Object.assign(panelStyle, {
|
|
1416
|
+
position: "absolute",
|
|
1417
|
+
left: 0,
|
|
1418
|
+
top: 0,
|
|
1419
|
+
bottom: 0,
|
|
1420
|
+
width: `${leftPanelWidth}%`,
|
|
1421
|
+
overflowY: "auto"
|
|
1422
|
+
});
|
|
1423
|
+
} else {
|
|
1424
|
+
Object.assign(panelStyle, {
|
|
1425
|
+
marginTop: `calc(-1 * (${MOBILE_CONTROL_PANEL_PEEK}px + env(safe-area-inset-bottom, 0px)))`,
|
|
1426
|
+
paddingBottom: `calc(${MOBILE_CONTROL_PANEL_PEEK}px + env(safe-area-inset-bottom, 0px))`
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return /* @__PURE__ */ jsx10(
|
|
1431
|
+
"div",
|
|
1432
|
+
{
|
|
1433
|
+
className: `order-2 md:order-1 w-full md:h-auto p-2 md:p-4 bg-stone-900 font-mono text-stone-300 transition-opacity duration-300 z-max ${!isHydrated ? "opacity-0" : "opacity-100"}`,
|
|
1434
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
1435
|
+
onTouchStart: (e) => e.stopPropagation(),
|
|
1436
|
+
style: panelStyle,
|
|
1437
|
+
children: /* @__PURE__ */ jsxs5("div", { className: "dark mb-10 space-y-6 p-4 md:p-6 bg-stone-900/95 backdrop-blur-md border-2 border-stone-700 rounded-xl shadow-lg", children: [
|
|
1438
|
+
/* @__PURE__ */ jsx10("div", { className: "space-y-1", children: /* @__PURE__ */ jsx10("h1", { className: "text-lg text-stone-100 font-semibold", children: config?.mainLabel ?? "Controls" }) }),
|
|
1439
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
|
|
1440
|
+
topFolderSections,
|
|
1441
|
+
hasRootButtonControls && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2", children: rootButtonControls.map(
|
|
1442
|
+
([key, control]) => renderButtonControl(key, control, "root")
|
|
1443
|
+
) }),
|
|
1444
|
+
advancedPaletteControlNode,
|
|
1445
|
+
rootNormalControls.map(
|
|
1446
|
+
([key, control]) => renderControl(key, control, "root")
|
|
1447
|
+
),
|
|
1448
|
+
bottomFolderSections,
|
|
1449
|
+
shouldShowCopyButton && /* @__PURE__ */ jsx10("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx10(
|
|
1450
|
+
"button",
|
|
1451
|
+
{
|
|
1452
|
+
onClick: () => {
|
|
1453
|
+
if (!copyText) return;
|
|
1454
|
+
navigator.clipboard.writeText(copyText);
|
|
1455
|
+
setCopied(true);
|
|
1456
|
+
setTimeout(() => setCopied(false), 5e3);
|
|
1457
|
+
},
|
|
1458
|
+
className: "w-full px-4 py-2 text-sm bg-stone-800 hover:bg-stone-700 text-white rounded-md flex items-center justify-center gap-2 shadow",
|
|
1459
|
+
children: copied ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1460
|
+
/* @__PURE__ */ jsx10(Check2, { className: "w-4 h-4" }),
|
|
1461
|
+
"Copied"
|
|
1462
|
+
] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1463
|
+
/* @__PURE__ */ jsx10(Copy, { className: "w-4 h-4" }),
|
|
1464
|
+
"Copy to Clipboard"
|
|
1465
|
+
] })
|
|
1466
|
+
}
|
|
1467
|
+
) }, "control-panel-jsx")
|
|
701
1468
|
] }),
|
|
702
|
-
previewUrl && /* @__PURE__ */
|
|
703
|
-
"
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1469
|
+
previewUrl && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
|
|
1470
|
+
/* @__PURE__ */ jsx10(Button, { asChild: true, className: "w-full", children: /* @__PURE__ */ jsxs5(
|
|
1471
|
+
"a",
|
|
1472
|
+
{
|
|
1473
|
+
href: previewUrl,
|
|
1474
|
+
target: "_blank",
|
|
1475
|
+
rel: "noopener noreferrer",
|
|
1476
|
+
className: "w-full px-4 py-2 text-sm text-center bg-stone-900 hover:bg-stone-800 text-white rounded-md border border-stone-700",
|
|
1477
|
+
children: [
|
|
1478
|
+
/* @__PURE__ */ jsx10(SquareArrowOutUpRight, {}),
|
|
1479
|
+
" Open in a New Tab"
|
|
1480
|
+
]
|
|
1481
|
+
}
|
|
1482
|
+
) }),
|
|
1483
|
+
config?.showPresentationButton && presentationUrl && /* @__PURE__ */ jsxs5(
|
|
1484
|
+
Button,
|
|
1485
|
+
{
|
|
1486
|
+
type: "button",
|
|
1487
|
+
onClick: handlePresentationClick,
|
|
1488
|
+
variant: "secondary",
|
|
1489
|
+
className: "w-full bg-stone-800 text-white hover:bg-stone-700 border border-stone-700",
|
|
1490
|
+
children: [
|
|
1491
|
+
/* @__PURE__ */ jsx10(Presentation, {}),
|
|
1492
|
+
" Presentation Mode"
|
|
1493
|
+
]
|
|
1494
|
+
}
|
|
1495
|
+
)
|
|
1496
|
+
] })
|
|
715
1497
|
] })
|
|
716
1498
|
}
|
|
717
1499
|
);
|
|
@@ -719,12 +1501,12 @@ var ControlPanel = () => {
|
|
|
719
1501
|
var ControlPanel_default = ControlPanel;
|
|
720
1502
|
|
|
721
1503
|
// src/components/PreviewContainer/PreviewContainer.tsx
|
|
722
|
-
import { useRef as
|
|
1504
|
+
import { useRef as useRef4 } from "react";
|
|
723
1505
|
|
|
724
1506
|
// src/components/Grid/Grid.tsx
|
|
725
|
-
import { jsx as
|
|
1507
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
726
1508
|
function Grid() {
|
|
727
|
-
return /* @__PURE__ */
|
|
1509
|
+
return /* @__PURE__ */ jsx11(
|
|
728
1510
|
"div",
|
|
729
1511
|
{
|
|
730
1512
|
className: "absolute inset-0 w-screen h-screen z-[0] blur-[1px]",
|
|
@@ -742,12 +1524,12 @@ function Grid() {
|
|
|
742
1524
|
var Grid_default = Grid;
|
|
743
1525
|
|
|
744
1526
|
// src/components/PreviewContainer/PreviewContainer.tsx
|
|
745
|
-
import { jsx as
|
|
1527
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
746
1528
|
var PreviewContainer = ({ children, hideControls }) => {
|
|
747
1529
|
const { config } = useControlsContext();
|
|
748
1530
|
const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
|
|
749
|
-
const previewRef =
|
|
750
|
-
return /* @__PURE__ */
|
|
1531
|
+
const previewRef = useRef4(null);
|
|
1532
|
+
return /* @__PURE__ */ jsx12(
|
|
751
1533
|
"div",
|
|
752
1534
|
{
|
|
753
1535
|
ref: previewRef,
|
|
@@ -756,9 +1538,9 @@ var PreviewContainer = ({ children, hideControls }) => {
|
|
|
756
1538
|
width: `${100 - leftPanelWidth}%`,
|
|
757
1539
|
marginLeft: `${leftPanelWidth}%`
|
|
758
1540
|
} : {},
|
|
759
|
-
children: /* @__PURE__ */
|
|
760
|
-
config?.showGrid && /* @__PURE__ */
|
|
761
|
-
/* @__PURE__ */
|
|
1541
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "w-screen h-screen", children: [
|
|
1542
|
+
config?.showGrid && /* @__PURE__ */ jsx12(Grid_default, {}),
|
|
1543
|
+
/* @__PURE__ */ jsx12("div", { className: "w-screen h-screen flex items-center justify-center relative", children })
|
|
762
1544
|
] })
|
|
763
1545
|
}
|
|
764
1546
|
);
|
|
@@ -766,166 +1548,178 @@ var PreviewContainer = ({ children, hideControls }) => {
|
|
|
766
1548
|
var PreviewContainer_default = PreviewContainer;
|
|
767
1549
|
|
|
768
1550
|
// src/components/Playground/Playground.tsx
|
|
769
|
-
import { jsx as
|
|
770
|
-
var
|
|
1551
|
+
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1552
|
+
var HiddenPreview = ({ children }) => /* @__PURE__ */ jsx13("div", { "aria-hidden": "true", className: "hidden", children });
|
|
771
1553
|
function Playground({ children }) {
|
|
772
1554
|
const [isHydrated, setIsHydrated] = useState5(false);
|
|
773
1555
|
const [copied, setCopied] = useState5(false);
|
|
774
|
-
|
|
1556
|
+
useEffect5(() => {
|
|
775
1557
|
setIsHydrated(true);
|
|
776
1558
|
}, []);
|
|
777
|
-
const
|
|
778
|
-
if (typeof window === "undefined")
|
|
779
|
-
|
|
1559
|
+
const { showControls, isPresentationMode, isControlsOnly } = useMemo4(() => {
|
|
1560
|
+
if (typeof window === "undefined") {
|
|
1561
|
+
return {
|
|
1562
|
+
showControls: true,
|
|
1563
|
+
isPresentationMode: false,
|
|
1564
|
+
isControlsOnly: false
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
const params = new URLSearchParams(window.location.search);
|
|
1568
|
+
const presentation = params.get(PRESENTATION_PARAM) === "true";
|
|
1569
|
+
const controlsOnly = params.get(CONTROLS_ONLY_PARAM) === "true";
|
|
1570
|
+
const noControlsParam = params.get(NO_CONTROLS_PARAM) === "true";
|
|
1571
|
+
const showControlsValue = controlsOnly || !presentation && !noControlsParam;
|
|
1572
|
+
return {
|
|
1573
|
+
showControls: showControlsValue,
|
|
1574
|
+
isPresentationMode: presentation,
|
|
1575
|
+
isControlsOnly: controlsOnly
|
|
1576
|
+
};
|
|
780
1577
|
}, []);
|
|
1578
|
+
const shouldShowShareButton = !showControls && !isPresentationMode;
|
|
1579
|
+
const layoutHideControls = !showControls || isControlsOnly;
|
|
781
1580
|
const handleCopy = () => {
|
|
782
1581
|
navigator.clipboard.writeText(window.location.href);
|
|
783
1582
|
setCopied(true);
|
|
784
1583
|
setTimeout(() => setCopied(false), 2e3);
|
|
785
1584
|
};
|
|
786
1585
|
if (!isHydrated) return null;
|
|
787
|
-
return /* @__PURE__ */
|
|
788
|
-
|
|
1586
|
+
return /* @__PURE__ */ jsx13(ResizableLayout, { hideControls: layoutHideControls, children: /* @__PURE__ */ jsxs7(ControlsProvider, { children: [
|
|
1587
|
+
shouldShowShareButton && /* @__PURE__ */ jsxs7(
|
|
789
1588
|
"button",
|
|
790
1589
|
{
|
|
791
1590
|
onClick: handleCopy,
|
|
792
1591
|
className: "absolute top-4 right-4 z-50 flex items-center gap-1 rounded bg-black/70 px-3 py-1 text-white hover:bg-black",
|
|
793
1592
|
children: [
|
|
794
|
-
copied ? /* @__PURE__ */
|
|
1593
|
+
copied ? /* @__PURE__ */ jsx13(Check3, { size: 16 }) : /* @__PURE__ */ jsx13(Copy2, { size: 16 }),
|
|
795
1594
|
copied ? "Copied!" : "Share"
|
|
796
1595
|
]
|
|
797
1596
|
}
|
|
798
1597
|
),
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
|
|
1598
|
+
isControlsOnly ? /* @__PURE__ */ jsx13(HiddenPreview, { children }) : /* @__PURE__ */ jsx13(PreviewContainer_default, { hideControls: layoutHideControls, children }),
|
|
1599
|
+
showControls && /* @__PURE__ */ jsx13(ControlPanel_default, {})
|
|
801
1600
|
] }) });
|
|
802
1601
|
}
|
|
803
1602
|
|
|
804
|
-
// src/
|
|
805
|
-
import
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}, 200);
|
|
823
|
-
}, [camera]);
|
|
824
|
-
useEffect5(() => {
|
|
825
|
-
const controls = controlsRef.current;
|
|
826
|
-
const handler = logRef.current;
|
|
827
|
-
if (!controls || !handler) return;
|
|
828
|
-
controls.addEventListener("change", handler);
|
|
829
|
-
return () => controls.removeEventListener("change", handler);
|
|
830
|
-
}, []);
|
|
831
|
-
return /* @__PURE__ */ jsx13(OrbitControls, { ref: controlsRef });
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// src/components/Canvas/Canvas.tsx
|
|
835
|
-
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
836
|
-
var ResponsiveCamera = ({
|
|
837
|
-
height,
|
|
838
|
-
width
|
|
839
|
-
}) => {
|
|
840
|
-
const { camera } = useThree2();
|
|
841
|
-
useEffect6(() => {
|
|
842
|
-
const isMobile = width < 768;
|
|
843
|
-
const zoomFactor = isMobile ? 70 : 100;
|
|
844
|
-
camera.position.z = height / zoomFactor;
|
|
845
|
-
camera.updateProjectionMatrix();
|
|
846
|
-
}, [height, camera, width]);
|
|
847
|
-
return null;
|
|
848
|
-
};
|
|
849
|
-
var Canvas = ({
|
|
850
|
-
mediaProps,
|
|
851
|
-
children,
|
|
852
|
-
...otherProps
|
|
853
|
-
}) => {
|
|
854
|
-
const canvasRef = useRef4(null);
|
|
855
|
-
const [parentSize, setParentSize] = useState6(null);
|
|
1603
|
+
// src/hooks/useAdvancedPaletteControls.ts
|
|
1604
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef5, useState as useState6 } from "react";
|
|
1605
|
+
var cloneForCallbacks = (palette) => clonePalette(palette);
|
|
1606
|
+
var useAdvancedPaletteControls = (options = {}) => {
|
|
1607
|
+
const resolvedDefaultPalette = useMemo5(
|
|
1608
|
+
() => createAdvancedPalette(options.defaultPalette),
|
|
1609
|
+
[options.defaultPalette]
|
|
1610
|
+
);
|
|
1611
|
+
const resolvedFallbackPalette = useMemo5(
|
|
1612
|
+
() => options.fallbackPalette ? createAdvancedPalette(options.fallbackPalette) : resolvedDefaultPalette,
|
|
1613
|
+
[options.fallbackPalette, resolvedDefaultPalette]
|
|
1614
|
+
);
|
|
1615
|
+
const [palette, setPaletteState] = useState6(
|
|
1616
|
+
() => clonePalette(resolvedDefaultPalette)
|
|
1617
|
+
);
|
|
1618
|
+
const defaultSignatureRef = useRef5(
|
|
1619
|
+
createPaletteSignature(resolvedDefaultPalette)
|
|
1620
|
+
);
|
|
856
1621
|
useEffect6(() => {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1622
|
+
const nextSignature = createPaletteSignature(resolvedDefaultPalette);
|
|
1623
|
+
if (defaultSignatureRef.current === nextSignature) return;
|
|
1624
|
+
defaultSignatureRef.current = nextSignature;
|
|
1625
|
+
setPaletteState(clonePalette(resolvedDefaultPalette));
|
|
1626
|
+
}, [resolvedDefaultPalette]);
|
|
1627
|
+
const notifyChange = useCallback4(
|
|
1628
|
+
(nextPalette) => {
|
|
1629
|
+
options.onChange?.(cloneForCallbacks(nextPalette));
|
|
1630
|
+
},
|
|
1631
|
+
[options.onChange]
|
|
1632
|
+
);
|
|
1633
|
+
const setPalette = useCallback4(
|
|
1634
|
+
(source) => {
|
|
1635
|
+
const nextPalette = createAdvancedPalette(
|
|
1636
|
+
source ?? resolvedDefaultPalette
|
|
1637
|
+
);
|
|
1638
|
+
setPaletteState(clonePalette(nextPalette));
|
|
1639
|
+
notifyChange(nextPalette);
|
|
1640
|
+
},
|
|
1641
|
+
[notifyChange, resolvedDefaultPalette]
|
|
1642
|
+
);
|
|
1643
|
+
const updatePalette = useCallback4(
|
|
1644
|
+
(updater) => {
|
|
1645
|
+
setPaletteState((current) => {
|
|
1646
|
+
const nextSource = updater(clonePalette(current));
|
|
1647
|
+
const nextPalette = createAdvancedPalette(
|
|
1648
|
+
nextSource ?? current ?? resolvedDefaultPalette
|
|
1649
|
+
);
|
|
1650
|
+
notifyChange(nextPalette);
|
|
1651
|
+
return clonePalette(nextPalette);
|
|
868
1652
|
});
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
tryObserve();
|
|
872
|
-
return () => {
|
|
873
|
-
if (observer) observer.disconnect();
|
|
874
|
-
};
|
|
875
|
-
}, []);
|
|
876
|
-
const mergedMediaProps = {
|
|
877
|
-
...mediaProps || {},
|
|
878
|
-
size: mediaProps?.size || { width: 400, height: 400 }
|
|
879
|
-
};
|
|
880
|
-
return /* @__PURE__ */ jsx14(
|
|
881
|
-
"div",
|
|
882
|
-
{
|
|
883
|
-
ref: canvasRef,
|
|
884
|
-
className: "w-full h-full pointer-events-none relative touch-none",
|
|
885
|
-
children: /* @__PURE__ */ jsxs7(
|
|
886
|
-
ThreeCanvas,
|
|
887
|
-
{
|
|
888
|
-
resize: { polyfill: ResizeObserver },
|
|
889
|
-
style: { width: parentSize?.width, height: parentSize?.height },
|
|
890
|
-
gl: { preserveDrawingBuffer: true },
|
|
891
|
-
...otherProps,
|
|
892
|
-
children: [
|
|
893
|
-
parentSize?.height && parentSize?.width && /* @__PURE__ */ jsx14(
|
|
894
|
-
ResponsiveCamera,
|
|
895
|
-
{
|
|
896
|
-
height: parentSize.height,
|
|
897
|
-
width: parentSize.width
|
|
898
|
-
}
|
|
899
|
-
),
|
|
900
|
-
mediaProps?.debugOrbit && /* @__PURE__ */ jsx14(CameraLogger, {}),
|
|
901
|
-
/* @__PURE__ */ jsx14("ambientLight", { intensity: 1 }),
|
|
902
|
-
/* @__PURE__ */ jsx14("pointLight", { position: [10, 10, 10] }),
|
|
903
|
-
React11.cloneElement(children, mergedMediaProps)
|
|
904
|
-
]
|
|
905
|
-
}
|
|
906
|
-
)
|
|
907
|
-
}
|
|
1653
|
+
},
|
|
1654
|
+
[notifyChange, resolvedDefaultPalette]
|
|
908
1655
|
);
|
|
1656
|
+
const resetPalette = useCallback4(() => {
|
|
1657
|
+
setPaletteState(clonePalette(resolvedDefaultPalette));
|
|
1658
|
+
notifyChange(resolvedDefaultPalette);
|
|
1659
|
+
}, [notifyChange, resolvedDefaultPalette]);
|
|
1660
|
+
const handleControlPaletteChange = useCallback4(
|
|
1661
|
+
(nextPalette) => {
|
|
1662
|
+
setPaletteState(clonePalette(nextPalette));
|
|
1663
|
+
notifyChange(nextPalette);
|
|
1664
|
+
},
|
|
1665
|
+
[notifyChange]
|
|
1666
|
+
);
|
|
1667
|
+
const controlConfig = useMemo5(
|
|
1668
|
+
() => ({
|
|
1669
|
+
...options.control ?? {},
|
|
1670
|
+
defaultPalette: resolvedDefaultPalette,
|
|
1671
|
+
onPaletteChange: handleControlPaletteChange
|
|
1672
|
+
}),
|
|
1673
|
+
[handleControlPaletteChange, options.control, resolvedDefaultPalette]
|
|
1674
|
+
);
|
|
1675
|
+
const hexColors = useMemo5(
|
|
1676
|
+
() => advancedPaletteToHexColors(palette, {
|
|
1677
|
+
sectionOrder: options.sectionOrder,
|
|
1678
|
+
fallbackPalette: resolvedFallbackPalette,
|
|
1679
|
+
defaultColor: options.defaultColor
|
|
1680
|
+
}),
|
|
1681
|
+
[
|
|
1682
|
+
options.defaultColor,
|
|
1683
|
+
options.sectionOrder,
|
|
1684
|
+
palette,
|
|
1685
|
+
resolvedFallbackPalette
|
|
1686
|
+
]
|
|
1687
|
+
);
|
|
1688
|
+
const paletteSignature = useMemo5(
|
|
1689
|
+
() => createPaletteSignature(palette),
|
|
1690
|
+
[palette]
|
|
1691
|
+
);
|
|
1692
|
+
const paletteGradient = useMemo5(
|
|
1693
|
+
() => computePaletteGradient(palette, options.gradientSteps),
|
|
1694
|
+
[options.gradientSteps, palette]
|
|
1695
|
+
);
|
|
1696
|
+
return {
|
|
1697
|
+
palette,
|
|
1698
|
+
hexColors,
|
|
1699
|
+
controlConfig,
|
|
1700
|
+
paletteGradient,
|
|
1701
|
+
setPalette,
|
|
1702
|
+
updatePalette,
|
|
1703
|
+
resetPalette,
|
|
1704
|
+
paletteSignature
|
|
1705
|
+
};
|
|
909
1706
|
};
|
|
910
|
-
var
|
|
911
|
-
|
|
912
|
-
// src/components/PlaygroundCanvas/PlaygroundCanvas.tsx
|
|
913
|
-
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
914
|
-
var PlaygroundCanvas = ({
|
|
915
|
-
children,
|
|
916
|
-
mediaProps,
|
|
917
|
-
...otherProps
|
|
918
|
-
}) => {
|
|
919
|
-
return /* @__PURE__ */ jsx15(Playground, { children: /* @__PURE__ */ jsx15(Canvas_default, { mediaProps, ...otherProps, children }) });
|
|
920
|
-
};
|
|
921
|
-
var PlaygroundCanvas_default = PlaygroundCanvas;
|
|
1707
|
+
var useDefaultAdvancedPaletteControls = () => useAdvancedPaletteControls({ defaultPalette: DEFAULT_ADVANCED_PALETTE });
|
|
922
1708
|
export {
|
|
923
1709
|
Button,
|
|
924
|
-
CameraLogger,
|
|
925
|
-
Canvas_default as Canvas,
|
|
926
1710
|
ControlsProvider,
|
|
1711
|
+
DEFAULT_ADVANCED_PALETTE,
|
|
1712
|
+
DEFAULT_HEX_PALETTE,
|
|
927
1713
|
Playground,
|
|
928
|
-
|
|
1714
|
+
advancedPaletteToHexColors,
|
|
1715
|
+
clonePalette,
|
|
1716
|
+
computePaletteGradient,
|
|
1717
|
+
createAdvancedPalette,
|
|
1718
|
+
createPaletteSignature,
|
|
1719
|
+
hexToPaletteValue,
|
|
1720
|
+
paletteValueToHex,
|
|
1721
|
+
useAdvancedPaletteControls,
|
|
929
1722
|
useControls,
|
|
1723
|
+
useDefaultAdvancedPaletteControls,
|
|
930
1724
|
useUrlSyncedControls
|
|
931
1725
|
};
|