@toriistudio/v0-playground 0.5.5 → 0.7.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 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +519 -26
- package/dist/index.mjs +534 -35
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/Playground/Playground.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect6, 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
|
|
@@ -131,6 +131,28 @@ var getUrlParams = () => {
|
|
|
131
131
|
return entries;
|
|
132
132
|
};
|
|
133
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
|
+
|
|
134
156
|
// src/lib/advancedPalette.ts
|
|
135
157
|
var CHANNEL_KEYS = ["r", "g", "b"];
|
|
136
158
|
var DEFAULT_CHANNEL_LABELS = {
|
|
@@ -389,9 +411,22 @@ var ControlsProvider = ({ children }) => {
|
|
|
389
411
|
const [schema, setSchema] = useState2({});
|
|
390
412
|
const [values, setValues] = useState2({});
|
|
391
413
|
const [config, setConfig] = useState2({
|
|
392
|
-
showCopyButton: true
|
|
414
|
+
showCopyButton: true,
|
|
415
|
+
showCodeSnippet: false
|
|
393
416
|
});
|
|
394
417
|
const [componentName, setComponentName] = useState2();
|
|
418
|
+
const [channelName, setChannelName] = useState2(null);
|
|
419
|
+
const channelRef = useRef2(null);
|
|
420
|
+
const instanceIdRef = useRef2(null);
|
|
421
|
+
const skipBroadcastRef = useRef2(false);
|
|
422
|
+
const latestValuesRef = useRef2(values);
|
|
423
|
+
useEffect2(() => {
|
|
424
|
+
latestValuesRef.current = values;
|
|
425
|
+
}, [values]);
|
|
426
|
+
useEffect2(() => {
|
|
427
|
+
if (typeof window === "undefined") return;
|
|
428
|
+
setChannelName(getControlsChannelName());
|
|
429
|
+
}, []);
|
|
395
430
|
const setValue = (key, value) => {
|
|
396
431
|
setValues((prev) => ({ ...prev, [key]: value }));
|
|
397
432
|
};
|
|
@@ -426,6 +461,66 @@ var ControlsProvider = ({ children }) => {
|
|
|
426
461
|
return updated;
|
|
427
462
|
});
|
|
428
463
|
};
|
|
464
|
+
useEffect2(() => {
|
|
465
|
+
if (!channelName) return;
|
|
466
|
+
if (typeof window === "undefined") return;
|
|
467
|
+
if (typeof window.BroadcastChannel === "undefined") return;
|
|
468
|
+
const instanceId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
|
|
469
|
+
instanceIdRef.current = instanceId;
|
|
470
|
+
const channel = new BroadcastChannel(channelName);
|
|
471
|
+
channelRef.current = channel;
|
|
472
|
+
const sendValues = () => {
|
|
473
|
+
if (!instanceIdRef.current) return;
|
|
474
|
+
channel.postMessage({
|
|
475
|
+
type: "controls-sync-values",
|
|
476
|
+
source: instanceIdRef.current,
|
|
477
|
+
values: latestValuesRef.current
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
const handleMessage = (event) => {
|
|
481
|
+
const data = event.data;
|
|
482
|
+
if (!data || data.source === instanceIdRef.current) return;
|
|
483
|
+
if (data.type === "controls-sync-request") {
|
|
484
|
+
sendValues();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (data.type === "controls-sync-values" && data.values) {
|
|
488
|
+
const incoming = data.values;
|
|
489
|
+
setValues((prev) => {
|
|
490
|
+
const prevKeys = Object.keys(prev);
|
|
491
|
+
const incomingKeys = Object.keys(incoming);
|
|
492
|
+
const sameLength = prevKeys.length === incomingKeys.length;
|
|
493
|
+
const sameValues = sameLength && incomingKeys.every((key) => prev[key] === incoming[key]);
|
|
494
|
+
if (sameValues) return prev;
|
|
495
|
+
skipBroadcastRef.current = true;
|
|
496
|
+
return { ...incoming };
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
channel.addEventListener("message", handleMessage);
|
|
501
|
+
channel.postMessage({
|
|
502
|
+
type: "controls-sync-request",
|
|
503
|
+
source: instanceId
|
|
504
|
+
});
|
|
505
|
+
return () => {
|
|
506
|
+
channel.removeEventListener("message", handleMessage);
|
|
507
|
+
channel.close();
|
|
508
|
+
channelRef.current = null;
|
|
509
|
+
instanceIdRef.current = null;
|
|
510
|
+
};
|
|
511
|
+
}, [channelName]);
|
|
512
|
+
useEffect2(() => {
|
|
513
|
+
if (!channelRef.current || !instanceIdRef.current) return;
|
|
514
|
+
if (skipBroadcastRef.current) {
|
|
515
|
+
skipBroadcastRef.current = false;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
channelRef.current.postMessage({
|
|
519
|
+
type: "controls-sync-values",
|
|
520
|
+
source: instanceIdRef.current,
|
|
521
|
+
values
|
|
522
|
+
});
|
|
523
|
+
}, [values]);
|
|
429
524
|
const contextValue = useMemo(
|
|
430
525
|
() => ({
|
|
431
526
|
schema,
|
|
@@ -535,8 +630,14 @@ var useControls = (schema, options) => {
|
|
|
535
630
|
var useUrlSyncedControls = useControls;
|
|
536
631
|
|
|
537
632
|
// src/components/ControlPanel/ControlPanel.tsx
|
|
538
|
-
import { useState as useState4, useMemo as useMemo3, useCallback as useCallback3 } from "react";
|
|
539
|
-
import {
|
|
633
|
+
import { useState as useState4, useMemo as useMemo3, useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4 } from "react";
|
|
634
|
+
import {
|
|
635
|
+
Check as Check2,
|
|
636
|
+
Copy,
|
|
637
|
+
SquareArrowOutUpRight,
|
|
638
|
+
ChevronDown as ChevronDown2,
|
|
639
|
+
Presentation
|
|
640
|
+
} from "lucide-react";
|
|
540
641
|
|
|
541
642
|
// src/hooks/usePreviewUrl.ts
|
|
542
643
|
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
@@ -545,7 +646,7 @@ var usePreviewUrl = (values, basePath = "") => {
|
|
|
545
646
|
useEffect3(() => {
|
|
546
647
|
if (typeof window === "undefined") return;
|
|
547
648
|
const params = new URLSearchParams();
|
|
548
|
-
params.set(
|
|
649
|
+
params.set(NO_CONTROLS_PARAM, "true");
|
|
549
650
|
for (const [key, value] of Object.entries(values)) {
|
|
550
651
|
if (value !== void 0 && value !== null) {
|
|
551
652
|
params.set(key, value.toString());
|
|
@@ -966,12 +1067,280 @@ var AdvancedPaletteControl_default = AdvancedPaletteControl;
|
|
|
966
1067
|
|
|
967
1068
|
// src/components/ControlPanel/ControlPanel.tsx
|
|
968
1069
|
import { Fragment, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1070
|
+
var splitPropsString = (input) => {
|
|
1071
|
+
const props = [];
|
|
1072
|
+
let current = "";
|
|
1073
|
+
let curlyDepth = 0;
|
|
1074
|
+
let squareDepth = 0;
|
|
1075
|
+
let parenDepth = 0;
|
|
1076
|
+
let inSingleQuote = false;
|
|
1077
|
+
let inDoubleQuote = false;
|
|
1078
|
+
let inBacktick = false;
|
|
1079
|
+
let escapeNext = false;
|
|
1080
|
+
for (const char of input) {
|
|
1081
|
+
if (escapeNext) {
|
|
1082
|
+
current += char;
|
|
1083
|
+
escapeNext = false;
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (char === "\\") {
|
|
1087
|
+
current += char;
|
|
1088
|
+
escapeNext = true;
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (char === "'" && !inDoubleQuote && !inBacktick) {
|
|
1092
|
+
inSingleQuote = !inSingleQuote;
|
|
1093
|
+
current += char;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (char === '"' && !inSingleQuote && !inBacktick) {
|
|
1097
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1098
|
+
current += char;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (char === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1102
|
+
inBacktick = !inBacktick;
|
|
1103
|
+
current += char;
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) {
|
|
1107
|
+
if (char === "{") {
|
|
1108
|
+
curlyDepth += 1;
|
|
1109
|
+
} else if (char === "}") {
|
|
1110
|
+
curlyDepth = Math.max(0, curlyDepth - 1);
|
|
1111
|
+
} else if (char === "[") {
|
|
1112
|
+
squareDepth += 1;
|
|
1113
|
+
} else if (char === "]") {
|
|
1114
|
+
squareDepth = Math.max(0, squareDepth - 1);
|
|
1115
|
+
} else if (char === "(") {
|
|
1116
|
+
parenDepth += 1;
|
|
1117
|
+
} else if (char === ")") {
|
|
1118
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
const atTopLevel = !inSingleQuote && !inDoubleQuote && !inBacktick && curlyDepth === 0 && squareDepth === 0 && parenDepth === 0;
|
|
1122
|
+
if (atTopLevel && /\s/.test(char)) {
|
|
1123
|
+
if (current.trim()) {
|
|
1124
|
+
props.push(current.trim());
|
|
1125
|
+
}
|
|
1126
|
+
current = "";
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
current += char;
|
|
1130
|
+
}
|
|
1131
|
+
if (current.trim()) {
|
|
1132
|
+
props.push(current.trim());
|
|
1133
|
+
}
|
|
1134
|
+
return props;
|
|
1135
|
+
};
|
|
1136
|
+
var formatJsxCodeSnippet = (input) => {
|
|
1137
|
+
const trimmed = input.trim();
|
|
1138
|
+
if (!trimmed) return "";
|
|
1139
|
+
if (trimmed.includes("\n")) {
|
|
1140
|
+
return trimmed;
|
|
1141
|
+
}
|
|
1142
|
+
if (!trimmed.startsWith("<") || !trimmed.endsWith(">")) {
|
|
1143
|
+
return trimmed;
|
|
1144
|
+
}
|
|
1145
|
+
if (!trimmed.endsWith("/>")) {
|
|
1146
|
+
return trimmed;
|
|
1147
|
+
}
|
|
1148
|
+
const inner = trimmed.slice(1, -2).trim();
|
|
1149
|
+
const firstSpaceIndex = inner.indexOf(" ");
|
|
1150
|
+
if (firstSpaceIndex === -1) {
|
|
1151
|
+
return `<${inner} />`;
|
|
1152
|
+
}
|
|
1153
|
+
const componentName = inner.slice(0, firstSpaceIndex);
|
|
1154
|
+
const propsString = inner.slice(firstSpaceIndex + 1).trim();
|
|
1155
|
+
if (!propsString) {
|
|
1156
|
+
return `<${componentName} />`;
|
|
1157
|
+
}
|
|
1158
|
+
const propsList = splitPropsString(propsString);
|
|
1159
|
+
if (propsList.length === 0) {
|
|
1160
|
+
return `<${componentName} ${propsString} />`;
|
|
1161
|
+
}
|
|
1162
|
+
const formattedProps = propsList.map((prop) => ` ${prop}`).join("\n");
|
|
1163
|
+
return `<${componentName}
|
|
1164
|
+
${formattedProps}
|
|
1165
|
+
/>`;
|
|
1166
|
+
};
|
|
1167
|
+
var isWhitespace = (char) => /\s/.test(char);
|
|
1168
|
+
var isAttrNameChar = (char) => /[A-Za-z0-9_$\-.:]/.test(char);
|
|
1169
|
+
var isAlphaStart = (char) => /[A-Za-z_$]/.test(char);
|
|
1170
|
+
var tokenizeJsx = (input) => {
|
|
1171
|
+
const tokens = [];
|
|
1172
|
+
let i = 0;
|
|
1173
|
+
while (i < input.length) {
|
|
1174
|
+
const char = input[i];
|
|
1175
|
+
if (char === "<") {
|
|
1176
|
+
tokens.push({ type: "punctuation", value: "<" });
|
|
1177
|
+
i += 1;
|
|
1178
|
+
if (input[i] === "/") {
|
|
1179
|
+
tokens.push({ type: "punctuation", value: "/" });
|
|
1180
|
+
i += 1;
|
|
1181
|
+
}
|
|
1182
|
+
const start = i;
|
|
1183
|
+
while (i < input.length && isAttrNameChar(input[i])) {
|
|
1184
|
+
i += 1;
|
|
1185
|
+
}
|
|
1186
|
+
if (i > start) {
|
|
1187
|
+
tokens.push({ type: "tag", value: input.slice(start, i) });
|
|
1188
|
+
}
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (char === "/" && input[i + 1] === ">") {
|
|
1192
|
+
tokens.push({ type: "punctuation", value: "/>" });
|
|
1193
|
+
i += 2;
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (char === ">") {
|
|
1197
|
+
tokens.push({ type: "punctuation", value: ">" });
|
|
1198
|
+
i += 1;
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
if (char === "=") {
|
|
1202
|
+
tokens.push({ type: "punctuation", value: "=" });
|
|
1203
|
+
i += 1;
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
1207
|
+
const quote = char;
|
|
1208
|
+
let j = i + 1;
|
|
1209
|
+
let value = quote;
|
|
1210
|
+
while (j < input.length) {
|
|
1211
|
+
const current = input[j];
|
|
1212
|
+
value += current;
|
|
1213
|
+
if (current === quote && input[j - 1] !== "\\") {
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
j += 1;
|
|
1217
|
+
}
|
|
1218
|
+
tokens.push({ type: "string", value });
|
|
1219
|
+
i = j + 1;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (char === "{") {
|
|
1223
|
+
let depth = 1;
|
|
1224
|
+
let j = i + 1;
|
|
1225
|
+
while (j < input.length && depth > 0) {
|
|
1226
|
+
if (input[j] === "{") {
|
|
1227
|
+
depth += 1;
|
|
1228
|
+
} else if (input[j] === "}") {
|
|
1229
|
+
depth -= 1;
|
|
1230
|
+
}
|
|
1231
|
+
j += 1;
|
|
1232
|
+
}
|
|
1233
|
+
const expression = input.slice(i, j);
|
|
1234
|
+
tokens.push({ type: "expression", value: expression });
|
|
1235
|
+
i = j;
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
if (isAlphaStart(char)) {
|
|
1239
|
+
const start = i;
|
|
1240
|
+
i += 1;
|
|
1241
|
+
while (i < input.length && isAttrNameChar(input[i])) {
|
|
1242
|
+
i += 1;
|
|
1243
|
+
}
|
|
1244
|
+
const word = input.slice(start, i);
|
|
1245
|
+
let k = i;
|
|
1246
|
+
while (k < input.length && isWhitespace(input[k])) {
|
|
1247
|
+
k += 1;
|
|
1248
|
+
}
|
|
1249
|
+
if (input[k] === "=") {
|
|
1250
|
+
tokens.push({ type: "attrName", value: word });
|
|
1251
|
+
} else {
|
|
1252
|
+
tokens.push({ type: "plain", value: word });
|
|
1253
|
+
}
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1256
|
+
tokens.push({ type: "plain", value: char });
|
|
1257
|
+
i += 1;
|
|
1258
|
+
}
|
|
1259
|
+
return tokens;
|
|
1260
|
+
};
|
|
1261
|
+
var TOKEN_CLASS_MAP = {
|
|
1262
|
+
tag: "text-sky-300",
|
|
1263
|
+
attrName: "text-amber-200",
|
|
1264
|
+
string: "text-emerald-300",
|
|
1265
|
+
expression: "text-purple-300",
|
|
1266
|
+
punctuation: "text-stone-400"
|
|
1267
|
+
};
|
|
1268
|
+
var highlightJsx = (input) => {
|
|
1269
|
+
const tokens = tokenizeJsx(input);
|
|
1270
|
+
const nodes = [];
|
|
1271
|
+
tokens.forEach((token, index) => {
|
|
1272
|
+
if (token.type === "plain") {
|
|
1273
|
+
nodes.push(token.value);
|
|
1274
|
+
} else {
|
|
1275
|
+
nodes.push(
|
|
1276
|
+
/* @__PURE__ */ jsx10("span", { className: TOKEN_CLASS_MAP[token.type], children: token.value }, `token-${index}`)
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
return nodes;
|
|
1281
|
+
};
|
|
969
1282
|
var ControlPanel = () => {
|
|
970
1283
|
const [copied, setCopied] = useState4(false);
|
|
1284
|
+
const [codeCopied, setCodeCopied] = useState4(false);
|
|
1285
|
+
const [isCodeVisible, setIsCodeVisible] = useState4(false);
|
|
971
1286
|
const [folderStates, setFolderStates] = useState4({});
|
|
1287
|
+
const codeCopyTimeoutRef = useRef4(null);
|
|
972
1288
|
const { leftPanelWidth, isDesktop, isHydrated } = useResizableLayout();
|
|
973
1289
|
const { schema, setValue, values, componentName, config } = useControlsContext();
|
|
1290
|
+
const isControlsOnlyView = typeof window !== "undefined" && new URLSearchParams(window.location.search).get(CONTROLS_ONLY_PARAM) === "true";
|
|
974
1291
|
const previewUrl = usePreviewUrl(values);
|
|
1292
|
+
const buildUrl = useCallback3(
|
|
1293
|
+
(modifier) => {
|
|
1294
|
+
if (!previewUrl) return "";
|
|
1295
|
+
const [path, search = ""] = previewUrl.split("?");
|
|
1296
|
+
const params = new URLSearchParams(search);
|
|
1297
|
+
modifier(params);
|
|
1298
|
+
const query = params.toString();
|
|
1299
|
+
return query ? `${path}?${query}` : path;
|
|
1300
|
+
},
|
|
1301
|
+
[previewUrl]
|
|
1302
|
+
);
|
|
1303
|
+
const presentationUrl = useMemo3(() => {
|
|
1304
|
+
if (!previewUrl) return "";
|
|
1305
|
+
return buildUrl((params) => {
|
|
1306
|
+
params.set(PRESENTATION_PARAM, "true");
|
|
1307
|
+
});
|
|
1308
|
+
}, [buildUrl, previewUrl]);
|
|
1309
|
+
const controlsOnlyUrl = useMemo3(() => {
|
|
1310
|
+
if (!previewUrl) return "";
|
|
1311
|
+
return buildUrl((params) => {
|
|
1312
|
+
params.delete(NO_CONTROLS_PARAM);
|
|
1313
|
+
params.delete(PRESENTATION_PARAM);
|
|
1314
|
+
params.set(CONTROLS_ONLY_PARAM, "true");
|
|
1315
|
+
});
|
|
1316
|
+
}, [buildUrl, previewUrl]);
|
|
1317
|
+
const handlePresentationClick = useCallback3(() => {
|
|
1318
|
+
if (typeof window === "undefined" || !presentationUrl) return;
|
|
1319
|
+
window.open(presentationUrl, "_blank", "noopener,noreferrer");
|
|
1320
|
+
if (controlsOnlyUrl) {
|
|
1321
|
+
const viewportWidth = window.innerWidth || 1200;
|
|
1322
|
+
const viewportHeight = window.innerHeight || 900;
|
|
1323
|
+
const controlsWidth = Math.max(
|
|
1324
|
+
320,
|
|
1325
|
+
Math.min(
|
|
1326
|
+
600,
|
|
1327
|
+
Math.round(viewportWidth * leftPanelWidth / 100)
|
|
1328
|
+
)
|
|
1329
|
+
);
|
|
1330
|
+
const controlsHeight = Math.max(600, viewportHeight);
|
|
1331
|
+
const controlsFeatures = [
|
|
1332
|
+
"noopener",
|
|
1333
|
+
"noreferrer",
|
|
1334
|
+
"toolbar=0",
|
|
1335
|
+
"menubar=0",
|
|
1336
|
+
"resizable=yes",
|
|
1337
|
+
"scrollbars=yes",
|
|
1338
|
+
`width=${controlsWidth}`,
|
|
1339
|
+
`height=${controlsHeight}`
|
|
1340
|
+
].join(",");
|
|
1341
|
+
window.open(controlsOnlyUrl, "v0-controls", controlsFeatures);
|
|
1342
|
+
}
|
|
1343
|
+
}, [controlsOnlyUrl, leftPanelWidth, presentationUrl]);
|
|
975
1344
|
const jsx14 = useMemo3(() => {
|
|
976
1345
|
if (!componentName) return "";
|
|
977
1346
|
const props = Object.entries(values).map(([key, val]) => {
|
|
@@ -1092,6 +1461,65 @@ var ControlPanel = () => {
|
|
|
1092
1461
|
jsonToComponentString
|
|
1093
1462
|
}) ?? jsx14;
|
|
1094
1463
|
const shouldShowCopyButton = config?.showCopyButton !== false && Boolean(copyText);
|
|
1464
|
+
const baseSnippet = copyText || jsx14;
|
|
1465
|
+
const formattedCode = useMemo3(
|
|
1466
|
+
() => formatJsxCodeSnippet(baseSnippet),
|
|
1467
|
+
[baseSnippet]
|
|
1468
|
+
);
|
|
1469
|
+
const hasCodeSnippet = Boolean(config?.showCodeSnippet && formattedCode);
|
|
1470
|
+
const highlightedCode = useMemo3(
|
|
1471
|
+
() => formattedCode ? highlightJsx(formattedCode) : null,
|
|
1472
|
+
[formattedCode]
|
|
1473
|
+
);
|
|
1474
|
+
useEffect5(() => {
|
|
1475
|
+
if (!hasCodeSnippet) {
|
|
1476
|
+
setIsCodeVisible(false);
|
|
1477
|
+
}
|
|
1478
|
+
}, [hasCodeSnippet]);
|
|
1479
|
+
useEffect5(() => {
|
|
1480
|
+
setCodeCopied(false);
|
|
1481
|
+
if (codeCopyTimeoutRef.current) {
|
|
1482
|
+
clearTimeout(codeCopyTimeoutRef.current);
|
|
1483
|
+
codeCopyTimeoutRef.current = null;
|
|
1484
|
+
}
|
|
1485
|
+
}, [formattedCode]);
|
|
1486
|
+
useEffect5(() => {
|
|
1487
|
+
return () => {
|
|
1488
|
+
if (codeCopyTimeoutRef.current) {
|
|
1489
|
+
clearTimeout(codeCopyTimeoutRef.current);
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
}, []);
|
|
1493
|
+
const handleToggleCodeVisibility = useCallback3(() => {
|
|
1494
|
+
setIsCodeVisible((prev) => {
|
|
1495
|
+
const next = !prev;
|
|
1496
|
+
if (!next) {
|
|
1497
|
+
setCodeCopied(false);
|
|
1498
|
+
if (codeCopyTimeoutRef.current) {
|
|
1499
|
+
clearTimeout(codeCopyTimeoutRef.current);
|
|
1500
|
+
codeCopyTimeoutRef.current = null;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return next;
|
|
1504
|
+
});
|
|
1505
|
+
}, []);
|
|
1506
|
+
const handleCodeCopy = useCallback3(() => {
|
|
1507
|
+
if (!formattedCode) return;
|
|
1508
|
+
if (typeof navigator === "undefined" || !navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
navigator.clipboard.writeText(formattedCode).then(() => {
|
|
1512
|
+
setCodeCopied(true);
|
|
1513
|
+
if (codeCopyTimeoutRef.current) {
|
|
1514
|
+
clearTimeout(codeCopyTimeoutRef.current);
|
|
1515
|
+
}
|
|
1516
|
+
codeCopyTimeoutRef.current = setTimeout(() => {
|
|
1517
|
+
setCodeCopied(false);
|
|
1518
|
+
codeCopyTimeoutRef.current = null;
|
|
1519
|
+
}, 3e3);
|
|
1520
|
+
}).catch(() => {
|
|
1521
|
+
});
|
|
1522
|
+
}, [formattedCode]);
|
|
1095
1523
|
const labelize = (key) => key.replace(/([A-Z])/g, " $1").replace(/[\-_]/g, " ").replace(/\s+/g, " ").trim().replace(/(^|\s)\S/g, (s) => s.toUpperCase());
|
|
1096
1524
|
const renderButtonControl = (key, control, variant) => /* @__PURE__ */ jsx10(
|
|
1097
1525
|
"div",
|
|
@@ -1258,7 +1686,7 @@ var ControlPanel = () => {
|
|
|
1258
1686
|
height: "auto",
|
|
1259
1687
|
flex: "0 0 auto"
|
|
1260
1688
|
};
|
|
1261
|
-
if (isHydrated) {
|
|
1689
|
+
if (isHydrated && !isControlsOnlyView) {
|
|
1262
1690
|
if (isDesktop) {
|
|
1263
1691
|
Object.assign(panelStyle, {
|
|
1264
1692
|
position: "absolute",
|
|
@@ -1294,12 +1722,51 @@ var ControlPanel = () => {
|
|
|
1294
1722
|
([key, control]) => renderControl(key, control, "root")
|
|
1295
1723
|
),
|
|
1296
1724
|
bottomFolderSections,
|
|
1725
|
+
hasCodeSnippet && /* @__PURE__ */ jsxs5("div", { className: "border border-stone-700/60 rounded-lg bg-stone-900/70", children: [
|
|
1726
|
+
/* @__PURE__ */ jsxs5(
|
|
1727
|
+
"button",
|
|
1728
|
+
{
|
|
1729
|
+
type: "button",
|
|
1730
|
+
onClick: handleToggleCodeVisibility,
|
|
1731
|
+
className: "w-full flex items-center justify-between px-4 py-3 text-left font-semibold text-stone-200 tracking-wide",
|
|
1732
|
+
"aria-expanded": isCodeVisible,
|
|
1733
|
+
children: [
|
|
1734
|
+
/* @__PURE__ */ jsx10("span", { children: isCodeVisible ? "Hide Code" : "Show Code" }),
|
|
1735
|
+
/* @__PURE__ */ jsx10(
|
|
1736
|
+
ChevronDown2,
|
|
1737
|
+
{
|
|
1738
|
+
className: `w-4 h-4 transition-transform duration-200 ${isCodeVisible ? "rotate-180" : ""}`
|
|
1739
|
+
}
|
|
1740
|
+
)
|
|
1741
|
+
]
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
isCodeVisible && /* @__PURE__ */ jsxs5("div", { className: "relative border-t border-stone-700/60 bg-stone-950/60 px-4 py-4 rounded-b-lg", children: [
|
|
1745
|
+
/* @__PURE__ */ jsx10(
|
|
1746
|
+
"button",
|
|
1747
|
+
{
|
|
1748
|
+
type: "button",
|
|
1749
|
+
onClick: handleCodeCopy,
|
|
1750
|
+
className: "absolute top-3 right-3 flex items-center gap-1 rounded-md border border-stone-700 bg-stone-800 px-2 py-1 text-xs font-medium text-white shadow hover:bg-stone-700",
|
|
1751
|
+
children: codeCopied ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1752
|
+
/* @__PURE__ */ jsx10(Check2, { className: "h-3.5 w-3.5" }),
|
|
1753
|
+
"Copied"
|
|
1754
|
+
] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1755
|
+
/* @__PURE__ */ jsx10(Copy, { className: "h-3.5 w-3.5" }),
|
|
1756
|
+
"Copy"
|
|
1757
|
+
] })
|
|
1758
|
+
}
|
|
1759
|
+
),
|
|
1760
|
+
/* @__PURE__ */ jsx10("pre", { className: "whitespace-pre overflow-x-auto text-xs md:text-sm text-stone-200 pr-14", children: /* @__PURE__ */ jsx10("code", { className: "block text-stone-200", children: highlightedCode ?? formattedCode }) })
|
|
1761
|
+
] })
|
|
1762
|
+
] }),
|
|
1297
1763
|
shouldShowCopyButton && /* @__PURE__ */ jsx10("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx10(
|
|
1298
1764
|
"button",
|
|
1299
1765
|
{
|
|
1300
1766
|
onClick: () => {
|
|
1301
|
-
|
|
1302
|
-
|
|
1767
|
+
const copyPayload = formattedCode || baseSnippet;
|
|
1768
|
+
if (!copyPayload) return;
|
|
1769
|
+
navigator.clipboard.writeText(copyPayload);
|
|
1303
1770
|
setCopied(true);
|
|
1304
1771
|
setTimeout(() => setCopied(false), 5e3);
|
|
1305
1772
|
},
|
|
@@ -1314,19 +1781,34 @@ var ControlPanel = () => {
|
|
|
1314
1781
|
}
|
|
1315
1782
|
) }, "control-panel-jsx")
|
|
1316
1783
|
] }),
|
|
1317
|
-
previewUrl && /* @__PURE__ */
|
|
1318
|
-
"
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1784
|
+
previewUrl && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
|
|
1785
|
+
/* @__PURE__ */ jsx10(Button, { asChild: true, className: "w-full", children: /* @__PURE__ */ jsxs5(
|
|
1786
|
+
"a",
|
|
1787
|
+
{
|
|
1788
|
+
href: previewUrl,
|
|
1789
|
+
target: "_blank",
|
|
1790
|
+
rel: "noopener noreferrer",
|
|
1791
|
+
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",
|
|
1792
|
+
children: [
|
|
1793
|
+
/* @__PURE__ */ jsx10(SquareArrowOutUpRight, {}),
|
|
1794
|
+
" Open in a New Tab"
|
|
1795
|
+
]
|
|
1796
|
+
}
|
|
1797
|
+
) }),
|
|
1798
|
+
config?.showPresentationButton && presentationUrl && /* @__PURE__ */ jsxs5(
|
|
1799
|
+
Button,
|
|
1800
|
+
{
|
|
1801
|
+
type: "button",
|
|
1802
|
+
onClick: handlePresentationClick,
|
|
1803
|
+
variant: "secondary",
|
|
1804
|
+
className: "w-full bg-stone-800 text-white hover:bg-stone-700 border border-stone-700",
|
|
1805
|
+
children: [
|
|
1806
|
+
/* @__PURE__ */ jsx10(Presentation, {}),
|
|
1807
|
+
" Presentation Mode"
|
|
1808
|
+
]
|
|
1809
|
+
}
|
|
1810
|
+
)
|
|
1811
|
+
] })
|
|
1330
1812
|
] })
|
|
1331
1813
|
}
|
|
1332
1814
|
);
|
|
@@ -1334,7 +1816,7 @@ var ControlPanel = () => {
|
|
|
1334
1816
|
var ControlPanel_default = ControlPanel;
|
|
1335
1817
|
|
|
1336
1818
|
// src/components/PreviewContainer/PreviewContainer.tsx
|
|
1337
|
-
import { useRef as
|
|
1819
|
+
import { useRef as useRef5 } from "react";
|
|
1338
1820
|
|
|
1339
1821
|
// src/components/Grid/Grid.tsx
|
|
1340
1822
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
@@ -1361,7 +1843,7 @@ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
1361
1843
|
var PreviewContainer = ({ children, hideControls }) => {
|
|
1362
1844
|
const { config } = useControlsContext();
|
|
1363
1845
|
const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
|
|
1364
|
-
const previewRef =
|
|
1846
|
+
const previewRef = useRef5(null);
|
|
1365
1847
|
return /* @__PURE__ */ jsx12(
|
|
1366
1848
|
"div",
|
|
1367
1849
|
{
|
|
@@ -1382,25 +1864,42 @@ var PreviewContainer_default = PreviewContainer;
|
|
|
1382
1864
|
|
|
1383
1865
|
// src/components/Playground/Playground.tsx
|
|
1384
1866
|
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1385
|
-
var
|
|
1867
|
+
var HiddenPreview = ({ children }) => /* @__PURE__ */ jsx13("div", { "aria-hidden": "true", className: "hidden", children });
|
|
1386
1868
|
function Playground({ children }) {
|
|
1387
1869
|
const [isHydrated, setIsHydrated] = useState5(false);
|
|
1388
1870
|
const [copied, setCopied] = useState5(false);
|
|
1389
|
-
|
|
1871
|
+
useEffect6(() => {
|
|
1390
1872
|
setIsHydrated(true);
|
|
1391
1873
|
}, []);
|
|
1392
|
-
const
|
|
1393
|
-
if (typeof window === "undefined")
|
|
1394
|
-
|
|
1874
|
+
const { showControls, isPresentationMode, isControlsOnly } = useMemo4(() => {
|
|
1875
|
+
if (typeof window === "undefined") {
|
|
1876
|
+
return {
|
|
1877
|
+
showControls: true,
|
|
1878
|
+
isPresentationMode: false,
|
|
1879
|
+
isControlsOnly: false
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
const params = new URLSearchParams(window.location.search);
|
|
1883
|
+
const presentation = params.get(PRESENTATION_PARAM) === "true";
|
|
1884
|
+
const controlsOnly = params.get(CONTROLS_ONLY_PARAM) === "true";
|
|
1885
|
+
const noControlsParam = params.get(NO_CONTROLS_PARAM) === "true";
|
|
1886
|
+
const showControlsValue = controlsOnly || !presentation && !noControlsParam;
|
|
1887
|
+
return {
|
|
1888
|
+
showControls: showControlsValue,
|
|
1889
|
+
isPresentationMode: presentation,
|
|
1890
|
+
isControlsOnly: controlsOnly
|
|
1891
|
+
};
|
|
1395
1892
|
}, []);
|
|
1893
|
+
const shouldShowShareButton = !showControls && !isPresentationMode;
|
|
1894
|
+
const layoutHideControls = !showControls || isControlsOnly;
|
|
1396
1895
|
const handleCopy = () => {
|
|
1397
1896
|
navigator.clipboard.writeText(window.location.href);
|
|
1398
1897
|
setCopied(true);
|
|
1399
1898
|
setTimeout(() => setCopied(false), 2e3);
|
|
1400
1899
|
};
|
|
1401
1900
|
if (!isHydrated) return null;
|
|
1402
|
-
return /* @__PURE__ */ jsx13(ResizableLayout, { hideControls, children: /* @__PURE__ */ jsxs7(ControlsProvider, { children: [
|
|
1403
|
-
|
|
1901
|
+
return /* @__PURE__ */ jsx13(ResizableLayout, { hideControls: layoutHideControls, children: /* @__PURE__ */ jsxs7(ControlsProvider, { children: [
|
|
1902
|
+
shouldShowShareButton && /* @__PURE__ */ jsxs7(
|
|
1404
1903
|
"button",
|
|
1405
1904
|
{
|
|
1406
1905
|
onClick: handleCopy,
|
|
@@ -1411,13 +1910,13 @@ function Playground({ children }) {
|
|
|
1411
1910
|
]
|
|
1412
1911
|
}
|
|
1413
1912
|
),
|
|
1414
|
-
/* @__PURE__ */ jsx13(PreviewContainer_default, { hideControls, children }),
|
|
1415
|
-
|
|
1913
|
+
isControlsOnly ? /* @__PURE__ */ jsx13(HiddenPreview, { children }) : /* @__PURE__ */ jsx13(PreviewContainer_default, { hideControls: layoutHideControls, children }),
|
|
1914
|
+
showControls && /* @__PURE__ */ jsx13(ControlPanel_default, {})
|
|
1416
1915
|
] }) });
|
|
1417
1916
|
}
|
|
1418
1917
|
|
|
1419
1918
|
// src/hooks/useAdvancedPaletteControls.ts
|
|
1420
|
-
import { useCallback as useCallback4, useEffect as
|
|
1919
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo5, useRef as useRef6, useState as useState6 } from "react";
|
|
1421
1920
|
var cloneForCallbacks = (palette) => clonePalette(palette);
|
|
1422
1921
|
var useAdvancedPaletteControls = (options = {}) => {
|
|
1423
1922
|
const resolvedDefaultPalette = useMemo5(
|
|
@@ -1431,10 +1930,10 @@ var useAdvancedPaletteControls = (options = {}) => {
|
|
|
1431
1930
|
const [palette, setPaletteState] = useState6(
|
|
1432
1931
|
() => clonePalette(resolvedDefaultPalette)
|
|
1433
1932
|
);
|
|
1434
|
-
const defaultSignatureRef =
|
|
1933
|
+
const defaultSignatureRef = useRef6(
|
|
1435
1934
|
createPaletteSignature(resolvedDefaultPalette)
|
|
1436
1935
|
);
|
|
1437
|
-
|
|
1936
|
+
useEffect7(() => {
|
|
1438
1937
|
const nextSignature = createPaletteSignature(resolvedDefaultPalette);
|
|
1439
1938
|
if (defaultSignatureRef.current === nextSignature) return;
|
|
1440
1939
|
defaultSignatureRef.current = nextSignature;
|