@btst/stack 2.5.4 → 2.5.6
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/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.cjs +3 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.mjs +3 -1
- package/dist/packages/stack/src/plugins/ui-builder/client/registry.cjs +309 -37
- package/dist/packages/stack/src/plugins/ui-builder/client/registry.mjs +310 -38
- package/dist/packages/ui/src/lib/ui-builder/registry/form-field-overrides.cjs +207 -0
- package/dist/packages/ui/src/lib/ui-builder/registry/form-field-overrides.mjs +206 -1
- package/dist/plugins/ui-builder/client/index.d.cts +6 -1
- package/dist/plugins/ui-builder/client/index.d.mts +6 -1
- package/dist/plugins/ui-builder/client/index.d.ts +6 -1
- package/package.json +1 -1
- package/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.tsx +2 -0
- package/src/plugins/ui-builder/client/overrides.ts +10 -1
- package/src/plugins/ui-builder/client/registry.ts +295 -19
|
@@ -20,6 +20,7 @@ const card = require('../../../components/card.cjs');
|
|
|
20
20
|
const breakpointClassnameControl = require('../../../components/ui-builder/internal/form-fields/classname-control/breakpoint-classname-control.cjs');
|
|
21
21
|
const label = require('../../../components/label.cjs');
|
|
22
22
|
const badge = require('../../../components/badge.cjs');
|
|
23
|
+
const select = require('../../../components/select.cjs');
|
|
23
24
|
|
|
24
25
|
const classNameFieldOverrides = (layer) => {
|
|
25
26
|
return {
|
|
@@ -167,6 +168,210 @@ const commonFieldOverrides = (allowBinding = true) => {
|
|
|
167
168
|
memoizedCommonFieldOverrides.set(allowBinding, overrides);
|
|
168
169
|
return overrides;
|
|
169
170
|
};
|
|
171
|
+
function FunctionPropField({
|
|
172
|
+
propName,
|
|
173
|
+
label,
|
|
174
|
+
isRequired,
|
|
175
|
+
fieldConfigItem
|
|
176
|
+
}) {
|
|
177
|
+
const selectedLayerId = layerStore.useLayerStore((state) => state.selectedLayerId);
|
|
178
|
+
const findLayerById = layerStore.useLayerStore((state) => state.findLayerById);
|
|
179
|
+
const updateLayer = layerStore.useLayerStore((state) => state.updateLayer);
|
|
180
|
+
const incrementRevision = editorStore.useEditorStore((state) => state.incrementRevision);
|
|
181
|
+
const functionRegistry = editorStore.useEditorStore((state) => state.functionRegistry);
|
|
182
|
+
const unbindPropFromVariable = layerStore.useLayerStore(
|
|
183
|
+
(state) => state.unbindPropFromVariable
|
|
184
|
+
);
|
|
185
|
+
const selectedLayer = findLayerById(selectedLayerId);
|
|
186
|
+
if (!selectedLayer || !functionRegistry) {
|
|
187
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
188
|
+
FormFieldWrapper,
|
|
189
|
+
{
|
|
190
|
+
label,
|
|
191
|
+
isRequired,
|
|
192
|
+
fieldConfigItem,
|
|
193
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(select.Select, { disabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectTrigger, { children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectValue, { placeholder: "No function registry available" }) }) })
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const functionEntries = Object.entries(functionRegistry);
|
|
198
|
+
const getCurrentFunctionId = () => {
|
|
199
|
+
const directFuncId = selectedLayer.props[`__function_${propName}`];
|
|
200
|
+
if (typeof directFuncId === "string") {
|
|
201
|
+
return directFuncId;
|
|
202
|
+
}
|
|
203
|
+
return "";
|
|
204
|
+
};
|
|
205
|
+
const handleValueChange = (value) => {
|
|
206
|
+
if (value === "__none__") {
|
|
207
|
+
unbindPropFromVariable(selectedLayer.id, propName);
|
|
208
|
+
updateLayer(selectedLayer.id, {
|
|
209
|
+
[`__function_${propName}`]: void 0,
|
|
210
|
+
[propName]: void 0
|
|
211
|
+
});
|
|
212
|
+
incrementRevision();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const funcDef = functionRegistry[value];
|
|
216
|
+
if (funcDef) {
|
|
217
|
+
unbindPropFromVariable(selectedLayer.id, propName);
|
|
218
|
+
updateLayer(selectedLayer.id, {
|
|
219
|
+
[propName]: funcDef.fn,
|
|
220
|
+
[`__function_${propName}`]: value
|
|
221
|
+
});
|
|
222
|
+
incrementRevision();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const currentFunctionId = getCurrentFunctionId();
|
|
226
|
+
const getDisplayText = () => {
|
|
227
|
+
if (currentFunctionId) {
|
|
228
|
+
const funcDef = functionRegistry[currentFunctionId];
|
|
229
|
+
return funcDef?.name || currentFunctionId;
|
|
230
|
+
}
|
|
231
|
+
return "Select a function...";
|
|
232
|
+
};
|
|
233
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
234
|
+
FormFieldWrapper,
|
|
235
|
+
{
|
|
236
|
+
label,
|
|
237
|
+
isRequired,
|
|
238
|
+
fieldConfigItem,
|
|
239
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
240
|
+
select.Select,
|
|
241
|
+
{
|
|
242
|
+
value: currentFunctionId,
|
|
243
|
+
onValueChange: handleValueChange,
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ jsxRuntime.jsx(select.SelectTrigger, { className: "w-full mb-0", children: /* @__PURE__ */ jsxRuntime.jsx(select.SelectValue, { placeholder: "Select a function...", children: getDisplayText() }) }),
|
|
246
|
+
/* @__PURE__ */ jsxRuntime.jsxs(select.SelectContent, { children: [
|
|
247
|
+
/* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: "__none__", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "None (clear)" }) }),
|
|
248
|
+
functionEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: functionEntries.map(([id, funcDef]) => /* @__PURE__ */ jsxRuntime.jsx(select.SelectItem, { value: id, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
249
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: funcDef.name }),
|
|
250
|
+
funcDef.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: funcDef.description })
|
|
251
|
+
] }) }, id)) })
|
|
252
|
+
] })
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const functionPropFieldOverrides = (propName) => {
|
|
260
|
+
return {
|
|
261
|
+
renderParent: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(VariableBindingWrapper, { propName, isFunctionProp: true, children }),
|
|
262
|
+
fieldType: (props) => /* @__PURE__ */ jsxRuntime.jsx(FunctionPropField, { propName, ...props })
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
function VariableBindingWrapper({
|
|
266
|
+
propName,
|
|
267
|
+
children,
|
|
268
|
+
isFunctionProp = false
|
|
269
|
+
}) {
|
|
270
|
+
const variables = layerStore.useLayerStore((state) => state.variables);
|
|
271
|
+
const selectedLayerId = layerStore.useLayerStore((state) => state.selectedLayerId);
|
|
272
|
+
const findLayerById = layerStore.useLayerStore((state) => state.findLayerById);
|
|
273
|
+
const isBindingImmutable = layerStore.useLayerStore((state) => state.isBindingImmutable);
|
|
274
|
+
const incrementRevision = editorStore.useEditorStore((state) => state.incrementRevision);
|
|
275
|
+
const functionRegistry = editorStore.useEditorStore((state) => state.functionRegistry);
|
|
276
|
+
const unbindPropFromVariable = layerStore.useLayerStore(
|
|
277
|
+
(state) => state.unbindPropFromVariable
|
|
278
|
+
);
|
|
279
|
+
const bindPropToVariable = layerStore.useLayerStore((state) => state.bindPropToVariable);
|
|
280
|
+
const selectedLayer = findLayerById(selectedLayerId);
|
|
281
|
+
if (!selectedLayer) {
|
|
282
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
283
|
+
}
|
|
284
|
+
const filteredVariables = variables.filter(
|
|
285
|
+
(v) => isFunctionProp ? v.type === "function" : v.type !== "function"
|
|
286
|
+
);
|
|
287
|
+
const currentValue = selectedLayer.props[propName];
|
|
288
|
+
const isCurrentlyBound = types.isVariableReference(currentValue);
|
|
289
|
+
const boundVariable = isCurrentlyBound ? variables.find((v) => v.id === currentValue.__variableRef) : null;
|
|
290
|
+
const isImmutable = isBindingImmutable(selectedLayer.id, propName);
|
|
291
|
+
const getFunctionDisplayValue = (variable) => {
|
|
292
|
+
if (variable.type === "function" && functionRegistry) {
|
|
293
|
+
const funcId = String(variable.defaultValue);
|
|
294
|
+
const funcDef = functionRegistry[funcId];
|
|
295
|
+
return funcDef ? funcDef.name : funcId;
|
|
296
|
+
}
|
|
297
|
+
return String(variable.defaultValue);
|
|
298
|
+
};
|
|
299
|
+
const handleBindToVariable = (variableId) => {
|
|
300
|
+
bindPropToVariable(selectedLayer.id, propName, variableId);
|
|
301
|
+
incrementRevision();
|
|
302
|
+
};
|
|
303
|
+
const handleUnbind = () => {
|
|
304
|
+
unbindPropFromVariable(selectedLayer.id, propName);
|
|
305
|
+
incrementRevision();
|
|
306
|
+
};
|
|
307
|
+
const emptyMessage = isFunctionProp ? "No function variables defined" : "No variables defined";
|
|
308
|
+
const bindLabel = isFunctionProp ? "Bind to Function Variable" : "Bind to Variable";
|
|
309
|
+
const tooltipLabel = isFunctionProp ? "Bind Function" : "Bind Variable";
|
|
310
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full gap-2 items-end", children: isCurrentlyBound && boundVariable ? (
|
|
311
|
+
// Bound state - show variable info and unbind button
|
|
312
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsx(label.Label, { children: propName.charAt(0).toUpperCase() + propName.slice(1) }),
|
|
314
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 w-full", children: [
|
|
315
|
+
/* @__PURE__ */ jsxRuntime.jsx(card.Card, { className: "w-full overflow-hidden min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(card.CardContent, { className: "py-1 px-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
316
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "h-4 w-4 flex-shrink-0" }),
|
|
317
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
318
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
319
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: boundVariable.name }),
|
|
320
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: boundVariable.type }),
|
|
321
|
+
isImmutable && /* @__PURE__ */ jsxRuntime.jsx(badge.Badge, { "data-testid": "immutable-badge", className: "rounded", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.LockKeyhole, { strokeWidth: 3, className: "w-3 h-3" }) })
|
|
322
|
+
] }),
|
|
323
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(boundVariable) })
|
|
324
|
+
] })
|
|
325
|
+
] }) }) }),
|
|
326
|
+
!isImmutable && /* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
|
|
327
|
+
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
328
|
+
button.Button,
|
|
329
|
+
{
|
|
330
|
+
variant: "outline",
|
|
331
|
+
onClick: handleUnbind,
|
|
332
|
+
className: "px-3 h-10",
|
|
333
|
+
"data-testid": "unbind-variable-button",
|
|
334
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Unlink, { className: "h-4 w-4" })
|
|
335
|
+
}
|
|
336
|
+
) }),
|
|
337
|
+
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipContent, { children: "Unbind Variable" })
|
|
338
|
+
] })
|
|
339
|
+
] })
|
|
340
|
+
] })
|
|
341
|
+
) : (
|
|
342
|
+
// Unbound state - show normal field with bind button
|
|
343
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
344
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children }),
|
|
345
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(dropdownMenu.DropdownMenu, { children: [
|
|
346
|
+
/* @__PURE__ */ jsxRuntime.jsxs(tooltip.Tooltip, { children: [
|
|
347
|
+
/* @__PURE__ */ jsxRuntime.jsx(dropdownMenu.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: "outline", size: "sm", className: "px-3 h-10", "data-testid": "bind-variable-button", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "h-4 w-4 my-1" }) }) }) }),
|
|
348
|
+
/* @__PURE__ */ jsxRuntime.jsx(tooltip.TooltipContent, { children: tooltipLabel })
|
|
349
|
+
] }),
|
|
350
|
+
/* @__PURE__ */ jsxRuntime.jsxs(dropdownMenu.DropdownMenuContent, { align: "end", className: "w-56", children: [
|
|
351
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-medium text-muted-foreground border-b", children: bindLabel }),
|
|
352
|
+
filteredVariables.length > 0 ? filteredVariables.map((variable) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
353
|
+
dropdownMenu.DropdownMenuItem,
|
|
354
|
+
{
|
|
355
|
+
onClick: () => handleBindToVariable(variable.id),
|
|
356
|
+
className: "flex flex-col items-start p-3",
|
|
357
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
358
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Link, { className: "h-4 w-4 flex-shrink-0" }),
|
|
359
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 ", children: [
|
|
361
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: variable.name }),
|
|
362
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: variable.type })
|
|
363
|
+
] }),
|
|
364
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(variable) })
|
|
365
|
+
] })
|
|
366
|
+
] })
|
|
367
|
+
},
|
|
368
|
+
variable.id
|
|
369
|
+
)) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: emptyMessage })
|
|
370
|
+
] })
|
|
371
|
+
] }) })
|
|
372
|
+
] })
|
|
373
|
+
) });
|
|
374
|
+
}
|
|
170
375
|
function FormFieldWrapper({
|
|
171
376
|
label,
|
|
172
377
|
isRequired,
|
|
@@ -278,9 +483,11 @@ function ChildrenVariableBindingWrapper({
|
|
|
278
483
|
|
|
279
484
|
exports.ChildrenVariableBindingWrapper = ChildrenVariableBindingWrapper;
|
|
280
485
|
exports.FormFieldWrapper = FormFieldWrapper;
|
|
486
|
+
exports.VariableBindingWrapper = VariableBindingWrapper;
|
|
281
487
|
exports.childrenAsTextareaFieldOverrides = childrenAsTextareaFieldOverrides;
|
|
282
488
|
exports.childrenAsTipTapFieldOverrides = childrenAsTipTapFieldOverrides;
|
|
283
489
|
exports.childrenFieldOverrides = childrenFieldOverrides;
|
|
284
490
|
exports.classNameFieldOverrides = classNameFieldOverrides;
|
|
285
491
|
exports.commonFieldOverrides = commonFieldOverrides;
|
|
492
|
+
exports.functionPropFieldOverrides = functionPropFieldOverrides;
|
|
286
493
|
exports.iconNameFieldOverrides = iconNameFieldOverrides;
|
|
@@ -18,6 +18,7 @@ import { Card, CardContent } from '../../../components/card.mjs';
|
|
|
18
18
|
import { BreakpointClassNameControl } from '../../../components/ui-builder/internal/form-fields/classname-control/breakpoint-classname-control.mjs';
|
|
19
19
|
import { Label } from '../../../components/label.mjs';
|
|
20
20
|
import { Badge } from '../../../components/badge.mjs';
|
|
21
|
+
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../../components/select.mjs';
|
|
21
22
|
|
|
22
23
|
const classNameFieldOverrides = (layer) => {
|
|
23
24
|
return {
|
|
@@ -165,6 +166,210 @@ const commonFieldOverrides = (allowBinding = true) => {
|
|
|
165
166
|
memoizedCommonFieldOverrides.set(allowBinding, overrides);
|
|
166
167
|
return overrides;
|
|
167
168
|
};
|
|
169
|
+
function FunctionPropField({
|
|
170
|
+
propName,
|
|
171
|
+
label,
|
|
172
|
+
isRequired,
|
|
173
|
+
fieldConfigItem
|
|
174
|
+
}) {
|
|
175
|
+
const selectedLayerId = useLayerStore((state) => state.selectedLayerId);
|
|
176
|
+
const findLayerById = useLayerStore((state) => state.findLayerById);
|
|
177
|
+
const updateLayer = useLayerStore((state) => state.updateLayer);
|
|
178
|
+
const incrementRevision = useEditorStore((state) => state.incrementRevision);
|
|
179
|
+
const functionRegistry = useEditorStore((state) => state.functionRegistry);
|
|
180
|
+
const unbindPropFromVariable = useLayerStore(
|
|
181
|
+
(state) => state.unbindPropFromVariable
|
|
182
|
+
);
|
|
183
|
+
const selectedLayer = findLayerById(selectedLayerId);
|
|
184
|
+
if (!selectedLayer || !functionRegistry) {
|
|
185
|
+
return /* @__PURE__ */ jsx(
|
|
186
|
+
FormFieldWrapper,
|
|
187
|
+
{
|
|
188
|
+
label,
|
|
189
|
+
isRequired,
|
|
190
|
+
fieldConfigItem,
|
|
191
|
+
children: /* @__PURE__ */ jsx(Select, { disabled: true, children: /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "No function registry available" }) }) })
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const functionEntries = Object.entries(functionRegistry);
|
|
196
|
+
const getCurrentFunctionId = () => {
|
|
197
|
+
const directFuncId = selectedLayer.props[`__function_${propName}`];
|
|
198
|
+
if (typeof directFuncId === "string") {
|
|
199
|
+
return directFuncId;
|
|
200
|
+
}
|
|
201
|
+
return "";
|
|
202
|
+
};
|
|
203
|
+
const handleValueChange = (value) => {
|
|
204
|
+
if (value === "__none__") {
|
|
205
|
+
unbindPropFromVariable(selectedLayer.id, propName);
|
|
206
|
+
updateLayer(selectedLayer.id, {
|
|
207
|
+
[`__function_${propName}`]: void 0,
|
|
208
|
+
[propName]: void 0
|
|
209
|
+
});
|
|
210
|
+
incrementRevision();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const funcDef = functionRegistry[value];
|
|
214
|
+
if (funcDef) {
|
|
215
|
+
unbindPropFromVariable(selectedLayer.id, propName);
|
|
216
|
+
updateLayer(selectedLayer.id, {
|
|
217
|
+
[propName]: funcDef.fn,
|
|
218
|
+
[`__function_${propName}`]: value
|
|
219
|
+
});
|
|
220
|
+
incrementRevision();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const currentFunctionId = getCurrentFunctionId();
|
|
224
|
+
const getDisplayText = () => {
|
|
225
|
+
if (currentFunctionId) {
|
|
226
|
+
const funcDef = functionRegistry[currentFunctionId];
|
|
227
|
+
return funcDef?.name || currentFunctionId;
|
|
228
|
+
}
|
|
229
|
+
return "Select a function...";
|
|
230
|
+
};
|
|
231
|
+
return /* @__PURE__ */ jsx(
|
|
232
|
+
FormFieldWrapper,
|
|
233
|
+
{
|
|
234
|
+
label,
|
|
235
|
+
isRequired,
|
|
236
|
+
fieldConfigItem,
|
|
237
|
+
children: /* @__PURE__ */ jsxs(
|
|
238
|
+
Select,
|
|
239
|
+
{
|
|
240
|
+
value: currentFunctionId,
|
|
241
|
+
onValueChange: handleValueChange,
|
|
242
|
+
children: [
|
|
243
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full mb-0", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select a function...", children: getDisplayText() }) }),
|
|
244
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
245
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "__none__", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "None (clear)" }) }),
|
|
246
|
+
functionEntries.length > 0 && /* @__PURE__ */ jsx(Fragment, { children: functionEntries.map(([id, funcDef]) => /* @__PURE__ */ jsx(SelectItem, { value: id, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
247
|
+
/* @__PURE__ */ jsx("span", { children: funcDef.name }),
|
|
248
|
+
funcDef.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: funcDef.description })
|
|
249
|
+
] }) }, id)) })
|
|
250
|
+
] })
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const functionPropFieldOverrides = (propName) => {
|
|
258
|
+
return {
|
|
259
|
+
renderParent: ({ children }) => /* @__PURE__ */ jsx(VariableBindingWrapper, { propName, isFunctionProp: true, children }),
|
|
260
|
+
fieldType: (props) => /* @__PURE__ */ jsx(FunctionPropField, { propName, ...props })
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
function VariableBindingWrapper({
|
|
264
|
+
propName,
|
|
265
|
+
children,
|
|
266
|
+
isFunctionProp = false
|
|
267
|
+
}) {
|
|
268
|
+
const variables = useLayerStore((state) => state.variables);
|
|
269
|
+
const selectedLayerId = useLayerStore((state) => state.selectedLayerId);
|
|
270
|
+
const findLayerById = useLayerStore((state) => state.findLayerById);
|
|
271
|
+
const isBindingImmutable = useLayerStore((state) => state.isBindingImmutable);
|
|
272
|
+
const incrementRevision = useEditorStore((state) => state.incrementRevision);
|
|
273
|
+
const functionRegistry = useEditorStore((state) => state.functionRegistry);
|
|
274
|
+
const unbindPropFromVariable = useLayerStore(
|
|
275
|
+
(state) => state.unbindPropFromVariable
|
|
276
|
+
);
|
|
277
|
+
const bindPropToVariable = useLayerStore((state) => state.bindPropToVariable);
|
|
278
|
+
const selectedLayer = findLayerById(selectedLayerId);
|
|
279
|
+
if (!selectedLayer) {
|
|
280
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
281
|
+
}
|
|
282
|
+
const filteredVariables = variables.filter(
|
|
283
|
+
(v) => isFunctionProp ? v.type === "function" : v.type !== "function"
|
|
284
|
+
);
|
|
285
|
+
const currentValue = selectedLayer.props[propName];
|
|
286
|
+
const isCurrentlyBound = isVariableReference(currentValue);
|
|
287
|
+
const boundVariable = isCurrentlyBound ? variables.find((v) => v.id === currentValue.__variableRef) : null;
|
|
288
|
+
const isImmutable = isBindingImmutable(selectedLayer.id, propName);
|
|
289
|
+
const getFunctionDisplayValue = (variable) => {
|
|
290
|
+
if (variable.type === "function" && functionRegistry) {
|
|
291
|
+
const funcId = String(variable.defaultValue);
|
|
292
|
+
const funcDef = functionRegistry[funcId];
|
|
293
|
+
return funcDef ? funcDef.name : funcId;
|
|
294
|
+
}
|
|
295
|
+
return String(variable.defaultValue);
|
|
296
|
+
};
|
|
297
|
+
const handleBindToVariable = (variableId) => {
|
|
298
|
+
bindPropToVariable(selectedLayer.id, propName, variableId);
|
|
299
|
+
incrementRevision();
|
|
300
|
+
};
|
|
301
|
+
const handleUnbind = () => {
|
|
302
|
+
unbindPropFromVariable(selectedLayer.id, propName);
|
|
303
|
+
incrementRevision();
|
|
304
|
+
};
|
|
305
|
+
const emptyMessage = isFunctionProp ? "No function variables defined" : "No variables defined";
|
|
306
|
+
const bindLabel = isFunctionProp ? "Bind to Function Variable" : "Bind to Variable";
|
|
307
|
+
const tooltipLabel = isFunctionProp ? "Bind Function" : "Bind Variable";
|
|
308
|
+
return /* @__PURE__ */ jsx("div", { className: "flex w-full gap-2 items-end", children: isCurrentlyBound && boundVariable ? (
|
|
309
|
+
// Bound state - show variable info and unbind button
|
|
310
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 w-full", children: [
|
|
311
|
+
/* @__PURE__ */ jsx(Label, { children: propName.charAt(0).toUpperCase() + propName.slice(1) }),
|
|
312
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2 w-full", children: [
|
|
313
|
+
/* @__PURE__ */ jsx(Card, { className: "w-full overflow-hidden min-w-0", children: /* @__PURE__ */ jsx(CardContent, { className: "py-1 px-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
314
|
+
/* @__PURE__ */ jsx(Link, { className: "h-4 w-4 flex-shrink-0" }),
|
|
315
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
316
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
317
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: boundVariable.name }),
|
|
318
|
+
/* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: boundVariable.type }),
|
|
319
|
+
isImmutable && /* @__PURE__ */ jsx(Badge, { "data-testid": "immutable-badge", className: "rounded", children: /* @__PURE__ */ jsx(LockKeyhole, { strokeWidth: 3, className: "w-3 h-3" }) })
|
|
320
|
+
] }),
|
|
321
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(boundVariable) })
|
|
322
|
+
] })
|
|
323
|
+
] }) }) }),
|
|
324
|
+
!isImmutable && /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
325
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
326
|
+
Button,
|
|
327
|
+
{
|
|
328
|
+
variant: "outline",
|
|
329
|
+
onClick: handleUnbind,
|
|
330
|
+
className: "px-3 h-10",
|
|
331
|
+
"data-testid": "unbind-variable-button",
|
|
332
|
+
children: /* @__PURE__ */ jsx(Unlink, { className: "h-4 w-4" })
|
|
333
|
+
}
|
|
334
|
+
) }),
|
|
335
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: "Unbind Variable" })
|
|
336
|
+
] })
|
|
337
|
+
] })
|
|
338
|
+
] })
|
|
339
|
+
) : (
|
|
340
|
+
// Unbound state - show normal field with bind button
|
|
341
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
342
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children }),
|
|
343
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
344
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
345
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "px-3 h-10", "data-testid": "bind-variable-button", children: /* @__PURE__ */ jsx(Link, { className: "h-4 w-4 my-1" }) }) }) }),
|
|
346
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: tooltipLabel })
|
|
347
|
+
] }),
|
|
348
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
|
|
349
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs font-medium text-muted-foreground border-b", children: bindLabel }),
|
|
350
|
+
filteredVariables.length > 0 ? filteredVariables.map((variable) => /* @__PURE__ */ jsx(
|
|
351
|
+
DropdownMenuItem,
|
|
352
|
+
{
|
|
353
|
+
onClick: () => handleBindToVariable(variable.id),
|
|
354
|
+
className: "flex flex-col items-start p-3",
|
|
355
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
356
|
+
/* @__PURE__ */ jsx(Link, { className: "h-4 w-4 flex-shrink-0" }),
|
|
357
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [
|
|
358
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 ", children: [
|
|
359
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: variable.name }),
|
|
360
|
+
/* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 bg-muted rounded text-xs font-mono", children: variable.type })
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: getFunctionDisplayValue(variable) })
|
|
363
|
+
] })
|
|
364
|
+
] })
|
|
365
|
+
},
|
|
366
|
+
variable.id
|
|
367
|
+
)) : /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: emptyMessage })
|
|
368
|
+
] })
|
|
369
|
+
] }) })
|
|
370
|
+
] })
|
|
371
|
+
) });
|
|
372
|
+
}
|
|
168
373
|
function FormFieldWrapper({
|
|
169
374
|
label,
|
|
170
375
|
isRequired,
|
|
@@ -274,4 +479,4 @@ function ChildrenVariableBindingWrapper({
|
|
|
274
479
|
) });
|
|
275
480
|
}
|
|
276
481
|
|
|
277
|
-
export { ChildrenVariableBindingWrapper, FormFieldWrapper, childrenAsTextareaFieldOverrides, childrenAsTipTapFieldOverrides, childrenFieldOverrides, classNameFieldOverrides, commonFieldOverrides, iconNameFieldOverrides };
|
|
482
|
+
export { ChildrenVariableBindingWrapper, FormFieldWrapper, VariableBindingWrapper, childrenAsTextareaFieldOverrides, childrenAsTipTapFieldOverrides, childrenFieldOverrides, classNameFieldOverrides, commonFieldOverrides, functionPropFieldOverrides, iconNameFieldOverrides };
|
|
@@ -4,7 +4,7 @@ import react__default, { ComponentType } from 'react';
|
|
|
4
4
|
import * as _btst_yar from '@btst/yar';
|
|
5
5
|
import { QueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { c as UIBuilderClientHooks } from '../../../shared/stack.Cl7ok_cY.cjs';
|
|
7
|
-
import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer } from '../../../shared/stack.C5ZSOJGJ.cjs';
|
|
7
|
+
import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer, F as FunctionRegistry } from '../../../shared/stack.C5ZSOJGJ.cjs';
|
|
8
8
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
9
9
|
import { A as AutoFormInputComponentProps, F as FieldConfigItem } from '../../../shared/stack.8nldKomx.cjs';
|
|
10
10
|
export { PageBuilderPage, PageListPage, PageRenderer, PageRendererProps, SuspensePageRenderer } from './components/index.cjs';
|
|
@@ -342,6 +342,11 @@ interface UIBuilderPluginOverrides {
|
|
|
342
342
|
* Component registry for the UI Builder
|
|
343
343
|
*/
|
|
344
344
|
componentRegistry?: ComponentRegistry;
|
|
345
|
+
/**
|
|
346
|
+
* Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
|
|
347
|
+
* in the preview modal and layer renderer.
|
|
348
|
+
*/
|
|
349
|
+
functionRegistry?: FunctionRegistry;
|
|
345
350
|
/**
|
|
346
351
|
* Base path for UI Builder admin pages (default: /pages/ui-builder)
|
|
347
352
|
*/
|
|
@@ -4,7 +4,7 @@ import react__default, { ComponentType } from 'react';
|
|
|
4
4
|
import * as _btst_yar from '@btst/yar';
|
|
5
5
|
import { QueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { c as UIBuilderClientHooks } from '../../../shared/stack.VMmQdbsJ.mjs';
|
|
7
|
-
import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer } from '../../../shared/stack.Dq4qVr1F.mjs';
|
|
7
|
+
import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer, F as FunctionRegistry } from '../../../shared/stack.Dq4qVr1F.mjs';
|
|
8
8
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
9
9
|
import { A as AutoFormInputComponentProps, F as FieldConfigItem } from '../../../shared/stack.8nldKomx.mjs';
|
|
10
10
|
export { PageBuilderPage, PageListPage, PageRenderer, PageRendererProps, SuspensePageRenderer } from './components/index.mjs';
|
|
@@ -342,6 +342,11 @@ interface UIBuilderPluginOverrides {
|
|
|
342
342
|
* Component registry for the UI Builder
|
|
343
343
|
*/
|
|
344
344
|
componentRegistry?: ComponentRegistry;
|
|
345
|
+
/**
|
|
346
|
+
* Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
|
|
347
|
+
* in the preview modal and layer renderer.
|
|
348
|
+
*/
|
|
349
|
+
functionRegistry?: FunctionRegistry;
|
|
345
350
|
/**
|
|
346
351
|
* Base path for UI Builder admin pages (default: /pages/ui-builder)
|
|
347
352
|
*/
|
|
@@ -4,7 +4,7 @@ import react__default, { ComponentType } from 'react';
|
|
|
4
4
|
import * as _btst_yar from '@btst/yar';
|
|
5
5
|
import { QueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { c as UIBuilderClientHooks } from '../../../shared/stack.B2DwzF3r.js';
|
|
7
|
-
import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer } from '../../../shared/stack.D0QupDcQ.js';
|
|
7
|
+
import { a as ComponentRegistry, b as FieldConfigFunction, C as ComponentLayer, F as FunctionRegistry } from '../../../shared/stack.D0QupDcQ.js';
|
|
8
8
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
9
9
|
import { A as AutoFormInputComponentProps, F as FieldConfigItem } from '../../../shared/stack.8nldKomx.js';
|
|
10
10
|
export { PageBuilderPage, PageListPage, PageRenderer, PageRendererProps, SuspensePageRenderer } from './components/index.js';
|
|
@@ -342,6 +342,11 @@ interface UIBuilderPluginOverrides {
|
|
|
342
342
|
* Component registry for the UI Builder
|
|
343
343
|
*/
|
|
344
344
|
componentRegistry?: ComponentRegistry;
|
|
345
|
+
/**
|
|
346
|
+
* Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
|
|
347
|
+
* in the preview modal and layer renderer.
|
|
348
|
+
*/
|
|
349
|
+
functionRegistry?: FunctionRegistry;
|
|
345
350
|
/**
|
|
346
351
|
* Base path for UI Builder admin pages (default: /pages/ui-builder)
|
|
347
352
|
*/
|
package/package.json
CHANGED
|
@@ -215,6 +215,7 @@ function PageBuilderPageContent({
|
|
|
215
215
|
navigate,
|
|
216
216
|
Link,
|
|
217
217
|
componentRegistry: customRegistry,
|
|
218
|
+
functionRegistry,
|
|
218
219
|
} = usePluginOverrides<UIBuilderPluginOverrides>("ui-builder");
|
|
219
220
|
const basePath = useBasePath();
|
|
220
221
|
|
|
@@ -463,6 +464,7 @@ function PageBuilderPageContent({
|
|
|
463
464
|
}
|
|
464
465
|
onVariablesChange={handleVariablesChange}
|
|
465
466
|
componentRegistry={componentRegistry}
|
|
467
|
+
functionRegistry={functionRegistry}
|
|
466
468
|
persistLayerStore={false}
|
|
467
469
|
allowVariableEditing={true}
|
|
468
470
|
allowPagesCreation={false}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ComponentType } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ComponentRegistry,
|
|
4
|
+
FunctionRegistry,
|
|
5
|
+
} from "@workspace/ui/components/ui-builder/types";
|
|
3
6
|
import type { UIBuilderClientHooks } from "../types";
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -63,6 +66,12 @@ export interface UIBuilderPluginOverrides {
|
|
|
63
66
|
*/
|
|
64
67
|
componentRegistry?: ComponentRegistry;
|
|
65
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Function registry for resolving bindable event handlers (onClick, onSubmit, etc.)
|
|
71
|
+
* in the preview modal and layer renderer.
|
|
72
|
+
*/
|
|
73
|
+
functionRegistry?: FunctionRegistry;
|
|
74
|
+
|
|
66
75
|
/**
|
|
67
76
|
* Base path for UI Builder admin pages (default: /pages/ui-builder)
|
|
68
77
|
*/
|